fix(anthropic/stream): tolerate missing 'text' on content_block_start (#28067)

Anthropic Messages streaming spec: the initial text on a content_block_start
text block is conventionally an empty string and the real content arrives via
subsequent content_block_delta chunks. Some Anthropic-compatible upstreams
omit the field entirely rather than sending "", which previously caused
chunk_parser to raise KeyError and abort the stream with
MidStreamFallbackError. Use .get('text', '') so the parser keeps the existing
text='' default and lets the deltas accumulate normally.

Adds a regression test in tests/llm_translation/test_anthropic_completion.py.
This commit is contained in:
Tai An 2026-05-16 09:07:33 -07:00
parent e182a5e0ba
commit 1eb1d2388b
2 changed files with 26 additions and 1 deletions

View file

@ -820,7 +820,13 @@ class ModelResponseIterator:
"type"
]
if content_block_start["content_block"]["type"] == "text":
text = content_block_start["content_block"]["text"]
# Anthropic Messages streaming spec: the initial text on a
# content_block_start text block is conventionally "" and the
# real content arrives via subsequent content_block_delta
# chunks. Some Anthropic-compatible upstreams omit the field
# entirely rather than sending "", so default to "" to avoid
# aborting the stream with KeyError.
text = content_block_start["content_block"].get("text", "")
elif (
content_block_start["content_block"]["type"] == "tool_use"
or content_block_start["content_block"]["type"] == "server_tool_use"

View file

@ -262,6 +262,25 @@ def test_anthropic_tool_streaming():
assert tool_use["index"] == correct_tool_index
def test_anthropic_content_block_start_text_missing_field():
"""
Regression for #28067: some Anthropic-compatible upstreams omit the
optional `text` field on a content_block_start text block. The actual
content arrives via subsequent content_block_delta chunks, so the parser
must tolerate the missing field instead of raising KeyError and aborting
the stream.
"""
response_iter = ModelResponseIterator([], False)
chunk = {
"type": "content_block_start",
"index": 0,
# 'text' field intentionally omitted (observed in the wild)
"content_block": {"type": "text"},
}
# Should not raise.
response_iter.chunk_parser(chunk=chunk)
def test_process_anthropic_headers_empty():
result = process_anthropic_headers({})
assert result == {}, "Expected empty dictionary for no input"