Chat Template
Chat Template 做了什么转换?
- 输入: API 层面的结构化 JSON 消息(
role+content); - 输出: 模型实际处理的线性 token 流,用特殊 token 划分每条消息的边界与角色;
- 标记: 如
<|im_start|>system、<|im_end|>,是模型训练时见过的固定格式; - 类比: Chat Template 像信封格式——API 消息是信的内容,模板规定如何在信封上写明寄件人、收件人;
- 家族差异: Qwen、Llama、Gemma 使用不同的“信封格式”,就像不同国家有不同的邮政编码规则;
- 责任方: vLLM、Ollama 等 API 服务端根据模型的 Chat Template 自动完成转换,开发者通常无需手动处理;
为什么必须使用标准 API 格式而不能自行拼接消息?
- 直接原因: 结构化消息会被翻译成模型训练时见过的固定 token 序列,自行拼接会偏离该格式;
- 典型错误: 把工具结果当作普通 user 消息传递,而不是使用
tool角色; - 后果一: Chat Template 会误把工具响应识别为新的用户查询;
- 后果二: 触发 Qwen3 的思维链清理——模型以为“用户换了话题”,丢弃此前的内部思考,多步思考的连贯性被破坏;
- 比喻: 模型正算到一半,草稿纸被人收走了,只能从头再来;
- 缓存澄清: 缓存只认字节序列,拼接稳定时仍可命中;真正的问题是格式偏离而非缓存;
各模型家族如何处理历史思维链?
| 模型 | 策略 | 关键细节 |
|---|---|---|
| Qwen3 | 保留最后一个真实用户消息之后的思考 | 在 <think> 标签内保留推导步骤;检测到新用户查询即清理 |
| DeepSeek R1 | 剥离全部历史思考 | 多轮只回传 content,不回传 reasoning_content |
| DeepSeek V4 | 彻底反转:强制回传 | 只要请求带 tools,两个 user 消息之间的每条 assistant 消息都必须原样回传 reasoning_content,否则 API 返回 400 |
| Claude | 回传带签名校验的 thinking block | 循环中需原样回传;新用户输入后,服务端忽略最后一次用户输入之前的 thinking block |
| Kimi K2、GLM-5 | 与 V4 相同的强制回传协议 | 随 tools 参数触发的协议约定 |
- R1 剥离的理由: R1 训练时历史 CoT 从不出现在输入里,塞回去属于分布外输入,且能省下可观的 token;
- R1 策略的缺陷: 中间思考承载“为什么调用这个工具、排除了哪些假设”等关键状态,剥离后模型每轮从零推理,容易重复犯错、丢失长程计划;
- V4 反转的启示: Agent 天然携带
tools,强制规则躲不开——中间思考对 Agent 而言是必要状态而非可省 token; - 实践要求: 使用前查阅对应模型的最新文档;这些差异在多轮对话里关系到省 token,把跑到一半的轨迹交给另一家模型接着跑时会变成实打实的接口错误(相关工程实践见第五章);
Chat Template 与 KV Cache 有何关系?
- 固定前缀: Chat Template 把 system 消息和工具定义转换为固定 token 序列放在最前面;
- 可复用: 这些 token 的键值对缓存后可跨请求复用;
- 敏感性: 前缀中某个 token 变化(哪怕多一个空格),首个不同 token 及其后的缓存即失效;
- 依据: 因果注意力决定每个 token 的隐状态只依赖自身与之前的 token,因此末尾追加新内容不会改变任何已缓存 token 的 K、V;
- 展开: 缓存的两个层级见 Prompt Cache 与架构约束;