fix(proxy): send string and number subclasses whose repr shows their value as that value in Responses stream frames

This commit is contained in:
mateo-berri 2026-10-30 13:09:38 -07:00
parent 4180859208
commit 6b851e9371
2 changed files with 60 additions and 7 deletions

View file

@ -107,14 +107,27 @@ def _str_skips_its_repr(model: BaseModel) -> bool:
return kind.__str__ is BaseModel.__str__ and kind.__repr__ is not BaseModel.__repr__
def _plain_scalar(value: str | float) -> str | float:
if isinstance(value, str):
return str.__str__(value)
if isinstance(value, int):
return int.__int__(value)
return float.__float__(value)
def _repr_shows_its_value(value: str | float) -> bool:
plain: Final = _plain_scalar(value)
if isinstance(value, Enum):
return repr(value) == f"<{type(value).__name__}.{value.name}: {plain!r}>"
return repr(value) == repr(plain)
def _text(value: object, form: _BaseForm) -> object:
try:
if isinstance(value, (str, int, float)) and (form is _BaseForm.DUMPED or _repr_shows_its_value(value)):
return _plain_scalar(value)
if form is _BaseForm.PRINTED:
return repr(value)
if isinstance(value, int):
return int(value)
if isinstance(value, float):
return float(value)
if isinstance(value, BaseModel) and _str_skips_its_repr(value):
return repr(value)
return str(value)

View file

@ -64,6 +64,7 @@ from litellm.types.llms.openai import (
ResponseFailedEvent,
ResponseIncompleteEvent,
ResponsesAPIResponse,
ResponsesAPIStreamEvents,
)
from litellm.types.proxy.litellm_pre_call_utils import RedactedDict
from litellm.types.utils import Delta, ModelResponseStream, StreamingChoices, Usage
@ -340,6 +341,11 @@ class _Score(float):
pass
class _MaskedCount(int):
def __repr__(self) -> str:
return "_MaskedCount([secure])"
_WHEN: Final = datetime(2026, 10, 30, 12, 0, tzinfo=timezone.utc)
@ -348,10 +354,9 @@ _WHEN: Final = datetime(2026, 10, 30, 12, 0, tzinfo=timezone.utc)
[
(_WHEN, repr(_WHEN), str(_WHEN)),
(_RigColor.RED, repr(_RigColor.RED), str(_RigColor.RED)),
(HTTPStatus.BAD_REQUEST, repr(HTTPStatus.BAD_REQUEST), 400),
(_Score(0.5), repr(_Score(0.5)), 0.5),
(_MaskedCount(7), repr(_MaskedCount(7)), 7),
],
ids=["datetime", "enum", "int_enum", "float_subclass"],
ids=["datetime", "enum", "number_subclass_with_its_own_repr"],
)
def test_responses_stream_frames_print_values_json_cannot_take_as_base_printed_them(
nested: object, printed: str, dumped: object
@ -366,6 +371,41 @@ def test_responses_stream_frames_print_values_json_cannot_take_as_base_printed_t
assert json.loads(error_frame.split("data: ", 1)[1])["error"]["nested"] == dumped
class _RigCode(str, Enum):
BLOCKED = "blocked_by_rig"
@pytest.mark.parametrize(
("nested", "plain"),
[(_RigCode.BLOCKED, "blocked_by_rig"), (HTTPStatus.BAD_REQUEST, 400), (_Score(0.5), 0.5)],
ids=["str_enum", "int_enum", "float_subclass"],
)
def test_responses_stream_frames_send_string_and_number_subclasses_as_their_plain_value(
nested: object, plain: object
) -> None:
assert _responses_frame_payload(nested)["nested"] == plain
_DUMPED_DELTA: Final = OutputTextDeltaEvent(
type=ResponsesAPIStreamEvents.OUTPUT_TEXT_DELTA,
item_id="msg_1",
output_index=0,
content_index=0,
delta="hi",
sequence_number=3,
)
@pytest.mark.parametrize("mapping", [dict, MappingProxyType], ids=["dict", "mapping_proxy"])
def test_responses_sse_chunk_a_hook_dumped_from_an_event_streams_as_that_event(
mapping: Callable[[Mapping[str, object]], Mapping[str, object]],
) -> None:
frame: Final = _format_responses_sse_chunk(chunk=mapping(_DUMPED_DELTA.model_dump()))
assert isinstance(frame, str)
assert json.loads(frame.removeprefix("data: ")) == json.loads(_DUMPED_DELTA.model_dump_json())
@pytest.mark.parametrize("mapping", [dict, MappingProxyType], ids=["dict", "mapping_proxy"])
def test_responses_stream_frames_turn_mapping_keys_json_cannot_take_into_strings(
mapping: Callable[[Mapping[object, object]], Mapping[object, object]],