前置课 · 第一部分 LLM · L4

消息协议与角色:你和模型之间的合同格式

20 min · roles 语义、聊天模板本质、多模态、位置效应、历史管理三模式

🎒 预备知识:完成上一课「L3」(零基础入口:第 0 课)

本课唯一结论:chat API 的 messages 数组是一份结构化合同, 每个 role 有严格语义;而"聊天模型"的本质是补全模型 + 聊天模板的产物—— 理解这层包装,工具回填、多模态、注入风险、历史管理就全部串起来了。

一、基础:一次请求的完整组装过程

你发 messages=[...] 给 API,到模型真正"阅读"之间发生了什么: provider 把你的数组用聊天模板拼成一段带特殊标记的纯文本(见下节), 加上采样参数(L3)发给模型,模型逐 token 生成直到停止。理解这条流水线的好处: messages 数组的每个设计决定,最终都变成"模型看到的文本顺序和标记"—— 顺序、角色、内容类型全部有后果。

二、四种角色的语义(谁说的、什么地位)

role语义工程要点
system任务设定与规则,优先级最高是"强烈偏好"不是安全边界(A12);须稳定(缓存,A5)
user用户输入不可信输入的第一来源(直接注入面,A12)
assistant模型的历史输出含 tool_calls 声明时必须原样回填(L7 协议)
tool工具执行结果第二个不可信来源(外部内容=间接注入面)

协议红线:assistant 的 tool_calls 和对应 tool 结果必须成对,缺一边 API 直接 400; 且顺序敏感——tool 消息必须紧跟在带 tool_calls 的 assistant 之后,不能隔着别的消息。 也不要把工具结果塞进 user 消息偷懒:模型对 tool 角色有"这是事实数据"的先验, 对 user 有"这是指令"的先验,混用会悄悄改变模型对内容的解读方式 (把数据当指令读 = 注入面扩大)。

三、聊天模板:一层不该被忘记的包装

底层模型只会文本补全。chat API 是聊天模板把 messages 拼成带特殊标记的文本:

<|system|>你是助手<|end|>
<|user|>你好<|end|>
<|assistant|>

两个推论:① 模型是"被训练成"认这些标记的——如果你的用户输入里恰好包含类似标记的文本, 就可能越权变成"指令"(提示注入的技术根源,A12 的攻击剧本即此); ② 不同家模板不同——同一组 messages 换 provider 行为会漂移,跨 provider 迁移要重测。

四、多模态:content 不只是字符串

{"role": "user", "content": [
  {"type": "text", "text": "这张图里是什么?"},
  {"type": "image_url", "image_url": {"url": "https://... 或 base64:data:image/png;base64,..."}}
]}

图像占 token 的粗略手感:一张图几百到一千多 token(按分辨率计)。 agent 里传图给模型判断("这张封面合规吗")完全可行;但批量传图烧钱快—— 能用代码判断的(尺寸/格式/清晰度)先代码判断,图只留给真正需要视觉理解的决策。

五、位置效应:lost in the middle

长上下文里模型对信息的检索能力不均匀:开头和结尾最"显眼",中段容易漏 ("Lost in the Middle",Liu et al. 2023)。工程对策三条:① 最重要的规则放开头(system) 并在末尾重申(Easel 的 TURN_REMINDER 就是这个原理,L05/A3 都会再遇到); ② 长文档的关键信息用标记/标题提高显著性;③ 真正关键的契约别埋在 50K 历史的中段。

六、历史管理三模式(从协议到策略)

messages 数组每轮怎么组装?三种基本模式,复杂度递增:

模式做法代价适用
全量重发历史全部保留,每轮原样发送成本复利增长(L2),终会爆窗短会话默认
滑动窗口只保留最近 N 轮早期约束静默丢失闲聊类,约束少的场景
摘要+窗口远期压成摘要条目 + 近期保原文要管理压缩时机与边界长任务 agent(OpenClaw compaction 的思路,A6/L18)

注意滑动窗口的坑:用户第 2 轮说"以后都用中文回复",第 15 轮被滑出窗口——约束丢了。 对策:持久约束进 system 或长期记忆(A6),不靠历史轮次"活着"。

七、常见踩坑

坑 1:手拼聊天模板。直接往文本里拼 "User:" "Assistant:"——绕过模板等于绕过训练分布。永远走 chat API。
坑 2:role 语义混用。系统规则写进 user、工具数据写进 user——模型先验全乱。
坑 3:关键契约埋中段。第 8 轮的约定到第 20 轮就忘——要么进 system,要么每轮末尾重申。
坑 4:tool 结果塞 user 角色。省事的代价是模型把数据当指令读。

八、检索练习

九、出口检验

① 说出四种 role 各自的"可信度地位"和对应的注入面;② 解释为什么绕过 chat API 手拼模板是坏主意; ③ 你的 agent 有一条"发布前必须人确认"的铁律,用户在第 3 轮说的、第 15 轮才执行发布—— 用本课和 L2 的知识给出保证它不丢的两层方案。发到对话里我批改。