fix(responses-bridge): flatten namespace tools without recursion

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
yucheng 2026-08-08 03:37:38 +00:00
parent 434c04e7bc
commit 82df8a9489
3 changed files with 34 additions and 6 deletions

View file

@ -5,7 +5,9 @@ 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.
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
@ -19,9 +21,7 @@ ResponsesAPITool: TypeAlias = FunctionToolParam | OpenAIMcpServerTool
def flatten_namespace_tools(tools: Sequence[ResponsesAPITool]) -> tuple[ResponsesAPITool, ...]:
"""Replace every namespace tool with its nested tools, recursively."""
"""Replace every namespace tool with the tools it contains."""
return tuple(
nested
for tool in tools
for nested in (flatten_namespace_tools(tool.get("tools") or ()) if tool.get("type") == "namespace" else (tool,))
nested for tool in tools for nested in (tool.get("tools") or () if tool.get("type") == "namespace" else (tool,))
)

View file

@ -1324,7 +1324,7 @@ class LiteLLMCompletionResponsesConfig:
chat_completion_tools.append(converted)
else:
_tool_type = tool.get("type")
if _tool_type in ("computer_use", "image_generation", "shell"):
if _tool_type in ("computer_use", "image_generation", "namespace", "shell"):
# Drop unsupported Responses-API-only tool types that have no
# Chat Completions equivalent. Passing them through verbatim
# causes providers to reject the request with "'function' is a

View file

@ -1633,6 +1633,34 @@ class TestToolTransformation:
"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