Repository navigation
fix(bedrock): split the <reasoning> tag for gpt-oss only on native Chat Completions - #44432
Conversation
…at Completions The native Chat Completions route moved a leading <reasoning>...</reasoning> block into reasoning_content for every model, while only gpt-oss writes its reasoning inline. A GPT 5.6 or Grok answer that starts with a literal <reasoning> tag lost that text, streaming and non-streaming alike. Both paths now split only when the model id is gpt-oss.
|
I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
Merging this PR will not alter performance
Comparing |
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit fd23b9f. Configure here.
TLDR
Problem this solves:
<reasoning>tag out of every model's answerreasoning_content<reasoning>tag lost that text, streaming or notHow it solves it:
supports_bedrock_runtime_chat_completions_inline_reasoning, gates the split in both the non-streaming and the streaming pathUser Flow
Before: a developer whose prompt asks GPT 5.6 on Bedrock for tagged output gets the opening
<reasoning>block cut out of the answerbedrock/global.openai.gpt-5.6-solto the config and starts the proxy<reasoning>not thinking</reasoning> pong, with or without"stream": true"content": "pong"and"reasoning_content": "not thinking", so their parser never sees the tag they asked for; Grok throughbedrock/chat_completions/us.xai.grok-4.6answers the same wayAfter: the same answer comes back as the model wrote it
bedrock/global.openai.gpt-5.6-solto the config and starts the proxy"stream": true"content": "<reasoning>not thinking</reasoning> pong"and noreasoning_content, Grok included, while a gpt-oss deployment still gets its inline reasoning moved intoreasoning_contentPre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
uv run pytest tests/unit/<your_test_file>.py -v. Leave the suites (make test-unit-*,make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more@greptileaito re-request a review after pushing changes)Delays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
Screenshots / Proof of Fix
Shared setup: one proxy per commit, each started from a checkout of that commit with the same config and two uvicorn workers, and the same 18 calls sent to each: the three unified endpoints, non-streaming and streaming, for GPT 5.6, Grok 4.6, and the gpt-oss control. Real Bedrock calls in us-west-2; the proxy logs show every GPT 5.6 Responses API call going to
https://bedrock-runtime.us-west-2.amazonaws.com/openai/v1/responsesand every other call tohttps://bedrock-runtime.us-west-2.amazonaws.com/openai/v1/chat/completionsBoth proxies read the cost map of their own checkout through
LITELLM_LOCAL_MODEL_COST_MAP=true. The After checkout's map carries the new flag, which the hosted map only gets once this merges. Each proxy logged twoStarted server processlines, one per workerThe GPT 5.6 and Grok cases send the prompt
Reply with exactly this text and nothing else: <reasoning>not thinking</reasoning> pong, so the expected answer starts with a literal tag. The gpt-oss control sendsReply with the single word: pong, since gpt-oss writes its own reasoning inside the tag. Each cell prints the answer text next to that API's reasoning field (reasoning_content, the Responsesreasoningitem, or the Messagesthinkingblock), with streaming deltas joinedBefore (dd31692)
GPT 5.6 and Grok lose the literal tag on
/v1/chat/completionsand/v1/messagesin both modes, and Grok loses it on/v1/responsestoo: the text comes back aspongwithnot thinkingmoved into the reasoning fieldAfter (fd23b9f)
GPT 5.6 and Grok keep
<reasoning>not thinking</reasoning> pongas sent on all three endpoints in both modes, with no reasoning field, while gpt-oss still gets its reasoning split out everywhereObservations from the run:
/v1/responsesrides AWS's native Responses routeus.and application inference profile ARN gpt-oss ids never reach the splitType
🐛 Bug Fix
Caveats (if any)
Medium
<reasoning>block inline on the native Chat Completions route, where main splits it todayLITELLM_MODEL_COST_MAP_URLkeeps the old behavior until it carries the flagged rows, and refreshing the mirror is the fixLITELLM_LOCAL_MODEL_COST_MAP=truefor the same reasonLow
modelfield streams unsplit; AWS's Chat Completions contract echoes it in every chunk, as the proof's gpt-oss streaming cell shows<reasoning>block in content when AWS streams more than one pair, on both legsFinal Attestation
The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR
fd23b9f passes /live-pr-risk
Note
Medium Risk
Changes response shaping for Bedrock Chat Completions (content vs reasoning_content) for all non–gpt-oss models; behavior depends on the hosted cost map until the new flag ships.
Overview
Bedrock native Chat Completions used to strip a leading
<reasoning>...</reasoning>block from every model’s answer intoreasoning_content. That was wrong for GPT 5.6 and Grok, which can emit that text literally incontentwhile reasoning lives elsewhere.The PR gates that split behind a new price-map flag,
supports_bedrock_runtime_chat_completions_inline_reasoning, exposed viabedrock_runtime_chat_completions_serves_reasoning_inline(). Only flagged gpt-oss rows (commercial, GovCloud, safeguard variants) get the split on non-streaming (transform_response) and streaming (BedrockRuntimeChatCompletionsStreamingHandler, using the model id on each chunk). Everyone else’s text is returned unchanged.Schema and both cost-map JSON files are updated; unit tests cover GPT 5.6, Grok, gpt-oss, and cost-map-driven behavior.
Reviewed by Cursor Bugbot for commit fd23b9f. Bugbot is set up for automated code reviews on this repo. Configure here.