返回文章

WORKING NOTE 02 · NLU

企业 Agent 的意图识别

taxonomy 设计错了,后面全是坑。

08.25

意图识别是什么

本质上就是把用户一句自由说的话,映射到系统预先定义好的、可执行的动作类别上。

比如用户说“我上个月的报销怎么还没到账”,系统要判断出这属于 查询报销状态 这个意图,然后才知道该调哪个接口、要哪些参数。

在传统对话系统里,意图识别是 NLU 模块的核心,跟槽位提取(slot filling)配套:意图决定“做什么”,槽位决定“对谁做、用什么参数做”。

在 LLM agent 时代,意图识别的形态变了但没消失。Function calling 里让模型从几十个工具中选一个,本质就是意图识别。区别在于:以前是训一个分类器,现在是让大模型基于工具描述去选。但当你的企业 agent 有 200 个工具、10 个业务域的时候,你会发现不能全塞给模型,必须先做一层粗粒度路由——这层就是显式的意图识别。

意图体系怎么设计

技术选型是次要的,意图 设计错了后面全是坑。几个原则:

  • 意图的粒度要对齐“可执行动作”,而不是对齐语义。判断标准很简单:如果两个意图后续走的是同一套流程、调同一个接口、要同一批参数,那它们就该合并成一个意图。反过来,如果同一个意图下需要 if-else 分岔到完全不同的处理逻辑,说明粒度太粗了。
  • 分层设计。一级域(HR / 财务 / IT 运维 / 数据查询)→ 二级意图(报销查询 / 报销提交 / 报销政策咨询)。分层的好处是路由可以逐级收敛,每层的分类难度都不高。
  • 必须有“闲聊”和“拒识/未知”这两个类。这是新手最常漏的。企业 agent 上线后你会发现 30% 的流量是体系外的东西,没有兜底类的话模型会强行把它们塞进某个意图,产生非常离谱的回答。
  • 区分“任务型”和“知识型”。“帮我提交报销单”是任务型,要走工具调用;“报销标准是多少”是知识型,要走 RAG。这两条路后端完全不同,路由第一刀往往就切在这里。

技术实现的几个档次

1. 规则 / 关键词 / 正则

别看不起它。对于强特征的意图(用户输入包含工单号格式、明确的命令词),规则的准确率和延迟都优于模型,而且可解释、可热更新。实际项目里通常作为前置短路层:命中规则直接返回,不命中才往下走。

2. 向量检索 + 相似度匹配

给每个意图准备 20~50 条标注语料,全部入库。线上把用户 query 向量化,做 top-k 检索,看命中哪个意图的样本最多、相似度最高。

这个方案在企业项目里性价比极高:冷启动快、加新意图不用重训(塞几条样本进库就行)、天然带。缺点是对语义相近的意图区分度不够。

3. 微调小模型分类

加个分类头,或者用更轻的 FastText。准确率高、延迟低(10ms 级)、成本几乎为零。但需要每类几百条标注数据,加意图要重训。适合意图集合已经稳定、流量大、对延迟敏感的场景。

4. LLM 直接分类

把意图列表和描述写进 prompt,让模型输出意图 ID。优点是零样本就能跑、能处理复杂语义和多轮上下文。缺点是贵、慢、结果不完全稳定。

实操上的关键技巧:让模型输出结构化结果(JSON / 约束解码),不要让它自由发挥;意图描述里给正例和反例(“本意图不包括 XXX,那属于 YYY”),这比只写正面描述效果好得多;要求输出置信度,或者用 logprobs 算。

5. 实践中的推荐架构

不是选一个,而是级联:

规则短路 → 向量召回 Top-N 候选意图 → LLM 在 N 个候选中精排

向量层负责从 200 个意图里粗筛出 5 个,LLM 只在这 5 个里选。这样既避免了把 200 个意图塞进 prompt 的成本,又保留了 LLM 的语义理解能力。工业界主流做法。

置信度方案怎么选

分类器一定会给出一个答案,问题是这个答案可不可信。置信度怎么算有三条路线,各自的长短板完全不同。

路线一:分类模型的 softmax

