Harden streaming deltas against null payloads

This commit is contained in:
Areon Lundkvist
2026-06-01 16:09:17 +02:00
committed by GitHub
parent e7d61c724f
commit f853a3fc67
+13 -13
View File
@@ -387,8 +387,8 @@ def _build_anthropic_payload(model, messages, temperature, max_tokens, stream=Fa
if m.get("content"):
content.append({"type": "text", "text": m["content"]})
for tc in m["tool_calls"]:
fn = tc.get("function", {})
args_str = fn.get("arguments", "{}")
fn = tc.get("function") or {}
args_str = fn.get("arguments") or "{}"
try:
args = json.loads(args_str) if isinstance(args_str, str) else args_str
except (json.JSONDecodeError, TypeError):
@@ -886,26 +886,26 @@ async def stream_llm(url: str, model: str, messages: List[Dict], temperature: fl
evt = j.get("type", "")
if evt == "content_block_start":
_anth_block_idx = j.get("index", _anth_block_idx + 1)
cb = j.get("content_block", {})
cb = j.get("content_block") or {}
_anth_block_type = cb.get("type", "text")
if _anth_block_type == "tool_use":
_anth_tool_blocks[_anth_block_idx] = {
"id": cb.get("id", f"call_{_anth_block_idx}"),
"name": cb.get("name", ""),
"id": cb.get("id") or f"call_{_anth_block_idx}",
"name": cb.get("name") or "",
"arguments": "",
}
elif evt == "content_block_delta":
delta = j.get("delta", {})
delta = j.get("delta") or {}
delta_type = delta.get("type", "")
if delta_type == "text_delta":
text = delta.get("text", "")
text = delta.get("text") or ""
if text:
yield f'data: {json.dumps({"delta": text})}\n\n'
elif delta_type == "input_json_delta":
# Accumulate tool arguments JSON
idx = j.get("index", _anth_block_idx)
if idx in _anth_tool_blocks:
partial = delta.get("partial_json", "")
partial = delta.get("partial_json") or ""
_anth_tool_blocks[idx]["arguments"] += partial
# Stream tool arg deltas for doc tools
if partial and _anth_tool_blocks[idx].get("name") in ("create_document", "update_document", "edit_document"):
@@ -1000,14 +1000,14 @@ async def stream_llm(url: str, model: str, messages: List[Dict], temperature: fl
u = j["usage"]
yield f'data: {json.dumps({"type": "usage", "data": {"input_tokens": u.get("prompt_tokens", 0), "output_tokens": u.get("completion_tokens", 0)}})}\n\n'
elif "choices" in j:
delta = j["choices"][0].get("delta", {})
delta = j["choices"][0].get("delta") or {}
if isinstance(delta, dict):
# Text content
# Reasoning tokens (VLLM --reasoning-parser, e.g. Qwen3/DeepSeek-R1)
reasoning = delta.get("reasoning_content", "")
reasoning = delta.get("reasoning_content") or ""
if reasoning:
yield f'data: {json.dumps({"delta": reasoning, "thinking": True})}\n\n'
content = delta.get("content", "")
content = delta.get("content") or ""
if content:
# Some thinking backends start normal content with a
# stray closing tag. Repair only that shape; do not
@@ -1018,13 +1018,13 @@ async def stream_llm(url: str, model: str, messages: List[Dict], temperature: fl
_first_content_sent = True
yield f'data: {json.dumps({"delta": content})}\n\n'
# Native tool calls — accumulate across chunks
for tc in delta.get("tool_calls", []):
for tc in delta.get("tool_calls") or []:
idx = tc.get("index", 0)
if idx not in _tc_acc:
_tc_acc[idx] = {"id": "", "name": "", "arguments": ""}
if tc.get("id"):
_tc_acc[idx]["id"] = tc["id"]
func = tc.get("function", {})
func = tc.get("function") or {}
if func.get("name"):
_tc_acc[idx]["name"] = func["name"]
if "arguments" in func: