Skip to content

fix(responses): emit content_part.added event for non-OpenAI models#24445

Merged
krrish-berri-2 merged 2 commits intoBerriAI:mainfrom
quora:fix/missing-content-part-added
Mar 31, 2026
Merged

fix(responses): emit content_part.added event for non-OpenAI models#24445
krrish-berri-2 merged 2 commits intoBerriAI:mainfrom
quora:fix/missing-content-part-added

Conversation

@nielei3
Copy link
Copy Markdown
Contributor

@nielei3 nielei3 commented Mar 23, 2026

Summary

LiteLLMCompletionStreamingIterator (used for non-OpenAI providers like Claude, Gemini) defined create_content_part_added_event() but never called it. This meant the spec-required response.content_part.added SSE event was missing from the stream.

Downstream parsers process content_part.added to initialize the text part structure. When output_text.delta arrived before the text part existed, they would fail with text part <id> not found.

Fix

Call create_content_part_added_event() in _ensure_output_item_for_chunk() immediately after appending output_item.added for message items. This matches the event ordering that native Responses API providers (OpenAI) already emit.

Event ordering (before → after)

Before (broken):

response.output_item.added (message)
response.output_text.delta  ← parser fails here

After (fixed):

response.output_item.added (message)
response.content_part.added  ← new
response.output_text.delta   ← parser works

Testing

Verified with e2e streaming tests against Claude and Gemini models through the Responses API.

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 23, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
litellm Ready Ready Preview, Comment Mar 23, 2026 10:31pm

Request Review

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Mar 23, 2026

CLA assistant check
All committers have signed the CLA.

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq bot commented Mar 23, 2026

Merging this PR will not alter performance

✅ 16 untouched benchmarks


Comparing quora:fix/missing-content-part-added (1716956) with main (63425b4)

Open in CodSpeed

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 23, 2026

Greptile Summary

This PR fixes a spec compliance gap in LiteLLMCompletionStreamingIterator where the required response.content_part.added SSE event was never emitted for non-OpenAI providers (Claude, Gemini, etc.). Downstream parsers need this event to initialise the text-part structure before any response.output_text.delta events arrive.

Changes:

  • streaming_iterator.py: Calls create_content_part_added_event() inside _ensure_output_item_for_chunk() immediately after enqueuing output_item.added for message items. A flag guard (sent_content_part_added_event) prevents double-emission. The sequence number increments correctly via the existing create_content_part_added_event() helper.
  • test_anthropic_completion.py: The existing integration test is updated to include CONTENT_PART_ADDED in its expected event sequence, making it correctly verify the new behaviour.
  • test_litellm_completion_responses.py: Three new unit tests (using __new__ + MagicMock, no network calls) cover: message items emit the event, reasoning items do not, and the event is not duplicated on repeated calls.

The fix is narrowly scoped to message items: reasoning-first output items still return early without emitting content_part.added, which is consistent with the spec and existing design.

Confidence Score: 4/5

  • This PR is safe to merge — it adds a missing required SSE event with no breaking changes to existing consumers.
  • The fix is minimal and well-targeted: a single if block added in one code path, guarded by an existing flag. Three new unit tests validate the added, not-added, and idempotency cases. The integration test is correctly updated. No backwards-incompatible changes; consumers that previously ignored the missing event will now receive the required event in spec-correct order. One point deducted because the inner if not self.sent_content_part_added_event guard is logically redundant (already covered by the outer sent_output_item_added_event gate), leaving a small ambiguity in the code's intent that could confuse future readers.
  • No files require special attention; all changes are straightforward and well-tested.

Important Files Changed

Filename Overview
litellm/responses/litellm_completion_transformation/streaming_iterator.py Core fix: emits content_part.added immediately after output_item.added for message items by calling the previously unused create_content_part_added_event() inside _ensure_output_item_for_chunk. Logic is correct; sequence numbers increment properly. The guard if not self.sent_content_part_added_event is technically redundant but harmless (outer sent_output_item_added_event already prevents re-entry).
tests/llm_translation/test_anthropic_completion.py Integration test updated to include CONTENT_PART_ADDED in the expected event sequence. The assertion block for the new event was already present; the diff only adds the event to expected_events, making the test stricter and correctly validating the fix.
tests/test_litellm/responses/litellm_completion_transformation/test_litellm_completion_responses.py New TestEnsureOutputItemContentPartAdded class covers three key cases (message item, reasoning item, idempotency). All tests are pure unit tests with no network calls, using __new__ to bypass __init__ and MagicMock for stream chunks. Missing a test for reasoning-then-text transitions, but that is a pre-existing design gap.

Sequence Diagram

sequenceDiagram
    participant Provider as Non-OpenAI Provider<br/>(Claude / Gemini)
    participant Iterator as LiteLLMCompletionStreamingIterator
    participant Consumer as Downstream Parser

    Note over Iterator,Consumer: Before fix (broken)
    Provider->>Iterator: streaming chunk (text delta)
    Iterator->>Consumer: response.created
    Iterator->>Consumer: response.in_progress
    Iterator->>Consumer: response.output_item.added (message)
    Iterator->>Consumer: response.output_text.delta
    Note over Consumer: ❌ Fails — text part not initialised

    Note over Iterator,Consumer: After fix (correct)
    Provider->>Iterator: streaming chunk (text delta)
    Iterator->>Consumer: response.created
    Iterator->>Consumer: response.in_progress
    Iterator->>Consumer: response.output_item.added (message)
    Iterator->>Consumer: response.content_part.added ← NEW
    Iterator->>Consumer: response.output_text.delta
    Note over Consumer: ✅ Works — text part already initialised
    Iterator->>Consumer: response.output_text.done
    Iterator->>Consumer: response.content_part.done
    Iterator->>Consumer: response.output_item.done
    Iterator->>Consumer: response.completed
Loading

Reviews (3): Last reviewed commit: "test(responses): update expected events ..." | Re-trigger Greptile

LiteLLMCompletionStreamingIterator defined create_content_part_added_event()
but never called it, so non-OpenAI providers (Claude, Gemini, etc.) skipped
this spec-required event. Downstream parsers that process content_part.added
to initialize the text part structure would fail when output_text.delta
arrived before the text part existed.
…_part.added

- Update test_anthropic_via_responses_api expected_events to include
  CONTENT_PART_ADDED between OUTPUT_ITEM_ADDED and OUTPUT_TEXT_DELTA
- Add TestEnsureOutputItemContentPartAdded with 3 mock tests:
  message item emits content_part.added, reasoning item does not,
  and the event is only emitted once
@nielei3 nielei3 force-pushed the fix/missing-content-part-added branch from a71808c to 1716956 Compare March 23, 2026 22:30
@krrish-berri-2 krrish-berri-2 merged commit ee4ded4 into BerriAI:main Mar 31, 2026
37 of 39 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants