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.
This commit is contained in:
Luis Pater
2026-06-18 03:43:03 +08:00
parent acaf250fa8
commit cde5081e94

View File

@@ -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), &param) {
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), &param) {
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)
}
}