yoi/tickets/anthropic-assistant-burst-bundling.md

2.9 KiB
Raw Blame History

Anthropic projection: assistant ターン内ブロックを 1 message に束ねる

背景

crates/llm-worker/src/llm_client/scheme/anthropic/request.rsconvert_items_to_messages は、Worker が 1 ターンで生成する [Reasoning, assistant_message, ToolCall] の連列を、Anthropic wire 上で 複数の隣接した assistant message に分割している。

具体的には:

  • Item::Reasoningpending_assistant に push
  • 次の Item::Message { Role::Assistant } が到来すると pending_assistant を flush し、自分自身は別 message として messages に直 push
  • 続く Item::ToolCall は再び pending_assistant に積まれ、turn 末で flush され 3 つ目の assistant message に

結果として 1 turn が assistant[Thinking] / assistant[text] / assistant[tool_use] の 3 message に展開される。

Anthropic Messages API は user/assistant の交互を要求し、同一論理 turn 内の thinking/text/tool_use は 1 つの assistant message の content 配列 に並べる仕様。新世代 Claude (Opus 4.5+/Sonnet 4.6+) で thinking signature を round-trip する際、隣接 assistant message に分かれていると signature の文脈が崩れて 400 になる懸念があるreasoning-history-persist のレビュー指摘)。

なお、本バグは reasoning-history-persist で導入されたものではなく、assistant_message + tool_call の組合せで以前から存在していた pre-existing な分割。Reasoning が同じ flush 経路を継承した形。

要件

  • 同一論理ターンに属する Item::Reasoning / Item::Message(Assistant) / Item::ToolCall を、Anthropic wire 上の 1 つの assistant message の content 配列 に束ねる
  • 順序は arrival 順 (= history 順)。Anthropic 仕様の典型は thinking → text → tool_use
  • user / system role の Item::MessageItem::ToolResult を境界として assistant burst を区切る
  • 既存の breakpoint (cache_control) 計算が壊れないこと: 各 item のオリジン index → (msg_idx, part_idx) マッピングは flush_pending 経由で記録されているので、Item::Message(Assistant) も pending を経由するように揃えれば自然に追従する
  • Single-text 専用の AnthropicContent::Text shorthand は assistant burst 内 1 part のみのときに限定して維持するか、簡潔さのために常に Parts 形式に統一するかは実装時に判断
  • 既存テスト群(completed_turn, single_text_message_uses_text_shorthand_without_breakpoint, breakpoint_on_tool_result_head 等)の意図を逸脱しないよう更新

スコープ外

  • モデル世代別の thinking keep/strip デフォルト分岐reasoning-history-persist のフォローアップ候補と同じ扱い)
  • clear_thinking_20251015 context-edit
  • prune.rs の reasoning aware 化