fix(anthropic): preserve interleaved thinking/tool_use block order on replay
Interleaved-thinking turns (adaptive thinking, Claude 4.6+/Opus 4.8) emit
content blocks like:
thinking_1(signed) tool_use_1 thinking_2(signed) tool_use_2
Anthropic signs each thinking block against the turn content preceding it
at its position. normalize_response split the turn into two parallel lists
(reasoning_details + tool_calls), discarding cross-type order, and
_convert_assistant_message rebuilt it as [all thinking][text][all tool_use].
That moved thinking_2 ahead of tool_use_1, invalidating its signature, so
Anthropic rejected the latest assistant message with HTTP 400:
messages.N.content.M: `thinking` or `redacted_thinking` blocks in the
latest assistant message cannot be modified.
Observed repeatedly in agent.conversation_loop against api.anthropic.com /
claude-opus-4-8, recurring across sessions on multi-thinking-block turns.
Fix: carry a verbatim, order-preserving copy of the turn's content blocks
(anthropic_content_blocks) end-to-end - capture in normalize_response,
persist/restore through state.db, and replay unchanged for the latest
assistant message. Gated to turns that actually interleave signed thinking
with tool_use, so normal turns are unaffected.
Adds 3 regression tests including a SQLite round-trip covering the
crash-recovery reload path.
This commit is contained in:
parent
ad9012097b
commit
aaccaada28
7 changed files with 344 additions and 7 deletions
|
|
@ -952,6 +952,18 @@ def build_assistant_message(agent, assistant_message, finish_reason: str) -> dic
|
|||
if preserved:
|
||||
msg["reasoning_details"] = preserved
|
||||
|
||||
# Anthropic interleaved-thinking replay: when a turn interleaves signed
|
||||
# thinking blocks with tool_use, the parallel reasoning_details +
|
||||
# tool_calls fields lose the cross-type ordering, and reconstruction
|
||||
# front-loads thinking — reordering signed blocks and triggering HTTP 400
|
||||
# ("thinking ... blocks in the latest assistant message cannot be
|
||||
# modified"). Carry the verbatim ordered block list so the adapter can
|
||||
# replay the latest assistant message unchanged. See
|
||||
# agent/transports/anthropic.py and agent/anthropic_adapter.py.
|
||||
ordered_blocks = getattr(assistant_message, "anthropic_content_blocks", None)
|
||||
if ordered_blocks:
|
||||
msg["anthropic_content_blocks"] = ordered_blocks
|
||||
|
||||
# Codex Responses API: preserve encrypted reasoning items for
|
||||
# multi-turn continuity. These get replayed as input on the next turn.
|
||||
codex_items = getattr(assistant_message, "codex_reasoning_items", None)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue