
本文以 nano-vllm 开源项目为例,探讨大模型(如 Qwen3-0.6B)是如何在底层处理标准的 OpenAI 格式聊天请求的。该模型尺寸较小,非常适合在本地电脑部署和调试。
用户通过 HTTP 发起一个符合 OpenAI 规范的 API 调用,携带了系统提示词和用户输入:
curl --request POST \
--url http://localhost:8000/v1/chat/completions \
--data '{
"model": "Qwen3-0.6B",
"stream": false,
"messages": [
{
"role": "system",
"content": "你是一个Java开发工程师"
},
{
"role": "user",
"content": "你是谁"
}
],
"temperature": 0.7
}'
接收到 JSON 请求后,模型引擎并不会直接吃下这串 JSON,而是会通过 tokenizer_config.json 中定义的一段 Jinja2 模板 (chat_template),将 messages 数组渲染成一段连续的纯文本。
在这个渲染过程中,模板会自动处理 <|im_start|>、<|im_end|> 等特殊控制符,甚至还会自动注入关于工具调用 (Tools) 的隐藏系统指令。
渲染后的纯文本请求体 (普通对话) 大概长这样:
<|im_start|>system
你是一个Java开发工程师<|im_end|>
<|im_start|>user
你是谁<|im_end|>
<|im_start|>assistant
如果带有工具 (Function Calling) 呢?
当 API 请求里带上了 tools 参数(比如在 Cherry Studio 里开启了 MCP 插件),模板会自动在开头注入极其详尽的 # Tools 系统指令和 XML 标签定义。
此时渲染出的纯文本请求体 (带工具) 长这样:
<|im_start|>system
# Tools
You may call one or more functions to assist with the user query.
You are provided with function signatures within <tools></tools> XML tags:
<tools>
{"type": "function", "function": {"name": "mcp__CherryFetch__fetchHtml", "description": "Fetch a website and return the content as HTML", "parameters": {"type": "object", "properties": {"url": {"type": "string", "description": "URL of the website to fetch"}}, "required": ["url"]}}}
... (这里会列出所有你开启的工具的详细 JSON Schema,篇幅较长)
</tools>
For each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:
<tool_call>
{"name": <function-name>, "arguments": <args-json-object>}
</tool_call><|im_end|>
<|im_start|>user
你是谁<|im_end|>
<|im_start|>assistant
提示:末尾的
<|im_start|>assistant同样是模板自动追加的(add_generation_prompt=True),相当于给大模型递上话筒,暗示它“接下来该你发言了,决定是输出工具调用指令,还是普通聊天”。
{%- if tools %}
{{- '<|im_start|>system\n' }}
... (此处省略复杂的判断逻辑,它主要负责处理 system/user/assistant 的拼接,以及 function calling 标签的注入)
{%- if add_generation_prompt %}
{{- '<|im_start|>assistant\n' }}
{%- if enable_thinking is defined and enable_thinking is false %}
{{- '<think>\n\n</think>\n\n' }}
{%- endif %}
{%- endif %}
大模型是不认识汉字和字母的,它只认识数字。渲染好的长文本会被喂给模型的 Tokenizer,被切分成词元并转换成对应的 token_ids:
# 转换结果示例:
[151644, 8948, 198, 56568, 101909, 15041, 100013, 105503, 151645, 198, 151644, 872, 198, 105043, 100165, 151645, 198, 151644, 77091, 198]
(比如 151644 就代表了特殊的 <|im_start|> 标签)
拿到 token_ids 后,任务会被放入 LLMEngine 的调度队列 (Scheduler) 中。大模型的生成实际上是一个不断循环的过程。
# 文件:nanovllm/engine/llm_engine.py
# 这是引擎的控制循环
while not self.is_finished():
self.step()
每次调用 step(),引擎都会做一件事:从调度器里拿任务 -> 送给 GPU 算 -> 算出一个字 -> 存起来。
大模型的推理严格分为两个截然不同的阶段:Prefill(预填充) 和 Decode(解码)。
这是处理用户新请求的第一步,也是大模型性能爆发的阶段。假设用户发来了长度为 $N=100$ 个 Token 的 Prompt(包含了系统提示词、历史记录和新问题):
[1, 100, 隐藏层维度] 的矩阵,一次性、并行地扔给 GPU 的所有计算核心。Key (K) 和 Value (V)。为了避免以后重复算,引擎会把这 100 个词的 K 和 V 统统存进 GPU 的显存里(在咱们这个仓库里是由 block_manager 按页表分页存储的)。这就形成了所谓的 KV Cache(上下文记忆)。当第一个字生成后(假设叫 $T_1$),就进入了漫长的 Decode 阶段。这个时候,推理过程发生了本质的变化:
<|im_end|> 等结束符,循环才会终止。让我们看看仓库里 scheduler.py 和 llm_engine.py 的具体源码是怎么配合实现这一点的:
scheduler.py 中的调度与预填充(Prefill)逻辑# 文件:nanovllm/engine/scheduler.py
def schedule(self) -> tuple[list[Sequence], bool]:
scheduled_seqs = []
num_batched_tokens = 0
# 【1. Prefill 预填充阶段】
# 优先看 waiting 队列里有没有刚进来的新请求
while self.waiting and len(scheduled_seqs) < self.max_num_seqs:
seq = self.waiting[0]
# 计算当前这批还能塞下多少个 Token
remaining = self.max_num_batched_tokens - num_batched_tokens
if remaining == 0:
break
# 如果是全新的请求(还没分配页表 block_table)
if not seq.block_table:
# 检查显存里有没有能够共享的前缀缓存 (Prefix Caching)
num_cached_blocks = self.block_manager.can_allocate(seq)
if num_cached_blocks == -1: # 显存不足
break
# 扣除掉已经缓存的,剩下的就是要实际计算的 Token 数量
num_tokens = seq.num_tokens - num_cached_blocks * self.block_size
else:
num_tokens = seq.num_tokens - seq.num_cached_tokens
if remaining < num_tokens and scheduled_seqs:
# 只有第一个请求允许被“截断(Chunked Prefill)”,后面的放不下就不塞了
break
# 正式向显存申请物理块 (Block)
if not seq.block_table:
self.block_manager.allocate(seq, num_cached_blocks)
# 记录本次将要计算多少个 Token(如果是长文本会被截断分批算)
seq.num_scheduled_tokens = min(num_tokens, remaining)
num_batched_tokens += seq.num_scheduled_tokens
# 如果这个请求的所有 Token 都预填充完了,转移到 running 状态
if seq.num_cached_tokens + seq.num_scheduled_tokens == seq.num_tokens:
seq.status = SequenceStatus.RUNNING
self.waiting.popleft()
self.running.append(seq)
scheduled_seqs.append(seq)
if scheduled_seqs:
# 如果有新请求,直接返回,并且标记 is_prefill = True
return scheduled_seqs, True
# 【2. Decode 阶段】
# 如果没新请求了,那就接着处理 running 队列里正在生成的请求
while self.running and len(scheduled_seqs) < self.max_num_seqs:
seq = self.running.popleft()
# 检查是否还有显存能放得下新生成的这 1 个字
while not self.block_manager.can_append(seq):
# ... (此处省略显存不足时的抢占 preempt 逻辑)
pass
else:
# Decode 每次只计划生成 1 个 Token
seq.num_scheduled_tokens = 1
seq.is_prefill = False
self.block_manager.may_append(seq)
scheduled_seqs.append(seq)
self.running.extendleft(reversed(scheduled_seqs))
# 返回 False,表示当前是在 Decode 逐字解码
return scheduled_seqs, False
llm_engine.py 的单步执行逻辑# 文件:nanovllm/engine/llm_engine.py
def step(self):
# 1. 向 Scheduler 要任务。如果拿到了新请求 is_prefill 就是 True;否则是 False(Decode)
seqs, is_prefill = self.scheduler.schedule()
# 2. 把任务扔给底层模型 (ModelRunner) 跑一次 PyTorch 前向传播
# 如果是 Prefill,这里会一次性算完成千上万个词的特征;
# 如果是 Decode,这里只算这 1 个新词的特征。
token_ids = self.model_runner.call("run", seqs, is_prefill)
# 3. 后处理:把刚生成的这个字 (token_ids) 追加到请求的尾部
# 如果碰到了 <|im_end|>,就把这个请求标记为 FINISHED
self.scheduler.postprocess(seqs, token_ids, is_prefill)
# 4. 把已经完成(FINISHED)的请求捞出来,准备返回给客户端
outputs = [(seq.seq_id, seq.completion_token_ids) for seq in seqs if seq.is_finished]
return outputs, num_tokens
循环结束后,模型生成了一大串毫无意义的数字数组(token_ids)。此时还需要进行最后一步:将 Token ID 解码回人类能读懂的中文/英文字符。
这也非常简单,只需调用 Tokenizer 的 decode 方法。你可以把它理解为查字典的逆向操作:
# 模型在 Decode 阶段辛辛苦苦算出来的 Token IDs 数组:
[104198, 104210, 45, 12567, 99361, 100013, 9370, 15469, 110498]
# 调用 tokenizer.decode() 后还原的文本:
"我是基于NLP技术开发的AI助手"
llm_engine.py 中的解码代码# 文件:nanovllm/engine/llm_engine.py
def generate(...):
# ... (前面的 while not self.is_finished() 大循环结束)
# 拿到按顺序排好的 token_ids 数组
outputs = [outputs[seq_id] for seq_id in sorted(outputs.keys())]
# 调用 tokenizer.decode 进行查表解码,转换回字符串文本
outputs = [{"text": self.tokenizer.decode(token_ids), "token_ids": token_ids} for token_ids in outputs]
return outputs
得到解码后的纯文本后,API 层(api_server.py)会将这段文本包装成 OpenAI 标准的 JSON 格式返回给客户端:
{
"id": "chatcmpl-008d9871d53b4238bea4549a45af9686",
"object": "chat.completion",
"created": 1783751250,
"model": "Qwen3-0.6B",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "<think>\n好的,用户问我是谁。我需要先确认...避免冗长。\n</think>\n\n我是基于NLP技术开发的AI助手,主要为用户提供帮助和支持。如果您有任何问题或需要帮助,请随时告诉我!"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 10,
"completion_tokens": 211,
"total_tokens": 221
}
}
(如果是流式请求 stream: true,则会在第4步的循环中,每预测出一个字就立刻进行 decode 并通过 SSE 分块返回)
{%- macro render_tools_block(eff_tools) -%}
{%- set tl_ns = namespace(lines=[]) -%}
{%- for t in eff_tools -%}
{%- if t.function is defined -%}
{%- set tl_ns.lines = tl_ns.lines + [t.function | tojson(ensure_ascii=false)] -%}
{%- else -%}
{%- set tl_ns.lines = tl_ns.lines + [t | tojson(ensure_ascii=false)] -%}
{%- endif -%}
{%- endfor -%}
{{- "\n\n## Tools\n\nYou have access to a set of tools to help answer the user's question. You can invoke tools by writing a \"<|DSML|tool_calls>\" block like the following:\n\n<|DSML|tool_calls>\n<|DSML|invoke name=\"$TOOL_NAME\">\n<|DSML|parameter name=\"$PARAMETER_NAME\" string=\"true|false\">$PARAMETER_VALUE</|DSML|parameter>\n...\n</|DSML|invoke>\n<|DSML|invoke name=\"$TOOL_NAME2\">\n...\n</|DSML|invoke>\n</|DSML|tool_calls>\n\nString parameters should be specified as is and set `string=\"true\"`. For all other types (numbers, booleans, arrays, objects), pass the value in JSON format and set `string=\"false\"`.\n\nIf thinking_mode is enabled (triggered by <think>), you MUST output your complete reasoning inside <think>...</think> BEFORE any tool calls or final response.\n\nOtherwise, output directly after </think> with tool calls or final response.\n\n### Available Tool Schemas\n\n" -}}
{{- tl_ns.lines | join("\n") -}}
{{- "\n\nYou MUST strictly follow the above defined tool name and parameter schemas to invoke tool calls.\n" -}}
{%- endmacro -%}
{%- macro render_response_format(rf) -%}
{{- "\n\n## Response Format:\n\nYou MUST strictly adhere to the following schema to reply:\n" -}}
{{- rf | tojson(ensure_ascii=false) -}}
{%- endmacro -%}
{%- if thinking_mode is not defined -%}{%- set thinking_mode = "thinking" -%}{%- endif -%}
{%- if drop_thinking is not defined -%}{%- set drop_thinking = true -%}{%- endif -%}
{%- if reasoning_effort is not defined -%}{%- set reasoning_effort = none -%}{%- endif -%}
{%- if tools is not defined -%}{%- set tools = none -%}{%- endif -%}
{%- if add_generation_prompt is not defined -%}{%- set add_generation_prompt = false -%}{%- endif -%}
{%- set tools_ns = namespace(has_any=false) -%}
{%- if tools -%}{%- set tools_ns.has_any = true -%}{%- endif -%}
{%- for m in messages -%}
{%- if m.tools -%}{%- set tools_ns.has_any = true -%}{%- endif -%}
{%- endfor -%}
{%- set effective_drop = drop_thinking and (not tools_ns.has_any) -%}
{%- set mns = namespace(list=[]) -%}
{%- for msg in messages -%}
{%- if msg.role == "tool" -%}
{%- set tblock = {"type": "tool_result", "tool_use_id": msg.get("tool_call_id", ""), "content": msg.content} -%}
{%- if mns.list|length > 0 and mns.list[-1].role == "user" and "content_blocks" in mns.list[-1] -%}
{%- set last = mns.list[-1] -%}
{%- set mns.list = mns.list[:-1] + [dict(last, content_blocks=last.content_blocks + [tblock])] -%}
{%- else -%}
{%- set mns.list = mns.list + [{"role": "user", "content_blocks": [tblock]}] -%}
{%- endif -%}
{%- elif msg.role == "user" -%}
{%- set text_block = {"type": "text", "text": msg.get("content", "")} -%}
{%- if mns.list|length > 0 and mns.list[-1].role == "user" and "content_blocks" in mns.list[-1] and mns.list[-1].get("task") is none -%}
{%- set last = mns.list[-1] -%}
{%- set mns.list = mns.list[:-1] + [dict(last, content_blocks=last.content_blocks + [text_block])] -%}
{%- else -%}
{%- set new_msg = {"role": "user", "content": msg.get("content", ""), "content_blocks": [text_block]} -%}
{%- if msg.get("task") is not none -%}
{%- set new_msg = dict(new_msg, task=msg.task) -%}
{%- endif -%}
{%- if msg.get("wo_eos") is not none -%}
{%- set new_msg = dict(new_msg, wo_eos=msg.wo_eos) -%}
{%- endif -%}
{%- set mns.list = mns.list + [new_msg] -%}
{%- endif -%}
{%- else -%}
{%- set mns.list = mns.list + [msg] -%}
{%- endif -%}
{%- endfor -%}
{%- if tools -%}
{%- set anchor_ns = namespace(found=false) -%}
{%- for m in mns.list -%}
{%- if m.role == "system" or m.role == "developer" -%}{%- set anchor_ns.found = true -%}{%- endif -%}
{%- endfor -%}
{%- if not anchor_ns.found -%}
{%- set mns.list = [{"role": "system", "content": ""}] + mns.list -%}
{%- endif -%}
{%- endif -%}
{%- set lu = namespace(idx=-1) -%}
{%- for m in mns.list -%}
{%- if m.role == "user" or m.role == "developer" -%}
{%- set lu.idx = loop.index0 -%}
{%- endif -%}
{%- endfor -%}
{%- set fns = namespace(list=[], lu_idx=-1) -%}
{%- if thinking_mode == "thinking" and effective_drop -%}
{%- for m in mns.list -%}
{%- if not (m.role == "developer" and loop.index0 < lu.idx) -%}
{%- if loop.index0 == lu.idx -%}{%- set fns.lu_idx = fns.list|length -%}{%- endif -%}
{%- set fns.list = fns.list + [m] -%}
{%- endif -%}
{%- endfor -%}
{%- else -%}
{%- set fns.list = mns.list -%}
{%- set fns.lu_idx = lu.idx -%}
{%- endif -%}
{%- set att = namespace(idx=-1, sys=-1) -%}
{%- if tools -%}
{%- for m in fns.list -%}
{%- if m.role == "developer" and att.idx == -1 -%}{%- set att.idx = loop.index0 -%}{%- endif -%}
{%- if m.role == "system" and att.sys == -1 -%}{%- set att.sys = loop.index0 -%}{%- endif -%}
{%- endfor -%}
{%- if att.idx == -1 -%}{%- set att.idx = att.sys -%}{%- endif -%}
{%- endif -%}
{{- "<|begin▁of▁sentence|>" -}}
{%- if thinking_mode == "thinking" and reasoning_effort == "max" -%}
{{- "Reasoning Effort: Absolute maximum with no shortcuts permitted.\nYou MUST be very thorough in your thinking and comprehensively decompose the problem to resolve the root cause, rigorously stress-testing your logic against all potential paths, edge cases, and adversarial scenarios.\nExplicitly write out your entire deliberation process, documenting every intermediate step, considered alternative, and rejected hypothesis to ensure absolutely no assumption is left unchecked.\n\n" -}}
{%- endif -%}
{%- for msg in fns.list -%}
{%- set idx = loop.index0 -%}
{%- set is_last = (idx == fns.list|length - 1) -%}
{%- set next_role = (fns.list[idx + 1].role) if (not is_last) else none -%}
{%- set prev_has_task = (idx > 0) and (fns.list[idx - 1].get("task") is not none) -%}
{%- set eff_tools = none -%}
{%- if msg.tools -%}{%- set eff_tools = msg.tools -%}
{%- elif idx == att.idx -%}{%- set eff_tools = tools -%}{%- endif -%}
{%- if msg.role == "system" or msg.role == "developer" -%}
{%- if msg.role == "developer" -%}{{- "<|User|>" -}}{%- endif -%}
{{- (msg.get("content", "") or "") -}}
{%- if eff_tools -%}{{- render_tools_block(eff_tools) -}}{%- endif -%}
{%- if msg.response_format is defined and msg.response_format -%}{{- render_response_format(msg.response_format) -}}{%- endif -%}
{%- elif msg.role == "user" -%}
{{- "<|User|>" -}}
{%- set parts_ns = namespace(parts=[]) -%}
{%- for b in msg.content_blocks -%}
{%- if b.type == "text" -%}
{%- set parts_ns.parts = parts_ns.parts + [b.get("text", "")] -%}
{%- elif b.type == "tool_result" -%}
{%- set tc_content = b.get("content", "") -%}
{%- if tc_content is iterable and tc_content is not string and tc_content is not mapping -%}
{%- set txt_ns = namespace(texts=[]) -%}
{%- for sub in tc_content -%}
{%- if sub.type == "text" -%}
{%- set txt_ns.texts = txt_ns.texts + [sub.get("text", "")] -%}
{%- else -%}
{%- set txt_ns.texts = txt_ns.texts + ["[Unsupported " ~ sub.type ~ "]"] -%}
{%- endif -%}
{%- endfor -%}
{%- set tc_content = txt_ns.texts | join("\n\n") -%}
{%- endif -%}
{%- set parts_ns.parts = parts_ns.parts + ["<tool_result>" ~ tc_content ~ "</tool_result>"] -%}
{%- else -%}
{%- set parts_ns.parts = parts_ns.parts + ["[Unsupported " ~ b.type ~ "]"] -%}
{%- endif -%}
{%- endfor -%}
{{- parts_ns.parts | join("\n\n") -}}
{%- elif msg.role == "latest_reminder" -%}
{{- "<|latest_reminder|>" -}}{{- msg.content -}}
{%- elif msg.role == "assistant" -%}
{%- set rc = msg.get("reasoning_content", "") or "" -%}
{%- if (thinking_mode == "thinking") and (not prev_has_task) and ((not effective_drop) or idx > fns.lu_idx) -%}
{{- rc -}}{{- "</think>" -}}
{%- endif -%}
{{- msg.get("content", "") or "" -}}
{%- if msg.tool_calls -%}
{{- "\n\n<|DSML|tool_calls>\n" -}}
{%- set tc_ns = namespace(lines=[]) -%}
{%- for tc in msg.tool_calls -%}
{%- if tc.function is defined -%}
{%- set tc_name = tc.function.name -%}{%- set tc_args = tc.function.arguments -%}
{%- else -%}
{%- set tc_name = tc.name -%}{%- set tc_args = tc.arguments -%}
{%- endif -%}
{%- set p_ns = namespace(lines=[]) -%}
{%- if tc_args is mapping -%}
{%- for key, value in tc_args.items() -%}
{%- if value is string -%}
{%- set p_ns.lines = p_ns.lines + ['<|DSML|parameter name="' ~ key ~ '" string="true">' ~ value ~ '</|DSML|parameter>'] -%}
{%- else -%}
{%- set p_ns.lines = p_ns.lines + ['<|DSML|parameter name="' ~ key ~ '" string="false">' ~ (value | tojson(ensure_ascii=false)) ~ '</|DSML|parameter>'] -%}
{%- endif -%}
{%- endfor -%}
{%- else -%}
{%- set p_ns.lines = p_ns.lines + ['<|DSML|parameter name="arguments" string="true">' ~ (tc_args | string) ~ '</|DSML|parameter>'] -%}
{%- endif -%}
{%- set tc_ns.lines = tc_ns.lines + ['<|DSML|invoke name="' ~ tc_name ~ '">\n' ~ (p_ns.lines | join("\n")) ~ '\n</|DSML|invoke>'] -%}
{%- endfor -%}
{{- tc_ns.lines | join("\n") -}}{{- "\n</|DSML|tool_calls>" -}}
{%- endif -%}
{%- if not msg.get("wo_eos") -%}{{- "<|end▁of▁sentence|>" -}}{%- endif -%}
{%- else -%}
{{- raise_exception("Unknown role: " ~ msg.role) -}}
{%- endif -%}
{%- set need_transition = is_last or (next_role == "assistant") or (next_role == "latest_reminder") -%}
{%- set this_task = msg.get("task", none) -%}
{%- if need_transition and this_task is not none -%}
{%- set task_tokens = {"action": "<|action|>", "query": "<|query|>", "authority": "<|authority|>", "domain": "<|domain|>", "title": "<|title|>", "read_url": "<|read_url|>"} -%}
{%- if this_task not in task_tokens -%}{{- raise_exception("Invalid task: " ~ this_task) -}}{%- endif -%}
{%- if this_task == "action" -%}
{{- "<|Assistant|>" -}}{{- "<think>" if thinking_mode == "thinking" else "</think>" -}}
{%- endif -%}
{{- task_tokens[this_task] -}}
{%- elif need_transition and (msg.role == "user" or msg.role == "developer") and not (is_last and not add_generation_prompt) -%}
{{- "<|Assistant|>" -}}
{%- if thinking_mode == "thinking" -%}
{{- "<think>" if (not effective_drop) or idx >= fns.lu_idx else "</think>" -}}
{%- else -%}
{{- "</think>" -}}
{%- endif -%}
{%- endif -%}
{%- endfor -%}
messages(强制必传)role 和 content 外,这个模板还支持在单个 message 里解析 task、wo_eos(是否去掉结尾符)、reasoning_content(用来存 <think> 里的思考过程)等非常硬核的内部参数。tools(可选,默认 none)## Tools 以及 <|DSML|tool_calls> 的底层 XML 标签体系,也就是 DeepSeek 自己的工具调用格式。add_generation_prompt(可选,默认 false)<|Assistant|><think>)。在 API Server 生成请求时一般都会传 True。thinking_mode(可选,默认 "thinking")"thinking",模板会在最后引导模型回答时,自动帮你加上 <think> 标签,强制模型先进行深度思考再输出结果。drop_thinking(可选,默认 true)true(且当前没有工具调用),模板会在拼装历史记录时,自动丢弃掉历史轮次的思考内容,从而大幅节省你的 Context 长度和 Token 消耗。reasoning_effort(可选,默认 none)reasoning_effort="max",模板会在整段提示词的最开头强行注入一段“鸡血”级别的 System Prompt(“Reasoning Effort: Absolute maximum... You MUST be very thorough...”),强制要求模型做绝对极端的深度思考,绝不允许抄近道!{
"model": "DeepSeek-V4-Flash",
"stream": false,
"messages": [
{
"role": "system",
"content": "你是一个无所不知的AI助手,能够使用工具获取外部信息来解答用户问题。"
},
{
"role": "user",
"content": "请帮我查一下今天巴黎的天气情况。"
},
{
"role": "assistant",
"reasoning_content": "我调用工具查一下",
"content": null,
"tool_calls": [
{
"id": "call_8a9f2b1c",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"location\": \"Paris\", \"unit\": \"celsius\"}"
}
}
]
},
{
"role": "tool",
"tool_call_id": "call_8a9f2b1c",
"name": "get_weather",
"content": "{\"temperature\": 22, \"description\": \"晴朗,微风\", \"humidity\": \"45%\"}"
},
{
"role": "user",
"content": "听起来不错!那伦敦呢?也是晴天吗?"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的实时天气数据",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市名称,例如:Beijing, Paris, London"
},
"unit": {
"type": "string",
"enum": [
"celsius",
"fahrenheit"
],
"description": "温度单位"
}
},
"required": [
"location"
]
}
}
},
{
"type": "function",
"function": {
"name": "search_news",
"description": "在互联网上搜索指定话题的最新新闻",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "搜索关键词"
}
},
"required": [
"query"
]
}
}
}
]
}
渲染后的模板
<|begin▁of▁sentence|>Reasoning Effort: Absolute maximum with no shortcuts permitted.
You MUST be very thorough in your thinking and comprehensively decompose the problem to resolve the root cause, rigorously stress-testing your logic against all potential paths, edge cases, and adversarial scenarios.
Explicitly write out your entire deliberation process, documenting every intermediate step, considered alternative, and rejected hypothesis to ensure absolutely no assumption is left unchecked.
你是一个无所不知的AI助手,能够使用工具获取外部信息来解答用户问题。
## Tools
You have access to a set of tools to help answer the user's question. You can invoke tools by writing a "<|DSML|tool_calls>" block like the following:
<|DSML|tool_calls>
<|DSML|invoke name="$TOOL_NAME">
<|DSML|parameter name="$PARAMETER_NAME" string="true|false">$PARAMETER_VALUE</|DSML|parameter>
...
</|DSML|invoke>
<|DSML|invoke name="$TOOL_NAME2">
...
</|DSML|invoke>
</|DSML|tool_calls>
String parameters should be specified as is and set `string="true"`. For all other types (numbers, booleans, arrays, objects), pass the value in JSON format and set `string="false"`.
If thinking_mode is enabled (triggered by <think>), you MUST output your complete reasoning inside <think>...</think> BEFORE any tool calls or final response.
Otherwise, output directly after </think> with tool calls or final response.
### Available Tool Schemas
{"name": "get_weather", "description": "获取指定城市的实时天气数据", "parameters": {"type": "object", "properties": {"location": {"type": "string", "description": "城市名称,例如:Beijing, Paris, London"}, "unit": {"type": "string", "enum": ["celsius", "fahrenheit"], "description": "温度单位"}}, "required": ["location"]}}
{"name": "search_news", "description": "在互联网上搜索指定话题的最新新闻", "parameters": {"type": "object", "properties": {"query": {"type": "string", "description": "搜索关键词"}}, "required": ["query"]}}
You MUST strictly follow the above defined tool name and parameter schemas to invoke tool calls.
<|User|>请帮我查一下今天巴黎的天气情况。<|Assistant|><think>我调用工具查一下</think>
<|DSML|tool_calls>
<|DSML|invoke name="get_weather">
<|DSML|parameter name="arguments" string="true">{"location": "Paris", "unit": "celsius"}</|DSML|parameter>
</|DSML|invoke>
</|DSML|tool_calls><|end▁of▁sentence|><|User|><tool_result>{"temperature": 22, "description": "晴朗,微风", "humidity": "45%"}</tool_result>
听起来不错!那伦敦呢?也是晴天吗?<|Assistant|><think>