mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-02 02:11:58 +00:00
fix: complete anthropic json mode structured output handling
This commit is contained in:
parent
1b1df7af2f
commit
f5f2a93d26
4 changed files with 91 additions and 25 deletions
|
|
@ -589,6 +589,7 @@ def convert_to_model_response_object( # noqa: PLR0915
|
|||
choice["message"]["content"] = json_mode_content_str
|
||||
elif isinstance(choice["message"].get("content"), str):
|
||||
choice["message"]["content"] += f"\n{json_mode_content_str}"
|
||||
finish_reason = "tool_calls"
|
||||
if message is None:
|
||||
# Preserve provider_specific_fields if already present
|
||||
# in the response (e.g. from proxy passthrough)
|
||||
|
|
|
|||
|
|
@ -1456,28 +1456,8 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
|
|||
elif param == "top_p":
|
||||
optional_params["top_p"] = value
|
||||
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",
|
||||
"haiku-4.5",
|
||||
"haiku-4-5",
|
||||
"haiku_4.5",
|
||||
"haiku_4_5",
|
||||
}
|
||||
if litellm.supports_response_schema(
|
||||
model=model, custom_llm_provider="anthropic"
|
||||
):
|
||||
_output_format = (
|
||||
self.map_response_format_to_anthropic_output_format(value)
|
||||
|
|
@ -2443,7 +2423,9 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
|
|||
_message.provider_specific_fields = provider_specific_fields
|
||||
|
||||
if json_mode_message is not None:
|
||||
completion_response["stop_reason"] = "stop"
|
||||
completion_response["stop_reason"] = (
|
||||
"tool_use" if json_mode_message.tool_calls else "stop"
|
||||
)
|
||||
_message = json_mode_message
|
||||
|
||||
model_response.choices[0].message = _message
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ def test_convert_to_model_response_object_basic():
|
|||
"content": "Hi there! How can I assist you today?",
|
||||
"refusal": None,
|
||||
},
|
||||
"finish_reason": "stop",
|
||||
"finish_reason": None,
|
||||
}
|
||||
],
|
||||
"usage": {
|
||||
|
|
@ -344,7 +344,7 @@ def test_convert_to_model_response_object_json_mode():
|
|||
}
|
||||
],
|
||||
},
|
||||
"finish_reason": None,
|
||||
"finish_reason": "stop",
|
||||
}
|
||||
],
|
||||
"usage": {"total_tokens": 10, "prompt_tokens": 5, "completion_tokens": 5},
|
||||
|
|
@ -374,6 +374,61 @@ def test_convert_to_model_response_object_json_mode():
|
|||
assert result.usage.completion_tokens == 5
|
||||
|
||||
|
||||
def test_convert_to_model_response_object_json_mode_with_parallel_real_tool():
|
||||
model_response_object = ModelResponse(model="gpt-3.5-turbo")
|
||||
from litellm.constants import RESPONSE_FORMAT_TOOL_NAME
|
||||
|
||||
response_object = {
|
||||
"choices": [
|
||||
{
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": None,
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": "call_real",
|
||||
"type": "function",
|
||||
"function": {
|
||||
"arguments": '{"movie":"Inception"}',
|
||||
"name": "get_showtimes",
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "call_json",
|
||||
"type": "function",
|
||||
"function": {
|
||||
"arguments": '{"title":"Inception"}',
|
||||
"name": RESPONSE_FORMAT_TOOL_NAME,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
"finish_reason": "stop",
|
||||
}
|
||||
],
|
||||
"usage": {"total_tokens": 10, "prompt_tokens": 5, "completion_tokens": 5},
|
||||
"model": "gpt-3.5-turbo",
|
||||
}
|
||||
|
||||
result = convert_to_model_response_object(
|
||||
model_response_object=model_response_object,
|
||||
response_object=response_object,
|
||||
stream=False,
|
||||
start_time=datetime.now(),
|
||||
end_time=datetime.now(),
|
||||
hidden_params=None,
|
||||
_response_headers=None,
|
||||
convert_tool_call_to_json_mode=True,
|
||||
)
|
||||
|
||||
assert result.choices[0].message.content == '{"title":"Inception"}'
|
||||
assert result.choices[0].finish_reason == "tool_calls"
|
||||
tool_calls = result.choices[0].message.tool_calls
|
||||
assert tool_calls is not None
|
||||
assert len(tool_calls) == 1
|
||||
assert tool_calls[0].function.name == "get_showtimes"
|
||||
|
||||
|
||||
def test_convert_to_model_response_object_function_output():
|
||||
"""
|
||||
Test conversion with function output.
|
||||
|
|
|
|||
|
|
@ -73,6 +73,34 @@ def test_anthropic_json_mode_non_streaming_mixed_internal_and_user_tools():
|
|||
assert extra == '{"answer": 42}'
|
||||
|
||||
|
||||
def test_haiku_45_uses_model_metadata_for_native_structured_output():
|
||||
config = AnthropicConfig()
|
||||
mapped_params = config.map_openai_params(
|
||||
non_default_params={
|
||||
"response_format": {
|
||||
"type": "json_schema",
|
||||
"json_schema": {
|
||||
"name": "MovieReview",
|
||||
"strict": True,
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {"title": {"type": "string"}},
|
||||
"required": ["title"],
|
||||
"additionalProperties": False,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
optional_params={},
|
||||
model="claude-haiku-4-5-20251001",
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
assert "output_format" in mapped_params
|
||||
assert "tools" not in mapped_params
|
||||
assert "tool_choice" not in mapped_params
|
||||
|
||||
|
||||
def test_calculate_usage():
|
||||
"""
|
||||
Do not include cache_creation_input_tokens in the prompt_tokens
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue