Skip to content

fix: strip orphaned function_call_output items before sending Responses API request - #1767

Open
wgnrai wants to merge 2 commits into
agent0ai:mainfrom
wgnrai:fix/responses-orphaned-tool-outputs
Open

fix: strip orphaned function_call_output items before sending Responses API request#1767
wgnrai wants to merge 2 commits into
agent0ai:mainfrom
wgnrai:fix/responses-orphaned-tool-outputs

Conversation

@wgnrai

@wgnrai wgnrai commented Jul 13, 2026

Copy link
Copy Markdown

Problem

Strict providers (MiniMax, Anthropic, etc.) reject Responses API requests when a function_call_output item references a call_id that has no matching function_call in the same request:

HTTP 400: invalid params, tool result's tool id(call_xxx) not found (2013)

This is tracked in #1724 (Failure Modes 1 & 2).

Root Cause

History compression (compress_attention) replaces middle messages with a summary Message that has sequence=0 and no metadata. Tool result messages survive with their function_call_output metadata intact. On the next turn, clear_responses_provider_state forces _select_input_items to fall back to input_from_messages, which reconstructs items from the compressed history:

  • Summary message (no tool_calls) → no function_call items
  • Tool result message (has metadata) → produces function_call_output with a call_idorphaned

The transport's _recover method doesn't recognize this error pattern, so it's raised as an unrecoverable BadRequestError instead of retrying with local state or falling back to Chat Completions.

Fix

Add ResponsesTransport._sanitize_input_items() — validates function_call / function_call_output pairing and strips orphaned outputs before the request is sent. Applied in from_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
    • New _sanitize_input_items() static method on ResponsesTransport
    • Applied in from_chat() between _select_input_items() and request assembly

Testing

Verified on MiniMax-M3 via Agent Zero v2.4 with v21_responses_workaround plugin 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.

…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)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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 ""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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 ""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant