Repository navigation
Description
Description
An empty signed Anthropic thinking block is lost when streaming updates are combined and serialized for tool-result replay.
The stream contains a thinking block with thinking: "", followed by a signature_delta. ChatResponse.from_updates() merges these into reasoning content with text=None. The Anthropic serializer then skips it as an orphan signature. The associated tool_use block survives.
Expected: preserve {"type": "thinking", "thinking": "", "signature": "synthetic-signature"} before the tool call. Anthropic requires thinking blocks to be replayed unchanged, including empty thinking text.
Actual: only the tool call is serialized. Nonempty thinking text passes the control assertion below.
Code Sample
Offline reproduction using synthetic SDK events and signatures. No network call or real credentials are required; the model name is an unused constructor argument.
python -m pip install agent-framework-core==1.17.0 agent-framework-anthropic==1.0.0b260827 anthropic==0.116.0import json
from agent_framework import ChatResponse
from agent_framework_anthropic import AnthropicClient
from anthropic.types.beta import (
BetaRawContentBlockStartEvent,
BetaRawContentBlockDeltaEvent,
)
client = AnthropicClient(api_key='unused-offline-test', model='claude-sonnet-4-6')
def replay(thinking_text):
# Synthetic API events: no network, real provider parsing and SDK aggregation.
events = [
BetaRawContentBlockStartEvent.model_validate({
'type': 'content_block_start', 'index': 0,
'content_block': {'type': 'thinking', 'thinking': thinking_text, 'signature': ''},
}),
BetaRawContentBlockDeltaEvent.model_validate({
'type': 'content_block_delta', 'index': 0,
'delta': {'type': 'signature_delta', 'signature': 'synthetic-signature'},
}),
BetaRawContentBlockStartEvent.model_validate({
'type': 'content_block_start', 'index': 1,
'content_block': {'type': 'tool_use', 'id': 'toolu_test', 'name': 'lookup', 'input': {}},
}),
]
updates = [client._process_stream_event(event) for event in events]
response = ChatResponse.from_updates([u for u in updates if u is not None])
message = response.messages[0]
reasoning = next(c for c in message.contents if c.type == 'text_reasoning')
prepared = client._prepare_message_for_anthropic(message)
return {
'input_thinking': thinking_text,
'merged_thinking': reasoning.text,
'merged_signature': reasoning.protected_data,
'replayed_content': prepared['content'],
}
print(json.dumps(replay(''), indent=2))
assert replay('Visible summary.')['replayed_content'][0]['type'] == 'thinking'
assert replay('')['replayed_content'][0]['type'] == 'thinking' # FailsError Messages / Stack Traces
The final assertion fails. Before serialization, merged_thinking is null while merged_signature is still "synthetic-signature". replayed_content contains only tool_use.
This reports silent block loss, not a reproduced API error or a measured effect on model answers.
Package Versions
agent-framework-core==1.17.0, agent-framework-anthropic==1.0.0b260827, anthropic==0.116.0.
Python Version
3.13.15
Additional Context
Related to #5783 / #5784. Those cover orphan signatures; this case begins with an actual empty thinking block that aggregation turns into an apparent orphan.
The relevant code remains in upstream commit a03bb909c694ad27f0b14d310d39e7f9e232ad39 (source inspection): reasoning-content merge and Anthropic serializer.
Suggested regression coverage: preserve empty thinking text and its signature through stream parsing, aggregation, and replay, while retaining the existing true-orphan-signature behavior.
Metadata
Metadata
Labels
Type
Projects
- StatusShow more project fieldsDone
Activity
github-actions commentedon Sep 8, 2026
🤖 Automated triage reproduction notes (agent-authored — trust but verify)
Agent analysis
Repro:
python/packages/core/agent_framework/_types.py::Content._add_text_reasoning_contentat lines 1561-1570 turns an actual empty Anthropic thinking block plus its signature delta into reasoning withtext=Noneand retained protected data;python/packages/anthropic/agent_framework_anthropic/_chat_client.py::_prepare_message_for_anthropicthen omits it at lines 992-1001. The trigger is a streamingthinkingblock withthinking="", followed bysignature_deltaandtool_use. Minimal repro: runtest_streaming_replay_preserves_empty_signed_thinking_block, which observes only the tool call in serialized replay.python/packages/anthropic/tests/test_anthropic_client.py::test_streaming_replay_preserves_empty_signed_thinking_blockagent-framework-core==1.17.0, agent-framework-anthropic==1.0.0b260827agent-framework-core==1.17.0, agent-framework-anthropic==1.0.0b260827leilei3167 commentedon Sep 9, 2026
looking at this
Python: preserve empty signed Anthropic thinking blocks on streaming …