Qwen3.8-27B 的工具调用协议只写在模板里,README 全文 0 提及
想知道一个开源权重模型怎么调工具,绝大多数人第一反应是翻 model card。Qwen3.8-27B 这里,这条路会直接走空。
截至 2026-08-16(对应 Hugging Face 仓库 Qwen/Qwen3.8-27B 快照 1d4bf0f),我们把这个仓的 8 个文本文件抓到本地逐词统计过:README.md 共 65,012 字节、583 行,其中 tool_call 一词命中 0 次,function call 同样 0 次。README 里唯二和 tools 沾边的地方,一处是 README.md:16 提到 Qwen Cloud 托管版本会带「官方内置工具」——而那段原文写着该服务 coming soon、Stay tuned for updates.,按 model card 自述尚未上线;另一处是 README.md:27 讲对下游工具与 harness 的兼容性。两处都不构成协议说明。
而同一个仓里的 chat_template.jinja(8,952 字节、170 行)从第 57 行起,把工具调用的完整格式定义得很具体。也就是说,这套协议的唯一权威描述在模板文件里。
先说怎么自己核,不用信我
三条命令,读者可以自己在下载下来的仓库目录里跑(下面的路径都是仓库内相对路径):
python -c "import io;print(io.open('README.md',encoding='utf-8').read().count('tool_call'))"
python -c "import io;print(io.open('chat_template.jinja',encoding='utf-8').read().count('tool_call'))"
python -c "import json,io;ct=json.load(io.open('tokenizer_config.json',encoding='utf-8'))['chat_template'];print(len(ct), ct==io.open('chat_template.jinja',encoding='utf-8').read())"
第一条我们得到 0;第二条不是 0——tool_call 在 chat_template.jinja 里出现于 68、128、130、133、143 这五行。第三条比较值得单独说一句:tokenizer_config.json 里的 chat_template 字段与 chat_template.jinja 文件字节级完全一致,长度同为 8,952、md5 同为 519239a4908bb1f805bbce5fa8c8a242,两者都不以换行结尾。所以你在这两个地方任选其一读都行,它们是同一份内容的两个副本,不存在「哪个更新」的问题。
工具清单是怎么进 prompt 的
模板第 57 行的条件是 tools and tools is iterable and tools is not mapping——tools 存在、可迭代、且不是 mapping 时才进这个分支。进去之后的顺序是固定的:
- 第 58 行开一条
<|im_start|>system\n; - 第 59-61 行,如果
reasoning_instructions非空,先输出推理档位提示词,再加两个换行。也就是说推理档位说明排在工具说明之前; - 第 62 行输出固定文案
# Tools\n\nYou have access to the following functions:\n\n<tools>; - 第 63-66 行逐个工具
{{- tool | tojson }}序列化,每个前面加一个换行;第 67 行以\n</tools>闭合; - 第 68 行是一整行长文案,规定函数调用格式;
- 第 69-74 行,如果
messages[0].role == 'system',把渲染并trim后的 system 正文接在后面;第 75 行统一以<|im_end|>\n收尾。
注意第 2 步这个顺序:推理档位提示词和工具说明是拼在同一条 system 消息里的,档位提示词在前。这一点在 README 里同样找不到对应说明。
作为对照,tools 不存在、或不满足上面那个条件时,模板走的是 76-87 行的另一套分支:messages[0] 是 system 且正文非空时,输出 <|im_start|>system\n 加上(若有)reasoning_instructions 与两个换行,再接正文(80 行);messages[0] 是 system 但正文为空、而 reasoning_instructions 非空时,单独输出一条只含推理提示词的 system(81-82 行);messages[0] 根本不是 system 而 reasoning_instructions 非空时,同样单独插一条 system(84-85 行)。两条分支的共同点是:推理档位提示词都是以 system 消息的形式注入进 prompt 的,不是模型内部的某个开关;差别只在有没有那段 # Tools 固定文案与 <tools> 清单。写适配层时这一点值得先确认清楚,因为它决定了你拼出来的 system 消息里到底有几段内容。
调用格式原样长这样
第 68 行里嵌的模板示例,原样是:
<tool_call>
<function=example_function_name>
<parameter=example_parameter_1>
value_1
</parameter>
<parameter=example_parameter_2>
This is the value for the second parameter
that can span
multiple lines
</parameter>
</function>
</tool_call>
同一行还带着 <IMPORTANT> 提醒,共四条:① 函数调用必须遵循该格式,内层 <function=...></function> 必须嵌套在 <tool_call></tool_call> XML 标签内;② 必填参数必须给出;③ 可以在函数调用之前用自然语言给出可选的推理,但不能在之后;④ 如果没有可用的函数调用,就按正常方式用当前知识回答,且不要告诉用户函数调用的事。
第 ③ 条对写解析器的人是有分量的——它规定了自然语言与调用块在输出里的先后关系。这条约束的落点只在模板这一行,别处没有。
assistant 侧的序列化与 tool 角色的承载
模板第 121-145 行处理 assistant 消息里的 message.tool_calls:
- 第 123-125 行:若
tool_call.function is defined,先把tool_call整体替换成tool_call.function——这一步是为了兼容 OpenAI 风格那种嵌了一层function的结构; - 第 126-131 行:第一个 tool_call 分两种情况,正文非空时前置
'\n\n<tool_call>\n<function=' + tool_call.name + '>\n',正文为空时不加前导换行;第 132-133 行,后续每个前置'\n<tool_call>\n<function=...>\n'; - 第 136-141 行:参数遍历
tool_call.arguments|items,每个输出'<parameter=' + args_name + '>\n'→ 值 →'\n</parameter>\n'。值的处理写在第 138 行:args_value | string if args_value is string else args_value | tojson | safe,即字符串直出、其余走tojson; - 第 143 行以
'</function>\n</tool_call>'收尾,第 146 行整条 assistant 消息以<|im_end|>\n结束。
再往下第 147-158 行处理 tool 角色,这里有一处容易被漏掉的细节:当上一条消息存在且不是 tool 时,模板先输出 <|im_start|>user(第 148-150 行)。工具返回是以 user 角色的消息承载的,每条渲染成 \n<tool_response>\n + 正文 + \n</tool_response>(第 151-153 行),直到下一条不是 tool、或本条是最后一条时才补 <|im_end|>\n(第 154-158 行)——连续多条工具返回会合并在同一条 user 消息里。
这个「tool 走 user 壳」的设计还在别处留下了痕迹。模板第 88-98 行计算 ns.last_query_index 时,判定一条 user 消息算不算「真正的用户提问」,条件正是:trim 后的正文同时以 <tool_response> 开头、以 </tool_response> 结尾的,不算。两处是配套的。
与这段扫描配套的还有一个抛错点:倒序扫完全部消息、ns.multi_step_tool 仍为真时,模板在 99-101 行抛 No user query found in messages.。整份模板一共有 9 处 raise_exception,与消息和内容渲染相关的另外几处分别在 10 行(System message cannot contain images.)、21 行(System message cannot contain videos.)、33 行(Unexpected item type in content.)、106 行(System message must be at the beginning.)与 160 行(Unexpected message role.)。这些都只是模板里的字面分支与文案,我们没有渲染过任何一条消息,触发条件之外的事不做推断。
词表侧的一个旁证
tokenizer_config.json 的 added_tokens_decoder 共 33 个条目,id 从 248044 连续到 248076。其中 <tool_call>(tokenizer_config.json:116)、<tool_response>(:181)、<think>(:196)、</think>(:205)这几个条目的 "special" 字段都是 false,而 <|endoftext|>、<|im_start|>、<|im_end|> 这类是 true。同时 additional_special_tokens(tokenizer_config.json:269)里列的 13 个 token,不含 <think>、</think>、<tool_call>、</tool_call>、<tool_response>、</tool_response>。
另一件事:模板里出现的 <function=...>、<parameter=...>、<tools>、</tools>、<IMPORTANT> 这几个串,在 added_tokens_decoder 里一个都找不到,它们是普通文本,不是单独的 token。以上都只是我们从两个文件里读出来的字面状态,其在推理时的具体影响,我们没有依据评价。
这几处口径差异,只记录不延伸
按前面的实读,工具调用这条线上能对照出来的差异有三处,都写清位置:
| 差异 | 位置 A | 位置 B |
|---|---|---|
| 工具调用协议的记载 | README.md 全文 tool_call / function call 命中 0 | chat_template.jinja:57-68 定义系统提示与调用格式、:121-145 序列化 tool_calls、:147-158 渲染 tool_response |
reasoning_effort 的 medium 档 | README.md:260 把 medium 与 xhigh、low 并列描述 | chat_template.jinja:48 的合法值集合含 medium,但 51-55 行只有 xhigh 与 low 两个分支会设置 reasoning_instructions |
preserve_thinking 的措辞 | README.md:474 写设为 False 时只保留最新一条 user 消息的 thinking 块 | chat_template.jinja:116 的字面条件是 preserve_thinking is undefined or preserve_thinking is true or loop.index0 > ns.last_query_index |
以上只陈述两处各写了什么、分别在哪一行,以我们实读的仓库状态为准,不推断哪一处更准确、也不推断原因。
有两件事我们核不出来
第一,模型侧是否真的在训练中见过模板里的这套工具调用格式,我们无从核实。我们能证明的只是:给定 tools,这份模板会按上面的规则渲染出这些字符串。我们没有下载权重、没有加载模型、也没有用 apply_chat_template 实际渲染过任何一条消息,所以本文不给出「某种输入会渲染成某串文本」的最终结论,只列模板里的条件分支与字面输出片段。
第二,reasoning_effort 在各推理框架里如何映射到 chat_template_kwargs,本仓文件里没有说明。README 只给了 OpenAI SDK 侧的写法(README.md:300 把它作为 create() 的顶层参数传),我们没有查阅框架源码。
所以,如果你要为这个模型写一层工具调用的解析或适配,把 chat_template.jinja 从第 57 行读到第 158 行,比读 model card 更能拿到确切信息。模型仓内容随上游更新而变动,读之前建议先核一遍你手上那份文件的字节数与行数,别拿这篇文章里的行号当长期锚点用。关于该仓 generation_config.json 里那 7 个键、以及思考模式与推理档位的更多细节,我们另有专门的篇目在讲。
延伸阅读
- 从头读起:Qwen3.8-27B 是什么:一个模型仓里有哪些文件、各自负责什么
- 本专题共 35 篇,完整分组目录见专题页
- Qwen3.8-27B 的 thinking 默认开着:怎么按请求关掉
- Qwen3.8-27B 官方推荐的采样参数,配置文件里一个都找不到
本文依据 Hugging Face 仓库 Qwen/Qwen3.8-27B 的 model card 与随仓配置文件
(config.json、generation_config.json、preprocessor_config.json、chat_template.jinja 等)整理,
核对日 2026-08-16,对应仓库快照 1d4bf0f。
本文内容为 model card 与配置文件口径,我们没有下载权重、没有部署、也没有推理过这个模型,
因此不涉及生成质量、推理速度与显存占用的任何描述;文中所有评测数字均为 model card 自述,我们没有复现。
模型仓库内容随上游更新而变动,请以官方最新说明为准。