消息角色与多轮对话:system、user、assistant 和历史记录
文章目录
在上一篇文章中,我们发出了第一次 API 请求并得到了模型的回复。但你很快会发现一个问题:当你问第二个问题时,模型似乎完全”失忆”了——它不记得上一轮你们聊过什么。这不是 bug,而是大模型 API 的默认行为:每轮请求之间互不关联。想让模型”记住”之前的对话,就必须自己管理历史记录,而这背后依赖一个核心概念:消息角色。
三种消息角色
大模型 API(以 OpenAI Chat Completions 为例)的消息数组由一条条消息对象构成,每条消息有两个关键字段:role(角色)和 content(内容)。API 定义了三种角色:
| 角色 | 含义 | 用途 |
|---|---|---|
system |
系统指令 | 设定助手的行为、语气、专业领域和约束条件 |
user |
用户消息 | 代表终端用户的问题或指令 |
assistant |
助手回复 | 模型上一次给出的回答 |
一个完整的请求示例:
1 | messages = [ |
system:给模型定规矩
system 消息是对话的”宪法”。它不会被终端用户看到,但会影响模型的所有行为。可以在 system 消息中指定:
- 角色定位:”你是一个资深后端工程师”
- 回答风格:”用简洁的要点回答,不要长篇大论”
- 输出格式:”始终用 JSON 格式返回”
- 安全边界:”不回答任何涉及政治或违法的内容”
注意:部分开源模型(如 Llama 3、Qwen 等)对 system 消息的遵循程度不如 GPT-4,在实际使用中需要测试验证。
user 与 assistant:对话的”乒乓球”
user 和 assistant 交替出现,模拟真实的对话。模型只会根据消息数组中所有消息来生成回复——它没有自己的”记忆”。
1 | messages = [ |
在这个例子中,模型看到完整历史,才知道”再加 3”是指 2 + 3 = 5,而不是凭空猜一个数。
实现多轮对话
核心思路很简单:把所有对话记录存在一个列表里,每次请求都带上。
最小可运行示例
以下是一个命令行聊天程序的完整实现,保存为 chat.py:
1 | import os |
代码解析
| 步骤 | 操作 | 说明 |
|---|---|---|
| 初始化 | 创建 messages 列表,放入 system 消息 |
system 始终排在最前面 |
| 收到用户输入 | messages.append({"role": "user", ...}) |
把用户问题追加到列表末尾 |
| 发送请求 | 传入完整的 messages |
模型根据全部历史理解上下文 |
| 收到回复 | messages.append({"role": "assistant", ...}) |
把模型回答也追加进去,供下一轮使用 |
运行效果演示:
1 | 多轮对话程序已启动,输入 'quit' 退出。 |
第二轮的回复证明模型已经通过历史消息知道用户的名字——这就是多轮对话的核心价值。
关于 system 消息的位置
spec 建议将 system 消息放在消息数组的最前面,然后交替排列 user 和 assistant。部分模型对此有严格要求,错误顺序可能导致行为异常。以下是一个推荐的消息排列:
1 | # ✅ 正确 |
另外,不要在对话中间插入新的 system 消息——虽然某些模型允许用 system 消息来注入新的指令(如”从现在开始改用英文回答”),但这不是规范用法,兼容性没有保障。如果需要改变行为,可以改用 user 角色携带指令。
历史记录的三大陷阱
1. 上下文窗口溢出
每次请求都带着完整历史,历史越长,消息越多。当总 token 数超过模型的上下文窗口时,API 会返回错误。应对方法(后面文章会详细展开):
- 统计 token 数,在发送前检查
- 丢弃最早的消息(只保留 system + 最近 N 轮)
- 对历史做摘要压缩
2. 成本线性增长
因为每轮都要重新发送所有历史消息,所以对话越长,单次请求成本越高。第 10 轮请求的费用远高于第 1 轮。实际项目中需要做历史裁剪。
3. 思路会被历史”带偏”
如果早期对话出现了错误信息,后续所有轮次都会受其影响。可以在 system 消息中给出纠错指令,或在发现错误后手动编辑历史记录中的错误条消息。
多轮对话 vs. 单轮对话:选择指南
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 客服机器人、辅导老师 | 多轮 | 需要上下文理解用户意图 |
| 批量文本分类、翻译 | 单轮 | 每条独立,无需上下文 |
| 代码审查、文档问答 | 多轮 | 需要追问和细化 |
| 内容生成(标题、摘要) | 单轮 | 一次完成任务,历史无意义 |
单轮模式下每次请求只用一条 user 消息,不需要管理历史,成本和延迟最低。
小结
- 消息角色有三种:
system(设定行为)、user(用户输入)和assistant(模型回复) - 多轮对话的本质是把历史消息累积在列表中,每次请求都完整发送
- system 消息应放在数组最前面,user 和 assistant 交替排列
- 随对话增长需要注意上下文窗口、成本和历史污染三个问题
- 不是所有场景都需要多轮对话——单轮在批量任务中更高效
掌握了消息角色和历史管理,你就有了构建真正”对话式” AI 应用的基础。下一篇文章我们将深入 token 与上下文窗口,学会估算和控制对话的长度。