Add specialized tagged thinking tool parser - #24202
Conversation
|
Hi @bartdeboer, thanks for your contribution! Per our contribution guidelines, the automated PR checker found the following issue(s) that need your attention:
Please note that maintainers reserve the right to make final decisions on PRs. If you believe there is a mistake, please comment below. |
8595041 to
72deda5
Compare
|
fwiw, this solved the described problem, which I was seeing when using Qwen3.6-27B |
72deda5 to
2d8b218
Compare
|
Rebased this PR on current ./build/bin/test-chat --suppress-debug --template Qwen3.5
./build/bin/test-chat --suppress-debugI also pushed a CUDA Digest: |
|
When can this PR be reviewed? It's solving a real issue with Qwen that currently makes it a pain to use in very long tasks, as it will stop when it emits the toolcall in its thinking trace. |
|
If masking the tool calling token during reasoning is not an adequate solution, which appears to be the case, then I'm onboard with this change (with some adjustments). However, it's @pwilkin that you need to convince. |
|
Is there anything I can do to help move this forward? I don’t think this needs to come at the cost of the token-masking direction. @pwilkin? |
|
Sorry, I've been thinking about this. I'm seriously wondering whether not to just add a flag to the autoparser to allow specifying models that permit tool calling within reasoning instead of a dedicated parser, as I know Kimi tends to exhibit similar behavior as well. However, I can as well do that in a followup PR not to stall this anymore. |
|
I'll follow up with some adjustments and we can get this merged in. |
|
Any updates? |
- Add ggml-org/llama.cpp#20260 - Update the note about ggml-org/llama.cpp#24202
|
The reasoning budget sampler needs to support multiple terminating sequences. Right now anyone who uses it would still be met with errors. I will work this in a separate PR and then we can integrate it into this one. |
|
Sounds good. Let me know if there’s anything I can do to assist. |
|
Related to: QwenLM/Qwen3.8#150 |
|
@Slion some clients don't return back reasoning content from assistant responses, which is required by Qwen for agentic workflows. The Copilot agent in VS Code is one such client, which causes these errors. The remedy is to use a client that does or disable reasoning (which may also impact performance). |
You make it sound like it can't be fixed from llama.cpp. Now it looks like I fixed most llama.cpp issues with that patch: With that patch my agent could run for over an hour uninterrupted and actually managed to fix my issues. Here is my router mode version = 1
[*]
device = cuda0
host = 0.0.0.0
port = 8080
log-file = D:/Models/server.log
reasoning = on
;reasoning-format = deepseek-legacy
reasoning-format = deepseek
chat-template-kwargs = {"enable_thinking": false}
jinja = true
[qwen/qwen3.6-27b-Q8_0]
model = D:/Models/LM-Studio/lmstudio-community/Qwen3.6-27B-GGUF/Qwen3.6-27B-Q8_0.gguf
mmproj = D:/Models/LM-Studio/lmstudio-community/Qwen3.6-27B-GGUF/mmproj-Qwen3.6-27B-BF16.gguf
;chat-template-kwargs = {"enable_thinking": false}
;chat-template-file = D:/Dev/HuggingFace/froggeric/Qwen-Fixed-Chat-Templates/chat_template.jinja
;reasoning = off
;c = 196608
[qwen/qwen3.6-27b-BF16]
model = D:/Models/LM-Studio/lmstudio-community/Qwen3.6-27B-GGUF/Qwen3.6-27B-BF16.gguf
mmproj = D:/Models/LM-Studio/lmstudio-community/Qwen3.6-27B-GGUF/mmproj-Qwen3.6-27B-BF16.gguf
;chat-template-kwargs = {"enable_thinking": false}
;chat-template-file = D:/Dev/HuggingFace/froggeric/Qwen-Fixed-Chat-Templates/chat_template.jinja
;reasoning = off
;c = 196608
spec-type = draft-mtp
spec-draft-n-max = 6Here is the [
{
"name": "llama.cpp",
"vendor": "customendpoint",
"apiType": "chat-completions",
"apiKey": "local",
"models": [
{
"id": "qwen/qwen3.6-27b-Q8_0",
"name": "llama.cpp - Qwen3.6-27B - Q8_0",
"url": "http://LINUX-PC:8080/v1",
"toolCalling": true,
"vision": true,
"maxInputTokens": 131072,
"maxOutputTokens": 65536,
"thinking": true,
"streaming": true
},
{
"id": "qwen/qwen3.6-27b-BF16",
"name": "llama.cpp - Qwen3.6-27B - BF16",
"url": "http://LINUX-PC:8080/v1",
"toolCalling": true,
"vision": true,
"maxInputTokens": 131072,
"maxOutputTokens": 65536,
"thinking": true,
"streaming": true
}
]
}
] |
This is a workaround to handle a prompt construction issue. I have no interest in adding workarounds to handle this unless you can show the issue is internal to llama.cpp. It doesn't change the fact that you are running a lobotomized version of the model. |
|
Here is an AI generated explanations of the patch linked above, strangely I did not have to directly deal with tool call within think element for some reason. Maybe I just got lucky and will still have to fix that. Qwen 3.6 GHCP Interruption Fix SummaryScopeThis note summarizes the interruption issues we hit while using Qwen 3.6 as a coding agent in VS Code GHCP, what symptoms we observed, and what changes were made to resolve them. Primary commit reviewed:
Files changed in that commit:
Interruption Patterns We Faced1. Streaming protocol interruption at end of responseObserved behavior:
Impact:
Fix:
2. Reasoning-only output parsed as empty assistant messageObserved behavior:
Fix:
3. One-token immediate EOS interruptionObserved behavior:
Log signature:
Root cause:
Fixes:
Post-Commit Local AdjustmentAfter commit 6674ef2, one local adjustment was added:
Change:
Reason:
Diagnostics and VerificationDiagnostics helper added:
What it was used for:
Build and validation notes:
Net ResultThe fix set addresses three independent interruption classes:
This combination is intended to make Qwen 3.6 agent turns stable in GHCP while keeping changes narrow and model-safe. |
I don't have the pretention of thinking this could be merged as it is. I don't have the expertise to come up with some "proper fix". My goal is merely to get Qwen 3.6 agent working with Copilot. I'm happy keeping that patch on my fork and rebasing it as needed.
I'm not asking you to. Just thought it could help field experts understand the issues we are having with this use case and maybe come up with a "proper fix".
How so? It's pretty stellar if you ask me. Reasoning and calling tools for over an hour at full context on an RTX 6K and actually fixing complex issues, all in full BF16 while keeping my office space warm and cosy 🔥 That was pretty glorious, certainly my best run ever with that setup. |
|
Thanks, this helped narrow the investigation. I found a few parser edge cases in #24202 that could cause valid tagged tool calls to be missed and emitted as normal output/reasoning instead. I’m looking into whether those should be addressed in this PR. I’m not sure yet whether these edge cases are the exact cause of the Copilot/VS Code screenshot, though. @Slion could you share the raw assistant completion that produced the visible |
|
Let's tone down the AI analysis and, instead, if you could capture the request/response payloads when these issues occur that would greatly help identify the core issue. I know most people don't leave a mitm proxy running, or the data is sensitive, so it's always hard to get good reproductions. |
|
Sorry guys I don't think I kept all the server logs. They reset every time I restart the server. Here is how I got an AI agent to investigate and fix my issues:
@aldehir If you tell me how to collect the logs you need I may try find the time and patience to get them for you. Privacy should not be an issue. I can test that on some projects I own, even if they are not open source, it's no big deal. The thing is, there are multiple issues at play here. So we may have to do a lot of back and forth. The AI generated explanation above is actually quite good I find. Three interruption issues were found and fixed, see the 1., 2. and 3. bullet points above. Even I can make sense of some of that but I guess it is easier when you actually experience those issues and get a chance to look at the server logs too. If you are interested, let me know and I'll open an issue for us to work on that. To be fair, any llama.cpp developer serious about getting this fixed should first of all have a setup capable of reproducing the issue and would certainly not need me to collect those logs. I know that can be a lot of work and a big ask. Thus the home made patch. For weeks now I've seen issues with the same interruption outcome from various inference engines. It just does not seem to be something AI developers are willing to address. Again I'm assuming GHCP harness is not being used that much. However, for people like myself, transitioning away from AI cloud services, it is the easier path forward. In my above post, there is almost all you need to reproduce the issue. Only the BF16 model is missing but I just created it from the official Qwen safetensors using the Python script in this repo. The Q8 variant was downloaded from LM Studio. I also forgot to mention this was done in VS Code Insider. |
|
@bartdeboer Sorry for hijacking your PR 😁 I had an initial patch that was based on it but I had a feeling some of that I did not need. So I started again from master. |
|
No problem, @Slion. We first need to figure out which output edge-case caused Qwen’s tool call to be emitted as normal content. One possibility is that Qwen emitted the closing I created a more greedy version of my PR: Test image: Could you test it with your setup? If it fixes the raw tool-call output, we can check with @aldehir whether it makes sense to update this PR with those changes. I’ve kept the existing PR stable for now, in case he already has work based on it. |
I appreciate your effort and contributions as it sets me up on the path to roll out my own patch but I won't be testing your new changes any time soon I'm afraid. This is very time consuming and it seems we may have different use cases too. Mine is really just about getting Qwen agent actually working in GHCP with reasoning. As mentioned above I don't think you can efficiently work this out by having someone doing a blind fix and someone else testing it for him, that's just a waste of time for all parties:
The way forward is to get a llama.cpp developer to buy in that use case, reproduce the issue locally and come up with a proper fix of his own. Meanwhile, I am happy to keep on using my own patch improve it and rebase it as needed. |
That could be because I added the following to my config: chat-template-kwargs = {"enable_thinking": false} |
Maybe it help for you to know that that screenshot above was before I added |
…Quant features - Add tagged thinking tool parser for Qwen-style models (ggml-org/llama.cpp#24202) - Add common_chat_params_init_tagged_thinking_tools function - Add strict_eof_on_complete support for complete parses - Add test cases for tagged thinking/tool protocol - Preserve TurboQuant KV cache features
|
I'm running into a similar issue. The Qwen3 Coder ( From the llama.cpp logs, the lazy grammar never triggers because it is waiting for This is an issue with the model/training, but it seems consistent enough that this PR could add this as a recoverable failure case. I'm happy to contribute a patch to this branch if desired, but also understand you have to draw the line somewhere. |
|
Please try out #26252. The implementation is significantly different than this PR, so I opened a new one. |

Overview
This PR adds a specialized parser for tagged thinking/tool-call outputs.
It was motivated by testing Qwen3.5-9B with reasoning enabled and XML-style tool calls in an agent/tool-loop setup.
Two parser issues showed up:
<tool_call>emitted while the parser was inside a<think>...</think>reasoning block was surfaced asreasoning_contentinstead of being parsed as a tool call.<parameter=old_string>were required to appear in schema/property iteration order, even though the parameter name is already carried in-band by the tag.Observed Qwen behavior
Qwen3.5 appears to use three practical response modes:
The important detail is that Qwen does not always emit a complete
<think>...</think>block for every response mode.For pure reasoning messages, Qwen reliably emits/uses reasoning tags and closes with
</think>.For tool-call messages, Qwen may emit reasoning-like text before
<tool_call>, but it may not emit a matching</think>before the tool call.For final-answer messages, Qwen may emit plain assistant content without closing a template-prefilled
<think>block.The standard Qwen Jinja template helps by prefilling assistant generation with:
That works well for pure reasoning messages, because Qwen then emits:
reasoning... </think>However, for tool-call and final-answer messages, Qwen may effectively ignore that prefilled reasoning state. Some downstream template variants, such as no-prefill template fixes, remove this
<think>prefill entirely.So the parser should not rely only on whether the Jinja template prefilled
<think>. It needs to classify the emitted completion itself.Parser behavior added by this PR
This PR adds a specialized parser for the tagged thinking/tool-call protocol family used by Qwen-style templates:
The parser kicks in when this tag family is detected in the template.
It classifies emitted output as follows:
</think>marks preceding text asreasoning_content.<tool_call>marks preceding text asreasoning_content, even if no explicit<think>tag was emitted in the completion.<tool_call>...</tool_call>is parsed as a tool call.This makes the parser robust to both default Qwen templates that prefill
<think>and no-prefill template variants.Tagged parameter handling
For tagged tool calls, function argument names are already present in-band:
This PR parses tagged parameters by their emitted names rather than requiring schema/property iteration order.
It also allows string parameters to contain raw multiline content/code until the closing
</parameter>tag.This fixes Qwen-style tool calls such as edit/write calls where multiline code appears inside a string parameter.
Additional information
This was tested downstream with Qwen3.5-9B using reasoning enabled and XML-style tool calls.
A patched CUDA image used for downstream validation is available here:
Digest:
Validated locally with:
Related issues / prior art:
peg-nativechat format parser fails when model outputs text before<tool_call>(thinking model + tool calling) #20260<tool_call>can act as a reasoning boundary.Requirements