Add explicit qwen_chat thinking_mode wire policy (server_default/enabled/disabled) - #240
Merged
Yifan Yang (Yif-Yang) merged 1 commit intoAug 20, 2026
Conversation
chat_template_kwargs is a vLLM/SGLang extension. OpenAI, Azure, and strict OpenAI-compatible gateways reject the unknown body field with HTTP 400, and non-Qwen vLLM models served with it can emit <think> output with no <answer> tag (acc=0.000). c31c50b fixed that by only emitting the field when thinking was enabled, which closed microsoft#28 but left no supported way to send an explicit enable_thinking: false -- the request in microsoft#90/microsoft#109. The protocol has three states, so make the setting three-state: server_default (default) -> omit chat_template_kwargs enabled -> send enable_thinking: true disabled -> send enable_thinking: false server_default keeps every existing deployment on exactly the bytes it sends today, so microsoft#28 stays fixed, while disabled gives microsoft#90 the explicit false it asks for. The legacy enable_thinking boolean keeps its historical wire meaning (true -> send true, false -> omit), so no config changes behavior; setting both keys to conflicting values raises rather than silently picking a winner. Unknown tokens raise too -- a typo must not silently flip a reproducibility control. Because server_default delegates a result-affecting choice to the server's chat template, the backend warns once per role when it is used, and the resolved per-role mode is recorded in the run's config.json under resolved_qwen_thinking_modes. Also settles the docs contradiction between "local vLLM endpoint" and "OpenAI-compatible": qwen_chat speaks the OpenAI protocol and reaches both self-hosted servers and hosted gateways, which is exactly why the wire policy cannot be inferred and must be explicit. Closes microsoft#90
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resolves the tension between #28 and #90.
The problem
chat_template_kwargsis a vLLM/SGLang extension, not part of the OpenAI chat-completions API. OpenAI, Azure, and strict OpenAI-compatible gateways reject the unknown top-level body field with HTTP 400, and non-Qwen vLLM models served with it can emit<think>output with no<answer>tag — theacc=0.000reported in #28.c31c50b fixed that by only emitting the field when thinking was enabled. That closed #28, but it also means
enable_thinking=falseomits the field rather than sendingfalse, so the client has no supported way to explicitly disable thinking — the request in #90 (and #109).Both issues are legitimate, and neither is satisfiable by a boolean: the protocol has three states, and which one is correct depends on the serving stack, not on a user preference.
The change
A three-state, role-scoped
thinking_mode:server_default(default)chat_template_kwargsnot sent — the server's chat template decidesenabledchat_template_kwargs: {"enable_thinking": true}disabledchat_template_kwargs: {"enable_thinking": false}server_defaultsends exactly the bytes every existing deployment sends today.disabledsends the explicitfalse.enable_thinkingboolean keeps its historical wire meaning (true→ send true,false→ omit). No existing YAML, env var, or programmatic call changes behavior.ValueErrorrather than silently picking a winner, and unknown tokens raise too — a typo must never silently flip a reproducibility control.Nonestill means "do not modify" inconfigure_qwen_chat, so a missing YAML key cannot clear env-derived configuration.Because
server_defaultdelegates a result-affecting choice to the server's chat template, the backend warns once per role when a request goes out under it, and the resolved per-role mode is recorded in the run'sconfig.jsonunderresolved_qwen_thinking_modes— so a run's thinking policy is always recoverable from its artifacts.Rejected alternatives
disabled— would re-break use local llm train ,acc is 0 #28 for strict gateways with no config change.base_urlfor localhost — deployment topology is not a capability signal; self-hosted vLLM often sits behind a public domain.Surface
model.qwen_chat_thinking_modeplusoptimizer_/target_variants, wired through YAML,--cfg-options,scripts/train.pyCLI,eval_only.py, andQWEN_CHAT_THINKING_MODEenv (role-scoped variants take precedence). Docs updated indocs/reference/config.md,docs/guide/configuration.md, and.env.example— including the contradiction between "local vLLM endpoint" and "OpenAI-compatible" wording, which is precisely why the wire policy cannot be inferred and must be explicit.Tests
20 new tests covering all three payload states, the one-time warning, role/shared env precedence, legacy-key mapping, conflict detection, and invalid-token rejection. Full suite: 1134 passed, 10 skipped.
Supersedes #109 — credit to dylan (@LESdylan) for identifying the gap; the landed fix is the three-state enum rather than an unconditional emit, so #28 does not regress.