[Bug Report] chat_template is vulnerable to LLM hallucinated tool arguments, causing vLLM crash / chat_template 工具调用参数容错 Bug 及修复方案

#60
by reex3625 - opened

Hi Qwen Team and community,
Qwen 团队和各位开发者好,

I encountered a severe crash issue when deploying this model using vLLM for Function Calling / Tool Calling.
我在使用 vLLM 部署该模型并进行工具调用(Function Calling)时遇到了一个严重的崩溃问题。

The Problem / 问题:
If the model hallucinates and outputs a non-JSON object for arguments (e.g., just a raw string like "action"), the client (like LiteLLM or OpenClaw) usually catches this and sends back a tool role message like "Validation failed for tool..." to trigger self-correction.
However, when vLLM tries to render this conversation history using the default chat_template, it crashes immediately with a 500/400 Bad Request error. The model never gets a chance to correct itself.
当大模型产生幻觉,没有按照 JSON Object 格式输出 arguments(比如直接输出了一个纯字符串 "action")时,客户端(如 LiteLLM)通常会将这个错误记录作为历史上下文发回给模型,期望模型进行自我纠错(Self-Correction)。
但是,当 vLLM 尝试使用默认的 chat_template 渲染这段历史对话时,整个服务端会直接崩溃,抛出 500/400 Bad Request 错误,导致模型根本没机会看到报错信息。

Root Cause / 问题原因:
The bug is in the Jinja2 chat_template inside tokenizer_config.json. The template blindly assumes that tool_call.arguments is always a successfully parsed dict (Mapping).
问题出在 tokenizer_config.json 中的 chat_template。Jinja 模板代码强制假设 tool_call.arguments 永远是一个合法的字典(Mapping)。当接收到纯字符串时,调用 |items 过滤器会直接触发 Jinja2 的底层类型错误:TypeError: Can only get item pairs from a mapping.。

{# Defective code snippet in default template #}
{%- if tool_call.arguments is defined %}
    {%- for args_name, args_value in tool_call.arguments|items %}
        {{- '<parameter=' + args_name + '>\n' }}
        ...

When it receives a plain string (like "action"), the |items filter throws a native Jinja2 TypeError: Can only get item pairs from a mapping., terminating the entire request.

Solution / 修复方案
I have rewritten the arguments parsing logic in the chat_template to be highly robust. It now includes type sniffing and safe fallback mechanisms. If the model outputs garbage, the template wraps it in a special <parameter=raw_error_arguments> tag instead of crashing, allowing the LLM to see its mistake in the next turn.
我重写了 chat_template 中处理 arguments 的逻辑,提高了鲁棒性(包含类型嗅探和安全反序列化)。如果模型输出了无法解析的乱码或字符串,模板不再崩溃,而是将其安全地包裹在 <parameter=raw_error_arguments> 标签中,从而让大模型在下一轮能够感知并纠正自己的语法错误。

Robust Code Snippet (Replace the original arguments parsing loop):

{%- if tool_call.arguments is defined %}
    {%- set args = tool_call.arguments %}
    
    {# 1. Try safe JSON parsing if it's a string that looks like a dict / 如果是字符串且类似 JSON 字典,尝试安全解析 #}
    {%- if args is string %}
        {%- set str_args = args | trim %}
        {%- if str_args.startswith('{') and str_args.endswith('}') %}
            {%- set args = str_args | fromjson %}
        {%- endif %}
    {%- endif %}

    {# 2. Normal iteration if it's a valid dict / 如果是合法的字典,正常遍历 #}
    {%- if args is mapping %}
        {%- for args_name, args_value in args.items() %}
            {{- '<parameter=' + args_name + '>\n' }}
            {%- set args_value = args_value | tojson | safe if args_value is mapping or (args_value is sequence and args_value is not string) else args_value | string %}
            {{- args_value }}
            {{- '\n</parameter>\n' }}
        {%- endfor %}
    {%- else %}
        {# 3. Ultimate Fallback: Output as raw parameter to prevent rendering crash / 容错:如果LLM生成了非 JSON,作为特殊 raw parameter 输出,防止引擎崩溃 #}
        {{- '<parameter=raw_error_arguments>\n' }}
        {{- args | string }}
        {{- '\n</parameter>\n' }}
    {%- endif %}
{%- endif %}

Extra Debug Log (For Reference) / 附加 LiteLLM (我用于管理各个LLM并提供统一出口)报错日志供参考:

litellm.proxy.proxy_server._handle_llm_api_exception(): Exception occured - litellm.BadRequestError: Hosted_vllmException - {"error":{"message":"Can only get item pairs from a mapping.","type":"BadRequestError","param":null,"code":400}}. 
Traceback (most recent call last):
  File "/usr/local/lib/python3.12/dist-packages/jinja2/filters.py", line 249, in do_items
    raise TypeError("Can only get item pairs from a mapping.")
TypeError: Can only get item pairs from a mapping.

Hope this helps improve the robustness of Qwen's tool calling capabilities in production!
希望这能帮助提升 Qwen3.5 在生产环境中工具调用的稳定性!

Sign up or log in to comment