“帮我取消订单”和“取消订单会收手续费吗”,只差几个字,系统接下来应该做的事却完全不同。前一句表达操作请求,后一句是在咨询规则。如果把两者都识别成取消请求,系统就可能误办业务。
意图识别(Intent Recognition,也常见 Intent Detection 或 Intent Classification)要把用户表达转成系统能够处理的目标。本文从酒店预订与售后场景出发,解释意图、槽位、对话状态与动作执行的关系,比较常见实现方法,再用完整的多轮案例说明如何处理补充、改口、切换和未知请求。
本文也是“意图识别工程实践”系列的入口。需要语料示例,可以阅读复杂意图语料与 JSONL 下载;需要接入模型,可以阅读大模型识别与结构化输出。
配套资源:下载教学工程、48 条语料与测试代码。样例均为构造数据,离线代码已验证;真实模型调用尚未验证,具体说明见工程 README。

意图识别、对话状态与业务路由在系统中的位置
一、意图、槽位、对话状态与动作执行
假设用户说:
我想订北京的酒店,10 月 1 日入住,住两晚。
系统至少要区分四件事:
| 层次 | 回答的问题 | 这个例子中的内容 |
|---|---|---|
| 意图 | 用户想完成什么 | 预订酒店 book_room |
| 槽位 | 完成目标需要哪些参数 | 城市、入住时间、入住晚数 |
| 对话状态 | 已经知道什么,还在等什么 | 已确认北京,日期可能还缺年份,房型尚未确定 |
| 动作执行 | 现在允许做什么 | 追问、查房、展示价格;满足业务条件后才提交 |
实体与槽位也不完全相同。“北京”可以被识别为地点实体,但在不同任务中可能填入目的地、出发地或酒店所在城市。实体抽取提供候选信息,槽位填充把信息放进当前任务需要的位置。
日期则需要独立的归一化与业务校验。用户说“10 月 1 日”时,模型即使正确提取这段文字,也不代表年份已经明确;“住两晚”需要结合入住日期计算退房日期。识别层可以保留原表达,再由日期处理模块完成归一化和校验。
这几层可以由同一个模型辅助完成,也可以拆成多个模块。模块数量是实现选择,职责边界仍应保留。尤其不能把“识别为取消订单”直接视为“订单已经取消”。
当前输入 + 有用的历史 + 当前任务状态
↓
意图与槽位变化的候选结果
↓
格式校验、语义校验、状态更新
↓
不明确:澄清 / 缺参数:补槽 / 已就绪:业务验证
↓
查询或执行,再记录实际结果
二、意图识别的常见难点
难点往往出现在意图的边界与上下文上。
“有双床房吗”是查询房源,“给我订一间双床房”是预订。“不要取消,只查状态”包含取消关键词,但用户实际要做的是查询。“如果可以免费取消就取消”还携带一个必须保留的条件。
短回复则更依赖上下文。系统刚问“1 大床房,2 双床房”,用户回复“1”,是在选择房型;系统刚问“1 订单 A,2 订单 B”,同一个“1”是在选择订单。没有前面的选项,就无法确定“1”对应的业务含义。
多轮识别的难度不能仅用对话轮数衡量。影响表现的因素包括历史长度、任务是否交叉、关键信息离当前输入有多远、状态是否准确,以及训练或评测数据是否覆盖这些现象。需要用实际案例和测试集定位问题,而不是把轮数当成固定能力边界。
三、单轮意图识别方法
1. 关键词与规则匹配
规则适合范围明确、格式稳定的输入,例如固定菜单、明确命令或订单号格式。它容易解释和排查,但随着需求增加,需要处理更多优先级、否定表达和例外情况。
如果使用“包含取消两个字”就返回 cancel_booking,会把政策咨询、否定和引用中的“取消”也识别成操作请求。规则应在自己能覆盖的范围内工作,未覆盖时返回“未匹配”交给下一层处理,避免强行选择已有类别。
工程中可以把规则作为前置路由,也可以作为模型输出后的业务检查。前置规则的覆盖率与错误率需要分别记录:匹配少但可靠,与匹配多但经常误判,是两种不同的系统。
2. 向量检索与相似度判断
向量方法把用户表达和已标注例句转换成向量,再检索相近示例或意图中心。它比关键词更容易覆盖部分同义表达,但效果仍受模型、例句和意图边界影响。
一个常见错误是无条件返回相似度最高的类别。哪怕用户问股票,也总有一个酒店类别能排第一。排名只说明候选之间的相对关系,不能证明这个请求属于业务范围。
下面是只演示候选决策的函数,输入是已经计算好的分数;它不加载模型,也不把余弦相似度当作概率:
def choose_candidate(scores, min_score, min_gap):
"""阈值必须用验证数据选择;分数越大表示越相似。"""
if not scores:
return {"decision": "abstain", "intent": None}
ranked = sorted(scores.items(), key=lambda x: x[1], reverse=True)
label, top_score = ranked[0]
if top_score < min_score:
return {"decision": "abstain", "intent": None}
if len(ranked) > 1 and top_score - ranked[1][1] < min_gap:
return {"decision": "clarify", "intent": None}
return {"decision": "candidate", "intent": label}
这个分支仍有局限:不相关输入也可能得到很高的相似度,阈值需要域外样本和易混淆样本共同验证。阈值应根据模型和业务数据选择,不能直接套用示例数值。
3. 训练意图分类模型
TF-IDF 加线性分类器容易训练,也便于排查错误;编码器加分类头则可以学习更丰富的表达特征。是否使用神经网络,应由数据规模、标签边界、计算资源和实测效果共同决定。
数据准备往往比选择模型更早遇到问题:把“问取消规则”和“发起取消”标在同一类,更换模型也无法纠正标签定义的问题。训练集里充满同模板改写、测试集里也有同模板改写,会让成绩看起来很好,却无法证明能处理真实输入。
4. 大模型与结构化输出
大模型方案适合尝试标签定义较复杂、上下文依赖较多的任务。输入通常包含标签定义、边界反例、必要历史和当前状态,输出则采用结构化对象。
这里需要检查两种正确性。第一种是输出结构正确:字段、类型、枚举是否符合契约。第二种是语义正确:咨询取消政策是否被误判成了执行取消。JSON 合法只能解决第一种问题。
配套的大模型实现文章给出了请求构造、响应解析、业务校验和异常处理代码。示例没有让模型自报一个看似精确的“置信度”,因为自报数值未经过校准时,不能直接解释为正确概率。
5. 组合多种识别方法
可以先用规则处理确定的入口,用低成本模型覆盖高频请求,再把复杂输入交给更强的模型。但每增加一层,就多了阈值、回退和监控工作。只有分层后的实测收益足以抵消维护成本,才值得保留。
| 方法 | 值得尝试的条件 | 优先检查的失败 |
|---|---|---|
| 规则 | 命令稳定、边界明确 | 同义表达、否定、规则冲突 |
| TF-IDF/线性分类 | 标签较稳定、有标注样本 | 近义类、跨表达泛化 |
| 句向量检索 | 希望快速扩充例句、少量样本起步 | 无关输入被强制归类 |
| 编码器分类模型 | 有业务数据、需要控制推理成本 | 类别不平衡、标签变更 |
| 大模型 | 定义复杂、需要使用多轮上下文 | 合法但错误的结构、延迟和成本 |
可以根据表中的条件选择实验方案,再用业务数据比较实际效果。
四、多轮对话中的状态管理
最直接的方法是把最近几轮对话发给模型。这能保留局部上下文,却可能遗漏更早确认的订单号或城市。把全部历史都发进去又会增加成本,并让已取消或已过时的信息继续干扰判断。
更实用的输入通常由三部分组成:最近的必要消息、当前结构化任务状态、必须保留的历史事实。历史摘要可以辅助定位,但不应覆盖工具返回的执行结果。
状态更新至少要区分:新增、继续、切换、恢复与放弃任务。槽位还需要区分“本轮没提到”和“明确删除旧值”。用户说“日期先不定”,应清除日期;用户只补充城市,不应该顺带清空日期。
任务之间也需要隔离。预订任务保存的城市不能因为用户切换到查订单而成为订单查询参数。更多实现见多轮上下文、槽位与状态实践。
五、酒店预订的完整对话示例
下面假设用户尚未提交预订。日期明确给出年份,避免示例把日期归一化问题隐藏起来。
| 轮次 | 用户输入或系统动作 | 识别与状态变化 | 接下来做什么 |
|---|---|---|---|
| 1 | 用户:想订北京的酒店 | book_room;城市=北京 | 询问入住和退房日期 |
| 2 | 用户:2026-10-01 入住,10-03 退房 | 继续预订;补充日期,退房年份需按上下文规则确认 | 校验日期并询问偏好 |
| 3 | 系统:1 大床房,2 双床房;用户:1 | 继续预订;房型=大床房 | 查询可用房间与价格 |
| 4 | 用户:入住改成 2026-10-02,退房不变 | 修改预订任务的槽位;还不是修改已存在订单 | 重新查房并更新报价 |
| 5 | 用户:先说说取消会不会收费 | 切换为 policy_query;暂存预订 | 查询对应产品的取消规则 |
| 6 | 用户:继续刚才的预订 | 恢复预订,保留城市、日期、房型 | 展示经过验证的候选与价格 |
| 7 | 用户选择产品并完成必要确认 | 识别层输出已就绪,业务层检查参数与权限 | 调用模拟或真实预订工具 |
| 8 | 工具返回订单号与成功状态 | 记录工具事实,标记任务完成 | 告知真实结果 |
第 4 轮尤其容易混淆。用户虽然说“改”,但还没有订单,所以仍在编辑 book_room。只有已经存在订单、需要修改它时,才进入 change_booking。
第 7、8 轮属于业务执行职责。完整助手项目已经将状态更新接入本地模拟订单,展示确认、提交与结果记录;真实酒店服务需要在业务层单独适配。
六、复杂请求中的条件与任务关系
| 输入 | 应保留的信息 | 常见错误 |
|---|---|---|
| 不要取消,只查 DEMO-A | order_query,订单号 | 见到“取消”就触发取消 |
| 如果能免费取消就取消 DEMO-A | cancel_booking 和免费条件 | 丢掉条件 |
| 查 DEMO-A,再看看上海的房 | 两个任务及先后关系 | 只保留最后一个目标 |
| 把那个取消 | 指代是否唯一 | 默认选择最近出现的订单 |
| 日期先不定 | 清除旧日期 | 当成无更新,保留旧值 |
| 给我开张发票 | 当前能力目录是否支持开票 | 强行归为订单查询 |
“未知”也要区分原因。已知目标但缺参数,应补槽;两个解释都合理,应澄清;超出能力目录,应说明范围或转交。CLINC 的研究将范围外输入识别单独纳入评测,这提醒我们不能只看已知类别的分类成绩。论文
七、意图识别效果评估
先检查每类表现,再看总体指标。高频的订单查询可能掩盖低频但代价更高的取消误判。除准确率或 Macro-F1 外,还应观察未知请求被误接收的比例、澄清是否有效、槽位是否正确更新、错误动作数量及任务完成情况。
数据按完整对话、来源与改写家族分组,训练、阈值调整和最终测试分开。延迟与成本也要一起记录:平均很快但经常超时的链路,实际体验未必好。
配套工程提供酒店对话回归、三种基础识别方法及 BANKING77 八类训练实验,分别观察状态与业务流程、候选分类和蒸馏效果。具体指标与脚本见系列评测和小模型文章。
八、系列阅读导航
- 意图标签怎么设计:分类粒度、槽位边界与多级意图体系。
- 复杂意图识别语料:多轮对话样例、标注方法与 JSONL 下载。
- 意图识别模型怎么选:规则、TF-IDF、向量检索与小模型基线。
- 用大模型做意图识别:Prompt、Few-shot 与结构化输出实战。
- 多轮对话意图识别:上下文、槽位与对话状态如何协同。
- 一句话多个意图怎么办:多标签识别、条件与任务依赖。
- 意图不明确时怎么办:未知意图识别、拒识与澄清追问。
- 意图识别怎么评测:从分类准确率到多轮任务成功率。
- 意图识别小模型实战:分类头训练、知识蒸馏与低成本部署。
- RAG 与 Agent 中的意图路由:检索、工具、闲聊与澄清。
- 意图识别服务上线:延迟、成本、监控与错误闭环。
- 从零实现一个多轮意图识别助手:语料、状态、路由与评测。
下载完整工程 v3.0,其中包含标签定义、语料、状态管理、模型训练、路由和本地服务代码。
意图识别需要明确用户目标和缺失参数,业务模块再根据当前状态与执行条件决定下一步操作。
参考资料:CLINC 范围外识别、RiSAWOZ 中文多轮对话、OpenAI 结构化输出。接口文档核对日期:2026-09-23。