fix(proxy): validate Responses guardrail event names

This commit is contained in:
HootRock 2026-10-23 08:26:33 +08:00
parent dfe2b4fd87
commit 4fb052e786
2 changed files with 28 additions and 1 deletions

View file

@ -251,6 +251,7 @@ _TOOL_SEARCH_CALL_TYPE: Final = "tool_search_call"
_OUTPUT_TOOL_CALL_ITEM_TYPES: Final = _TOOL_CALL_ITEM_TYPES | {_TOOL_SEARCH_CALL_TYPE}
_SCANNED_TOOL_SEARCH_NAME: Final = "tool_search"
_TOOL_SEARCH_ARGUMENTS_ADAPTER: Final = TypeAdapter(dict[str, object])
_STREAM_EVENT_TYPE_ADAPTER: Final = TypeAdapter(str)
_TOOL_CALL_PAYLOAD_FIELDS: Final[Mapping[str, str]] = MappingProxyType(
{"function_call": "arguments", "custom_tool_call": "input"}
)
@ -1796,7 +1797,7 @@ class OpenAIResponsesHandler(BaseTranslation):
else self._standalone_block_events(exc)
)
return tuple(
f"event: {event.type.value if isinstance(event.type, ResponsesAPIStreamEvents) else event.type}\n"
f"event: {_STREAM_EVENT_TYPE_ADAPTER.validate_python(stream_item_field(event, 'type'))}\n"
f"data: {event.model_dump_json(exclude_none=True, exclude_unset=True, serialize_as_any=True)}\n\n".encode()
for event in events
)

View file

@ -34,6 +34,7 @@ from litellm.constants import RETURN_RAW_MODEL_NAME_METADATA_KEY
from litellm.integrations.custom_logger import CustomLogger
from litellm.proxy._types import UserAPIKeyAuth
from litellm.proxy.common_request_processing import create_response
from litellm.proxy.common_utils.responses_stream_errors import ResponsesStreamErrorState
from litellm.proxy.proxy_server import (
_apply_streaming_chunk_hooks,
_fast_serialize_simple_model_response_stream,
@ -145,6 +146,31 @@ async def test_responses_stream_dispatches_named_events_without_a_chat_done_sent
assert payload["type"] == event_type
assert json.loads(frames[1].split("data: ", 1)[1])["delta"] == "hello"
@pytest.mark.parametrize("binary", [False, True])
@pytest.mark.parametrize(
("event_type", "event_line"),
[
(None, ""),
("", ""),
("response.delta\ninjected", ""),
("response.delta\rinjected", ""),
("response.delta\r\ninjected", ""),
("response.vendor_delta", "event: response.vendor_delta\n"),
],
)
def test_responses_event_names_preserve_payload_without_injecting_sse_lines(
event_type: str | None, event_line: str, binary: bool
) -> None:
payload: Final = {"type": event_type, "sequence_number": 0}
data_line: Final = f"data: {json.dumps(payload)}\n\n"
frame: Final = data_line.encode("utf-8") if binary else data_line
expected: Final = (event_line + data_line).encode("utf-8") if binary else event_line + data_line
state: Final = ResponsesStreamErrorState()
state.observe_chunk(payload)
assert state.mark_emitted(frame) == expected
class _FakeStream:
def __init__(self, chunks, hidden_params=None):
self._chunks = chunks