mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-15 23:31:29 +00:00
fix(tools): resolve legacy definitions \$ref in tool schemas for Anthropic and Fireworks
MCP servers (e.g. DevRev) emit tool input schemas that use the draft-04 `definitions` keyword with `\$ref` pointers. When LiteLLM forwards these to Anthropic or Fireworks, the request fails: - Anthropic: "PointerToNowhere" — the `definitions` key is stripped by the `_allowed_properties` filter while `\$ref` pointers remain - Fireworks: "Error resolving schema reference" — provider rejects unresolved JSON Schema references Fix: before filtering/forwarding, detect `definitions` in the input schema and inline all `\$ref` pointers using the existing `unpack_defs` utility. `\$defs` refs (natively supported by Anthropic) are left as-is. OpenAI-compatible providers are unaffected — OpenAI resolves `\$ref` server-side and already works. Fixes https://github.com/BerriAI/litellm/issues/26692
This commit is contained in:
parent
62920a0cb2
commit
f1ab7da89d
4 changed files with 191 additions and 0 deletions
|
|
@ -448,6 +448,28 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
|
|||
if "properties" not in _input_schema:
|
||||
_input_schema["properties"] = {}
|
||||
|
||||
# Resolve legacy `definitions` $ref pointers inline before
|
||||
# filtering. Anthropic supports `$defs` natively but not
|
||||
# `definitions` (old JSON Schema draft-04 style). MCP servers
|
||||
# such as DevRev emit schemas with `definitions` + `$ref` which
|
||||
# causes Anthropic to return "PointerToNowhere" errors because
|
||||
# the `definitions` key is stripped by the _allowed_properties
|
||||
# filter below while the $ref pointers remain.
|
||||
if "definitions" in _input_schema:
|
||||
import copy
|
||||
|
||||
from litellm.litellm_core_utils.prompt_templates.common_utils import (
|
||||
unpack_defs,
|
||||
)
|
||||
|
||||
_input_schema = copy.deepcopy(_input_schema)
|
||||
defs: dict = {
|
||||
**_input_schema.pop("definitions", {}),
|
||||
**_input_schema.pop("$defs", {}),
|
||||
}
|
||||
if defs:
|
||||
unpack_defs(_input_schema, defs)
|
||||
|
||||
_allowed_properties = set(AnthropicInputSchema.__annotations__.keys())
|
||||
input_schema_filtered = {
|
||||
k: v for k, v in _input_schema.items() if k in _allowed_properties
|
||||
|
|
|
|||
|
|
@ -200,9 +200,27 @@ class FireworksAIConfig(OpenAIGPTConfig):
|
|||
def _transform_tools(
|
||||
self, tools: List[OpenAIChatCompletionToolParam]
|
||||
) -> List[OpenAIChatCompletionToolParam]:
|
||||
import copy
|
||||
|
||||
for tool in tools:
|
||||
if tool.get("type") == "function":
|
||||
tool["function"].pop("strict", None)
|
||||
# Resolve `definitions` $ref pointers inline — Fireworks
|
||||
# rejects tool schemas with unresolved JSON Schema references.
|
||||
params = tool["function"].get("parameters")
|
||||
if params and "definitions" in params:
|
||||
from litellm.litellm_core_utils.prompt_templates.common_utils import (
|
||||
unpack_defs,
|
||||
)
|
||||
|
||||
params = copy.deepcopy(params)
|
||||
defs: dict = {
|
||||
**params.pop("definitions", {}),
|
||||
**params.pop("$defs", {}),
|
||||
}
|
||||
if defs:
|
||||
unpack_defs(params, defs)
|
||||
tool["function"]["parameters"] = params
|
||||
return tools
|
||||
|
||||
def _transform_messages_helper(
|
||||
|
|
|
|||
|
|
@ -1885,3 +1885,110 @@ def test_metadata_filter_applies_to_azure_anthropic():
|
|||
headers={},
|
||||
)
|
||||
assert data.get("metadata") == {"user_id": "u2"}
|
||||
|
||||
|
||||
def test_anthropic_tool_with_legacy_definitions_ref_resolved_inline():
|
||||
"""MCP servers (e.g. DevRev) emit schemas with `definitions` + $ref.
|
||||
Anthropic doesn't support `definitions` — refs must be inlined before
|
||||
the _allowed_properties filter strips the definitions block."""
|
||||
args = {
|
||||
"non_default_params": {
|
||||
"tools": [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "update_contact",
|
||||
"description": "Update a contact.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {"type": "string"},
|
||||
"tags": {
|
||||
"$ref": "#/definitions/_gen:tags",
|
||||
"description": "Tags for the contact.",
|
||||
},
|
||||
},
|
||||
"required": ["id"],
|
||||
"definitions": {
|
||||
"_gen:tags": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"set": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
mapped_params = litellm.AnthropicConfig().map_openai_params(
|
||||
non_default_params=args["non_default_params"],
|
||||
optional_params={},
|
||||
model="claude-sonnet-4-5-20250929",
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
tool = mapped_params["tools"][0]
|
||||
input_schema = tool["input_schema"]
|
||||
|
||||
# `definitions` must not appear — it's not in AnthropicInputSchema
|
||||
assert "definitions" not in input_schema
|
||||
|
||||
# The $ref should have been resolved inline — no dangling pointer
|
||||
tags_prop = input_schema["properties"]["tags"]
|
||||
assert "$ref" not in tags_prop, "dangling $ref will cause Anthropic PointerToNowhere"
|
||||
assert tags_prop.get("type") == "object"
|
||||
assert "set" in tags_prop.get("properties", {})
|
||||
|
||||
|
||||
def test_anthropic_tool_defs_ref_still_preserved():
|
||||
"""$defs refs (officially supported by Anthropic) must not be touched."""
|
||||
args = {
|
||||
"non_default_params": {
|
||||
"tools": [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "create_user",
|
||||
"description": "Create a user.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user": {"$ref": "#/$defs/User"},
|
||||
},
|
||||
"required": ["user"],
|
||||
"$defs": {
|
||||
"User": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"email": {"type": "string"},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
mapped_params = litellm.AnthropicConfig().map_openai_params(
|
||||
non_default_params=args["non_default_params"],
|
||||
optional_params={},
|
||||
model="claude-sonnet-4-5-20250929",
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
tool = mapped_params["tools"][0]
|
||||
# $defs refs should be preserved as-is (Anthropic handles them natively)
|
||||
assert tool["input_schema"]["properties"]["user"]["$ref"] == "#/$defs/User"
|
||||
assert (
|
||||
tool["input_schema"]["$defs"]["User"]["properties"]["name"]["type"] == "string"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -242,3 +242,47 @@ def test_global_disable_flag_with_transform_messages_helper(monkeypatch):
|
|||
"#transform=inline"
|
||||
not in json_data["messages"][0]["content"][1]["image_url"]["url"]
|
||||
)
|
||||
|
||||
|
||||
def test_fireworks_transform_tools_resolves_definitions_refs():
|
||||
"""MCP servers emit tool schemas with `definitions` + $ref. Fireworks
|
||||
rejects these — verify _transform_tools resolves refs inline."""
|
||||
from litellm.llms.fireworks_ai.chat.transformation import FireworksAIConfig
|
||||
|
||||
tools = [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "update_contact",
|
||||
"description": "Update a contact.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {"type": "string"},
|
||||
"tags": {
|
||||
"$ref": "#/definitions/_gen:tags",
|
||||
"description": "Tags",
|
||||
},
|
||||
},
|
||||
"required": ["id"],
|
||||
"definitions": {
|
||||
"_gen:tags": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"set": {"type": "array", "items": {"type": "string"}}
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
result = FireworksAIConfig()._transform_tools(tools)
|
||||
params = result[0]["function"]["parameters"]
|
||||
|
||||
assert "definitions" not in params, "definitions block must be stripped"
|
||||
tags_prop = params["properties"]["tags"]
|
||||
assert "$ref" not in tags_prop, "dangling $ref must be resolved"
|
||||
assert tags_prop.get("type") == "object"
|
||||
assert "set" in tags_prop.get("properties", {})
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue