From cde5081e94809a61bd7faca5ffa77db478e7e1c8 Mon Sep 17 00:00:00 2001 From: Luis Pater Date: Thu, 18 Jun 2026 03:43:03 +0800 Subject: [PATCH] test(translator): add tests to validate omission of top-level `output_text` in OpenAI responses - Added `TestConvertOpenAIChatCompletionsResponseToOpenAIResponses_CompletedOmitsTopLevelOutputText` to ensure `output_text` is excluded in streamed responses. - Added `TestConvertOpenAIChatCompletionsResponseToOpenAIResponses_ToolCallCompletedOmitsTopLevelOutputText` to validate behavior during tool call completions. - Introduced `TestConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream_OmitsTopLevelOutputText` to confirm the omission of `output_text` in non-streamed responses. - Expanded test coverage to ensure consistency with native OpenAI responses. --- .../openai_openai-responses_response_test.go | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/internal/translator/openai/openai/responses/openai_openai-responses_response_test.go b/internal/translator/openai/openai/responses/openai_openai-responses_response_test.go index cafcacb72..4951d91d6 100644 --- a/internal/translator/openai/openai/responses/openai_openai-responses_response_test.go +++ b/internal/translator/openai/openai/responses/openai_openai-responses_response_test.go @@ -371,6 +371,72 @@ func TestConvertOpenAIChatCompletionsResponseToOpenAIResponses_MixedMessageAndTo } } +func TestConvertOpenAIChatCompletionsResponseToOpenAIResponses_CompletedOmitsTopLevelOutputText(t *testing.T) { + in := []string{ + `data: {"id":"resp_output_text","object":"chat.completion.chunk","created":1773896263,"model":"model","choices":[{"index":0,"delta":{"role":"assistant","content":"hello ","reasoning_content":null,"tool_calls":null},"finish_reason":null}]}`, + `data: {"id":"resp_output_text","object":"chat.completion.chunk","created":1773896263,"model":"model","choices":[{"index":0,"delta":{"role":null,"content":"world","reasoning_content":null,"tool_calls":null},"finish_reason":"stop"}],"usage":{"completion_tokens":2,"total_tokens":4,"prompt_tokens":2}}`, + `data: [DONE]`, + } + + request := []byte(`{"model":"gpt-5.4"}`) + + var param any + var completed gjson.Result + for _, line := range in { + for _, chunk := range ConvertOpenAIChatCompletionsResponseToOpenAIResponses(context.Background(), "model", request, request, []byte(line), ¶m) { + ev, data := parseOpenAIResponsesSSEEvent(t, chunk) + if ev == "response.completed" { + completed = data + } + } + } + + if !completed.Exists() { + t.Fatal("expected response.completed event") + } + if completed.Get("response.output_text").Exists() { + t.Fatalf("response.output_text should be omitted to match native Responses output: %s", completed.Get("response.output_text").Raw) + } + if got := completed.Get("response.output.0.content.0.text").String(); got != "hello world" { + t.Fatalf("response.output text = %q, want %q", got, "hello world") + } +} + +func TestConvertOpenAIChatCompletionsResponseToOpenAIResponses_ToolCallCompletedOmitsTopLevelOutputText(t *testing.T) { + in := []string{ + `data: {"id":"resp_tool_output_text","object":"chat.completion.chunk","created":1773896263,"model":"model","choices":[{"index":0,"delta":{"role":"assistant","content":"I will call the weather tool.","reasoning_content":null,"tool_calls":null},"finish_reason":null}]}`, + `data: {"id":"resp_tool_output_text","object":"chat.completion.chunk","created":1773896263,"model":"model","choices":[{"index":0,"delta":{"role":"assistant","content":null,"reasoning_content":null,"tool_calls":[{"index":0,"id":"call_weather","type":"function","function":{"name":"get_weather","arguments":""}}]},"finish_reason":null}]}`, + `data: {"id":"resp_tool_output_text","object":"chat.completion.chunk","created":1773896263,"model":"model","choices":[{"index":0,"delta":{"role":null,"content":null,"reasoning_content":null,"tool_calls":[{"index":0,"function":{"arguments":"{\"location\":\"北京\",\"unit\":\"celsius\"}"}}]},"finish_reason":"tool_calls"}],"usage":{"completion_tokens":10,"total_tokens":20,"prompt_tokens":10}}`, + `data: [DONE]`, + } + + request := []byte(`{"model":"gpt-5.4","tool_choice":"auto","parallel_tool_calls":true}`) + + var param any + var completed gjson.Result + for _, line := range in { + for _, chunk := range ConvertOpenAIChatCompletionsResponseToOpenAIResponses(context.Background(), "model", request, request, []byte(line), ¶m) { + ev, data := parseOpenAIResponsesSSEEvent(t, chunk) + if ev == "response.completed" { + completed = data + } + } + } + + if !completed.Exists() { + t.Fatal("expected response.completed event") + } + if completed.Get("response.output_text").Exists() { + t.Fatalf("response.output_text should be omitted to match native Responses output: %s", completed.Get("response.output_text").Raw) + } + if got := completed.Get("response.output.0.content.0.text").String(); got != "I will call the weather tool." { + t.Fatalf("response output text = %q, want %q", got, "I will call the weather tool.") + } + if got := completed.Get("response.output.1.arguments").String(); !strings.Contains(got, "北京") { + t.Fatalf("response function call arguments = %q, want Beijing argument", got) + } +} + func TestConvertOpenAIChatCompletionsResponseToOpenAIResponses_FunctionCallDoneAndCompletedOutputStayAscending(t *testing.T) { in := []string{ `data: {"id":"resp_order","object":"chat.completion.chunk","created":1773896263,"model":"model","choices":[{"index":0,"delta":{"role":"assistant","content":null,"reasoning_content":null,"tool_calls":[{"index":0,"id":"call_glob","type":"function","function":{"name":"glob","arguments":""}}]},"finish_reason":null}]}`, @@ -421,3 +487,18 @@ func TestConvertOpenAIChatCompletionsResponseToOpenAIResponses_FunctionCallDoneA t.Fatalf("unexpected completed function_call order: %v", completedOrder) } } + +func TestConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream_OmitsTopLevelOutputText(t *testing.T) { + request := []byte(`{"model":"gpt-5.4"}`) + raw := []byte(`{"id":"chatcmpl_output_text","object":"chat.completion","created":1773896263,"model":"model","choices":[{"index":0,"message":{"role":"assistant","content":"ping"},"finish_reason":"stop"}],"usage":{"prompt_tokens":2,"completion_tokens":1,"total_tokens":3}}`) + + resp := ConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream(context.Background(), "model", request, request, raw, nil) + data := gjson.ParseBytes(resp) + + if data.Get("output_text").Exists() { + t.Fatalf("output_text should be omitted to match native Responses output: %s", resp) + } + if got := data.Get("output.0.content.0.text").String(); got != "ping" { + t.Fatalf("output text = %q, want %q; response=%s", got, "ping", resp) + } +}