mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-29 01:42:19 +00:00
Merge 8092ed6b0a into e26a6450c8
This commit is contained in:
commit
85284436b9
2 changed files with 68 additions and 8 deletions
|
|
@ -5107,14 +5107,32 @@ def _bedrock_tools_pt(tools: list, model: str | None = None) -> list[BedrockTool
|
|||
tool_block_list.append(tool)
|
||||
continue
|
||||
|
||||
# Responses built-in tools (web_search, image_generation, namespace, tool_search,
|
||||
# custom) carry neither an OpenAI "function" nor an Anthropic "input_schema" and have
|
||||
# no Bedrock toolSpec equivalent; drop them instead of emitting an empty junk toolSpec.
|
||||
if isinstance(tool, dict) and "function" not in tool and "input_schema" not in tool:
|
||||
# OpenAI Agents SDK freeform tools ({"type": "custom", "name": ...,
|
||||
# "description": ..., "custom": {...}}) carry a top-level name and no
|
||||
# function payload; forward them as parameterless toolSpecs, dropping
|
||||
# provider-specific extras like `custom`, which Bedrock rejects (#38799).
|
||||
# Other named-but-builtin tools (e.g. namespace) and nameless Responses
|
||||
# built-ins (web_search, image_generation, ...) have no Bedrock toolSpec
|
||||
# equivalent and are still dropped below.
|
||||
if (
|
||||
isinstance(tool, dict)
|
||||
and "function" not in tool
|
||||
and "input_schema" not in tool
|
||||
and tool.get("type") == "custom"
|
||||
):
|
||||
freeform_name = tool.get("name")
|
||||
if not (isinstance(freeform_name, str) and freeform_name.strip()):
|
||||
continue
|
||||
parameters = { # mutable-ok: Bedrock toolSpec input schemas are plain JSON dicts
|
||||
"type": "object",
|
||||
"properties": {}, # mutable-ok: empty JSON schema object, consumed by the request builder
|
||||
}
|
||||
raw_name = freeform_name
|
||||
_tool_description = tool.get("description", None)
|
||||
elif isinstance(tool, dict) and "function" not in tool and "input_schema" not in tool:
|
||||
continue
|
||||
|
||||
# OpenAI function tools, or Anthropic Messages / Claude Code ({name, input_schema, type, ...})
|
||||
if isinstance(tool, dict) and "input_schema" in tool and "function" not in tool:
|
||||
elif isinstance(tool, dict) and "input_schema" in tool and "function" not in tool:
|
||||
parameters = copy.deepcopy(tool.get("input_schema") or {"type": "object", "properties": {}})
|
||||
raw_name = tool.get("name", "") or ""
|
||||
_tool_description = tool.get("description", None)
|
||||
|
|
|
|||
|
|
@ -1067,6 +1067,44 @@ def test_bedrock_tools_pt_strict_parameter():
|
|||
assert "additionalProperties" not in result[0]["toolSpec"]["inputSchema"]["json"]
|
||||
|
||||
|
||||
def test_bedrock_tools_pt_forwards_freeform_tool_without_custom_field():
|
||||
"""OpenAI Agents SDK freeform tools carry a top-level name and a provider-specific
|
||||
`custom` dict instead of a function payload. Bedrock rejects the `custom` key
|
||||
outright (400 "tools.0.custom.strict: Extra inputs are not permitted"), so the
|
||||
tool must be forwarded as a parameterless toolSpec with the extras stripped
|
||||
rather than dropped or passed through.
|
||||
"""
|
||||
tools = [
|
||||
{
|
||||
"type": "custom",
|
||||
"name": "apply_patch",
|
||||
"description": "Apply a unified diff patch to the workspace.",
|
||||
"custom": {"strict": None},
|
||||
}
|
||||
]
|
||||
result = _bedrock_tools_pt(tools, model="us.anthropic.claude-sonnet-5")
|
||||
assert len(result) == 1, "the freeform tool must not be dropped"
|
||||
tool_spec = result[0]["toolSpec"]
|
||||
assert tool_spec["name"] == "apply_patch"
|
||||
assert tool_spec["description"] == "Apply a unified diff patch to the workspace."
|
||||
assert tool_spec["inputSchema"]["json"] == {"type": "object", "properties": {}, "required": []}
|
||||
assert "custom" not in tool_spec
|
||||
assert "strict" not in tool_spec
|
||||
|
||||
|
||||
def test_bedrock_tools_pt_still_drops_unnamed_builtin_tools():
|
||||
"""Responses built-in tools (web_search, image_generation, ...) have no top-level
|
||||
name and no Bedrock toolSpec equivalent; they must stay dropped. A `custom`-typed
|
||||
tool without a name is equally unmappable — only a named one is forwarded.
|
||||
"""
|
||||
tools = [
|
||||
{"type": "web_search_preview"},
|
||||
{"type": "image_generation"},
|
||||
{"type": "custom"},
|
||||
]
|
||||
assert _bedrock_tools_pt(tools, model="us.anthropic.claude-sonnet-5") == []
|
||||
|
||||
|
||||
def test_bedrock_image_processor_content_type_fallback_url_extension():
|
||||
"""
|
||||
Test that _post_call_image_processing falls back to URL extension
|
||||
|
|
@ -1668,9 +1706,13 @@ def test_bedrock_tools_pt_does_not_handle_system_tool():
|
|||
def test_bedrock_tools_pt_drops_unmappable_responses_builtin_tools():
|
||||
"""
|
||||
Regression for LIT-3858: Responses built-in tools (image_generation, namespace,
|
||||
tool_search, custom) have no Bedrock toolSpec equivalent. They must be dropped, not
|
||||
tool_search) have no Bedrock toolSpec equivalent. They must be dropped, not
|
||||
emitted as junk ``litellm_unnamed_tool_N`` toolSpecs the model can hallucinate calls to.
|
||||
Mappable ``function`` and Anthropic ``input_schema`` tools must survive untouched.
|
||||
|
||||
Named ``custom`` tools (freeform tools, e.g. the OpenAI Agents SDK's apply_patch)
|
||||
ARE mappable — a parameterless toolSpec — and are forwarded since #38799; only
|
||||
nameless built-ins stay dropped.
|
||||
"""
|
||||
from litellm.litellm_core_utils.prompt_templates.factory import _bedrock_tools_pt
|
||||
|
||||
|
|
@ -1693,7 +1735,7 @@ def test_bedrock_tools_pt_drops_unmappable_responses_builtin_tools():
|
|||
)
|
||||
|
||||
names = [block["toolSpec"]["name"] for block in result if "toolSpec" in block]
|
||||
assert names == ["noop"]
|
||||
assert names == ["noop", "free_form"], "named built-ins like namespace must stay dropped"
|
||||
assert not any(name.startswith("litellm_unnamed_tool_") for name in names)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue