fix(anthropic): use native output capability (#33235)
Some checks are pending
CodSpeed Benchmarks / benchmarks (push) Waiting to run
GitHub Actions Security Analysis / zizmor (push) Waiting to run

* fix(anthropic): route native structured output

Use model capability metadata so new native structured-output models do not require transformation allowlist changes.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(anthropic): pass provider to capability

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(anthropic): cover dotted model IDs

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(anthropic): handle remote capability lag

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Krrish Dholakia 2026-07-14 14:23:49 -07:00 committed by GitHub
parent 6ad2f85e0c
commit 477ef3a7e2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 79 additions and 25 deletions

View file

@ -1441,24 +1441,10 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
output_key=param,
)
elif param == "response_format" and isinstance(value, dict):
if any(
substring in model
for substring in {
"sonnet-4.5",
"sonnet-4-5",
"opus-4.1",
"opus-4-1",
"opus-4.5",
"opus-4-5",
"opus-4.6",
"opus-4-6",
"opus-4.7",
"opus-4-7",
"sonnet-4.6",
"sonnet-4-6",
"sonnet_4.6",
"sonnet_4_6",
}
if AnthropicConfig._supports_model_capability(
model,
"supports_native_structured_output",
self._resolved_provider,
):
_output_format = self.map_response_format_to_anthropic_output_format(value)
if _output_format is not None:

View file

@ -340,11 +340,15 @@ class AnthropicModelInfo(BaseLLMModelInfo):
def _get_model_capability(model: str, key: str) -> Optional[bool]:
"""Read boolean capability ``key`` from the model map, or None when
no entry declares it."""
from litellm.utils import _get_bundled_model_cost_map
try:
for cand in AnthropicModelInfo._model_map_lookup_candidates(model):
value = litellm.model_cost.get(cand, {}).get(key)
if isinstance(value, bool):
return value
candidates = AnthropicModelInfo._model_map_lookup_candidates(model)
for model_cost in (litellm.model_cost, _get_bundled_model_cost_map()):
for cand in candidates:
value = model_cost.get(cand, {}).get(key)
if isinstance(value, bool):
return value
except Exception:
pass
return None

View file

@ -11331,6 +11331,7 @@
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_native_structured_output": true,
"supports_tool_choice": true,
"supports_vision": true
},
@ -11362,6 +11363,7 @@
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_native_structured_output": true,
"supports_tool_choice": true,
"supports_vision": true,
"supports_web_search": true
@ -11424,6 +11426,7 @@
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_native_structured_output": true,
"supports_max_reasoning_effort": true,
"supports_tool_choice": true,
"supports_vision": true,
@ -11479,6 +11482,7 @@
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_native_structured_output": true,
"supports_tool_choice": true,
"supports_vision": true
},
@ -11506,6 +11510,7 @@
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_native_structured_output": true,
"supports_tool_choice": true,
"supports_vision": true
},
@ -11559,6 +11564,7 @@
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_native_structured_output": true,
"supports_tool_choice": true,
"supports_vision": true,
"supports_output_config": true
@ -11586,6 +11592,7 @@
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_native_structured_output": true,
"supports_tool_choice": true,
"supports_vision": true,
"supports_output_config": true
@ -11614,6 +11621,7 @@
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_native_structured_output": true,
"supports_tool_choice": true,
"supports_vision": true,
"provider_specific_entry": {
@ -11648,6 +11656,7 @@
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_native_structured_output": true,
"supports_tool_choice": true,
"supports_vision": true,
"provider_specific_entry": {
@ -11682,6 +11691,7 @@
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_native_structured_output": true,
"supports_sampling_params": false,
"supports_tool_choice": true,
"supports_vision": true,
@ -11718,6 +11728,7 @@
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_native_structured_output": true,
"supports_sampling_params": false,
"supports_tool_choice": true,
"supports_vision": true,
@ -11788,6 +11799,7 @@
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_native_structured_output": true,
"supports_sampling_params": false,
"supports_tool_choice": true,
"supports_vision": true,

View file

@ -11331,6 +11331,7 @@
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_native_structured_output": true,
"supports_tool_choice": true,
"supports_vision": true
},
@ -11362,6 +11363,7 @@
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_native_structured_output": true,
"supports_tool_choice": true,
"supports_vision": true,
"supports_web_search": true
@ -11424,6 +11426,7 @@
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_native_structured_output": true,
"supports_max_reasoning_effort": true,
"supports_tool_choice": true,
"supports_vision": true,
@ -11479,6 +11482,7 @@
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_native_structured_output": true,
"supports_tool_choice": true,
"supports_vision": true
},
@ -11506,6 +11510,7 @@
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_native_structured_output": true,
"supports_tool_choice": true,
"supports_vision": true
},
@ -11559,6 +11564,7 @@
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_native_structured_output": true,
"supports_tool_choice": true,
"supports_vision": true,
"supports_output_config": true
@ -11586,6 +11592,7 @@
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_native_structured_output": true,
"supports_tool_choice": true,
"supports_vision": true,
"supports_output_config": true
@ -11614,6 +11621,7 @@
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_native_structured_output": true,
"supports_tool_choice": true,
"supports_vision": true,
"provider_specific_entry": {
@ -11648,6 +11656,7 @@
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_native_structured_output": true,
"supports_tool_choice": true,
"supports_vision": true,
"provider_specific_entry": {
@ -11682,6 +11691,7 @@
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_native_structured_output": true,
"supports_sampling_params": false,
"supports_tool_choice": true,
"supports_vision": true,
@ -11718,6 +11728,7 @@
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_native_structured_output": true,
"supports_sampling_params": false,
"supports_tool_choice": true,
"supports_vision": true,
@ -11788,6 +11799,7 @@
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_response_schema": true,
"supports_native_structured_output": true,
"supports_sampling_params": false,
"supports_tool_choice": true,
"supports_vision": true,

View file

@ -957,15 +957,15 @@ def test_anthropic_structured_output_beta_header():
@pytest.mark.parametrize(
"model_name",
[
"claude-opus-4-6-20250918",
"claude-opus-4.6-20250918",
"claude-opus-4-8",
"claude-opus-4-6-20260205",
"claude-opus-4-5-20251101",
"claude-opus-4.5-20251101",
],
)
def test_opus_uses_native_structured_output(model_name):
"""
Test that Opus 4.5 and 4.6 models use native Anthropic structured outputs
Test that supported Opus models use native Anthropic structured outputs
(output_format) rather than the tool-based workaround.
"""
config = AnthropicConfig()
@ -1005,6 +1005,43 @@ def test_opus_uses_native_structured_output(model_name):
assert optional_params.get("json_mode") is True
def test_native_structured_output_uses_bundled_capability_when_remote_map_lags(
monkeypatch: pytest.MonkeyPatch,
) -> None:
model = "claude-opus-4-8"
monkeypatch.setattr(
litellm,
"model_cost",
{model: {"supports_response_schema": True}},
)
litellm.get_model_info.cache_clear()
try:
optional_params = AnthropicConfig().map_openai_params(
non_default_params={
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "answer",
"schema": {
"type": "object",
"properties": {"answer": {"type": "string"}},
"required": ["answer"],
},
},
}
},
optional_params={},
model=model,
drop_params=False,
)
finally:
litellm.get_model_info.cache_clear()
assert "output_format" in optional_params
assert "tools" not in optional_params
def test_non_structured_output_model_uses_tool_workaround():
"""
Test that models NOT in the native structured output list still use the

View file

@ -95,6 +95,8 @@ def test_opus_4_8_model_pricing_and_capabilities():
assert info["supports_tool_choice"] is True
assert info["supports_vision"] is True
assert model_data["claude-opus-4-8"]["supports_native_structured_output"] is True
def test_opus_4_8_bedrock_regional_model_pricing():
model_data = _load_root_cost_map()
@ -165,6 +167,7 @@ def test_opus_4_8_present_in_bundled_backup():
"azure_ai/claude-opus-4-8",
):
assert model_name in backup, f"Missing from backup cost map: {model_name}"
assert backup["claude-opus-4-8"]["supports_native_structured_output"] is True
def test_opus_4_8_registered_for_bedrock_converse():