用户只回复一个“1”,系统应该查订单,还是选择房型?用户说“改成明天”,是在修改一个尚未提交的预订,还是修改已经存在的订单?
这些问题没有脱离上下文的固定答案。多轮意图识别需要结合当前任务理解输入,判断哪些信息继续保留、哪些需要修改或已经失效。
本文使用酒店助手,演示短回复、指代、槽位修改、任务切换和恢复。配套工程将“解释输入”和“更新状态”分开,便于复现错误。下载完整语料与 Python 工程,解压后使用 Python 3.10+ 即可运行离线示例。
一、区分意图与对话任务
用户说“想订北京的酒店”,建立了预订目标;接着说“2026-10-01 入住”,是在补参数;再说“不要大床房,换双床房”,是在修改参数。这三轮可以共用 book_room 意图,但对状态的影响不同。
反过来,同一个意图也可能对应新任务。用户说“另外订一间上海的房”,仍然属于 book_room,却不能把北京任务的城市覆盖成上海。
因此,仅保存 last_intent 不够。至少还要判断本轮是在新建、继续、切换、恢复还是放弃任务,并保留更新发生在哪个任务上的依据。
二、设计对话状态
附件使用以下教学结构:
active 表示当前任务,slots 保存该任务已经确定的参数。suspended 保存临时挂起的任务。pending 记录系统当前在等待什么,version 则帮助发现基于旧状态计算出的结果。
这个结构保存对话任务和参数,执行结果需要单独记录。生产系统还应单独保存任务 ID、完成状态、参数来源、工具结果、确认记录和过期时间。附件是单进程内存教学实现,尚未包含这些持久化能力。
为什么要保存 pending?因为“嗯”通常是在回答某个问题。如果不知道上一问是什么,就无法确定它确认了哪个参数,甚至无法确定它是不是有效回答。
三、结合上下文理解短回复
数字选项
系统问“1 大床房,2 双床房”时,待选项可以记录为:
用户回复“1”,表示选择大床房。系统问“1 DEMO-A,2 DEMO-B”时,相同数字则选择订单。数字来自某一轮具体选项,不是全局命令。
还要检查选项是否仍有效。用户中途换了话题或页面刷新后,旧选项可能已经过期。实际服务可以使用选项消息 ID 与状态版本绑定,防止迟到回复作用于新问题。附件通过状态字段演示如何解释选项,尚未实现消息过期和选项生命周期。
确认回复与参数补充
如果系统问“入住日期是 2026-10-01,对吗”,用户回复“嗯”,可以确认该候选值。如果系统问“你想哪天入住”,同样的“嗯”没有提供日期,应继续追问。
这说明需要区分确认问题与开放式补槽问题。识别到肯定词后,仍需根据上一轮问题判断用户确认了什么。尤其是参数确认、价格确认和实际操作授权,各自需要明确的业务语境。
业务范围外的称呼偏好
“以后叫我大英雄”表达称呼偏好。如果系统具备用户偏好模块,可以路由过去;如果当前识别器只有酒店业务标签,应走范围外分支。这不应覆盖正在进行的酒店任务,也不应该因为找不到合适类别就随意选择一个标签。
四、槽位的修改、清除与保留

