feat: enhance OpenAI response rendering to include reasoning and improve tool call detection from thinking channel, and refactor testing scripts for unified unit test execution.

This commit is contained in:
CJACK
2026-02-20 03:30:39 +08:00
parent 2781951ce7
commit dec9d03fc5
13 changed files with 133 additions and 27 deletions

View File

@@ -47,27 +47,46 @@ func BuildResponseObject(responseID, model, finalPrompt, finalThinking, finalTex
// 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)
toolCallsFromThinking := false
if len(detected) == 0 && strings.TrimSpace(finalThinking) != "" {
detected = util.ParseStandaloneToolCalls(finalThinking, toolNames)
toolCallsFromThinking = len(detected) > 0
}
exposedOutputText := finalText
output := make([]any, 0, 2)
if len(detected) > 0 {
exposedOutputText = ""
if !toolCallsFromThinking || strings.TrimSpace(finalText) != "" {
exposedOutputText = ""
} else {
exposedOutputText = finalThinking
}
if strings.TrimSpace(finalThinking) != "" {
output = append(output, map[string]any{
"type": "reasoning",
"text": finalThinking,
})
}
output = append(output, map[string]any{
"type": "tool_calls",
"tool_calls": util.FormatOpenAIToolCalls(detected),
})
} else {
content := []any{
map[string]any{
"type": "output_text",
"text": finalText,
},
}
content := make([]any, 0, 2)
if finalThinking != "" {
content = append([]any{map[string]any{
"type": "reasoning",
"text": finalThinking,
}}, content...)
}
if strings.TrimSpace(finalText) != "" {
content = append(content, map[string]any{
"type": "output_text",
"text": finalText,
})
}
if strings.TrimSpace(finalText) == "" && strings.TrimSpace(finalThinking) != "" {
exposedOutputText = finalThinking
}
output = append(output, map[string]any{
"type": "message",
"id": "msg_" + strings.ReplaceAll(uuid.NewString(), "-", ""),

View File

@@ -87,3 +87,60 @@ func TestBuildResponseObjectKeepsOutputTextForMixedProse(t *testing.T) {
t.Fatalf("expected output type message, got %#v", first["type"])
}
}
func TestBuildResponseObjectReasoningOnlyFallsBackToOutputText(t *testing.T) {
obj := BuildResponseObject(
"resp_test",
"gpt-4o",
"prompt",
"internal thinking content",
"",
nil,
)
outputText, _ := obj["output_text"].(string)
if outputText == "" {
t.Fatalf("expected output_text fallback from reasoning when final text is empty")
}
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"])
}
content, _ := first["content"].([]any)
if len(content) == 0 {
t.Fatalf("expected reasoning content, got %#v", first["content"])
}
block0, _ := content[0].(map[string]any)
if block0["type"] != "reasoning" {
t.Fatalf("expected first content block reasoning, got %#v", block0["type"])
}
}
func TestBuildResponseObjectDetectsToolCallFromThinkingChannel(t *testing.T) {
obj := BuildResponseObject(
"resp_test",
"gpt-4o",
"prompt",
`{"tool_calls":[{"name":"search","input":{"q":"from-thinking"}}]}`,
"",
[]string{"search"},
)
output, _ := obj["output"].([]any)
if len(output) != 2 {
t.Fatalf("expected reasoning + tool_calls outputs, got %#v", obj["output"])
}
first, _ := output[0].(map[string]any)
if first["type"] != "reasoning" {
t.Fatalf("expected first output reasoning, got %#v", first["type"])
}
second, _ := output[1].(map[string]any)
if second["type"] != "tool_calls" {
t.Fatalf("expected second output tool_calls, got %#v", second["type"])
}
}