优点是零成本,模型本来就输出这个;数值直观,0.87 就是 0.87,好跟业务方沟通。缺点有三个:系统性过度自信——训练目标就是把概率往 1 推,所以完全没见过的输入也可能拿到 0.95;对体系外问题基本失效——分类器只会在已知类别里分配概率,一个不该处理的问题它照样自信地塞给某一类;类别不均衡时大类分数虚高。

什么时候选:意图集合稳定、有几百条/类的标注数据、流量大对延迟敏感、且入口相对封闭(体系外流量少)。

路线二:向量检索的相似度 + 一致性

这是三条路线里唯一能有效识别“体系外问题”的——余弦相似度是绝对量,库里根本没有相近的东西,Top1 相似度自然就低,这是分类模型做不到的。而且可解释:能直接说“我判断是意图 A,因为它像库里这三条语料”,排查问题时极有用。加新意图不用重训,置信度体系也不受影响。

缺点:相似度绝对值不好解释,中文 embedding 经常两句不相关的话也有 0.6,没法凭经验设阈值,必须实测分布;严重依赖语料覆盖度——某个意图只写了 5 条语料,它的置信度就系统性偏低,这是数据问题伪装成置信度问题;各意图语料数量不均衡时,Top-K 投票会偏向语料多的意图,要做归一化。

什么时候选:冷启动阶段(零标注数据下唯一能给出有意义置信度的方案)、体系外流量占比高的开放入口。

路线三:LLM 的三种做法

logprobs 是唯一“真”的概率信号,能拿到就优先用。但很多 API 不返回、私有化部署要专门开启;输出 JSON 时意图 ID 可能被切成多个 token,取哪个要处理;而且受 prompt 里意图排列顺序影响。

让模型自报 confidence 实现最简单,但不可靠到几乎不能单独用——数值严重聚集在 0.8/0.9 这几个点上,分辨率极低,换个 prompt 措辞数就变。但如果同时让它输出理由,那段文本比数字有价值得多,可以直接拿去驱动澄清话术。当定性信号用,别当定量信号用。

自洽性投票是 LLM 路线里排序能力最强的,而且分歧的那几次输出本身就告诉你模型在哪两个意图间摇摆,可以直接做澄清选项。但成本和延迟乘 N,不要在主链路全量跑——只对首轮低置信的做二次投票,或者纯离线评估时用。

什么时候选 LLM 路线:意图体系还在频繁变动、没有标注数据、或者需要处理复杂多轮语义。

必须处理的几个硬问题

置信度与兜底。有了置信度就要设:高置信直接执行;中置信反问澄清(“您是想查报销进度,还是想了解报销政策?”);低置信走兜底(转 RAG 或转人工)。澄清能力是企业 agent 体验的分水岭——不敢反问的 agent 会疯狂答非所问。

高危操作的二次确认。涉及写操作、金额、删除的意图,即使置信度很高也要让用户确认一遍。这是产品设计问题,不是模型问题。

多意图。“帮我查一下这个月报销,顺便看看还有几天年假”——一句话两个意图。要么做多标签分类,要么先做 query 拆分再逐个识别。

上下文依赖。“那上个月呢?”这句话单独看毫无意图信号。所以线上进分类器的不能是裸 query,得先做 query 改写(rewrite),把历史对话补全成一个自包含的句子,再去做识别。这一步漏掉的话多轮体验会崩。

意图漂移。用户在报销流程走到一半突然问天气。要判断这是打断(切换意图)还是流程内的补充,并且能在处理完之后回到原流程。

冷启动和迭代

冷启动数据从哪来:历史客服会话 / 工单记录聚类是最好的来源;其次是让 LLM 根据每个意图的描述批量生成语料,人工筛一遍。

上线后必须建闭环:全量记录 query + 预测意图 + 置信度 + 用户是否重问/转人工。低置信样本和用户重问的样本就是最值钱的标注池,定期回流补充语料。

评估上别只看整体准确率。要看每个意图的混淆矩阵——真正的问题永远集中在少数几对容易搞混的意图上,以及 OOD 样本的拒识率。

先把“体系外流量”讲清楚

你的 taxonomy 里定义了多少个意图,那些就是“体系内”。用户实际说出来的话,只要不属于任何一个已定义意图,就是体系外流量(学术上叫 OOD,out-of-distribution;工程上也叫“拒识”)。

假设你的 HR agent 只定义了这 4 个意图:

1. 查询年假余额
2. 提交请假申请
3. 查询工资条
4. 咨询社保政策

那下面这些全都是体系外的:

用户实际说的为什么是体系外
“今天几号了”完全无关的闲聊
“帮我把上周的请假撤销掉”业务相关,但你没做这个功能
“年终奖什么时候发”业务相关,但不在这 4 个里
“那个东西你帮我弄一下”表达不清,无法判断
“你是不是 GPT”用户在试探系统
“我要投诉我领导”业务相关,但需要转人工

第二类是企业项目里最多、也最麻烦的——用户以为你能干,实际上你不能。这类话的用词、语气、业务领域跟体系内的高度重合(都是“请假”“年假”这些词),最容易被误判。

为什么分类器对这个基本没辙

分类器的 softmax 有个硬约束:所有类别的概率加起来必须等于 1

用户说“帮我把上周的请假撤销掉”,模型内部的判断是“这看起来最像‘提交请假申请’”,于是输出:

提交请假申请   0.91
查询年假余额   0.06
咨询社保政策   0.02
查询工资条     0.01

置信度 0.91,妥妥过阈值,系统开开心心地给用户走了个新的请假流程。

问题的根源在于:分类器没有“以上都不是”这个选项。它被迫把 100% 的概率分配到已知类别上。0.91 不代表“我很确定这是提交请假”,只代表“在这 4 个里面,它最像提交请假”——这是完全不同的两件事。

为什么向量检索能抓住

向量检索算的是绝对距离,没有归一化约束。

query: “帮我把上周的请假撤销掉”

Top1: “我要请假”           相似度 0.61
Top2: “帮我提交请假申请”    相似度 0.58
Top3: “下周三请一天假”      相似度 0.55

而一个正常的体系内 query:

query: “我还有几天年假”

Top1: “年假还剩多少”        相似度 0.92
Top2: “查一下我的年假余额”   相似度 0.89
Top3: “我年假用了几天”       相似度 0.87

0.61 和 0.92 的差距,就是“库里根本没有真正对得上的东西”和“精准命中”的差别。这个信号分类器给不了你,因为它压根不看绝对距离

(顺带说:分类器里加一个“其他”类能部分缓解,但你得往这个类里塞语料——而体系外的说法是无穷的,你永远塞不全。所以它只能解决你预料到的那部分体系外流量。)

三条路线的具体场景

场景 A:内部 IT 工单机器人 → 用分类模型

背景:公司内网 IT 支持入口,员工报障用。日均 8000 条。上线一年了,意图体系稳定在 15 个:重置密码、VPN 连不上、打印机故障、软件安装申请、权限申请、报修硬件……

为什么适合分类模型:

  • 入口封闭。这是 IT 服务台页面里的一个窗口,用户点进来就是来报障的,不会有人在这里问天气。体系外流量实测只有 5%——分类器最大的弱点在这里不构成威胁。
  • 意图边界清晰。“VPN 连不上”和“打印机故障”语义上离得很远,不存在大量易混淆对。
  • 数据充足。一年的历史工单,每个意图轻松几千条标注。
  • 量大、要求快。日均 8000 条走 LLM 的话,成本和延迟都不划算;BERT 单条 10ms,一张卡吃下全部流量。

置信度怎么用:

softmax ≥ 0.75 且 margin ≥ 0.2   → 直接建单
其中一个不满足                    → 反问:“您是要重置域账号密码,还是邮箱密码?”
softmax < 0.5 或命中“其他”类      → 转人工

margin 在这里的价值:软件安装申请权限申请 是这个体系里唯一一对易混的(“给我开个 Photoshop”到底是装软件还是要授权?),常出现 0.48 / 0.44 这种分布。绝对值可能刚过 0.5 的线,但 margin 只有 0.04,直接触发反问。

场景 B:新上线的销售赋能助手 → 用向量检索

