Pre-flight Checklist
Describe the Bug
ag_ui_langgraph drops Anthropic extended-thinking signature on Bedrock Converse streams
Summary
When ChatBedrockConverse streams Anthropic extended-thinking output, the reasoning block's cryptographic signature is silently dropped and the reasoning block is closed one chunk early. Downstream, no REASONING_ENCRYPTED_VALUE event is ever emitted, so the assistant message cannot be replayed to Bedrock on the next Converse turn.
Affected: ag_ui_langgraph/utils.py::resolve_reasoning_content and ag_ui_langgraph/agent.py::LangGraphAgent.handle_reasoning_event.
Bedrock Converse stream shape
Unlike Anthropic's native API, Bedrock splits a single thinking block across chunks and delivers the signature on its own chunk with no text field, preceded by an empty-text flush:
[{'type': 'reasoning_content', 'reasoning_content': {'text': 'The'}, 'index': 0}]
[{'type': 'reasoning_content', 'reasoning_content': {'text': ' user wants ...'}, 'index': 0}]
[{'type': 'reasoning_content', 'reasoning_content': {'text': ''}, 'index': 0}] # empty-text flush
[{'type': 'reasoning_content', 'reasoning_content': {'signature': 'EpcCC...=='}, 'index': 0}] # signature-only
[] # reasoning block closed
[{'type': 'text', 'text': 'Streaming LLM responses is', 'index': 1}]
...
Core issues
-
resolve_reasoning_content gates on truthy text — the signature-only chunk fails if rc.get("text"): and resolves to None:
if block_type == "reasoning_content" and isinstance(block.get("reasoning_content"), dict):
rc = block["reasoning_content"]
if rc.get("text"): # signature-only chunk falls through
result = LangGraphReasoning(text=rc["text"], ...)
if rc.get("signature"):
result["signature"] = rc["signature"]
return result
-
Empty-text chunk triggers premature close-out — the resolver also returns None for {"text": ""}. In agent.py, reasoning_data is None with an active reasoning_process fires REASONING_MESSAGE_END + REASONING_END immediately, one chunk before the signature arrives.
-
handle_reasoning_event returns before signature accumulation — even if (1) were fixed, the method's early return discards the signature:
if not reasoning_data["text"]: # signature-only chunk has "" text
if reasoning_data.get("id"):
self.active_run["pending_reasoning_id"] = reasoning_data["id"]
return # <-- signature never stored
...
if reasoning_data.get("signature"):
self.active_run["reasoning_process"]["signature"] = reasoning_data["signature"]
Net effect: REASONING_ENCRYPTED_VALUE is never emitted for Bedrock Converse; on the next turn, replaying the assistant message either silently degrades multi-turn thinking or, in strict-verification modes, is rejected by Bedrock.
Reproduction
Steps to Reproduce
import os
from langchain_aws import ChatBedrockConverse
llm = ChatBedrockConverse(
model="eu.anthropic.claude-sonnet-4-6",
region_name=os.environ.get("AWS_REGION", "eu-central-1"),
provider="anthropic",
max_tokens=128000,
additional_model_request_fields={
"thinking": {"type": "adaptive"},
"output_config": {"effort": "medium"},
},
timeout=3600,
)
for chunk in llm.stream([
("system", "You are a helpful assistant."),
("human", "Explain streaming LLM responses in 2 sentences."),
]):
print(chunk.content)
Observed: a {'reasoning_content': {'text': ''}} chunk followed by a {'reasoning_content': {'signature': '...'}} chunk, both index: 0.
Minimal isolation of the bug against the library:
from ag_ui_langgraph.utils import resolve_reasoning_content
class C:
def __init__(self, content): self.content = content
# Text chunk — works
print(resolve_reasoning_content(C([
{"type": "reasoning_content", "reasoning_content": {"text": "The"}, "index": 0}
])))
# -> {'text': 'The', 'type': 'text', 'index': 0}
# Signature-only chunk — BUG: returns None, signature lost
print(resolve_reasoning_content(C([
{"type": "reasoning_content", "reasoning_content": {"signature": "EpcCC=="}, "index": 0}
])))
# -> None (expected: LangGraphReasoning with signature="EpcCC==")
# Empty-text chunk — BUG: returns None, triggers premature reasoning close-out
print(resolve_reasoning_content(C([
{"type": "reasoning_content", "reasoning_content": {"text": ""}, "index": 0}
])))
# -> None (expected: pass-through no-op, keep reasoning open)
Expected Behavior
Correctly, parse
Environment
## Environment
- `langchain-aws` (`ChatBedrockConverse`) against Bedrock Converse
- Model: `eu.anthropic.claude-sonnet-4-6` with `additional_model_request_fields={"thinking": {"type": "adaptive"}}`
- `ag_ui_langgraph` —v0.0.42
- `ag_ui_protocol` —v0.1.19
Screenshots
No response
Logs & Errors
Additional Context
No response
Pre-flight Checklist
Describe the Bug
ag_ui_langgraphdrops Anthropic extended-thinking signature on Bedrock Converse streamsSummary
When
ChatBedrockConversestreams Anthropic extended-thinking output, the reasoning block's cryptographic signature is silently dropped and the reasoning block is closed one chunk early. Downstream, noREASONING_ENCRYPTED_VALUEevent is ever emitted, so the assistant message cannot be replayed to Bedrock on the next Converse turn.Affected:
ag_ui_langgraph/utils.py::resolve_reasoning_contentandag_ui_langgraph/agent.py::LangGraphAgent.handle_reasoning_event.Bedrock Converse stream shape
Unlike Anthropic's native API, Bedrock splits a single thinking block across chunks and delivers the signature on its own chunk with no
textfield, preceded by an empty-text flush:Core issues
resolve_reasoning_contentgates on truthy text — the signature-only chunk failsif rc.get("text"):and resolves toNone:Empty-text chunk triggers premature close-out — the resolver also returns
Nonefor{"text": ""}. Inagent.py,reasoning_data is Nonewith an activereasoning_processfiresREASONING_MESSAGE_END+REASONING_ENDimmediately, one chunk before the signature arrives.handle_reasoning_eventreturns before signature accumulation — even if (1) were fixed, the method's early return discards the signature:Net effect:
REASONING_ENCRYPTED_VALUEis never emitted for Bedrock Converse; on the next turn, replaying the assistant message either silently degrades multi-turn thinking or, in strict-verification modes, is rejected by Bedrock.Reproduction
Steps to Reproduce
Observed: a
{'reasoning_content': {'text': ''}}chunk followed by a{'reasoning_content': {'signature': '...'}}chunk, bothindex: 0.Minimal isolation of the bug against the library:
Expected Behavior
Correctly, parse
Environment
Screenshots
No response
Logs & Errors
Additional Context
No response