fix(guardrails): match Responses stream event types by value so enum-typed events close the open item

This commit is contained in:
mateo-berri 2026-08-31 17:19:55 -07:00
parent 9b15febe13
commit 38825cf9c6
2 changed files with 60 additions and 1 deletions

View file

@ -1053,7 +1053,7 @@ class _OpenItemState:
def _open_item_state(responses_so_far: Sequence[object]) -> _OpenItemState | None:
typed: Final = tuple((str(stream_item_field(event, "type") or ""), event) for event in responses_so_far)
typed: Final = tuple((stream_item_field(event, "type"), event) for event in responses_so_far)
added: Final = tuple(
(added_index, stream_item_field(event, "item"))
for event_type, event in typed

View file

@ -1297,6 +1297,65 @@ class TestBuildBlockSseChunks:
assert completed["output"][0]["content"][0]["text"] == "Blocked by policy."
assert completed["usage"] == {"input_tokens": 7, "output_tokens": 21, "total_tokens": 28}
def test_continuation_closes_open_item_given_pydantic_events_with_enum_types(self):
from litellm.types.llms.openai import (
BaseLiteLLMOpenAIResponseObject,
ContentPartAddedEvent,
OutputItemAddedEvent,
OutputTextDeltaEvent,
ResponsesAPIStreamEvents,
)
handler = OpenAIResponsesHandler()
open_item = GenericResponseOutputItem.model_validate(
{"type": "message", "id": "msg_live", "status": "in_progress", "role": "assistant", "content": []}
)
yielded = [
OutputItemAddedEvent(
type=ResponsesAPIStreamEvents.OUTPUT_ITEM_ADDED, output_index=0, item=open_item
),
ContentPartAddedEvent(
type=ResponsesAPIStreamEvents.CONTENT_PART_ADDED,
item_id="msg_live",
output_index=0,
content_index=0,
part=BaseLiteLLMOpenAIResponseObject.model_validate(
{"type": "output_text", "text": "", "annotations": []}
),
),
OutputTextDeltaEvent(
type=ResponsesAPIStreamEvents.OUTPUT_TEXT_DELTA,
item_id="msg_live",
output_index=0,
content_index=0,
delta="partial ",
),
OutputTextDeltaEvent(
type=ResponsesAPIStreamEvents.OUTPUT_TEXT_DELTA,
item_id="msg_live",
output_index=0,
content_index=0,
delta="text",
),
]
payloads = self._payloads(
handler.build_block_sse_chunks(
self._exc(original_response=yielded), stream_started=True, responses_so_far=yielded
)
)
types = [payload["type"] for payload in payloads]
assert types[:3] == [
"response.output_text.done",
"response.content_part.done",
"response.output_item.done",
]
assert payloads[0]["text"] == "partial text"
assert payloads[2]["item"]["id"] == "msg_live"
assert payloads[2]["item"]["status"] == "completed"
assert payloads[2]["item"]["content"][0]["text"] == "partial text"
assert types[3] == "response.output_item.added"
assert payloads[3]["output_index"] == 1
def test_continuation_without_open_item_emits_no_closing_events(self):
handler = OpenAIResponsesHandler()
yielded = [