Repository navigation
fix(responses): drop tool_search and local_shell in the chat completions bridge - #41953
Conversation
…ons 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
|
I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".
|
|
|
bugbot run |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 5477dbe. Configure here.
TLDR
Problem this solves:
tool_searchtool on every turntools: []withparallel_tool_calls, a second 400How it solves it:
tool_searchandlocal_shellwith the existing warning logcomputer_use,image_generation, andshellalready getparallel_tool_callsis dropped when no chat tools remainUser Flow
Before: a developer running Codex CLI with
wire_api = "responses"against a proxy deployment that hasuse_chat_completions_api: truegets a 400 on their first message and never an answer~/.codex/config.toml:base_url = "http://<proxy>/v1",wire_api = "responses",model = "gpt-5.4-mini"Reply with just the word pong.functiontools, thecustomapply_patch tool, one{"type": "tool_search", "execution": "client", ...}, and oneweb_search) plusparallel_tool_calls: trueInvalid value: 'tool_search'. Supported values are: 'function' and 'custom'.andparam: tools[8].typetools: []andparallel_tool_calls: true, also gets 400'parallel_tool_calls' is only allowed when 'tools' are specified., so the session never gets a titleAfter: the same Codex turn gets an answer and a session title
~/.codex/config.toml:base_url = "http://<proxy>/v1",wire_api = "responses",model = "gpt-5.4-mini"Reply with just the word pong.parallel_tool_calls: trueoutput_textispongpongin the chat pane and marks the turn donetools: []andparallel_tool_calls: true, answers 200 and the status line shows the generated titleRelevant issues
Relates to #27655 (its
local_shellrepro and thetool_searchpass-through it lists are fixed here; thecode_interpreterconversion and per-providercomputer_usemapping it also asks for are out of scope)Relates to #33779 (a Codex
namespacetool reaching a chat completions provider, a sibling shape the bridge already converts)Affected release
Linear ticket
Resolves LIT-7902
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
uv run pytest tests/test_litellm/<your_test_file>.py -v. Leave the suites (make test-unit-*,make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more@greptileaito re-request a review after pushing changes)Delays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
Screenshots / Proof of Fix
Both sides boot the proxy from a clean worktree at the named commit with two uvicorn workers (
--num_workers 2) on a random free port, no database, and the real Azure OpenAIgpt-5.4-minideployment behinduse_chat_completions_api: true, so every request below cost real Azure tokens. The end-user client is Codex CLI 0.155.1 driven interactively in its TUI under tmux. Before ran on port 21753 and After on port 37053;$PORTbelow stands for the side's portShared setup
proxy_config.yaml:Proxy boot, from the worktree checked out at the side's commit:
Codex
config.tomlin a scratchCODEX_HOME(the last two blocks are what Codex 0.155.1 writes itself after its folder-trust prompt and its model-migration notice, kept so the run lands straight on the prompt withgpt-5.4-mini):Codex launch and drive:
codex_tools_body.json is the
/v1/responsesbody Codex 0.155.1 sends for that turn: its 10 tools in its order (exec_command,write_stdin,request_user_input, thecustomapply_patch,view_image,get_goal,create_goal,update_goal,tool_search,web_search) plusparallel_tool_calls: true.tools[8]is the hosted tool this PR drops:{"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}}Before (825e287)
Codex CLI turn
Reply with just the word pong., press Entercurl, the exact Codex request
curl -sS -i http://127.0.0.1:$PORT/v1/responses -H 'Authorization: Bearer sk-1234' -H 'Content-Type: application/json' -d @codex_tools_body.json, run twice so each worker serves onecurl, Codex's session-title request
curl -sS -i http://127.0.0.1:$PORT/v1/responses -H 'Authorization: Bearer sk-1234' -H 'Content-Type: application/json' -d '{"model":"gpt-5.4-mini","input":"Reply with just the word pong.","tools":[],"parallel_tool_calls":true}', run twicecurl, one function tool with parallel_tool_calls
curl -sS -i http://127.0.0.1:$PORT/v1/responses -H 'Authorization: Bearer sk-1234' -H 'Content-Type: application/json' -d '{"model":"gpt-5.4-mini","input":"Reply with just the word pong.","parallel_tool_calls":true,"tools":[{"type":"function","name":"get_weather","description":"Get weather","parameters":{"type":"object","properties":{"city":{"type":"string"}},"required":["city"]},"strict":false}]}'HTTP/1.1 200 OK,"status":"completed",output_textpong,usage.total_tokens132curl, no tools
curl -sS -i http://127.0.0.1:$PORT/v1/responses -H 'Authorization: Bearer sk-1234' -H 'Content-Type: application/json' -d '{"model":"gpt-5.4-mini","input":"Reply with just the word pong."}'HTTP/1.1 200 OK,"status":"completed",output_textpong,usage.total_tokens17After (5477dbe)
Codex CLI turn
Reply with just the word pong., press Enter• pongwith the turn marked done, and the status line now ends in the generated session titleReply pong, so Codex's parallel title request went through as well:curl, the exact Codex request
curl -sS -i http://127.0.0.1:$PORT/v1/responses -H 'Authorization: Bearer sk-1234' -H 'Content-Type: application/json' -d @codex_tools_body.json, run twice so each worker serves oneHTTP/1.1 200 OK,"status":"completed",output_textpong,usage.total_tokens1551 against 17 with no tools, so the 8functionandcustomtools still reach the modelcurl, Codex's session-title request
curl -sS -i http://127.0.0.1:$PORT/v1/responses -H 'Authorization: Bearer sk-1234' -H 'Content-Type: application/json' -d '{"model":"gpt-5.4-mini","input":"Reply with just the word pong.","tools":[],"parallel_tool_calls":true}', run twiceHTTP/1.1 200 OK,"status":"completed",output_textpong,usage.total_tokens17curl, one function tool with parallel_tool_calls
curl -sS -i http://127.0.0.1:$PORT/v1/responses -H 'Authorization: Bearer sk-1234' -H 'Content-Type: application/json' -d '{"model":"gpt-5.4-mini","input":"Reply with just the word pong.","parallel_tool_calls":true,"tools":[{"type":"function","name":"get_weather","description":"Get weather","parameters":{"type":"object","properties":{"city":{"type":"string"}},"required":["city"]},"strict":false}]}'HTTP/1.1 200 OK,"status":"completed",output_textpong,usage.total_tokens132, unchanged from Beforecurl, no tools
curl -sS -i http://127.0.0.1:$PORT/v1/responses -H 'Authorization: Bearer sk-1234' -H 'Content-Type: application/json' -d '{"model":"gpt-5.4-mini","input":"Reply with just the word pong."}'HTTP/1.1 200 OK,"status":"completed",output_textpong,usage.total_tokens17, unchanged from BeforeObservations from the run:
tools: []andparallel_tool_calls: false; pre-existing, unchanged hereType
🐛 Bug Fix
Caveats (if any)
Medium
tool_searchis dropped, so Codex's deferred-tool discovery is unavailable through the bridge, the same treatment the bridge givescomputer_use. The alternative considered was converting Codex'sexecution: cliententry into afunctiontool namedtool_searchso the model could still call it; not done here because whether Codex accepts a plainfunction_callback for that tool is unverified and would need its own QA, while the drop matches how the bridge already treats every hosted tool without a Chat Completions equivalentLow
gpt-5.4-minitogpt-5.6-lunaon first launch, and that model sends tools as anadditional_toolsinput item instead, a shape this PR does not touchparallel_tool_callswithouttools; OpenAI documents the same rule but was not re-tested here, and dropping the flag is harmless either way since it only means something alongside toolsweb_searchentry was already turned intoweb_search_optionsand dropped for providers that reject it (Azure included); this PR leaves that path as it wascode_interpreterfrom Responses API → Chat Completions transformation silently passes through unsupported built-in tool types #27655 andfile_searchstill pass through verbatim (mcppasses through on purpose since Chat Completions accepts it); out of scope hereFinal Attestation
Note
Low Risk
Scoped to the Responses-to-Chat-Completions request transform; reduces invalid upstream payloads without changing auth or core routing.
Overview
Fixes 400 errors when Codex (and similar clients) hit LiteLLM’s Responses → Chat Completions bridge with hosted tools or empty tool lists.
The bridge now treats
tool_searchandlocal_shelllike other Responses-only tools (computer_use,shell, etc.): they are dropped with a warning instead of being forwarded to chat providers that only acceptfunction/custom.parallel_tool_callsis removed from the outgoing completion request whenever no chat tools remain after that filtering (e.g.tools: []or only dropped hosted tools), while it is still passed through when at least one convertible function/custom tool survives.Tests cover empty/hosted-only vs function-tool cases and Codex-style mixed tool lists.
Reviewed by Cursor Bugbot for commit 5477dbe. Bugbot is set up for automated code reviews on this repo. Configure here.