mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-07 08:26:10 +00:00
Merge 82df8a9489 into e6c4580a31
This commit is contained in:
commit
55e1373f96
3 changed files with 136 additions and 1 deletions
|
|
@ -0,0 +1,27 @@
|
|||
"""
|
||||
Utilities for handling OpenAI Responses API 'namespace' tools when bridging to Chat
|
||||
Completions providers.
|
||||
|
||||
A namespace tool is a grouping container: it carries no callable schema of its own and
|
||||
holds its callable tools under ``tools``. Chat Completions has no equivalent container,
|
||||
so the bridge replaces each namespace with the tools it contains, which then go through
|
||||
the same conversion as any top level tool. Namespaces are flat in practice, and a
|
||||
namespace that somehow contains another one keeps the inner container, which the
|
||||
conversion then drops as an unsupported type.
|
||||
"""
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import TypeAlias
|
||||
|
||||
from openai.types.responses.tool_param import FunctionToolParam
|
||||
|
||||
from litellm.types.llms.openai import OpenAIMcpServerTool
|
||||
|
||||
ResponsesAPITool: TypeAlias = FunctionToolParam | OpenAIMcpServerTool
|
||||
|
||||
|
||||
def flatten_namespace_tools(tools: Sequence[ResponsesAPITool]) -> tuple[ResponsesAPITool, ...]:
|
||||
"""Replace every namespace tool with the tools it contains."""
|
||||
return tuple(
|
||||
nested for tool in tools for nested in (tool.get("tools") or () if tool.get("type") == "namespace" else (tool,))
|
||||
)
|
||||
|
|
@ -96,6 +96,7 @@ from .custom_tools import (
|
|||
unwrap_custom_tool_arguments,
|
||||
validated_allowed_callers,
|
||||
)
|
||||
from .namespace_tools import flatten_namespace_tools
|
||||
|
||||
NamespaceNameMap: TypeAlias = Mapping[str, tuple[str, str]]
|
||||
NamespaceTool: TypeAlias = Mapping[str, object]
|
||||
|
|
@ -1848,7 +1849,7 @@ class LiteLLMCompletionResponsesConfig:
|
|||
LiteLLMCompletionResponsesConfig._validate_namespace_name_collisions(tools)
|
||||
chat_completion_tools: Final[list[ChatCompletionToolParam | OpenAIMcpServerTool]] = []
|
||||
web_search_options: OpenAIWebSearchOptions | None = None
|
||||
for tool in tools:
|
||||
for tool in flatten_namespace_tools(tools):
|
||||
if tool.get("type") == "mcp":
|
||||
chat_completion_tools.append(cast(OpenAIMcpServerTool, tool))
|
||||
elif tool.get("type") == "web_search_preview" or tool.get("type") == "web_search":
|
||||
|
|
|
|||
|
|
@ -1870,6 +1870,113 @@ class TestToolTransformation:
|
|||
== "string"
|
||||
)
|
||||
|
||||
def test_transform_namespace_tool_expands_nested_function_tools(self):
|
||||
"""
|
||||
Regression for issue 2 of #35878: a namespace tool is a grouping container whose
|
||||
nested tools are ordinary function tools. Dropping the container dropped every
|
||||
nested tool with it, so a bridged client lost the sub-agent tools it declared.
|
||||
"""
|
||||
namespace_tool = {
|
||||
"type": "namespace",
|
||||
"name": "collaboration",
|
||||
"description": "sub-agent management",
|
||||
"tools": [
|
||||
{
|
||||
"type": "function",
|
||||
"name": "spawn_agent",
|
||||
"description": "start a sub-agent",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {"task": {"type": "string"}},
|
||||
"required": ["task"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "close_agent",
|
||||
"description": "stop a sub-agent",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
(
|
||||
result_tools,
|
||||
web_search_options,
|
||||
) = LiteLLMCompletionResponsesConfig.transform_responses_api_tools_to_chat_completion_tools(
|
||||
tools=[namespace_tool]
|
||||
)
|
||||
|
||||
assert [tool["type"] for tool in result_tools] == ["function", "function"]
|
||||
assert [tool["function"]["name"] for tool in result_tools] == [
|
||||
"spawn_agent",
|
||||
"close_agent",
|
||||
]
|
||||
assert result_tools[0]["function"]["parameters"]["properties"] == {
|
||||
"task": {"type": "string"}
|
||||
}
|
||||
assert result_tools[1]["function"]["parameters"] == {"type": "object"}
|
||||
assert not any(tool.get("type") == "namespace" for tool in result_tools)
|
||||
assert web_search_options is None
|
||||
|
||||
def test_transform_namespace_tool_alongside_top_level_tools(self):
|
||||
"""Nested tools land in the flat list next to top-level tools, and unsupported
|
||||
built-ins inside the namespace are still dropped rather than passed through."""
|
||||
tools = [
|
||||
{
|
||||
"type": "function",
|
||||
"name": "shell_command",
|
||||
"parameters": {"type": "object", "properties": {}},
|
||||
},
|
||||
{
|
||||
"type": "namespace",
|
||||
"name": "codex_app",
|
||||
"tools": [
|
||||
{"type": "function", "name": "read_file"},
|
||||
{"type": "image_generation", "output_format": "png"},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
(
|
||||
result_tools,
|
||||
_,
|
||||
) = LiteLLMCompletionResponsesConfig.transform_responses_api_tools_to_chat_completion_tools(
|
||||
tools=tools
|
||||
)
|
||||
|
||||
assert [tool["function"]["name"] for tool in result_tools] == [
|
||||
"shell_command",
|
||||
"read_file",
|
||||
]
|
||||
|
||||
def test_transform_nested_namespace_container_is_dropped(self):
|
||||
"""A namespace inside a namespace is not something clients send, but its inner
|
||||
container must never reach the provider as a tool without a function schema."""
|
||||
tools = [
|
||||
{
|
||||
"type": "namespace",
|
||||
"name": "outer",
|
||||
"tools": [
|
||||
{"type": "function", "name": "outer_tool"},
|
||||
{
|
||||
"type": "namespace",
|
||||
"name": "inner",
|
||||
"tools": [{"type": "function", "name": "inner_tool"}],
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
|
||||
(
|
||||
result_tools,
|
||||
_,
|
||||
) = LiteLLMCompletionResponsesConfig.transform_responses_api_tools_to_chat_completion_tools(
|
||||
tools=tools
|
||||
)
|
||||
|
||||
assert [tool["function"]["name"] for tool in result_tools] == ["outer_tool"]
|
||||
assert not any(tool.get("type") == "namespace" for tool in result_tools)
|
||||
|
||||
def test_bedrock_anthropic_drops_derived_web_search_options(self):
|
||||
"""
|
||||
Regression for LIT-3858: a Responses web_search tool becomes a derived
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue