Skip to content

fix: preserve tool_calls when content is non-empty in chat completions parser#1779

Open
wgnrai wants to merge 1 commit into
agent0ai:mainfrom
wgnrai:fix/chat-completions-parser-tool-calls
Open

fix: preserve tool_calls when content is non-empty in chat completions parser#1779
wgnrai wants to merge 1 commit into
agent0ai:mainfrom
wgnrai:fix/chat-completions-parser-tool-calls

Conversation

@wgnrai

@wgnrai wgnrai commented Jul 17, 2026

Copy link
Copy Markdown

Problem

ChatCompletionsTransport.parse() only extracts tool_calls when content is empty:

if not response_delta:  # skipped when content exists
    tool_calls = ...

Some OpenAI-compatible models (e.g., Zhipu GLM-4.7 via Z.AI Coding Plan) return both content and tool_calls simultaneously. When this happens, the parser uses the text content and silently drops the tool calls, breaking the agentic loop.

Same pattern exists in ResponsesTransport.parse_response().

Fix

Check for tool_calls first, then fall back to content. Two locations:

  1. ChatCompletionsTransport.parse() (line ~520): Extract tool_calls unconditionally; use them as response_delta when present.
  2. ResponsesTransport.parse_response() (line ~1049): Same approach for the Responses API path.

Testing

Verified live against all three Z.AI Coding Plan models:

Model Behavior Before Fix After Fix
GLM-5.2 content="" + tool_calls
GLM-5-Turbo content="" + tool_calls
GLM-4.7 content="text" + tool_calls ❌ dropped ✅ preserved

Full agentic pipeline tested: tool call → tool result → natural language response, all through A0's LiteLLMTransport.complete().

Closes #1778

…s parser

Some OpenAI-compatible models (e.g., Zhipu GLM-4.7) return both content and
tool_calls in the same response. The previous parser logic only extracted
tool_calls when content was empty, silently dropping tool calls when both
were present. This broke the agentic loop for these models.

Fix: check for tool_calls first, then fall back to content text. Applied to
both ChatCompletionsTransport.parse() and ResponsesTransport.parse_response().

Closes agent0ai#1778

@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: 7bcce6d2dc

ℹ️ 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".

Comment on lines +522 to +523
if tool_calls_text:
parsed["response_delta"] = tool_calls_text

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 Update the helper DOX contract

This parser precedence change is a behavioral change to helpers/litellm_transport.py, but the matching helpers/litellm_transport.py.dox.md was not updated. 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." Please update the DOX contract to capture that tool/function calls now take precedence even when text content is present.

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.

ChatCompletionsTransport.parse() drops tool_calls when content is non-empty (affects GLM-4.7, other OpenAI-compatible models)

1 participant