mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-22 00:31:44 +00:00
fix(anthropic): place strict flag at tool level, not inside input_schema
Anthropic enforces strict tool use only when `strict` is set at the tool top level (sibling of name/input_schema). LiteLLM was placing it inside input_schema where Anthropic silently ignores it, so callers thought strict was engaged but the model emitted unconstrained tool arguments. - Remove `strict` from AnthropicInputSchema (not part of the schema) - Add `strict` to AnthropicMessagesTool (where Anthropic expects it) - Extract strict from tool.function.strict (OpenAI canonical) or from parameters.strict (legacy), and set it on the tool object Fixes #27490
This commit is contained in:
parent
fa81017e12
commit
0b966ff825
3 changed files with 102 additions and 1 deletions
|
|
@ -676,12 +676,17 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
|
|||
**input_schema_filtered
|
||||
)
|
||||
|
||||
_strict = tool["function"].get("strict") or _input_schema.get("strict")
|
||||
|
||||
_tool = AnthropicMessagesTool(
|
||||
name=tool["function"]["name"],
|
||||
input_schema=input_anthropic_schema,
|
||||
type="custom",
|
||||
)
|
||||
|
||||
if _strict is True:
|
||||
_tool["strict"] = True
|
||||
|
||||
_description = tool["function"].get("description")
|
||||
if _description is not None:
|
||||
_tool["description"] = _description
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ AnthropicInputSchema = TypedDict(
|
|||
"additionalProperties": Optional[bool],
|
||||
"required": Optional[List[str]],
|
||||
"$defs": Optional[Dict],
|
||||
"strict": Optional[bool],
|
||||
},
|
||||
total=False,
|
||||
)
|
||||
|
|
@ -51,6 +50,7 @@ class AnthropicMessagesTool(TypedDict, total=False):
|
|||
defer_loading: bool
|
||||
allowed_callers: Optional[List[str]]
|
||||
input_examples: Optional[List[Dict[str, Any]]]
|
||||
strict: bool
|
||||
|
||||
|
||||
class AnthropicComputerTool(TypedDict, total=False):
|
||||
|
|
|
|||
|
|
@ -4703,3 +4703,99 @@ def test_sanitize_tool_names_in_request_no_tools_is_noop():
|
|||
forward, reverse = AnthropicConfig._sanitize_tool_names_in_request({"tools": []})
|
||||
assert forward == {}
|
||||
assert reverse == {}
|
||||
|
||||
|
||||
def test_map_tool_helper_strict_from_function_level():
|
||||
"""strict on tool.function (OpenAI canonical location) should be placed
|
||||
at the tool top level, not inside input_schema."""
|
||||
config = AnthropicConfig()
|
||||
|
||||
tool = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_weather",
|
||||
"description": "Get weather",
|
||||
"strict": True,
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {"city": {"type": "string"}},
|
||||
"required": ["city"],
|
||||
"additionalProperties": False,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result, _ = config._map_tool_helper(tool)
|
||||
assert result is not None
|
||||
assert result.get("strict") is True
|
||||
assert "strict" not in result["input_schema"]
|
||||
|
||||
|
||||
def test_map_tool_helper_strict_from_parameters():
|
||||
"""strict inside tool.function.parameters (legacy placement) should be
|
||||
moved to the tool top level."""
|
||||
config = AnthropicConfig()
|
||||
|
||||
tool = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_weather",
|
||||
"description": "Get weather",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {"city": {"type": "string"}},
|
||||
"required": ["city"],
|
||||
"additionalProperties": False,
|
||||
"strict": True,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result, _ = config._map_tool_helper(tool)
|
||||
assert result is not None
|
||||
assert result.get("strict") is True
|
||||
assert "strict" not in result["input_schema"]
|
||||
|
||||
|
||||
def test_map_tool_helper_no_strict_omits_field():
|
||||
"""When strict is not set, it should not appear on the tool."""
|
||||
config = AnthropicConfig()
|
||||
|
||||
tool = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_weather",
|
||||
"description": "Get weather",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {"city": {"type": "string"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result, _ = config._map_tool_helper(tool)
|
||||
assert result is not None
|
||||
assert "strict" not in result
|
||||
assert "strict" not in result["input_schema"]
|
||||
|
||||
|
||||
def test_map_tool_helper_strict_false_omits_field():
|
||||
"""strict=False should not be forwarded to Anthropic."""
|
||||
config = AnthropicConfig()
|
||||
|
||||
tool = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_weather",
|
||||
"description": "Get weather",
|
||||
"strict": False,
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {"city": {"type": "string"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result, _ = config._map_tool_helper(tool)
|
||||
assert result is not None
|
||||
assert "strict" not in result
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue