fix(anthropic adapter): stop indexing choices[0] on choiceless streaming chunks - #35314
Conversation
…ing chunks Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
|
Greptile SummaryFixes Anthropic-compatible streaming for OpenAI chunks with empty
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| litellm/llms/anthropic/experimental_pass_through/adapters/streaming_iterator.py | Adds equivalent sync and async handling for choiceless metadata and final usage chunks while preserving the existing message-delta merge lifecycle. |
| tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_streaming_iterator_empty_choices.py | Adds focused regression tests covering a leading metadata-only chunk and a final choiceless usage chunk. |
Reviews (2): Last reviewed commit: "fix(anthropic adapter): type _handle_cho..." | Re-trigger Greptile
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
…_choices Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
Merge conflict with Proof of fix, re-run on the merged head. Upstream is a local OpenAI-compatible server on :8123 that streams a metadata chunk with After, on :4000 at 4068b4a; the choiceless usage chunk lands as Before, on :4001 at 81ff7cb; the stream dies right after Screenshots of the same two runs, captured on the pre-merge commit 0b809cf: |



TLDR
Problem this solves:
choices: []chunks crash/v1/messagesstreamingIndexError: list index out of rangemid-streamchoicesis valid OpenAI-compatible streaming (vLLM usage chunk)How it solves it:
message_deltaRelevant issues
Linear ticket
Resolves LIT-5034
Pre-Submission checklist
@greptileaito re-request a review after pushing changes)Delays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
Screenshots / Proof of Fix
An OpenAI-compatible upstream that emits a chunk with
"choices": []is required to hit this, and no hosted provider I have credentials for emits one on demand, so the upstream here is a small OpenAI-compatible server that streams the exact chunk shape vLLM sends (vllm/entrypoints/openai/serving_chat.pyemitschoices=[], usage=final_usage). Everything else in the path is the real proxy, the real router, and the real/v1/messagesadapterUpstream server (
mock_vllm.py), streaming a choiceless chunk plus three content chunks, a finish chunk, and a final usage chunk:Config:
Command (identical for both runs):
Before, at
71b825a7f0; the stream dies right aftermessage_start, no content ever reaches the client:After, at
0b809cf7d6; full Anthropic event sequence, content intact, upstream usage carried ontomessage_delta:Same repro without
stream_options.include_usagein the config crashes identically before the fix and passes after, so this is not gated on the usage optionType
🐛 Bug Fix
Changes
AnthropicStreamWrapper.__next__and__anext__ran_should_start_new_content_block(chunk)on every chunk, and that helper (along with_is_blank_deltaand theis_final_chunkcomputation) readschunk.choices[0]unconditionally. A chunk with no choices carries no content-block information at all, so both loops now consume it before the state machine runs:_handle_choiceless_chunkkeeps the existing hold-and-merge semantics: when amessage_deltais being held for its stop_reason and the choiceless chunk carries usage, the usage is merged into that held chunk and the mergedmessage_deltais queued, exactly as the choice-carrying usage chunk path already did. Otherwise the chunk is dropped, since Anthropic's SSE has no event to represent itRegression coverage lives in
tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_streaming_iterator_empty_choices.py: one test streams a metadata-only chunk ahead of the content and asserts the full text still arrives, the other puts the choiceless usage chunk last and asserts its tokens land onmessage_delta. Both fail on the parent commit (the sync path swallows the IndexError into a truncated stream, the async path propagates it)Final Attestation