fix(anthropic): type the decoded JSON mode answer as object and test non-object answers

Backport of #50470 to lts/1.108.x.
Cherry-picked from 6809224df9 (litellm_fix_anthropic_stream_fragment_json).
This commit is contained in:
mateo-berri 2026-11-07 01:00:36 -08:00
parent 6b2f831c9d
commit 302d5d64d1
3 changed files with 25 additions and 2 deletions

View file

@ -229,7 +229,9 @@ def _is_decoded_json_object(
def decoded_answer_object(text: str) -> Mapping[str, AnswerValue] | None:
try:
decoded: Final[object] = json.loads(text, parse_float=_source_float, parse_int=_source_int)
decoded: Final = _DECODED_JSON.validate_python(
json.loads(text, parse_float=_source_float, parse_int=_source_int)
)
except (json.JSONDecodeError, RecursionError):
return None
return decoded if _is_decoded_json_object(decoded) else None

View file

@ -2958,6 +2958,9 @@ _DIGITS_PAST_THE_INT_LIMIT: Final = "1" * (sys.get_int_max_str_digits() + 1)
(('{"n": 1e5}',), '{"n": 1e5}'),
(('{"values": false}',), "false"),
(('{"values": null}',), '{"values": null}'),
(("[1, 2]",), "[1, 2]"),
(('"x"',), '"x"'),
(("5",), "5"),
pytest.param(
('{"values": ' + "[" * 300 + "]" * 300 + "}",),
"[" * 300 + "]" * 300,

View file

@ -7909,6 +7909,24 @@ def test_convert_tool_response_to_message_keeps_arguments_too_deep_to_decode(arg
assert message.content == arguments
@pytest.mark.parametrize("arguments", ["[1, 2]", '"x"', "5"])
def test_convert_tool_response_to_message_keeps_a_non_object_answer_as_written(arguments: str) -> None:
message: Final = AnthropicConfig.convert_tool_response_to_message(
tool_calls=[
ChatCompletionToolCallChunk(
id="toolu_1",
type="function",
function=ChatCompletionToolCallFunctionChunk(name=RESPONSE_FORMAT_TOOL_NAME, arguments=arguments),
index=0,
)
],
response_format_schema=None,
)
assert message is not None
assert message.content == arguments
_DEEPLY_NESTED: Final = "[" * 300 + "]" * 300
@ -7926,7 +7944,7 @@ _DEEPLY_NESTED: Final = "[" * 300 + "]" * 300
)
def test_convert_tool_response_to_message_unwraps_a_deeply_nested_answer(
arguments: str, schema: dict[str, object] | None, expected: str
):
) -> None:
message = AnthropicConfig.convert_tool_response_to_message(
tool_calls=[
ChatCompletionToolCallChunk(