vllm - ✅(Solved) Fix [Bug]: glm4_moe_tool_parser._is_string_type crashes on /v1/responses streaming with 'FunctionTool' object has no attribute 'function' (affects GLM-4.5/4.7/5.1) [1 pull requests, 1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
vllm-project/vllm#39574Fetched 2026-04-12 13:24:42
View on GitHub
Comments
1
Participants
2
Timeline
3
Reactions
0
Author
Participants
Timeline (top)
commented ×1cross-referenced ×1referenced ×1

Error Message

ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File ".../starlette/responses.py", line 253, in stream_response
    async for chunk in self.body_iterator:
  File ".../vllm/entrypoints/openai/responses/api_router.py", line 38, in _convert_stream_to_sse_events
    async for event in generator:
  File ".../vllm/entrypoints/openai/responses/serving.py", line 1997, in responses_stream_generator
    async for event_data in processor(
  File ".../vllm/entrypoints/openai/responses/serving.py", line 1403, in _process_simple_streaming_events
    delta_message = tool_parser.extract_tool_calls_streaming(
  File ".../vllm/tool_parsers/glm4_moe_tool_parser.py", line 329, in extract_tool_calls_streaming
    is_string = self._is_string_type(
  File ".../vllm/tool_parsers/glm4_moe_tool_parser.py", line 130, in _is_string_type
    if tool.function.name != tool_name:
       ^^^^^^^^^^^^^
AttributeError: 'FunctionTool' object has no attribute 'function'

Root Cause

Root cause: the parser assumes tools arrive in the /v1/chat/completions shape ({"type":"function","function":{"name":...,"parameters":...}}), so tool.function.name works there. But on the /v1/responses endpoint, tools arrive in the new flat shape ({"type":"function","name":...,"parameters":...}, a FunctionTool object), so tool.function doesn't exist.

Fix Action

Fix / Workaround

Verified locally against vLLM 0.19 on 8×H20 with GLM-5.1-FP8: after the patch, the full streaming sequence for a tool call completes correctly — response.reasoning_text.delta × N → response.output_item.added (function_call) → response.function_call_arguments.delta × N → response.function_call_arguments.doneresponse.output_item.doneresponse.completed.

PR fix notes

PR #39601: [Bugfix] Fix glm4_moe_tool_parser._is_string_type for /v1/responses FunctionTool format

Description (problem / solution / changelog)

Summary

Fix AttributeError: 'FunctionTool' object has no attribute 'function' in Glm4MoeModelToolParser._is_string_type when using the /v1/responses endpoint.

Fixes #39574

Root Cause

_is_string_type directly accessed tool.function.name / tool.function.parameters, assuming the /v1/chat/completions tool shape. When called via /v1/responses, tools arrive as FunctionTool (flat structure: tool.name / tool.parameters), causing AttributeError.

Both streaming (extract_tool_calls_streaming) and non-streaming (extract_tool_calls) paths are affected.

Fix

Replace direct attribute access with _extract_tool_info(tool) from vllm/tool_parsers/utils.py, which already handles both ChatCompletionToolsParam and FunctionTool via isinstance. This is the established pattern used throughout the codebase.

Tests

Added two tests in tests/tool_parsers/test_glm4_moe_tool_parser.py:

  • test_extract_tool_calls_with_responses_format_tools: non-streaming path with FunctionTool
  • test_extract_tool_calls_with_responses_format_tools_streaming: streaming path with FunctionTool

Both tests create the parser with FunctionTool in the constructor (so self.tools contains FunctionTool), ensuring the fix is exercised.

Note

PR #38544 addresses the same issue using hasattr-style duck-typing. This PR instead reuses the existing _extract_tool_info utility for DRY consistency with the codebase.

Changed files

  • tests/tool_parsers/test_glm4_moe_tool_parser.py (modified, +104/-0)
  • vllm/tool_parsers/glm4_moe_tool_parser.py (modified, +5/-4)

Code Example

vllm version: 0.19.0
OS: Ubuntu 22.04
GPU: 8 x NVIDIA H20-3e
Model: GLM-5.1-FP8 (zai-org/GLM-5.1)
Serve cmd:
  vllm serve /path/to/GLM-5.1-FP8 \
    --tensor-parallel-size 8 \
    --served-model-name GLM-5.1 \
    --reasoning-parser glm45 \
    --tool-call-parser glm47 \
    --enable-auto-tool-choice \
    --trust-remote-code

---

vllm serve zai-org/GLM-5.1 \
  --tensor-parallel-size 8 \
  --reasoning-parser glm45 \
  --tool-call-parser glm47 \
  --enable-auto-tool-choice

---

curl -s -N -X POST http://localhost:8000/v1/responses \
  -H "Content-Type: application/json" \
  -d '{
    "model":"zai-org/GLM-5.1",
    "input":"What is the weather in Beijing?",
    "tools":[{
      "type":"function",
      "name":"get_weather",
      "description":"Get weather for a city",
      "parameters":{
        "type":"object",
        "properties":{"city":{"type":"string"}},
        "required":["city"]
      }
    }],
    "tool_choice":"auto",
    "max_output_tokens":500,
    "stream":true
  }'

---

ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File ".../starlette/responses.py", line 253, in stream_response
    async for chunk in self.body_iterator:
  File ".../vllm/entrypoints/openai/responses/api_router.py", line 38, in _convert_stream_to_sse_events
    async for event in generator:
  File ".../vllm/entrypoints/openai/responses/serving.py", line 1997, in responses_stream_generator
    async for event_data in processor(
  File ".../vllm/entrypoints/openai/responses/serving.py", line 1403, in _process_simple_streaming_events
    delta_message = tool_parser.extract_tool_calls_streaming(
  File ".../vllm/tool_parsers/glm4_moe_tool_parser.py", line 329, in extract_tool_calls_streaming
    is_string = self._is_string_type(
  File ".../vllm/tool_parsers/glm4_moe_tool_parser.py", line 130, in _is_string_type
    if tool.function.name != tool_name:
       ^^^^^^^^^^^^^
AttributeError: 'FunctionTool' object has no attribute 'function'

---

@staticmethod
def _is_string_type(
    tool_name: str,
    arg_name: str,
    tools: list[Tool] | None,
) -> bool:
    if tools is None:
        return False
    for tool in tools:
        # /v1/chat/completions wraps function data under `.function`;
        # /v1/responses uses a flat `FunctionTool` shape with name/parameters
        # directly on the tool itself. Support both transparently.
        func = tool.function if hasattr(tool, "function") else tool
        if func.name != tool_name:
            continue
        if func.parameters is None:
            return False
        arg_type = (
            func.parameters.get("properties", {})
            .get(arg_name, {})
            .get("type", None)
        )
        return arg_type == "string"
    logger.debug("No tool named '%s'.", tool_name)
    return False
RAW_BUFFERClick to expand / collapse

Your current environment

vllm version: 0.19.0
OS: Ubuntu 22.04
GPU: 8 x NVIDIA H20-3e
Model: GLM-5.1-FP8 (zai-org/GLM-5.1)
Serve cmd:
  vllm serve /path/to/GLM-5.1-FP8 \
    --tensor-parallel-size 8 \
    --served-model-name GLM-5.1 \
    --reasoning-parser glm45 \
    --tool-call-parser glm47 \
    --enable-auto-tool-choice \
    --trust-remote-code

Per the official vLLM recipe for GLM-5 (docs.vllm.ai/projects/recipes/en/latest/GLM/GLM5.html), --tool-call-parser glm47 is the recommended parser for GLM-5.1 — so this path is the canonical way to use GLM-5.1 with vLLM 0.19.

🐛 Describe the bug

Glm4MoeModelToolParser._is_string_type (inherited by Glm47MoeModelToolParser) crashes with AttributeError: 'FunctionTool' object has no attribute 'function' during streaming tool calls on the /v1/responses endpoint. The SSE stream ends silently after response.output_item.added for the function_call item, never emitting response.function_call_arguments.delta / .done / response.completed, causing downstream clients (e.g., OpenAI Codex CLI) to disconnect with "stream closed before response.completed".

Root cause: the parser assumes tools arrive in the /v1/chat/completions shape ({"type":"function","function":{"name":...,"parameters":...}}), so tool.function.name works there. But on the /v1/responses endpoint, tools arrive in the new flat shape ({"type":"function","name":...,"parameters":...}, a FunctionTool object), so tool.function doesn't exist.

This affects both glm45 and glm47 tool parsers since Glm47MoeModelToolParser inherits _is_string_type unchanged from Glm4MoeModelToolParser.

This bug still exists on main as of 2026-04-11: https://github.com/vllm-project/vllm/blob/main/vllm/tool_parsers/glm4_moe_tool_parser.py#L120-L142

🔁 Reproduce

vllm serve zai-org/GLM-5.1 \
  --tensor-parallel-size 8 \
  --reasoning-parser glm45 \
  --tool-call-parser glm47 \
  --enable-auto-tool-choice
curl -s -N -X POST http://localhost:8000/v1/responses \
  -H "Content-Type: application/json" \
  -d '{
    "model":"zai-org/GLM-5.1",
    "input":"What is the weather in Beijing?",
    "tools":[{
      "type":"function",
      "name":"get_weather",
      "description":"Get weather for a city",
      "parameters":{
        "type":"object",
        "properties":{"city":{"type":"string"}},
        "required":["city"]
      }
    }],
    "tool_choice":"auto",
    "max_output_tokens":500,
    "stream":true
  }'

Observed: stream sends response.output_item.added with the function_call item (status in_progress), then dies silently. No response.completed.

Stack trace

ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File ".../starlette/responses.py", line 253, in stream_response
    async for chunk in self.body_iterator:
  File ".../vllm/entrypoints/openai/responses/api_router.py", line 38, in _convert_stream_to_sse_events
    async for event in generator:
  File ".../vllm/entrypoints/openai/responses/serving.py", line 1997, in responses_stream_generator
    async for event_data in processor(
  File ".../vllm/entrypoints/openai/responses/serving.py", line 1403, in _process_simple_streaming_events
    delta_message = tool_parser.extract_tool_calls_streaming(
  File ".../vllm/tool_parsers/glm4_moe_tool_parser.py", line 329, in extract_tool_calls_streaming
    is_string = self._is_string_type(
  File ".../vllm/tool_parsers/glm4_moe_tool_parser.py", line 130, in _is_string_type
    if tool.function.name != tool_name:
       ^^^^^^^^^^^^^
AttributeError: 'FunctionTool' object has no attribute 'function'

Proposed fix

_is_string_type in vllm/tool_parsers/glm4_moe_tool_parser.py should normalise once and then access the name/parameters, supporting both tool shapes:

@staticmethod
def _is_string_type(
    tool_name: str,
    arg_name: str,
    tools: list[Tool] | None,
) -> bool:
    if tools is None:
        return False
    for tool in tools:
        # /v1/chat/completions wraps function data under `.function`;
        # /v1/responses uses a flat `FunctionTool` shape with name/parameters
        # directly on the tool itself. Support both transparently.
        func = tool.function if hasattr(tool, "function") else tool
        if func.name != tool_name:
            continue
        if func.parameters is None:
            return False
        arg_type = (
            func.parameters.get("properties", {})
            .get(arg_name, {})
            .get("type", None)
        )
        return arg_type == "string"
    logger.debug("No tool named '%s'.", tool_name)
    return False

Verified locally against vLLM 0.19 on 8×H20 with GLM-5.1-FP8: after the patch, the full streaming sequence for a tool call completes correctly — response.reasoning_text.delta × N → response.output_item.added (function_call) → response.function_call_arguments.delta × N → response.function_call_arguments.doneresponse.output_item.doneresponse.completed.

Happy to submit a PR with the fix + a small test covering the /v1/responses tool shape if that helps.

Before submitting a new issue...

  • Make sure you already searched for relevant issues, and asked the chatbot living at the bottom right corner of the documentation page, which can answer lots of frequently asked questions.

extent analysis

TL;DR

The most likely fix is to update the _is_string_type method in vllm/tool_parsers/glm4_moe_tool_parser.py to support both tool shapes.

Guidance

  • Verify that the issue is indeed caused by the AttributeError: 'FunctionTool' object has no attribute 'function' exception.
  • Apply the proposed fix to the _is_string_type method to support both tool shapes.
  • Test the fix locally to ensure that the full streaming sequence for a tool call completes correctly.
  • Consider submitting a PR with the fix and a small test covering the /v1/responses tool shape.

Example

The proposed fix for the _is_string_type method is:

@staticmethod
def _is_string_type(
    tool_name: str,
    arg_name: str,
    tools: list[Tool] | None,
) -> bool:
    if tools is None:
        return False
    for tool in tools:
        # /v1/chat/completions wraps function data under `.function`;
        # /v1/responses uses a flat `FunctionTool` shape with name/parameters
        # directly on the tool itself. Support both transparently.
        func = tool.function if hasattr(tool, "function") else tool
        if func.name != tool_name:
            continue
        if func.parameters is None:
            return False
        arg_type = (
            func.parameters.get("properties", {})
            .get(arg_name, {})
            .get("type", None)
        )
        return arg_type == "string"
    logger.debug("No tool named '%s'.", tool_name)
    return False

Notes

The fix assumes that the FunctionTool object has a name and parameters attribute. If this is not the case, additional modifications may be necessary.

Recommendation

Apply the workaround by updating the _is_string_type method with the proposed fix, as it has been verified to work locally and addresses the root cause of the issue.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING