Skip to content

fix(translator): support message.reasoning fallback in responses non-stream - #191

Open
warelik wants to merge 3 commits into
kaitranntt:mainfrom
warelik:fix/responses-nonstream-reasoning
Open

fix(translator): support message.reasoning fallback in responses non-stream#191
warelik wants to merge 3 commits into
kaitranntt:mainfrom
warelik:fix/responses-nonstream-reasoning

Conversation

@warelik

@warelik warelik commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Problem

When translating OpenAI Chat Completions responses to OpenAI Responses API in non-streaming mode (ConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream), reasoning content was extracted solely from choices.0.message.reasoning_content. When upstream providers (such as o1/o3 via compatible gateways, Grok, DeepSeek, OpenRouter, or third-party OpenAI endpoints) return reasoning under choices.0.message.reasoning, the reasoning text was completely omitted from the converted Responses output.

In the same file, the streaming converter (ConvertOpenAIChatCompletionsResponseToOpenAIResponses) already fell back to reasoning when reasoning_content was absent or empty, causing an unintended discrepancy between streaming and non-streaming responses.

Fix

  • internal/translator/openai/openai/responses/openai_openai-responses_response.go:889: Updated ConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream to check choices.0.message.reasoning_content and fall back to choices.0.message.reasoning if missing or empty, establishing stream/non-stream parity while preserving reasoning_content priority.

Tests

  • TestConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream_ReasoningFallback:
    • reasoning_content field present: Verifies standard reasoning_content extraction.
    • reasoning fallback field present: Verifies fallback to reasoning when reasoning_content is absent.
    • both reasoning_content and reasoning present (reasoning_content priority): Verifies priority.
    • empty reasoning_content falls back to reasoning: Verifies fallback when reasoning_content is "".
    • neither field present without request reasoning: Verifies no reasoning item is emitted.
    • neither field present with request reasoning produces empty summary: Verifies empty summary when requested.

Reverse bite-check

Reverting internal/translator/openai/openai/responses/openai_openai-responses_response.go to the pre-fix state causes TestConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream_ReasoningFallback to fail on both fallback subtests:

=== RUN   TestConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream_ReasoningFallback
=== RUN   TestConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream_ReasoningFallback/reasoning_content_field_present
=== RUN   TestConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream_ReasoningFallback/reasoning_fallback_field_present
    openai_openai-responses_response_test.go:1331: reasoning found = false, want true; out={"id":"chatcmpl_r","object":"response","created_at":1773896263,"status":"completed","background":false,"error":null,"incomplete_details":null,"model":"o3-mini","output":[{"id":"msg_chatcmpl_r_0","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"hello"}],"role":"assistant"}]}
=== RUN   TestConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream_ReasoningFallback/both_reasoning_content_and_reasoning_present_(reasoning_content_priority)
=== RUN   TestConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream_ReasoningFallback/empty_reasoning_content_falls_back_to_reasoning
    openai_openai-responses_response_test.go:1331: reasoning found = false, want true; out={"id":"chatcmpl_empty_rc","object":"response","created_at":1773896263,"status":"completed","background":false,"error":null,"incomplete_details":null,"model":"o3-mini","output":[{"id":"msg_chatcmpl_empty_rc_0","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"hello"}],"role":"assistant"}]}
=== RUN   TestConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream_ReasoningFallback/neither_field_present_without_request_reasoning
=== RUN   TestConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream_ReasoningFallback/neither_field_present_with_request_reasoning_produces_empty_summary
--- FAIL: TestConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream_ReasoningFallback (0.00s)
    --- PASS: TestConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream_ReasoningFallback/reasoning_content_field_present (0.00s)
    --- FAIL: TestConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream_ReasoningFallback/reasoning_fallback_field_present (0.00s)
    --- PASS: TestConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream_ReasoningFallback/both_reasoning_content_and_reasoning_present_(reasoning_content_priority) (0.00s)
    --- FAIL: TestConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream_ReasoningFallback/empty_reasoning_content_falls_back_to_reasoning (0.00s)
    --- PASS: TestConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream_ReasoningFallback/neither_field_present_without_request_reasoning (0.00s)
    --- PASS: TestConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream_ReasoningFallback/neither_field_present_with_request_reasoning_produces_empty_summary (0.00s)
FAIL
FAIL	github.com/router-for-me/CLIProxyAPI/v7/internal/translator/openai/openai/responses	0.372s
FAIL

Verification

TMPDIR=/Users/warelik/.cache/gotmp GOCACHE=/Users/warelik/.cache/gocache go build ./...
TMPDIR=/Users/warelik/.cache/gotmp GOCACHE=/Users/warelik/.cache/gocache go vet ./internal/translator/openai/openai/responses/...
gofmt -l internal/translator/openai/openai/responses/openai_openai-responses_response.go internal/translator/openai/openai/responses/openai_openai-responses_response_test.go
TMPDIR=/Users/warelik/.cache/gotmp GOCACHE=/Users/warelik/.cache/gocache go test -v -run "TestConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream_ReasoningFallback" ./internal/translator/openai/openai/responses/...

Output:

=== RUN   TestConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream_ReasoningFallback
=== RUN   TestConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream_ReasoningFallback/reasoning_content_field_present
=== RUN   TestConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream_ReasoningFallback/reasoning_fallback_field_present
=== RUN   TestConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream_ReasoningFallback/both_reasoning_content_and_reasoning_present_(reasoning_content_priority)
=== RUN   TestConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream_ReasoningFallback/empty_reasoning_content_falls_back_to_reasoning
=== RUN   TestConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream_ReasoningFallback/neither_field_present_without_request_reasoning
=== RUN   TestConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream_ReasoningFallback/neither_field_present_with_request_reasoning_produces_empty_summary
--- PASS: TestConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream_ReasoningFallback (0.00s)
    --- PASS: TestConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream_ReasoningFallback/reasoning_content_field_present (0.00s)
    --- PASS: TestConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream_ReasoningFallback/reasoning_fallback_field_present (0.00s)
    --- PASS: TestConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream_ReasoningFallback/both_reasoning_content_and_reasoning_present_(reasoning_content_priority) (0.00s)
    --- PASS: TestConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream_ReasoningFallback/empty_reasoning_content_falls_back_to_reasoning (0.00s)
    --- PASS: TestConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream_ReasoningFallback/neither_field_present_without_request_reasoning (0.00s)
    --- PASS: TestConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream_ReasoningFallback/neither_field_present_with_request_reasoning_produces_empty_summary (0.00s)
PASS
ok  	github.com/router-for-me/CLIProxyAPI/v7/internal/translator/openai/openai/responses	0.389s

Tooling note

The jbcontext semantic-search CLI is installed but non-functional in this environment
(jbcontext search fails with the OS keychain is not accessible). The equivalent
review passes were performed with the repository's own tooling and manual inspection
instead; this is disclosed for transparency about how the change was reviewed.

W ARELIK added 2 commits August 20, 2026 13:14
Add message.reasoning fallback to ConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream to match the fallback behavior in the streaming path.

Refs router-for-me/CLIProxyAPI#5105
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant