diff --git a/internal/format/openai/render.go b/internal/format/openai/render.go index 3d2f967..02c536e 100644 --- a/internal/format/openai/render.go +++ b/internal/format/openai/render.go @@ -43,7 +43,10 @@ func BuildChatCompletion(completionID, model, finalPrompt, finalThinking, finalT } func BuildResponseObject(responseID, model, finalPrompt, finalThinking, finalText string, toolNames []string) map[string]any { - detected := util.ParseToolCalls(finalText, toolNames) + // Responses output should only be treated as tool calls when the model + // produced a standalone structured payload. This prevents accidental + // empty output_text on normal prose that merely contains tool_call-like text. + detected := util.ParseStandaloneToolCalls(finalText, toolNames) exposedOutputText := finalText output := make([]any, 0, 2) if len(detected) > 0 { diff --git a/internal/format/openai/render_test.go b/internal/format/openai/render_test.go index 1da68d0..50589e0 100644 --- a/internal/format/openai/render_test.go +++ b/internal/format/openai/render_test.go @@ -62,3 +62,28 @@ func TestBuildResponseObjectToolCallsFollowChatShape(t *testing.T) { t.Fatalf("unexpected arguments: %#v", args) } } + +func TestBuildResponseObjectKeepsOutputTextForMixedProse(t *testing.T) { + obj := BuildResponseObject( + "resp_test", + "gpt-4o", + "prompt", + "", + `示例格式:{"tool_calls":[{"name":"search","input":{"q":"golang"}}]},但这条是普通回答。`, + []string{"search"}, + ) + + outputText, _ := obj["output_text"].(string) + if outputText == "" { + t.Fatalf("expected output_text to be preserved for mixed prose") + } + + output, _ := obj["output"].([]any) + if len(output) != 1 { + t.Fatalf("expected one output item, got %#v", obj["output"]) + } + first, _ := output[0].(map[string]any) + if first["type"] != "message" { + t.Fatalf("expected output type message, got %#v", first["type"]) + } +}