From 0d29da0af53bab2fca6775378a370f0c096bcdb8 Mon Sep 17 00:00:00 2001 From: Smeet Agrawal Date: Thu, 9 Apr 2026 10:28:27 +0530 Subject: [PATCH] fix: add claude-haiku-4-5 to native structured output allowlist claude-haiku-4-5-20251001 supports native structured output via the output_format parameter but was missing from the model substring allowlist in map_openai_params, causing it to fall back to the json_tool_call synthesis workaround. This broke downstream tool dispatch when the model also returned legitimate tool calls. Adds test in tests/test_litellm/llms/anthropic/test_anthropic_structured_output.py. Fixes https://github.com/BerriAI/litellm/issues/25308 Co-Authored-By: Claude Sonnet 4.6 --- .../test_anthropic_completion.py | 38 ------------------ .../test_anthropic_structured_output.py | 39 +++++++++++++++++++ 2 files changed, 39 insertions(+), 38 deletions(-) diff --git a/tests/llm_translation/test_anthropic_completion.py b/tests/llm_translation/test_anthropic_completion.py index 1765a3cb3a5..fdf8c24ac9e 100644 --- a/tests/llm_translation/test_anthropic_completion.py +++ b/tests/llm_translation/test_anthropic_completion.py @@ -447,44 +447,6 @@ def test_anthropic_tool_helper(cache_control_location): assert tool["cache_control"] == {"type": "ephemeral"} -def test_haiku_4_5_uses_native_structured_output(): - """ - Haiku 4.5 supports native structured output via output_format. - It should NOT fall back to json_tool_call synthesis. - Regression test for https://github.com/BerriAI/litellm/issues/25308 - """ - from litellm.llms.anthropic.chat.transformation import AnthropicConfig - - config = AnthropicConfig() - response_format = { - "type": "json_schema", - "json_schema": { - "name": "MovieReview", - "schema": { - "type": "object", - "properties": { - "title": {"type": "string"}, - "rating": {"type": "number"}, - }, - "required": ["title", "rating"], - }, - }, - } - optional_params = config.map_openai_params( - non_default_params={"response_format": response_format}, - optional_params={}, - model="claude-haiku-4-5-20251001", - drop_params=False, - ) - # Should use native output_format, not json_tool_call - assert "output_format" in optional_params, ( - "haiku-4-5 should use native output_format, not json_tool_call" - ) - assert "tools" not in optional_params or not any( - t.get("name") == "json_tool_call" for t in optional_params.get("tools", []) - ), "haiku-4-5 should not synthesize a json_tool_call" - - def test_create_json_tool_call_for_response_format(): """ tests using response_format=json with anthropic diff --git a/tests/test_litellm/llms/anthropic/test_anthropic_structured_output.py b/tests/test_litellm/llms/anthropic/test_anthropic_structured_output.py index 705edeaf69c..ed0c7849f7b 100644 --- a/tests/test_litellm/llms/anthropic/test_anthropic_structured_output.py +++ b/tests/test_litellm/llms/anthropic/test_anthropic_structured_output.py @@ -128,6 +128,45 @@ class TestAnthropicStructuredOutput: if "properties" in nested_item_schema and "tags" in nested_item_schema["properties"]: assert "maxItems" not in nested_item_schema["properties"]["tags"] + def test_haiku_4_5_uses_native_structured_output(self): + """ + claude-haiku-4-5 supports native structured output via output_format. + It should NOT fall back to the json_tool_call synthesis workaround. + + Regression test for https://github.com/BerriAI/litellm/issues/25308 + """ + from litellm.llms.anthropic.chat.transformation import AnthropicConfig + + config = AnthropicConfig() + response_format = { + "type": "json_schema", + "json_schema": { + "name": "MovieReview", + "schema": { + "type": "object", + "properties": { + "title": {"type": "string"}, + "rating": {"type": "number"}, + }, + "required": ["title", "rating"], + }, + }, + } + optional_params = config.map_openai_params( + non_default_params={"response_format": response_format}, + optional_params={}, + model="claude-haiku-4-5-20251001", + drop_params=False, + ) + # Should use native output_format, not json_tool_call + assert "output_format" in optional_params, ( + "claude-haiku-4-5 should use native output_format, not json_tool_call" + ) + assert not any( + t.get("name") == "json_tool_call" + for t in optional_params.get("tools", []) + ), "claude-haiku-4-5 should not synthesize a json_tool_call" + def test_other_constraints_preserved(self): """ Test that constraints are properly handled (removed from schema, added to description).