diff --git a/internal/adapter/openai/prompt_build_test.go b/internal/adapter/openai/prompt_build_test.go index bc89bd6..0d7e1c5 100644 --- a/internal/adapter/openai/prompt_build_test.go +++ b/internal/adapter/openai/prompt_build_test.go @@ -87,3 +87,17 @@ func TestBuildOpenAIFinalPrompt_VercelPreparePathKeepsFinalAnswerInstruction(t * t.Fatalf("vercel prepare finalPrompt should not require fenced tool calls: %q", finalPrompt) } } + +func TestBuildOpenAIFinalPromptWithThinkingAddsContinuationContract(t *testing.T) { + messages := []any{ + map[string]any{"role": "user", "content": "继续回答上一个问题"}, + } + + finalPrompt, _ := buildOpenAIFinalPrompt(messages, nil, "", true) + if !strings.Contains(finalPrompt, "Continue the conversation from the full prior context") { + t.Fatalf("expected continuation contract in thinking prompt, got=%q", finalPrompt) + } + if !strings.Contains(finalPrompt, "final user-facing answer only in reasoning") { + t.Fatalf("expected visible-answer contract in thinking prompt, got=%q", finalPrompt) + } +} diff --git a/internal/prompt/messages.go b/internal/prompt/messages.go index d882f34..993eeef 100644 --- a/internal/prompt/messages.go +++ b/internal/prompt/messages.go @@ -30,6 +30,11 @@ func MessagesPrepareWithThinking(messages []map[string]any, thinkingEnabled bool Text string } processed := make([]block, 0, len(messages)) + if thinkingEnabled { + if instruction := buildConversationContinuityInstructions(thinkingEnabled); strings.TrimSpace(instruction) != "" { + processed = append(processed, block{Role: "system", Text: instruction}) + } + } for _, m := range messages { role, _ := m["role"].(string) text := NormalizeContent(m["content"]) @@ -88,6 +93,17 @@ func formatRoleBlock(marker, text, endMarker string) string { return out } +func buildConversationContinuityInstructions(thinkingEnabled bool) string { + lines := []string{ + "Continue the conversation from the full prior context and the latest tool results.", + "Treat earlier messages as binding context; answer the user's current request as a continuation, not a restart.", + } + if thinkingEnabled { + lines = append(lines, "Keep reasoning internal. Do not leave the final user-facing answer only in reasoning; always provide the answer in visible assistant content.") + } + return strings.Join(lines, "\n") +} + func NormalizeContent(v any) string { if v == nil { return "" diff --git a/internal/prompt/messages_test.go b/internal/prompt/messages_test.go index 6d9a034..8be34b2 100644 --- a/internal/prompt/messages_test.go +++ b/internal/prompt/messages_test.go @@ -58,17 +58,23 @@ func TestNormalizeContentArrayFallsBackToContentWhenTextEmpty(t *testing.T) { } } -func TestMessagesPrepareWithThinkingIgnoresThinkingFlag(t *testing.T) { +func TestMessagesPrepareWithThinkingAddsContinuityContract(t *testing.T) { messages := []map[string]any{{"role": "user", "content": "Question"}} gotThinking := MessagesPrepareWithThinking(messages, true) gotPlain := MessagesPrepareWithThinking(messages, false) - if gotThinking != gotPlain { - t.Fatalf("expected thinking flag to be ignored, got %q vs %q", gotThinking, gotPlain) + if gotThinking == gotPlain { + t.Fatalf("expected thinking-enabled prompt to include extra continuity instructions") } if !strings.HasSuffix(gotThinking, "<|Assistant|>") { - t.Fatalf("expected assistant suffix without think tags, got %q", gotThinking) + t.Fatalf("expected assistant suffix, got %q", gotThinking) } - if strings.Contains(gotThinking, "") || strings.Contains(gotThinking, "") { - t.Fatalf("did not expect think tags in prompt, got %q", gotThinking) + if !strings.Contains(gotThinking, "Continue the conversation from the full prior context") { + t.Fatalf("expected continuity instruction in thinking prompt, got %q", gotThinking) + } + if !strings.Contains(gotThinking, "final user-facing answer only in reasoning") { + t.Fatalf("expected visible-answer instruction in thinking prompt, got %q", gotThinking) + } + if strings.Contains(gotPlain, "Continue the conversation from the full prior context") { + t.Fatalf("did not expect thinking-only instruction in plain prompt, got %q", gotPlain) } }