Skip to content

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

Description

@wgnrai

Description

ChatCompletionsTransport.parse() in helpers/litellm_transport.py (line ~520) only extracts tool_calls from the response message when content is empty:

if not response_delta:
    tool_calls = _as_list(_get_value(message, "tool_calls"))
    response_delta = ChatCompletionsTransport.tool_calls_text(tool_calls)

Some OpenAI-compatible models (e.g., Zhipu GLM-4.7 via the Z.AI Coding Plan endpoint) 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.

The same pattern exists in ResponsesTransport.parse_response() (line ~1049).

Steps to Reproduce

  1. Configure a model that returns both content + tool_calls (e.g., openai/glm-4.7 via api.z.ai/api/coding/paas/v4)
  2. Send a request with tools and tool_choice: "auto"
  3. Observe that the response contains both message.content (non-empty text) and message.tool_calls
  4. ChatCompletionsTransport.parse() returns the text content and _output_items is missing

Expected Behavior

Tool calls should be preserved regardless of whether content is also present. The finish_reason: "tool_calls" field indicates the model intended a tool call.

Suggested Fix

Check for tool calls first, then fall back to content:

# ChatCompletionsTransport.parse() — line ~518
parsed = {"reasoning_delta": reasoning_delta, "response_delta": response_delta}
tool_calls = _as_list(_get_value(message, "tool_calls"))
tool_calls_text = ChatCompletionsTransport.tool_calls_text(tool_calls) if tool_calls else ""
if tool_calls_text:
    parsed["response_delta"] = tool_calls_text
    parsed["_output_items"] = ChatCompletionsTransport.output_items(tool_calls)
return parsed

Environment

  • Agent Zero v2.5
  • LiteLLM via litellm_provider: openai with custom api_base
  • Z.AI Coding Plan endpoint (api.z.ai/api/coding/paas/v4)
  • GLM-4.7 model
  • a0_api_mode: chat

Additional Context

Tested all three models on the Z.AI Coding Plan:

  • GLM-5.2: Returns empty content + tool_calls (standard format) ✅
  • GLM-5-Turbo: Returns empty content + tool_calls (standard format) ✅
  • GLM-4.7: Returns both content + tool_calls (non-standard) ❌ — breaks parser

The fix is backward-compatible — when no tool_calls are present, behavior is unchanged.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions