A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 10, 20:15 UTC
feat(frontend): add --default-chat-template-kwargs CLI argument (#31343)
Signed-off-by: effortprogrammer <yhjhoward7@gmail.com>
Hojin Yang committed
Dec 30, 2025 at 12:38 UTC
dc837bc23e70eed89b562774c7ca8c1bec9f87ae
7 files changed
+91
-2
docs/features/reasoning_outputs.md
+36
@@ -204,6 +204,42 @@ The reasoning content is also available when both tool calling and the reasoning
204
205
For more examples, please refer to [examples/online_serving/openai_chat_completion_tool_calls_with_reasoning.py](../../examples/online_serving/openai_chat_completion_tool_calls_with_reasoning.py).
206
207
+## Server-Level Default Chat Template Kwargs
208
+
209
+You can set default `chat_template_kwargs` at the server level using the `--default-chat-template-kwargs` CLI argument. This is useful for configuring reasoning behavior across all requests without requiring clients to specify it in each request.
210
+
211
+### Disabling Thinking Mode by Default
212
+
213
+For models like Qwen3 where thinking is enabled by default, you can disable it server-wide:
214
+
215
+```bash
216
+vllm serve Qwen/Qwen3-8B \
217
+ --reasoning-parser qwen3 \
218
+ --default-chat-template-kwargs '{"enable_thinking": false}'
219
+```
220
+
221
+### Enabling Thinking Mode by Default
222
+
223
+For models like IBM Granite 3.2 or DeepSeek-V3.1 where thinking is disabled by default, you can enable it server-wide:
224
+
225
+```bash
226
+vllm serve ibm-granite/granite-3.2-2b-instruct \
227
+ --reasoning-parser granite \
228
+ --default-chat-template-kwargs '{"thinking": true}'
229
+```
230
+
231
+### Request-Level Override
232
+
233
+Request-level `chat_template_kwargs` always take priority over server defaults. For example, if the server is started with `enable_thinking=false`, a client can still enable it for a specific request:
234
+
235
+```python
236
+response = client.chat.completions.create(
237
+ model=model,
238
+ messages=messages,
239
+ extra_body={"chat_template_kwargs": {"enable_thinking": True}} # Overrides server default
240
+)
241
+```
242
+
243
## Limitations
244
245
- The reasoning content is only available for online serving's chat completion endpoint (`/v1/chat/completions`).
tests/entrypoints/openai/test_cli_args.py
+33
@@ -208,3 +208,36 @@ def test_middleware(serve_parser, cli_args, expected_middleware):
208
"""Ensure multiple middleware args are parsed properly"""
209
args = serve_parser.parse_args(args=cli_args)
210
assert args.middleware == expected_middleware
211
+
212
+
213
+def test_default_chat_template_kwargs_parsing(serve_parser):
214
+ """Ensure default_chat_template_kwargs JSON is parsed correctly"""
215
+ args = serve_parser.parse_args(
216
+ args=["--default-chat-template-kwargs", '{"enable_thinking": false}']
217
+ )
218
+ assert args.default_chat_template_kwargs == {"enable_thinking": False}
219
+
220
+
221
+def test_default_chat_template_kwargs_complex(serve_parser):
222
+ """Ensure complex default_chat_template_kwargs JSON is parsed correctly"""
223
+ kwargs_json = '{"enable_thinking": false, "custom_param": "value", "num": 42}'
224
+ args = serve_parser.parse_args(args=["--default-chat-template-kwargs", kwargs_json])
225
+ assert args.default_chat_template_kwargs == {
226
+ "enable_thinking": False,
227
+ "custom_param": "value",
228
+ "num": 42,
229
+ }
230
+
231
+
232
+def test_default_chat_template_kwargs_default_none(serve_parser):
233
+ """Ensure default_chat_template_kwargs defaults to None"""
234
+ args = serve_parser.parse_args(args=[])
235
+ assert args.default_chat_template_kwargs is None
236
+
237
+
238
+def test_default_chat_template_kwargs_invalid_json(serve_parser):
239
+ """Ensure invalid JSON raises an error"""
240
+ with pytest.raises(SystemExit):
241
+ serve_parser.parse_args(
242
+ args=["--default-chat-template-kwargs", "not valid json"]
243
+ )
vllm/entrypoints/openai/api_server.py
+1
@@ -1081,6 +1081,7 @@ async def init_app_state(
1081
request_logger=request_logger,
1082
chat_template=resolved_chat_template,
1083
chat_template_content_format=args.chat_template_content_format,
1084
+ default_chat_template_kwargs=args.default_chat_template_kwargs,
1085
trust_request_chat_template=args.trust_request_chat_template,
1086
return_tokens_as_token_ids=args.return_tokens_as_token_ids,
1087
enable_auto_tools=args.enable_auto_tool_choice,
vllm/entrypoints/openai/cli_args.py
+10
-1
@@ -11,7 +11,7 @@ import json
11
import ssl
12
from collections.abc import Sequence
13
from dataclasses import field
14
-from typing import Literal
14
+from typing import Any, Literal
15
16
from pydantic.dataclasses import dataclass
17
@@ -114,6 +114,12 @@ class FrontendArgs:
114
"""Whether to trust the chat template provided in the request. If False,
115
the server will always use the chat template specified by `--chat-template`
116
or the ones from tokenizer."""
117
+ default_chat_template_kwargs: dict[str, Any] | None = None
118
+ """Default keyword arguments to pass to the chat template renderer.
119
+ These will be merged with request-level chat_template_kwargs,
120
+ with request values taking precedence. Useful for setting default
121
+ behavior for reasoning models. Example: '{"enable_thinking": false}'
122
+ to disable thinking mode by default for Qwen3/DeepSeek models."""
123
response_role: str = "assistant"
124
"""The role name to return if `request.add_generation_prompt=true`."""
125
ssl_keyfile: str | None = None
@@ -216,6 +222,9 @@ class FrontendArgs:
222
del frontend_kwargs["allowed_methods"]["nargs"]
223
del frontend_kwargs["allowed_headers"]["nargs"]
224
225
+ # Special case: default_chat_template_kwargs needs json.loads type
226
+ frontend_kwargs["default_chat_template_kwargs"]["type"] = json.loads
227
+
228
# Special case: LoRA modules need custom parser action and
229
# optional_type(str)
230
frontend_kwargs["lora_modules"]["type"] = optional_type(str)
vllm/entrypoints/openai/run_batch.py
+3
@@ -468,6 +468,9 @@ async def run_batch(
468
reasoning_parser=args.structured_outputs_config.reasoning_parser,
469
enable_prompt_tokens_details=args.enable_prompt_tokens_details,
470
enable_force_include_usage=args.enable_force_include_usage,
471
+ default_chat_template_kwargs=getattr(
472
+ args, "default_chat_template_kwargs", None
473
+ ),
474
)
475
if "generate" in supported_tasks
476
else None
vllm/entrypoints/openai/serving_chat.py
+5
-1
@@ -6,7 +6,7 @@ import json
6
import time
7
from collections.abc import AsyncGenerator, AsyncIterator
8
from collections.abc import Sequence as GenericSequence
9
-from typing import Final
9
+from typing import Any, Final
10
11
import jinja2
12
import partial_json_parser
@@ -102,6 +102,7 @@ class OpenAIServingChat(OpenAIServing):
102
enable_force_include_usage: bool = False,
103
enable_log_outputs: bool = False,
104
log_error_stack: bool = False,
105
+ default_chat_template_kwargs: dict[str, Any] | None = None,
106
) -> None:
107
super().__init__(
108
engine_client=engine_client,
@@ -115,6 +116,7 @@ class OpenAIServingChat(OpenAIServing):
116
self.chat_template = chat_template
117
self.chat_template_content_format: Final = chat_template_content_format
118
self.trust_request_chat_template = trust_request_chat_template
119
+ self.default_chat_template_kwargs = default_chat_template_kwargs or {}
120
self.enable_log_outputs = enable_log_outputs
121
122
# set up logits processors
@@ -203,6 +205,7 @@ class OpenAIServingChat(OpenAIServing):
205
tool_dicts=None,
206
documents=None,
207
chat_template_kwargs=None,
208
+ default_chat_template_kwargs=self.default_chat_template_kwargs,
209
tool_parser=None,
210
add_special_tokens=False,
211
)
@@ -310,6 +313,7 @@ class OpenAIServingChat(OpenAIServing):
313
tool_dicts=tool_dicts,
314
documents=request.documents,
315
chat_template_kwargs=request.chat_template_kwargs,
316
+ default_chat_template_kwargs=self.default_chat_template_kwargs,
317
tool_parser=tool_parser,
318
add_special_tokens=request.add_special_tokens,
319
)
vllm/entrypoints/openai/serving_engine.py
+3
@@ -1158,6 +1158,7 @@ class OpenAIServing:
1158
tool_dicts: list[dict[str, Any]] | None = None,
1159
documents: list[dict[str, str]] | None = None,
1160
chat_template_kwargs: dict[str, Any] | None = None,
1161
+ default_chat_template_kwargs: dict[str, Any] | None = None,
1162
tool_parser: Callable[[TokenizerLike], ToolParser] | None = None,
1163
add_special_tokens: bool = False,
1164
) -> tuple[list[ConversationMessage], list[TokensPrompt]]:
@@ -1183,6 +1184,8 @@ class OpenAIServing:
1184
tools=tool_dicts,
1185
documents=documents,
1186
)
1187
+ if default_chat_template_kwargs:
1188
+ _chat_template_kwargs.update(default_chat_template_kwargs)
1189
_chat_template_kwargs.update(chat_template_kwargs or {})
1190
1191
request_prompt: str | list[int]