fix(bedrock): preserve reasoning signature on Converse interleaved-thinking replay - #36261
fix(bedrock): preserve reasoning signature on Converse interleaved-thinking replay#36261fesalfayed wants to merge 1 commit into
Conversation
…inking replay
normalize_converse_response read reasoningContent.text, but the Converse
schema nests it at reasoningContent.reasoningText.{text,signature}, so on a
real Bedrock response nothing was captured and the signature was dropped.
convert_messages_to_converse also rebuilt assistant turns as [text][toolUse]
only, discarding signed thinking. Replaying signed reasoning without its
original signature (or omitting it) breaks multi-turn thinking on Bedrock —
the Converse analog of the native-endpoint reorder in NousResearch#35975.
Read reasoningText.{text,signature}, carry it verbatim through the existing
reasoning_details channel, and re-emit the reasoningContent block ahead of
tool_use on replay. Streaming deltas (flat shape) now also retain the
signature.
|
Validation refresh:
Why this remains needed after the Anthropic fixes: those preserve Anthropic |
teknium1
left a comment
There was a problem hiding this comment.
Thanks for identifying the real Converse schema and the missing signature path. Current main still reads the flattened key at agent/bedrock_adapter.py:705-710 and emits no replayed reasoning blocks at agent/bedrock_adapter.py:603-626.
Problems
agent/bedrock_adapter.py:557-581reconstructs allreasoning_detailsbefore all tool calls. For the interleaved signed-thinking case in this PR's title, this changes[reasoning_1, tool_1, reasoning_2, tool_2]into[reasoning_1, reasoning_2, tool_1, tool_2]. The project’s native Anthropic implementation records an ordered-block channel for exactly this loss of cross-type ordering (agent/transports/anthropic.py:167-183;tests/agent/test_anthropic_thinking_block_order.py:9-29).
Suggested changes
- Preserve and replay an ordered Bedrock block sequence when signed reasoning and tool uses coexist, rather than reconstructing from parallel lists.
- Add a round-trip regression with two signed reasoning blocks separated by tool uses; the current new test covers only one leading reasoning block.
Automated hermes-sweeper review.
| # and rejects it if the text/signature are modified or dropped on a | ||
| # multi-turn replay. reasoning_details carries the verbatim | ||
| # reasoningText payload captured in normalize_converse_response. | ||
| for detail in (msg.get("reasoning_details") or []): |
There was a problem hiding this comment.
This front-loads every reasoning block before the separate tool-call loop below. For an interleaved signed response [reasoning_1, tool_1, reasoning_2, tool_2], replay becomes [reasoning_1, reasoning_2, tool_1, tool_2], invalidating the positional guarantee this PR targets. Preserve an ordered Bedrock block sequence instead of reconstructing from parallel lists.
What does this PR do?
Fixes the AWS Bedrock (Converse API) analog of #35975 / #35586: signed Claude extended-thinking blocks were not round-tripped, so multi-turn agentic turns with interleaved thinking + tool_use silently lost their reasoning (and replaying reasoning without its original signature is rejected by Bedrock).
Root cause (three defects).
normalize_converse_responsereadreasoningContent.text, but the Converse schema nests it atreasoningContent.reasoningText.{text, signature}(botocoreReasoningContentBlock/ReasoningTextBlock). On a real Bedrock responsereasoning.get("text")is always"", so nothing was captured. The fix in 8d363f8 (fix(bedrock): preserve reasoningContent in normalized responses #20366/fix(bedrock): preserve reasoningContent in normalized responses #21202) only passed because its fixture used a flattened shape that does not match AWS.reasoningText.signaturewas never read. AWS requires it on replay: "include the text and its signature unmodified."reasoningContent.convert_messages_to_converserebuilt assistant turns as[text][toolUse]only.Fix. Read
reasoningText.{text, signature}, carry it verbatim through the existingreasoning_detailschannel (same mechanismAnthropicTransportuses — no new state column), and re-emit thereasoningContentblock ahead oftool_useon replay. Streaming deltas (flat shape) now also retain the signature.How was it tested?
TestReasoningSignatureRoundTrip(normalize →reasoning_details→ replay, asserting order + signature). Fails onmain(reasoning is None), passes here.test_raw_reasoning_content_responsefixture to the real nested schema; added aprovider_data["reasoning_details"]assertion.tests/agent/test_bedrock_adapter.py+tests/agent/transports/: 435 passed, 0 regressions.Closes #36260. Native-endpoint counterpart: #35586. Builds on the reasoningContent work in 8d363f8.