From 8bf909dbe07ead51571602eda3db960615b9512f Mon Sep 17 00:00:00 2001 From: Joshua Valluru <326636767+joshua-berri@users.noreply.github.com> Date: Tue, 20 Oct 2026 23:39:40 -0700 Subject: [PATCH] fix(anthropic): preserve streamed values conversion from main --- litellm/llms/anthropic/chat/handler.py | 2 + .../chat/test_anthropic_chat_handler.py | 41 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/litellm/llms/anthropic/chat/handler.py b/litellm/llms/anthropic/chat/handler.py index 0dd64b69e40..718ff73a956 100644 --- a/litellm/llms/anthropic/chat/handler.py +++ b/litellm/llms/anthropic/chat/handler.py @@ -116,6 +116,8 @@ def _prefix_verdict(buffered_json: str, schema: ResponseFormatSchema, tool_name: return "hold" if first_value is None and _NAME_VALUE_PREFIX.match(buffered_json): return "undecided" + if first_key.group(1) == "values" and "values" not in schema.property_names: + return "hold" if first_key.group(1) in schema.property_names or not schema.property_names or schema.accepts_a_lone_undeclared_key: return "pass_through" return _first_value_verdict(buffered_json[first_key.end() :], schema) diff --git a/tests/unit/llms/anthropic/chat/test_anthropic_chat_handler.py b/tests/unit/llms/anthropic/chat/test_anthropic_chat_handler.py index 7ecd43a72c4..41d22259edd 100644 --- a/tests/unit/llms/anthropic/chat/test_anthropic_chat_handler.py +++ b/tests/unit/llms/anthropic/chat/test_anthropic_chat_handler.py @@ -2742,3 +2742,44 @@ def test_json_envelope_split_before_its_value_keeps_the_schema_object() -> None: split: Final = envelope.index(":") + 1 contents: Final = _streamed_json_mode_contents((envelope[:split], envelope[split:])) assert json.loads("".join(contents)) == _STREAMED_RISK_OBJECT + + +@pytest.mark.parametrize( + "schema, values", + [ + pytest.param( + {"type": "object", "additionalProperties": True}, {"alpha": 1, "beta": 2}, id="schema_without_properties" + ), + pytest.param( + { + "type": "object", + "properties": {"summary": {"type": "string"}}, + "additionalProperties": {"type": "object"}, + }, + {"user_1": {"summary": "likes tea"}}, + id="undeclared_key_the_schema_allows", + ), + pytest.param(_STREAMED_RISK_SCHEMA, [3, 5, 8], id="values_holding_a_list"), + ], +) +def test_streamed_json_mode_values_wrapper_is_unwrapped_like_the_non_stream_answer( + schema: dict[str, object], values: object +): + wrapped = {"values": values} + encoded = json.dumps(wrapped) + + contents = _streamed_json_mode_contents((encoded[:6], encoded[6:14], encoded[14:]), schema) + + assert json.loads("".join(contents)) == values + assert json.loads("".join(contents)) == unwrap_response_format_envelope( + wrapped, response_format_schema(schema), RESPONSE_FORMAT_TOOL_NAME + ) + +def test_streamed_json_mode_declared_values_key_streams_as_it_arrives(): + schema = {"type": "object", "properties": {"values": {"type": "array"}}, "required": ["values"]} + answer = json.dumps({"values": [3, 5, 8]}) + + contents = _streamed_json_mode_contents((answer[:6], answer[6:14], answer[14:]), schema) + + assert "".join(contents) == answer + assert any(contents[:-2])