Fix KeyError 'text' on content_block_start in Anthropic streaming

Some Anthropic-compatible upstream providers (e.g. api-cc.freemodel.dev
proxying Claude Haiku 4.5) emit content_block_start events for text
blocks without the optional 'text' field, causing KeyError in the
chunk_parser. Per Anthropic's streaming spec the initial text on
content_block_start is conventionally empty (""), and the actual
content arrives via subsequent content_block_delta chunks regardless.

Use .get("text", "") so the parser tolerates the missing field
instead of aborting the stream with MidStreamFallbackError.

Reproduction:
  curl -sN -X POST http://proxy/v1/chat/completions \
    -H 'Authorization: Bearer $KEY' \
    -d '{"model":"claude-haiku-4-5","messages":[...],"stream":true}'

Before patch: stream aborts with KeyError: 'text' at handler.py:789
After patch: stream completes normally.
This commit is contained in:
Qisthi Ramadhani 2026-05-16 22:24:27 +07:00
parent e58a561caa
commit 9befaab288
2 changed files with 33 additions and 1 deletions

View file

@ -820,7 +820,12 @@ class ModelResponseIterator:
"type"
]
if content_block_start["content_block"]["type"] == "text":
text = content_block_start["content_block"]["text"]
# Defensive .get() — some upstream Anthropic-compatible
# providers omit the "text" field on content_block_start
# for "text" blocks (spec says it should be ""). Fall back
# to "" instead of raising KeyError, since the actual text
# arrives via subsequent content_block_delta chunks anyway.
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

@ -1901,3 +1901,30 @@ def test_non_bash_tool_result_skipped():
assert (
len(code_results) == 0
), f"Expected 0 code_interpreter_results for text_editor result, got {len(code_results)}"
def test_content_block_start_text_missing_text_field():
"""
Regression test: some Anthropic-compatible upstreams omit the optional
"text" field on content_block_start events whose type == "text". Per
the Anthropic streaming spec the value is conventionally "", and the
real text arrives via subsequent content_block_delta chunks anyway,
so the parser must tolerate the missing field instead of raising
KeyError mid-stream.
"""
chunk = {
"type": "content_block_start",
"index": 0,
# NOTE: "text" field intentionally omitted to mirror upstream payloads
# observed from Anthropic-compatible providers (e.g. proxies fronting
# claude-haiku-4-5-20251001) that skip the empty default.
"content_block": {"type": "text"},
}
iterator = ModelResponseIterator(None, sync_stream=True)
# Must not raise KeyError: 'text'
parsed = iterator.chunk_parser(chunk=chunk)
assert parsed is not None
assert parsed.choices[0].delta.content == ""