test(responses): update expected events and add mock test for content_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
This commit is contained in:
Lei Nie 2026-03-23 22:21:29 +00:00
parent d560e4c009
commit 1716956520
3 changed files with 99 additions and 3 deletions

View File

@ -850,9 +850,10 @@ class LiteLLMCompletionStreamingIterator(ResponsesAPIStreamingIterator):
# items. The OpenAI Responses spec requires this event before any
# output_text.delta events so downstream parsers can initialize the
# text part structure.
self.sent_content_part_added_event = True
content_part_event = self.create_content_part_added_event()
self._pending_response_events.append(content_part_event)
if not self.sent_content_part_added_event:
self.sent_content_part_added_event = True
content_part_event = self.create_content_part_added_event()
self._pending_response_events.append(content_part_event)
return
async def __anext__(

View File

@ -1594,6 +1594,7 @@ def test_anthropic_via_responses_api():
ResponsesAPIStreamEvents.RESPONSE_CREATED,
ResponsesAPIStreamEvents.RESPONSE_IN_PROGRESS,
ResponsesAPIStreamEvents.OUTPUT_ITEM_ADDED,
ResponsesAPIStreamEvents.CONTENT_PART_ADDED,
ResponsesAPIStreamEvents.OUTPUT_TEXT_DELTA, # Can occur multiple times
ResponsesAPIStreamEvents.OUTPUT_TEXT_DONE,
ResponsesAPIStreamEvents.CONTENT_PART_DONE,

View File

@ -1899,3 +1899,97 @@ class TestStreamingIDConsistency:
else getattr(assistant_messages[0], "tool_calls", None)
)
assert tool_calls is not None and len(tool_calls) == 1
class TestEnsureOutputItemContentPartAdded:
"""Test that _ensure_output_item_for_chunk emits content_part.added after
output_item.added for message items."""
def _make_iterator(self):
"""Create a minimal LiteLLMCompletionStreamingIterator for testing."""
from unittest.mock import MagicMock
from litellm.responses.litellm_completion_transformation.streaming_iterator import (
LiteLLMCompletionStreamingIterator,
)
iterator = LiteLLMCompletionStreamingIterator.__new__(
LiteLLMCompletionStreamingIterator
)
iterator.sent_output_item_added_event = False
iterator.sent_content_part_added_event = False
iterator._sequence_number = 0
iterator._cached_item_id = None
iterator._cached_reasoning_item_id = None
iterator._reasoning_active = False
iterator._pending_response_events = []
return iterator
def _make_text_chunk(self):
"""Create a mock ModelResponseStream with a text delta."""
from unittest.mock import MagicMock
chunk = MagicMock()
delta = MagicMock()
delta.reasoning_content = None
delta.tool_calls = None
chunk.choices = [MagicMock(delta=delta)]
return chunk
def _make_reasoning_chunk(self):
"""Create a mock ModelResponseStream with a reasoning delta."""
from unittest.mock import MagicMock
chunk = MagicMock()
delta = MagicMock()
delta.reasoning_content = "thinking..."
delta.tool_calls = None
chunk.choices = [MagicMock(delta=delta)]
return chunk
def test_message_item_emits_content_part_added(self):
"""content_part.added must follow output_item.added for message items."""
from litellm.types.llms.openai import (
ContentPartAddedEvent,
OutputItemAddedEvent,
ResponsesAPIStreamEvents,
)
iterator = self._make_iterator()
chunk = self._make_text_chunk()
iterator._ensure_output_item_for_chunk(chunk)
events = iterator._pending_response_events
assert len(events) == 2
assert isinstance(events[0], OutputItemAddedEvent)
assert events[0].type == ResponsesAPIStreamEvents.OUTPUT_ITEM_ADDED
assert isinstance(events[1], ContentPartAddedEvent)
assert events[1].type == ResponsesAPIStreamEvents.CONTENT_PART_ADDED
assert events[1].part.type == "output_text"
assert iterator.sent_content_part_added_event is True
def test_reasoning_item_does_not_emit_content_part_added(self):
"""Reasoning items should not get a content_part.added event."""
from litellm.types.llms.openai import OutputItemAddedEvent
iterator = self._make_iterator()
chunk = self._make_reasoning_chunk()
iterator._ensure_output_item_for_chunk(chunk)
events = iterator._pending_response_events
assert len(events) == 1
assert isinstance(events[0], OutputItemAddedEvent)
assert iterator.sent_content_part_added_event is False
def test_only_emits_once(self):
"""Calling _ensure_output_item_for_chunk twice should not duplicate events."""
iterator = self._make_iterator()
chunk = self._make_text_chunk()
iterator._ensure_output_item_for_chunk(chunk)
iterator._ensure_output_item_for_chunk(chunk)
events = iterator._pending_response_events
assert len(events) == 2