fix: strip orphaned function_call_output items before sending Responses API request - #1767
fix: strip orphaned function_call_output items before sending Responses API request#1767wgnrai wants to merge 2 commits into
Conversation
…es API request History compression (compress_attention) can replace messages with a summary while leaving tool result messages intact. When _select_input_items falls back to input_from_messages after clear_responses_provider_state, the summary produces no function_call items but the tool result's metadata produces a function_call_output — an orphan that strict providers reject with 'tool id not found'. Add _sanitize_input_items() to ResponsesTransport that validates function_call / function_call_output pairing and strips orphaned outputs before the request is sent. Fixes agent0ai#1724 (Failure Mode 1 & 2)
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3ae53e5d9f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| state = _normalize_responses_state(kwargs.get("responses_state")) | ||
| input_items = cls._select_input_items(kwargs, messages, state) | ||
| request["input"] = input_items or "" | ||
| request["input"] = cls._sanitize_input_items(input_items) or "" |
There was a problem hiding this comment.
Preserve tool outputs for provider-state continuations
When previous_response_id is present, _select_input_items intentionally sends only the new responses_input_items; in the normal Responses tool loop that list is a lone function_call_output because the matching function_call is stored in the provider's previous response, not repeated in this request. This unconditional sanitizer drops that valid output via the no-call_ids branch, turning the request input into "", so the model never receives the tool result and provider-state tool calls stall (the existing test_responses_provider_state_uses_previous_response_and_new_items covers this expected output-only continuation).
Useful? React with 👍 / 👎.
Project-scoped agents (copywriter, art_director, etc.) were invisible to call_subordinate because get_available_agents_dict() called get_agents_dict() without project_name, so project agent directories were never scanned. One-line fix: pass project_name through. Fixes the 16 missing agency profiles reported in agent0ai#1724.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e2dbbb12f6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| state = _normalize_responses_state(kwargs.get("responses_state")) | ||
| input_items = cls._select_input_items(kwargs, messages, state) | ||
| request["input"] = input_items or "" | ||
| request["input"] = cls._sanitize_input_items(input_items) or "" |
There was a problem hiding this comment.
Keep helper DOX contracts in sync
This behavioral change to helpers/litellm_transport.py (and the same commit's helpers/subagents.py change) was not accompanied by updates to the matching *.py.dox.md files. helpers/AGENTS.md explicitly requires: “When a helper module is added, removed, renamed, or behaviorally changed, update its matching *.py.dox.md in the same change,” so the helper contracts are now stale for future transport/subagent work.
Useful? React with 👍 / 👎.
Problem
Strict providers (MiniMax, Anthropic, etc.) reject Responses API requests when a
function_call_outputitem references acall_idthat has no matchingfunction_callin the same request:This is tracked in #1724 (Failure Modes 1 & 2).
Root Cause
History compression (
compress_attention) replaces middle messages with a summaryMessagethat hassequence=0and no metadata. Tool result messages survive with theirfunction_call_outputmetadata intact. On the next turn,clear_responses_provider_stateforces_select_input_itemsto fall back toinput_from_messages, which reconstructs items from the compressed history:tool_calls) → nofunction_callitemsfunction_call_outputwith acall_id→ orphanedThe transport's
_recovermethod doesn't recognize this error pattern, so it's raised as an unrecoverableBadRequestErrorinstead of retrying with local state or falling back to Chat Completions.Fix
Add
ResponsesTransport._sanitize_input_items()— validatesfunction_call/function_call_outputpairing and strips orphaned outputs before the request is sent. Applied infrom_chat()which is the entry point for all Responses API requests built from conversation messages.This is a defensive guard that catches the orphan regardless of how it was created (compression, truncation, or future context-management bugs).
Changes
helpers/litellm_transport.py: +38 lines, -1 line_sanitize_input_items()static method onResponsesTransportfrom_chat()between_select_input_items()and request assemblyTesting
Verified on MiniMax-M3 via Agent Zero v2.4 with
v21_responses_workaroundplugin logging — the plugin's equivalent patch was catching orphans in production before this upstream fix was applied.Tradeoff
Stripped orphaned outputs mean the model loses one turn of tool context for that specific call. The agent's internal history still has the result, so it self-corrects on the next turn. This is preferable to a hard crash.