Qwen3.8-27B 的工具调用协议只写在模板里,README 全文 0 提及

2026-08-16

想知道一个开源权重模型怎么调工具,绝大多数人第一反应是翻 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 soonStay 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_callchat_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 时才进这个分支。进去之后的顺序是固定的:

  1. 第 58 行开一条 <|im_start|>system\n
  2. 第 59-61 行,如果 reasoning_instructions 非空,输出推理档位提示词,再加两个换行。也就是说推理档位说明排在工具说明之前
  3. 第 62 行输出固定文案 # Tools\n\nYou have access to the following functions:\n\n<tools>
  4. 第 63-66 行逐个工具 {{- tool | tojson }} 序列化,每个前面加一个换行;第 67 行以 \n</tools> 闭合;
  5. 第 68 行是一整行长文案,规定函数调用格式;
  6. 第 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.jsonadded_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_tokenstokenizer_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 命中 0chat_template.jinja:57-68 定义系统提示与调用格式、:121-145 序列化 tool_calls:147-158 渲染 tool_response
reasoning_effortmediumREADME.md:260mediumxhighlow 并列描述chat_template.jinja:48 的合法值集合含 medium,但 51-55 行只有 xhighlow 两个分支会设置 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 个键、以及思考模式与推理档位的更多细节,我们另有专门的篇目在讲。

延伸阅读


本文依据 Hugging Face 仓库 Qwen/Qwen3.8-27B 的 model card 与随仓配置文件 (config.jsongeneration_config.jsonpreprocessor_config.jsonchat_template.jinja 等)整理, 核对日 2026-08-16,对应仓库快照 1d4bf0f。 本文内容为 model card 与配置文件口径,我们没有下载权重、没有部署、也没有推理过这个模型, 因此不涉及生成质量、推理速度与显存占用的任何描述;文中所有评测数字均为 model card 自述,我们没有复现。 模型仓库内容随上游更新而变动,请以官方最新说明为准。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。