写在前面
最近在做 Agent 相关的事情,踩了不少坑。其中让我印象最深的,就是意图识别。
说实话,刚开始做的时候,我觉得意图识别不就是调用一次大模型,让它判断用户想干嘛嘛。结果真正跑起来才发现,这玩意儿远没有想象中那么简单。用户的输入千奇百怪——病句、代词、缩写、黑话,想到什么说什么。你要是不处理好这一步,后面所有的流程都是在沙子上盖楼。
这篇文章是”AI Agent 系统设计”系列的第一篇,我想把自己在意图识别上的实践和思考整理下来。后续会陆续聊规划执行、上下文管理、工具调用等话题。
从一个让我头疼的场景说起
前阵子遇到一个典型的 case。用户在聊了一轮云服务的话题后,突然来了一句:
“帮我找个更靠谱的”
就这么一句话。缺主语、缺宾语,”靠谱”还是个主观判断词。Agent 该怎么理解?是找更靠谱的云服务商?还是更靠谱的技术方案?还是更靠谱的部署架构?
我当时的第一反应是:这有啥难的,让大模型推理一下不就行了。结果发现,大模型确实能猜,但猜对猜错全看运气。而且更麻烦的是,这种模糊输入不是个例,用户说话就是这么随意。
意图识别不是给用户输入贴个标签就完事了。它决定了三件事:
加载哪套 Prompt / 业务规范。 技术选型和架构设计的思考过程完全不同——技术选型要考虑业务场景、性能要求、团队技术栈、成本预算、运维能力;架构设计要看现有系统瓶颈、数据量级、可用性要求、扩展性需求。两个流程的 Prompt / Spec 完全不一样,意图识别错了,后面加载的业务规范就是错的。
往上下文窗口里塞什么内容。 不同意图需要的上下文不一样。技术选型重点放候选方案对比、社区活跃度、性能基准测试;架构设计重点放现有系统拓扑、流量模型、故障历史。在架构设计场景里塞一堆候选方案的社区 star 数,就是浪费上下文窗口。
调用哪个工具、进入哪个业务流程。 技术选型调方案检索工具,架构设计调系统拓扑分析工具,性能优化调压测工具。意图识别错了,工具调用大概率也错,或者大模型根本选不出合适的工具。最极端的情况下,意图识别之后会分发到不同的子 Agent 上,子 Agent 内部还可以进一步做子意图识别和分发。
方向不对,后面做得再好也是白费功夫。
用户输入能有多乱
在说怎么做之前,先感受一下真实的用户输入:
| 类型 | 例子 |
|---|---|
| 代词指代 | “刚才那个方案可行吗?” |
| 说话不完整 | “成功率有多高?” |
| 口语化 / 病句 | “不对不对,我说的是另外那个” |
| 黑话 / 缩写 | “K8s / Kubernetes / 容器编排” |
| 主观判断 | “哪个更稳定?” |
| 依赖实时信息 | “目前哪家响应最快?” |
这些问题不是调用一次大模型就能解决的。你得先做一步用户输入规范化,把用户的口语化表达整理成 Agent 能处理的结构化信息。
用户输入规范化
这一步我一开始没做,直接跳到了意图识别。结果发现 Agent 经常”听不懂”用户在说什么,尤其是涉及代词和省略句的时候。
后来我加了这一步,效果好了很多。它的职责很明确:
能做的:
- 指代消解(”那个方案” → 具体是哪个方案)
- 省略句补全(补主语、宾语)
- 病句修正
- 术语标准化
- 多义词消歧
不能做的:
- 直接回答用户问题(这个最容易犯)
- 执行工具
- 做最终推荐
- 自行补充事实
我之前犯过一个错误:没有在 Prompt 里约束好职责边界,结果大模型”过于自主”,一股脑把输入规范化和意图识别全干了,甚至直接给了答案。所以 Prompt 里一定要明确说清楚:这一步只做规范化,不做别的。
处理时机
输入规范化不是一步到位的,我把它分成了两个阶段:
初步处理:在意图识别之前,处理大部分输入。代词消解、病句修正这类工作在这一步完成。
深度处理:在 ReAct 循环中,处理那些依赖上下文的输入。比如”更靠谱”这种主观判断词,得结合具体场景才能理解它到底在说什么。
几个实用技巧
1. 命名很重要
这个是我后来才意识到的。Agent 输出的时候,如果输出”方案一”、”方案二”这种,用户后续就会用”方案一”来引用。但如果你输出的是”TCC 方案”、”Redis 方案”这种有具体名字的,用户后续就容易用”TCC 方案”来代指。
这种按名索引的好处是:能有效规避大模型推理阶段的引用错误。”方案一”和”方案二”太容易搞混了,但”TCC 方案”和”Redis 方案”不会。
2. 代词消解表格
我现在的做法是让模型输出一个代词消解的表格,作为关键事实贯穿整个对话:
1 | "那个方案" → 基于 Redis 缓存的方案 |
这个表格在多轮对话里特别有用,不然 Agent 经常搞混指代关系。
3. 可量化
这很重要。任何未量化的形容词都会导致 Agent 输出漂移。
用户说”找个更靠谱的”,你得让 Agent 把”靠谱”转化成具体指标:可用性 99.9%?响应时间 < 100ms?有 SLA 保障?不量化的话,每次跑出来的结果可能都不一样。
4. 迭代式词汇表
同一个词在不同行业意思完全不同。CRM 在 IT 行业是客户关系管理,在医疗行业是临床研究管理。
我的做法是维护一个词汇表,输入规范化前先检索,把结果注入上下文。然后离线分析多轮对话,把频繁出现的新词汇沉淀下来,达到一定阈值后自动入表。可以拆成公共词汇表和用户个人词汇表,效果更好。
5. 基于属性 + 检索的推理
这个是处理长时间跨度指代问题的。比如用户上周让你帮他选一个缓存方案,你推荐了 Redis Cluster。一周后用户说:
“你上次说的那个支持分片的缓存方案……”
这种问题有两个难点:一是时间跨度长,短期记忆没了;二是”支持分片的缓存方案”不是简单的”那个”,而是带属性的指代。
我的做法分两步:先用”缓存、分片”这些属性关键词去历史对话里召回细节,再让大模型基于召回内容做指代消解。如果第一步检索不顺利,还可以让模型提取属性发起工具调用,再做一轮推理。
6. 结构化输出
有些时候,光靠自然语言处理还不够。可以让模型输出更加规范的结构,比如主谓宾三个字段,再加上定状补的解释。这样做的好处是:后续的工具调用可以直接从结构化输出里取参数,不用再让模型二次解析。
比如用户说”帮我找个更靠谱的”,结构化输出可以是:
1 | { |
深度结合上面几点技巧还不够,还得结合更深层的上下文
上面说的都是输入层面的处理。但很多时候,理解用户输入需要结合更深层的上下文信息:
- 用户画像:如果一个用户反复讨论某个技术栈,那他后续提到相关术语时,应该倾向于用他熟悉的语境去理解
- 关键事实:前面几轮 Agent 输出的要点、用户已经认可的结论、用户的偏好和态度
- 对话历史召回:可以是摘要,也可以是细节
这些信息不是输入规范化本身能搞定的,需要在整个 Agent 系统层面来支持。但它们对输入规范化的效果影响很大——上下文越丰富,模型理解用户输入就越准确。
三层意图识别
输入规范化之后,才是真正的意图识别。
但在说三层模型之前,先聊一个问题:意图识别在什么时候做?
我见过三种做法:
- 和输入规范化混在一起:简单系统、领域专属 Agent 常用,一次性搞定
- 在进入 ReAct 循环之前独立做:这是我推荐的方式,边界清晰、可评估、可调试
- 在 ReAct 循环中做:更灵活,但更难控制,调试复杂
我一开始就是直接调用深度推理大模型,简单粗暴。后来发现两个问题:一是成本高,二是大部分用户输入其实很标准,用深度推理有点杀鸡用牛刀。
所以我改成了三层递进的方式:
1 | 第一层:代码规则 |
你会发现,大部分请求在第一层或第二层就搞定了,真正需要第三层的其实不多。
但这里有个问题:第二层怎么判断”搞定了没有”?总不能让轻量模型随便输出一个意图就直接用吧。这就引出了置信度的机制。
置信度怎么用
模型输出意图的时候,同时输出一个置信度:
1 | { "intent": "tech_selection", "confidence": 0.91 } |
我的阈值设置:
- 大于 0.85:直接用
- 0.6 到 0.85:再想想,或者问用户
- 小于 0.6:交给深度推理模型
有一点要注意:大模型输出的置信度不一定是真正的概率,它更像是一个”自我感觉分数”。别太当真,得结合其他信号一起判断。
到这里,意图本身基本能识别了。但光知道用户想”做技术选型”还不够——什么场景?性能要求多高?预算多少?这些参数不补全,后面的流程也没法走。这就是接下来要聊的 Slot Filling。
Slot Filling
Slot Filling 说白了就是从用户输入里抽取意图所需的参数。比如用户说”帮我找个消息队列方案”,意图是技术选型,但还缺一些信息:
1 | { |
这里有个细节我觉得特别重要:约束要区分硬约束和软约束。
- 硬约束:必须满足(”必须支持水平扩展”)
- 软约束:尽量满足,不满足要告知用户(”最好是开源的”)
我在用各种 Agent 的时候,最恼火的就是它无视我的硬约束。比如我明确说不要推荐某个品牌,结果它还是推了。这种体验非常差,用户会直接失去信任。
上面说的是正常流程——意图识别到了,参数也补全了。但如果识别不出来呢?很多系统的做法是强行塞一个意图,结果驴唇不对马嘴。
识别不出来怎么办
很多系统的问题是,不管用户输入什么,都强行识别到某个意图上。但真实的用户输入经常是不完整、不清楚,甚至超出系统能力的。我觉得应该提供两个出口:
- unsupported:用户的意图完全不在候选范围。比如用户对技术方案 Agent 说”帮我写一首诗”,这时候就该拒识,老老实实告诉用户你做不了。
- unclear:意图之间有混淆,或者参数不够。比如用户说”帮我选个方案”,你其实可以先问一下场景和约束,而不是上来就推。
我的理念是:好的回答比快的回答,用户体验更好。先收集完整参数再回答,比急着给一个可能不对的答案要好。
说完了意图识别的整个流程,还有一个绕不开的话题:Prompt 到底怎么写。这一步写不好,前面说的全是白搭。
Prompt 怎么写
意图识别的 Prompt 我踩过不少坑。一开始写得很简单,就一句”请判断用户的意图”,结果大模型自由发挥,输出的意图名称、参数字段、置信度都不稳定。
后来我总结了几个要点:
1. 定义候选意图
告诉模型当前系统支持哪些意图,形成一个封闭的枚举集合。不要让模型自己发明意图。
2. 定义意图边界
光列意图名称不够,还要解释边界。尤其是两个意图语义接近的时候,一定要澄清。比如”技术选型”和”架构设计”,用户说”帮我看看用什么数据库合适”,到底算哪个?你得在 Prompt 里说清楚。
3. 定义意图参数
告诉模型每个意图需要抽取哪些参数,哪些必选,哪些可选,可选参数的默认值是什么。
4. 加入 few-shot
不需要很多,关键是覆盖容易混淆的边界。如果 few-shot 太多不知道选哪些,可以用检索机制动态注入——这就是下面要说的动态 few-shot。
到这里,意图识别的基本流程就跑通了。但跑通不等于跑好。做了一段时间后,我发现识别率上不去,很多时候不是模型的问题,而是系统设计本身有缺陷。
意图识别不准怎么办
总结下来,我遇到过五类原因,每类都有对应的解法。
意图太多
候选意图一多,问题就来了:把所有意图的定义、边界、参数、few-shot 全部塞进 Prompt,叠加已有的上下文内容,很容易超过大模型的窗口大小。而且就算没超,候选集合越大,分类混淆和上下文噪声也越高,准确率跟着掉。
解法一:层次意图
像菜单一样分级,先识别大类,再识别细类:
1 | 技术选型 / 架构设计 / 性能优化 / ... |
解法二:结合检索缩小候选集合
在意图识别之前加一个检索步骤,从全部候选意图里先粗召回 Top-N 个可能的意图,再只把这 N 个交给大模型精筛。N 可以动态调整——上下文窗口空闲的时候大一些,识别失败重试的时候也扩大。
解法三:动态 few-shot
意图多了,few-shot 也多了,全部塞进 Prompt 会很长。我的做法是把 few-shot 存到数据库里,每次根据用户输入检索最相关的 few-shot 注入 Prompt。这样既减少了无关 few-shot 的干扰,又保证了模型看到的都是最相关的案例。
可以分成固定的 few-shot(比如 unclear 怎么处理、unsupported 怎么处理)和动态的 few-shot(跟当前输入相关的),效果更好。
意图边界模糊
“帮我看看有什么合适的消息队列”——这是想查信息,还是想做技术选型?
解法:保证意图正交
如果两个意图有重叠,要么拆出第三个意图,要么合并成一个意图用参数区分。
说到底,不要把所有差异都建模成不同的意图,很多差异应该建模成不同的参数。
用户表达太口语化
“这代码写得跟屎一样,怎么让它跑快点?”——没有出现”性能优化”,但意思就是这个。
解法:向量匹配兜底
把常见的口语化表达做成向量,长期积累。新输入进来先向量匹配,匹配上了就直接用对应的意图。
这个方法在专属领域特别好用,因为用户表达就那么几种模式,甚至可以直接用内存做向量匹配,性能极好。而且这个方案有个额外的好处:它可以作为长期迭代的兜底机制——离线分析多轮对话,把那些”大模型有时候能识别、有时候识别不了”的奇怪输入沉淀下来,逐步充实向量库。
一句话多个意图
“帮我查一下最新版本,然后给个升级方案。”
解法:先判断是不是真的多意图
很多时候用户一句话里虽然提到了多件事,但本质上只有一个意图。比如”帮我查一下当前的 QPS,然后看看要不要加机器”——用户的真正意图是”容量评估”,”查 QPS”只是过程性的步骤描述,不是独立意图。如果系统真的拆成两个意图分别处理,反而把问题搞复杂了。
所以第一步是让模型过滤掉这种过程性描述,只抓住最终意图。
如果确实是多个独立意图,我的做法是每次只做一件事,做完问用户要不要继续。交互感更强,用户也不用等太久。而且说实话,用户真的不在意你是不是一次性解决了多个问题——你一次性处理多个意图,用户反而要等更长时间。
多轮对话的上下文问题
第二轮说”预算降到两万以内吧,另外要支持水平扩展”,单独看不知道在说什么,得结合第一轮。
解法:先跳过,后回滚
大多数时候用户意图不会变,可以先复用上一轮的意图直接执行。发现不对再回退重新识别。这个策略简单有效,值得一试。
多识别器仲裁
这个思路是引入多个识别器,每个独立识别,投票决定最终结果。而且识别器不一定都是大模型——可以一个是向量匹配,一个是大模型,一个是规则引擎。三者投票,票数相当就澄清。
还可以给不同识别器设权重,加权计算分数。比如向量匹配在专属领域很准,权重可以高一些;规则引擎在特定场景很准,但泛化能力差,权重低一些。
微调
除了上面这些工程手段,还有一个选项:微调大模型。
微调通常用在第二层——轻量级大模型上。通过准备一批标注好的意图识别数据,对模型进行微调,可以让它在你的业务领域上表现更好。
但说实话,微调是个双刃剑。好处是一旦调好了,效果提升很明显;坏处是数据准备、效果评估、参数调优都有坑,踩进去很费时间。
我的建议是:如果你的意图识别准确率已经到了一个瓶颈,工程手段都试过了,再考虑微调。别上来就微调,先把基础方案跑通。
迭代式反馈:一个通用的思维
上面提到了不少方案,其实它们有一个共同点:迭代式反馈。
向量兜底、词汇表沉淀、动态 few-shot 积累——这些本质上都是同一种机制:在线上运行过程中,不断收集新出现的问题输入,沉淀下来,逐步完善系统。
这个思维不仅适用于意图识别,也适用于 Agent 的其他环节。先跑起来,发现问题,沉淀经验,再优化。周而复始。
怎么评估效果
做系统不能只做不评。我主要看这几个维度的指标:
意图识别本身:
- Top-1 准确率:第一个意图是否正确,这是我最常看的指标
- Top-K 准确率:前 K 个候选中是否包含正确意图
- 分意图准确率:低频意图的识别率,别被整体准确率骗了——如果 80% 的请求都是某一个意图,模型全猜它也能有 80% 的准确率
拒识和澄清:
- 拒识准确率:应该拒识的时候,系统是否拒识了
- 误拒率:能处理的被错误拒识了
- 澄清触发准确率:缺信息的时候,有没有正确追问
Slot Filling:
- 槽位准确率:抽出来的值对不对
- 槽位召回率:用户提供的信息有没有被抽出来
- 必填槽位完整率:启动流程必须的槽位齐不齐
- 约束识别准确率:硬约束 / 软偏好有没有识别出来
最后一个是目前所有 Agent 做得最差的,包括我自己。
有一点很有意思:我们还可以结合用户的下一轮输入来评估上一轮的意图识别效果。如果用户说”不是,我不是这个意思”或者”你理解错了”,这就是上一轮意图识别失败的信号。
准确率参考:
- 专属领域 Agent:99%+ 是可以做到的
- 通用 Agent:Top-1 大概在 80%-90% 之间
一个完整的案例
说了这么多,最后用一个完整的案例把上面的内容串起来。假设我们有一个旅行助手 Agent,用户是这样对话的:
第一轮
用户输入:
“十一想去云南玩,帮我看下”
第一步:用户输入规范化
模型输出:
1 | { |
输入本身还算完整,没有太多需要消解的。
第二步:意图识别
走第二层轻量模型,输出:
1 | { |
置信度 0.93,直接接受。但缺少关键参数,触发澄清。
Agent 回复:询问天数、预算、旅行偏好。
第二轮
用户输入:
“六七天吧,别太贵,想看风景”
第一步:用户输入规范化
模型输出:
1 | { |
“别太贵”是个主观词,先记录下来,后面结合工具参数来量化。
第二步:意图识别
复用上一轮的 trip_planning 意图,补充槽位:
1 | { |
参数齐了,进入执行流程。Agent 调用行程规划工具,参数中 budget_level 设为 “economy”,scenery_weight 设为 0.8。
Agent 回复:推荐了一条昆明-大理-丽江的经典线路。
第三轮
用户输入:
“那个雪山能不能上去?”
第一步:用户输入规范化
这里有个指代问题——“那个雪山”是哪个?
模型结合上一轮 Agent 推荐的内容,输出:
1 | { |
代词消解成功,因为上一轮 Agent 提到了丽江线路中的玉龙雪山。
第二步:意图识别
这是一个新的意图——景点查询:
1 | { |
Agent 调用景点信息工具,返回玉龙雪山的索道信息和注意事项。
第四轮
用户输入:
“行,就这个方案吧,酒店别太差”
第一步:用户输入规范化
“就这个方案”需要消解:
1 | { |
第二步:意图识别
意图切换到了酒店预订:
1 | { |
注意”酒店别太差”是个软约束,先不量化,追问一下具体预算。
Agent 回复:询问酒店预算。
第五轮
用户输入:
“三星四星都行,别超500一晚”
第一步:用户输入规范化
1 | { |
第二步:意图识别
复用 hotel_booking,补充约束:
1 | { |
“别超500”是硬约束,”三星四星都行”是软约束。Agent 调用酒店搜索工具,参数中 max_price = 500,star_level = [3, 4]。
小结
这个案例走下来,可以看到意图识别在实际对话中是怎么运作的:
- 用户输入规范化消解了”那个雪山”、”这个方案”、”别太贵”这些模糊表达
- 三层识别在大部分情况下用轻量模型就够了,置信度都在 0.85 以上
- Slot Filling逐步补全参数,缺什么问什么
- 约束处理区分了硬约束(价格上限)和软约束(酒店星级偏好)
- 意图切换从行程规划到景点查询到酒店预订,每轮都在动态调整
- 代词消解表格贯穿整个对话,”那个雪山”和”这个方案”都能正确指代
这就是意图识别在真实场景中的全貌。
还没解决的问题
说几个我目前也没找到好办法的问题,写出来一方面是记录,另一方面也想看看有没有人有更好的思路。
反话和嘲讽。 大部分 Agent 不需要考虑这个,但有些场景绕不开。比如会议摘要 Agent,有人在会上阴阳怪气说”这个方案真是太棒了”,你得能判断出他其实是在否定。目前没有特别好的通用解法。
数量范围歧义。 用户说”帮我草拟一个题目”,他其实不是只要一个,而是一组题目让他挑。这种意图和字面意思不一致的情况,只能靠上下文和业务场景来推断。
复杂人物关系下的指代。 “A 的前任的前任今天遇到了 B 的现任的前任”——这种在社交、心理咨询类 Agent 里偶尔会遇到。需要对人物关系做结构化建模,难度不小。
写在最后
意图识别这事,说简单也简单——调用一次大模型就能出结果;说难也难——要做到真正好用,需要在输入规范化、三层识别、参数补全、持续优化上下不少功夫。
核心就一句话:深度整合上下文。
说到底,Agent 的能力上限不在于模型有多强,而在于你能不能把用户的意图搞清楚。搞不清楚意图,后面的一切都是空中楼阁。
如果你也在做 Agent,建议先跑起来,再逐步优化。先有效果,再谈效率。
本系列文章基于个人在 AI Agent 系统设计中的学习和实践,持续更新中。