多轮对话的本质:状态管理、上下文理解、记忆机制
说实话,很多做Prompt的人,把多轮对话想简单了。
他们以为就是“你一句,我一句”,把历史消息拼在一起就行。
嗯,我刚开始也这么干过。结果呢?模型聊着聊着就“失忆”了,或者答非所问。
后来我花了大量时间调优线上对话系统,才真正搞明白——多轮对话的核心,其实是三个东西:状态管理、上下文理解、记忆机制。 这三者缺一不可,而且顺序不能乱。
1. 状态管理:对话的“方向盘”
什么叫状态?说白了,就是当前对话进行到哪一步了。
你想想看,一个客服对话,用户刚说完“我要退货”,下一秒又说“我的订单号是123”。如果系统没有状态管理,它可能以为用户要重新开一个话题。
我个人习惯,在设计多轮Prompt时,会先定义一个状态机。每个状态对应一个意图或一个槽位。
核心思路: 状态决定了模型下一步该做什么,而不是让模型自由发挥。
举个例子,一个订餐机器人的状态流转:
状态定义:
- INIT: 初始状态,等待用户输入
- COLLECTING_ADDRESS: 收集地址
- COLLECTING_ORDER: 收集菜品
- CONFIRMING: 确认订单
- COMPLETED: 完成
Prompt设计(简化版):
当前状态:{{state}}
用户输入:{{user_input}}
历史槽位:{{slots}}
请根据当前状态处理用户输入:
- 如果状态是INIT,判断用户意图(点餐/查询/取消)
- 如果状态是COLLECTING_ADDRESS,提取地址信息并更新槽位
- 如果状态是COLLECTING_ORDER,提取菜品名称和数量
- 如果状态是CONFIRMING,确认用户是否要提交
我在项目中遇到过一个问题:状态定义得太细,导致模型频繁切换,反而容易出错。后来我学乖了,状态数量控制在5个以内,每个状态只做一件事。
避坑指南: 我曾经把“确认地址”和“确认菜品”分成两个独立状态,结果模型在确认地址时,用户突然说“再加一个菜”,系统就卡住了。后来我把所有确认逻辑合并成一个“CONFIRMING”状态,反而更灵活。
3. 上下文理解:对话的“地图”
状态管理解决了“走到哪一步”,但上下文理解解决的是“刚才说了什么”。
这里有个常见的误区:很多人以为把历史消息全部塞进Prompt里就行。其实不是的。
为什么?因为Token有限,而且模型对长上下文的注意力会衰减。你想想看,如果用户聊了20轮,你把所有内容都堆进去,模型很可能只关注最后几轮,前面的关键信息就丢了。
我建议的做法是:对上下文进行结构化压缩。
| 上下文类型 | 处理方式 | 示例 |
|---|---|---|
| 当前轮次 | 完整保留 | 用户最新输入 |
| 最近3轮 | 保留关键信息 | 提取意图、实体、槽位 |
| 历史轮次 | 摘要压缩 | “用户已确认地址,正在选择支付方式” |
| 全局信息 | 持久化存储 | 用户ID、偏好、历史订单 |
说白了,就是分层管理。近的详细,远的概括。这样既不会丢失关键信息,也不会让模型被噪音淹没。
一个小技巧: 我习惯在Prompt里加一个“上下文摘要”字段,每次对话结束后,让模型自动生成一句摘要。比如:“用户想订一份披萨,已确认地址是北京市朝阳区,正在等待支付确认。” 下一轮对话直接把这个摘要放进去,效果比堆历史消息好得多。
3. 记忆机制:对话的“长期存储”
状态管理和上下文理解,解决的是单次对话内的问题。但真正的多轮对话,往往跨越多天、多次会话。
比如用户今天问“我的订单到哪了”,明天又问“上次那个订单能退款吗”。如果系统没有记忆,它就会把用户当成一个全新的人。
嗯,这里要注意:记忆不等于历史消息。记忆是经过提炼的、结构化的信息。
我一般把记忆分为三类:
- 短期记忆: 当前对话内的关键信息,比如用户刚说的地址、订单号。对话结束后可以丢弃。
- 长期记忆: 跨会话的持久信息,比如用户偏好、历史投诉记录、会员等级。需要存储到数据库。
- 工作记忆: 当前正在处理的任务状态,比如“用户正在申请退款,已进入审核流程”。
在Prompt设计上,我通常会这样处理长期记忆:
用户画像(长期记忆):
- 用户ID: 12345
- 会员等级: 黄金
- 历史偏好: 喜欢辣味、偏好微信支付
- 最近投诉: 2024-03-01 订单延迟
当前对话上下文(短期记忆):
- 当前状态: REFUND_PROCESSING
- 已收集信息: 订单号 #8888,退款原因“商品破损”
- 最近3轮摘要: 用户提供了订单号,确认了退款原因
请基于以上信息,回答用户的问题。
核心原则: 记忆要“主动遗忘”。不是所有信息都值得记住。我一般只保留对后续对话有影响的信息,比如用户明确表达的偏好、未完成的任务、重要的状态变更。那些“嗯”、“好的”、“谢谢”之类的,直接丢掉就好。
我曾经犯过一个错误:把用户所有历史对话都存下来,然后一股脑塞进Prompt。结果模型被大量无关信息干扰,回答质量反而下降。后来我改成只保留最近5轮对话 + 结构化摘要,效果立竿见影。
总结一下
状态管理、上下文理解、记忆机制,这三者其实是层层递进的关系:
- 状态管理 告诉你“现在该做什么”
- 上下文理解 告诉你“刚才发生了什么”
- 记忆机制 告诉你“用户是谁、想要什么”
你想想看,如果一个对话系统能把这三件事做好,那它基本就不会“失忆”了。我见过太多失败的案例,都是因为只关注了其中一点,忽略了其他两点。
嗯,下一章我会讲具体的Prompt模板设计,到时候会结合这三个机制,给出可以直接用的代码。今天就先到这里。
课后思考: 如果你现在要设计一个“多轮客服对话”系统,你会先定义状态,还是先设计记忆结构?我个人建议先定义状态,因为状态决定了对话的骨架,记忆只是填充血肉。但如果你做的是“闲聊型”对话,那记忆可能比状态更重要。没有标准答案,看场景。