[genai-fork] anthropic: preserve per-block thinking text on stream ca… - #6
Merged
Conversation
…pture + outbound
The streaming capture path discarded per-block reasoning text
(`let _ = reasoning;` at anthropic/streamer.rs) and reconstructed
ThinkingBlock parts with empty text paired to real signatures. The
outbound assistant-message serializer compounded this with a
`reasoning_texts.join("")` + zip-by-index strategy that concatenated
all reasoning into block[0]'s text and left block[1..] empty, scrambling
the text<->signature pairing.
Anthropic validates thinking-block signatures byte-exact against the
paired `thinking` text on replay. Both bugs silently invalidated every
signature on multi-block thinking turns; the single-block case worked
by coincidence because the "combined" text equalled the "per-block"
text.
Fix:
- Widen StreamerCapturedData::thought_signatures (Vec<String>) to
thought_blocks: Option<Vec<ThinkingBlock>>. push_thought_block
takes (text, sig, provenance) and preserves the pairing through
reconstruction.
- Anthropic streamer pushes (reasoning, signature) together at
content_block_stop instead of dropping reasoning.
- chat_stream::StreamEnd::from rebuilds ContentPart::ThinkingBlock
with real per-block text. ToolCall.thought_signatures (Gemini-shaped
opaque-signatures-on-tool-call semantic) is backfilled with
signature strings only -- correct for Gemini's wire format which
has no paired thinking text.
- Anthropic outbound (adapter_impl) walks ContentPart::ThinkingBlock
parts in order and emits one {thinking, signature} per block 1:1;
non-Anthropic-provenance blocks are dropped silently.
- Gemini and openai_resp streamers pass empty text via push_thought_block
(their signatures are opaque with no paired text). Gemini's outbound
adapter already ignores block.text and emits just {thoughtSignature:
sig}, so cross-provider round-trip stays correct.
Tests:
- New tests/tests_signature_roundtrip.rs asserts inbound multi-block
fixture produces two ThinkingBlocks with correct per-block text and
signatures.
- New unit test in anthropic/adapter_impl replaces the previous one
that codified the broken "concat into block[0], empty block[1..]"
behaviour; new test asserts 1:1 text<->signature pairing on
outbound.
- tests/data/yakbak/anthropic/thinking_multi_block_stream/ new fixture
with two distinct thinking blocks and a trailing tool_use.
78 unit tests + yakbak (anthropic + gemini) + signature roundtrip
integration pass. No behavioural changes for single-block thinking.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
…pture + outbound
The streaming capture path discarded per-block reasoning text (
let _ = reasoning;at anthropic/streamer.rs) and reconstructed ThinkingBlock parts with empty text paired to real signatures. The outbound assistant-message serializer compounded this with areasoning_texts.join("")+ zip-by-index strategy that concatenated all reasoning into block[0]'s text and left block[1..] empty, scrambling the text<->signature pairing.Anthropic validates thinking-block signatures byte-exact against the paired
thinkingtext on replay. Both bugs silently invalidated every signature on multi-block thinking turns; the single-block case worked by coincidence because the "combined" text equalled the "per-block" text.Fix:
Tests:
78 unit tests + yakbak (anthropic + gemini) + signature roundtrip integration pass. No behavioural changes for single-block thinking.