房型改变时保留城市与入住日期,只更新对应槽位
假设当前状态是:
三个用户输入分别带来不同更新:
| 输入 | 操作 | 其他字段 |
|---|---|---|
| 入住改成 2026-10-02 | set check_in | 保持不变 |
| 日期先不定,删掉刚才的日期 | clear check_in | 保持不变 |
| 换成双床房 | set room_type | 日期与城市保持不变 |
本版用明确的补丁表达:
如果直接让模型每次输出完整槽位字典,再用新字典覆盖旧字典,就容易丢失当前轮没提到的信息。反过来,只做 dict.update 又无法区分真正删除与未知值。
补丁同样要校验。同一个槽位在一次输出中既 set 又 clear,应拒绝冲突结果。修改入住日期后,还要重新检查退房日期是否晚于入住;通过了意图与槽位校验,不等于通过日期业务校验。
五、任务切换与恢复
考虑这段对话:
| 轮次 | 用户输入 | 当前任务 | 挂起任务 |
|---|---|---|---|
| 1 | 想订北京的酒店 | 预订:北京 | 无 |
| 2 | 先说说退房时间 | 政策:退房时间 | 预订:北京 |
| 3 | 继续刚才的预订 | 预订:北京 | 政策:退房时间 |
| 4 | 另外订一间上海的房 | 新预订:上海 | 预订:北京,以及之前政策任务 |
附件保留挂起任务,便于展示隔离与恢复。真正服务需要把已经回答完的政策任务标记完成并清理,不能让任务栈无限增长。
恢复也不能只取“最近一个标签”。如果挂起列表里有北京和上海两个预订,“继续刚才那个”未必有唯一解释。应该结合可用上下文确认任务,无法唯一确定时追问。实际系统应使用稳定的任务 ID 区分不同任务,不能只用意图标签作为标识。
六、实现状态更新
完整识别契约在大模型意图识别实战中说明。下面直接构造解释结果,验证状态机的行为。这些结果是教学标注,不是模型预测。
把代码保存到解压后的工程根目录运行,或在该目录的 Python 交互环境中执行:
执行后,状态版本从 1 增长到 4。最后恢复的任务仍包含:
由于还缺退房日期,下一步仍是 ask_slot。政策查询的 topic 没有混进预订参数,切换话题时,城市与入住日期也得到了保留。
附件的 reducer 大致按以下顺序工作:先校验输出和状态版本;不确定输入保持任务状态;多意图或条件请求转交规划器;单任务再根据动作新建、继续或恢复;最后应用槽位补丁并计算缺失参数。
它返回 ready_for_business_validation 而不是“执行成功”。必填槽位是否存在只是一个初步判断;类型、日期范围、库存、身份和授权仍需要业务层检查。
七、用版本检查处理状态冲突
假设两个请求都读取了版本 10。用户先补入住日期,随后修改城市。如果较慢的旧识别结果最后写回,直接覆盖可能把新城市或新状态丢掉。
附件要求调用者传入 expected_version,不匹配就抛出错误。这演示了乐观并发检查,但内存中的检查不是数据库级并发控制。
实际持久化时,需要在同一次原子写入中比较版本,例如只有 version=10 时才能更新为 11;若影响行数为零,则重新读取状态并决定如何处理。还需要按消息或事件 ID 去重,以免重试造成重复状态变化。识别结果可以重新计算,已经发生的写操作则必须遵守业务幂等规则。
对历史和当前意图进行分数加权,无法处理消息先后顺序,也无法防止旧结果覆盖新状态。状态冲突仍需版本检查和原子更新。
八、历史消息、状态与摘要的分工
| 信息形式 | 适合保留的内容 | 风险 |
|---|---|---|
| 最近消息 | 刚提出的选项、指代与纠正措辞 | 截断后丢失远处参数 |
| 结构化状态 | 当前任务、槽位、待回答问题 | 错误更新会持续传播 |
| 历史摘要 | 较长对话的背景与曾讨论的话题 | 摘要可能遗漏否定或改口 |
| 工具事实 | 订单状态、执行结果、价格有效期 | 需要来源、时间和权限检查 |
它们的作用互补。已经保存在状态中的城市可以直接读取,无需每轮重新从历史消息中提取;刚显示的选项则不能只保留“助手提供了几个房型”这种摘要。
上下文压缩之后,应专门测试否定、条件和已取消任务有没有被保留。可以将相同案例分别送入完整历史与压缩历史链路,观察状态更新是否一致,再定位摘要引入的错误。
多轮状态是成熟的研究任务。MultiWOZ 和中文 RiSAWOZ 可以帮助理解多领域对话及状态标注;本文使用简化的教学结构,与这些数据集的原始标注格式不同。MultiWOZ、RiSAWOZ
九、设计回归测试
测试不应只验证正常补槽。下面这些案例直接影响任务是否走错:
- 同样的“1”在两个选项上下文中更新不同槽位。
- “嗯”确认候选日期,但不能凭空回答开放式日期问题。
- “日期先不定”应删除旧日期。
- 切到订单查询后不携带预订任务的城市和房型。
- 恢复任务时保留原槽位,多个候选时先澄清。
- 新建同类任务时不覆盖旧任务。
- 放弃未提交预订不触发取消真实订单。
- 带条件的取消请求不被单任务更新器自动执行。
- 旧版本结果不能修改新版本状态。
配套工程的 28 个离线测试覆盖其中的状态更新与契约检查。短回复的语义判断则通过语料提供期望结果,尚未用真实模型进行识别效果测试。状态更新测试与模型识别评测需要分别记录。
十、运行示例与扩展功能
它们依次展示选房型、开放式问题下的“嗯”、恢复预订,以及恢复目标不明确时保持状态。回放使用标注,默认不产生网络请求或费用。
第一版实现了输出校验、单任务状态更新、挂起恢复和版本检查的教学逻辑。选择题生成、任务完成清理、真实存储、日期归一化、权限以及工具执行尚未实现;多意图与条件任务保留结构后交给规划器。路由与完整助手文章已继续实现本地持久化、选项衔接、确认和模拟工具,可沿文末链接运行。
多轮意图识别需要把每轮输入对应到正确的任务和参数。系统应保存必要上下文,按明确规则更新状态;无法确定用户含义时,先追问,再继续处理。
相关内容:意图识别总览、48 条复杂意图语料、大模型结构化识别。
延伸阅读:标签、方法与决策
- 意图标签怎么设计:分类粒度、槽位边界与多级意图体系。
- 意图识别模型怎么选:规则、TF-IDF、向量检索与小模型基线。
- 一句话多个意图怎么办:多标签识别、条件与任务依赖。
- 意图不明确时怎么办:未知意图识别、拒识与澄清追问。