🛠️ Qwen Tool-Calling Template Fix
A focused chat-template fix for more reliable Qwen tool-call history rendering.
Start here: use
chat_template.jinjaas your chat-template override. If your runtime only accepts an inline value, usechat_template_oneline.txtinstead.
✨ What's included
| File | Purpose |
|---|---|
chat_template.jinja |
Readable Jinja template for direct use and inspection. |
chat_template_oneline.txt |
Inline, single-line form for runtimes that require it. |
🚀 Usage
Use either file as your runtime's chat-template override. For vLLM, pair it with a Qwen-compatible tool parser such as qwen3_xml.
This repository targets template-level tool-history rendering. It is not a universal claim that every repeated tool call is caused by the template or the model.
🔬 What the investigation found
The visible symptom—an agent calling the same tool again and again—does not identify the cause by itself. The failure can occur in the template, at the API boundary, in a runtime parser, in the model's tool grammar, or in the agent's control flow.
| ✅ This template helps with | ⚠️ This template alone cannot guarantee |
|---|---|
Preserving tool-call history when arguments is a mapping, JSON string, or array-like value. |
That a model will never choose a repeated action. |
| Avoiding history corruption during compatible template rendering. | That every parser or agent harness handles the model output correctly. |
| Retaining the native Qwen-style tool grammar where appropriate. | That a terminal tool stops the agent if the harness keeps sending new turns. |
🧩 Example 1 — a JSON-string argument mismatch
An OpenAI-compatible client may preserve a previous tool call as:
{
"name": "read_file",
"arguments": "{\"path\": \"README.md\"}"
}
But a mapping-only template may use logic equivalent to:
tool_call.arguments | items
That expression expects an object, whereas the client supplied a string. If the history is rendered incorrectly, the model may no longer see a faithful record of the completed call—and may attempt it again.
Normalize incompatible JSON-string histories at the runtime boundary when possible, then preserve the model's native tool grammar when rendering the conversation.
🛑 Example 2 — terminal tools need the harness to stop
For a terminal tool such as submit_implementation, the intended flow is:
model → submit_implementation → success → stop
If the harness instead appends the successful result to the conversation and invokes the model again, another submit_implementation call can follow. That resembles a model loop, but it is an agent-control-flow problem.
A chat template cannot make a terminal action terminal. The harness must end the run immediately after a successful terminal tool.
🧠 Practical checklist
- 🔄 Normalize incompatible JSON-string histories at the runtime boundary when needed.
- 🧬 Preserve the tool-call grammar the model learned during training.
- 🧪 Test Hugging Face Jinja and llama.cpp/minja independently when supporting both.
- 📋 Keep the exact rendered conversation in debug logs.
- 🧯 Add a maximum tool-turn limit and detect unchanged repeated tool signatures.
📚 Background research
The accompanying write-up documents the targeted regression and behavior investigations behind this template. It is not a general benchmark or a replacement claim for every stock Qwen template.
👉 Why Some Qwen Tool-Calling Loops Are Not Model Loops
📄 License
Apache-2.0