背景:给销售团队做的助手,刚立项 3 周。产品经理拍了 30 多个意图(查客户信息、查产品报价、生成拜访纪要、查竞品对比、查政策折扣……),一条标注数据都没有,而且下周就要给销售团队试用。

为什么必须走向量检索:

  • 零数据冷启动。每个意图让业务方口述 20 条典型说法,两天就能凑齐 600 条语料入库。这个量级训 BERT 完全不够,但做检索绰绰有余。
  • 意图还在剧烈变动。试用两周后 PM 肯定要加意图、拆意图、合并意图。向量方案改语料库即可,秒级生效;分类模型每改一次要重训、重评估、重定阈值。
  • 体系外流量会很高。销售是开放心态在用,什么都会问——“这个客户上次是谁跟的”“帮我写个开场白”“竞品降价了我能跟多少”。前期实测体系外能占到 40%,分类器在这种流量结构下会灾难性地胡乱分配。
  • 可解释性在试用期是刚需。销售反馈“它把我的话理解错了”,你能立刻看到是命中了哪三条语料,马上知道该补什么、该删什么。这个排查效率在快速迭代期非常关键。

置信度怎么用:

Top1 相似度 ≥ 0.82 且 Top10 里 ≥ 6 条指向同一意图  → 执行
Top1 ≥ 0.82 但意图分散(比如 4/3/3)               → 反问
Top1 < 0.70                                        → 兜底:“这个我暂时帮不上,要不要转 RAG 查文档?”

注意那两个数(0.82 / 0.70)不是拍的——是拿 200 条 query 实跑一遍,把正确命中、错误命中、体系外三组的相似度分布画出来,找重叠区定的。换个 embedding 模型这两个数就得重定。

场景 C:跨域的企业统一入口 → 用 LLM

背景:把 HR、财务、IT、法务四个域的能力收到一个入口,一共 120 多个意图,接了 40 多个工具。而且要支持多轮。

为什么需要 LLM:

  • 多轮上下文。用户:“我上个月的差旅报销批了吗” → “那这个月的呢” → “帮我把没批的催一下”。第二三轮单看文本没有意图信号,需要模型结合上下文理解。这是前两个方案的死穴。
  • 跨域语义细分。“公司给我买的商业保险,我离职了还能用吗”——这同时沾 HR(离职流程)、财务(保险费用)、法务(合同条款)。规则和向量都容易在这里翻车,需要真正的语义推理。
  • 一句多意图。“帮我查下报销进度,顺便看还剩几天年假”,需要拆成两个任务分别执行。
  • 单条成本可接受。这个入口日均可能只有几百条,走 LLM 完全付得起。

但注意实际架构不是纯 LLM——120 个意图塞进 prompt 太贵,而且体系外判断没人管。实际是:

向量检索从 120 个意图召回 Top 5 候选  ← 顺带给出体系外信号
        ↓
LLM 在这 5 个里精排 + 处理多轮/多意图

置信度怎么用:

向量层 Top1 相似度普遍低(< 0.65)  → 直接兜底,LLM 都不用调
向量 Top1 与 LLM 结果一致           → 高置信
两者不一致                          → 拿这两个意图去反问用户
LLM 输出“无法判断” + 给出理由        → 用它的理由生成澄清话术

最后一条很实用。让 LLM 在 JSON 里带一个 reason 字段,它可能输出:

{
  "intent": "uncertain",
  "candidates": ["查询报销进度", "咨询报销政策"],
  "reason": "用户说‘报销这事儿怎么弄’,无法判断是问已提交单据的状态,还是问流程规则"
}

这段 reason 直接就能转成一句自然的反问。LLM 自报的那个 confidence 数字没什么用,但这段理由很有用——这就是上文说的“当定性信号用”。

一张表收尾

入口体系外占比意图数标注数据变更频率多轮
分类模型封闭低(<10%)少(<30)充足不需要
向量检索半开放中(30~100)无/很少弱需求
LLM(+向量)开放中高多(>100)强需求

选型的第一刀,往往就切在“你的入口有多开放”上——这直接决定了体系外流量占比,而这恰恰是三条路线能力差异最大的地方。