mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-21 00:21:49 +00:00
fix(responses): drop tool_search and local_shell in the chat completions bridge
Hosted Responses API tools with no Chat Completions equivalent were forwarded verbatim, so Codex 0.140+ got a 400 from the provider on every turn. The bridge now drops tool_search and local_shell the same way it drops computer_use, image_generation, and shell, and also drops parallel_tool_calls when no chat tools remain, since chat completions only accepts it alongside tools
This commit is contained in:
parent
825e287f63
commit
5477dbe74c
2 changed files with 117 additions and 1 deletions
|
|
@ -466,6 +466,7 @@ class LiteLLMCompletionResponsesConfig:
|
|||
if not tools:
|
||||
litellm_completion_request.pop("tool_choice", None)
|
||||
litellm_completion_request.pop("tools", None)
|
||||
litellm_completion_request.pop("parallel_tool_calls", None)
|
||||
|
||||
# Responses API `Completed` events require usage, we pass `stream_options` to litellm.completion to include usage
|
||||
if stream is True:
|
||||
|
|
@ -2036,7 +2037,7 @@ class LiteLLMCompletionResponsesConfig:
|
|||
if tool_type == "custom":
|
||||
converted: Final = convert_custom_tool_to_function_tool(tool)
|
||||
return ResponsesToolChatForm(chat_tools=() if converted is None else (converted,), web_search_options=None)
|
||||
if tool_type in ("computer_use", "image_generation", "shell"):
|
||||
if tool_type in ("computer_use", "image_generation", "local_shell", "shell", "tool_search"):
|
||||
verbose_logger.warning(
|
||||
"Dropping Responses API tool of type '%s': it has no Chat Completions "
|
||||
"equivalent and the target provider would reject the request.",
|
||||
|
|
|
|||
|
|
@ -1248,6 +1248,45 @@ class TestFunctionCallTransformation:
|
|||
assert "tool_choice" not in result
|
||||
assert "tools" not in result
|
||||
|
||||
def test_parallel_tool_calls_dropped_when_no_chat_tools_remain(self) -> None:
|
||||
transform: Final = LiteLLMCompletionResponsesConfig.transform_responses_api_request_to_chat_completion_request
|
||||
codex_tool_search: Final = {
|
||||
"type": "tool_search",
|
||||
"execution": "client",
|
||||
"description": "Searches over deferred tool metadata with BM25.",
|
||||
"parameters": {"type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"]},
|
||||
}
|
||||
function_tool: Final = {
|
||||
"type": "function",
|
||||
"name": "get_goal",
|
||||
"description": "Returns the current goal.",
|
||||
"parameters": {"type": "object", "properties": {}},
|
||||
"strict": True,
|
||||
}
|
||||
|
||||
empty_tools_result: Final = transform(
|
||||
model="azure/gpt-5.4-mini",
|
||||
input="Reply with just the word pong.",
|
||||
responses_api_request={"tools": [], "parallel_tool_calls": True},
|
||||
custom_llm_provider="azure",
|
||||
)
|
||||
hosted_only_result: Final = transform(
|
||||
model="azure/gpt-5.4-mini",
|
||||
input="Reply with just the word pong.",
|
||||
responses_api_request={"tools": [codex_tool_search], "parallel_tool_calls": True},
|
||||
custom_llm_provider="azure",
|
||||
)
|
||||
function_tools_result: Final = transform(
|
||||
model="azure/gpt-5.4-mini",
|
||||
input="Reply with just the word pong.",
|
||||
responses_api_request={"tools": [function_tool], "parallel_tool_calls": True},
|
||||
custom_llm_provider="azure",
|
||||
)
|
||||
|
||||
assert "parallel_tool_calls" not in empty_tools_result
|
||||
assert "parallel_tool_calls" not in hosted_only_result
|
||||
assert function_tools_result["parallel_tool_calls"] is True
|
||||
|
||||
def test_function_call_without_call_id_fallback_to_id(self):
|
||||
"""Test that function_call items can use 'id' field when 'call_id' is missing"""
|
||||
function_call_item = {
|
||||
|
|
@ -1659,6 +1698,82 @@ class TestToolTransformation:
|
|||
assert len(result_tools) == 0
|
||||
assert web_search_options is None
|
||||
|
||||
def test_transform_codex_tools_drops_hosted_tool_search(self) -> None:
|
||||
codex_tools: Final = [
|
||||
{
|
||||
"type": "function",
|
||||
"name": "exec_command",
|
||||
"description": "Runs a command in a PTY.",
|
||||
"parameters": {"type": "object", "properties": {"cmd": {"type": "string"}}, "required": ["cmd"]},
|
||||
"strict": True,
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "write_stdin",
|
||||
"description": "Writes characters to an existing session's stdin.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {"session_id": {"type": "number"}, "chars": {"type": "string"}},
|
||||
"required": ["session_id", "chars"],
|
||||
},
|
||||
"strict": True,
|
||||
},
|
||||
{
|
||||
"type": "custom",
|
||||
"name": "apply_patch",
|
||||
"description": "The `apply_patch` tool can be used to edit files.",
|
||||
"format": {
|
||||
"type": "grammar",
|
||||
"syntax": "lark",
|
||||
"definition": 'start: begin_patch hunk+ end_patch\nbegin_patch: "*** Begin Patch" LF\n',
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "tool_search",
|
||||
"execution": "client",
|
||||
"description": (
|
||||
"# Tool discovery\n\nSearches over deferred tool metadata with BM25 and exposes matching tools "
|
||||
"for the next model call.\n\nYou have access to tools from the following sources:\n"
|
||||
"- Multi-agent tools: Spawn and manage sub-agents.\nSome of the tools may not have been provided "
|
||||
"to you upfront, and you should use this tool (`tool_search`) to search for the required tools. "
|
||||
"For MCP tool discovery, always use `tool_search` instead of `list_mcp_resources` or "
|
||||
"`list_mcp_resource_templates`."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"limit": {"type": "number", "description": "Maximum number of tools to return. Defaults to 8."},
|
||||
"query": {"type": "string", "description": "Search query for deferred tools."},
|
||||
},
|
||||
"required": ["query"],
|
||||
"additionalProperties": False,
|
||||
},
|
||||
},
|
||||
{"type": "web_search", "external_web_access": False, "search_content_types": ["text", "image"]},
|
||||
]
|
||||
function_and_custom_count: Final = sum(1 for tool in codex_tools if tool["type"] in ("function", "custom"))
|
||||
|
||||
(
|
||||
result_tools,
|
||||
web_search_options,
|
||||
) = LiteLLMCompletionResponsesConfig.transform_responses_api_tools_to_chat_completion_tools(tools=codex_tools)
|
||||
|
||||
assert not any(tool.get("type") == "tool_search" for tool in result_tools)
|
||||
assert all(tool.get("type") == "function" for tool in result_tools)
|
||||
assert len(result_tools) == function_and_custom_count
|
||||
assert web_search_options is not None
|
||||
|
||||
def test_transform_local_shell_tools_dropped(self) -> None:
|
||||
(
|
||||
result_tools,
|
||||
web_search_options,
|
||||
) = LiteLLMCompletionResponsesConfig.transform_responses_api_tools_to_chat_completion_tools(
|
||||
tools=[{"type": "local_shell"}]
|
||||
)
|
||||
|
||||
assert result_tools == []
|
||||
assert web_search_options is None
|
||||
|
||||
def test_transform_custom_tools_to_function_tools(self):
|
||||
"""Test that custom (freeform/grammar) tools are converted to function tools"""
|
||||
custom_tool = {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue