news 2026/11/1 11:34:58

Agent意图识别分层漏斗设计与实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Agent意图识别分层漏斗设计与实践

做Agent开发时间久了,你会发现一个很现实的问题:模型能力再强,也架不住线上流量乱来。我这里说的"乱来",不是用户素质问题,而是真实生产环境里那种无规律、无下限的输入——有空字段、乱码、中英混输、错别字、隔壁业务串过来的话,还有专门设计来试探边界的提示词攻击。如果让核心模型对每一条输入都做无差别的深度推理,成本爆掉还是小事,拦不住的高危输入才是真麻烦。

所以我最终把意图识别这一层拆成了一个"分层漏斗"的结构:先用最便宜的手段把明显不靠谱的东西过滤掉,再把有价值的候选交给更贵的模型,最后用置信度校准决定到底走哪条路。这套东西从设计到上线,前前后后迭代了三四个月,期间踩了不少坑。这篇文章把整体设计、关键参数、实现细节和排障经验都写出来,给做Agent、对话系统、智能客服的同学做个参考。

1. 为什么是分层漏斗,而不是一个模型打天下

1.1 "一个模型解决所有意图"的三个坑

最初我并没有设计这个漏斗,而是把意图识别当成一个单独的分类任务:给定一个训练好的多分类模型,输入一句话,输出意图标签。在Demo里效果很好看,但上到生产之后,问题逐步暴露出来。

第一个坑叫"无效流量稀释"。实际线上输入里,真正需要Agent处理的请求可能只占一半,剩下的是测试数据、纯垃圾文本、字段缺失的异常请求、上下文依赖过重的片段。这些输入如果全部进入一个深度模型,模型被无效样本干扰,预测分布会变扭曲,而且计算资源被白白消耗。

第二个坑叫"延迟不达标"。用户通过Agent调用业务工具时,链路是:输入→意图识别→工具调度→业务执行。意图识别只是其中一环,如果它要跑一次大模型推理,200毫秒起步,整个请求的体感就会变得很差。部分交易类场景甚至要求意图识别环节在100毫秒以内完成,单一深度模型根本满足不了。

第三个坑是"不可解释、不可控制"。深度学习模型给出的标签,一旦出错,你很难在线上快速定位是训练数据问题还是输入长尾问题。更麻烦的是,当某个意图被误判,用户会被路由到完全错误的工具,这种错误的代价往往远高于"我拒绝识别"。

这三个坑指向同一个结论:在工业级场景里,意图识别不是"越准确越好",而是"在正确的资源开销下,给出足够可靠的分流决策"。由此引出的设计思路就是分层与漏斗。

1.2 漏斗的本质:把开销花在值得花的地方

分层漏斗不是什么新概念,它本质上是一个"风险递进式决策"结构。越靠前的层,单位成本越低、速度越快、覆盖范围越广;越靠后的层,计算越精细、判断越准确、但开销也越高。低成本的层负责快速排除高确定性负样本,高成本的层只处理真正需要深度推理的候选。

举一个身边很好理解的类比:机场安检。第一道关卡是证件查验和行李扫描,速度快、成本低,先排除绝大多数人;第二道关卡是针对可疑行李的单独开箱检查,成本高但精度高;最后才轮到人工复检。如果你一上来就让每一个旅客都做开箱检查,流程必然瘫痪。

工业级Agent的场景也完全一样。输入流是海量的、杂乱的,意图空间是长尾的、动态的。一个设计合理的漏斗,能让你用不到5%的深度推理成本,覆盖95%以上的核心意图识别需求,同时把危险输入挡在最外层。这样一个结构还带来了运维上的额外好处:每一层可以独立升级、独立回滚,不会因为一个模型的迭代拖垮整条链路。

2. 分层漏斗整体架构与各层核心职责

2.1 整体分层思路

我把整套漏斗分为四层,分别命名为"入口守卫层""轻量粗筛层""深度精判层""决策兜底层"。每一层都只做一件事,输出也都是结构化结果,所以层与层之间可以分别测试、分别替换,出问题时不至于牵一发动全身。

层级目标输入输出技术手段耗时预算相对成本
L1 入口守卫排除非法/异常输入原始文本通过/拒绝+原因码正则、规范化、哈希去重、规则<1ms极低
L2 轻量粗筛高召回识别常见意图清洗后的文本候选意图+粗置信度词典、FastText、近义召回<5ms低
L3 深度精判精细分类与上下文理解候选文本+上下文意图+实体+细置信度BERT类模型/大模型精调20-300ms高
L4 决策兜底最终路由与拒识前面所有结果路由指令/澄清/转人工阈值规则、校准、策略引擎<1ms极低

这个表格里最需要注意的一点是:L3的成本是前两层的几十倍甚至上百倍,所以漏斗优化的核心指标不是"单层准确率",而是"每一层能筛掉多少不需要进下一层的样本"。一个只筛掉10%的粗筛层,从成本角度讲是失败的,因为真正昂贵的计算发生在后面,你给后面多放进来一分流量,就多花一分钱。

2.2 L1:入口守卫要挡住的四类输入

L1这层的设计目标是"零成本拦截垃圾"。我归纳了四类输入,在L1阶段就应该被拦下,而不是留给后面的昂贵模型:

第一类:格式不合格的输入。比如空字符串、纯标点、长度超过上限的长文本、明显不是目标语言的语种。这些输入在语义上毫无价值,用正则和基础统计就能判断。

第二类:重复与刷屏输入。实际线上经常会有同一个用户短时间重复发送相同内容,或者脚本工具高频调用。这类输入可以对文本做归一化哈希后,结合滑动窗口做去重或限流。归一化要注意细节,比如全角半角、大小写、连续空格都得先处理,否则"你好"和"你 好"会被当成两个完全不同的内容。

第三类:高危与违规输入。这指的是包含攻击性内容、引导模型输出违规内容的输入。L1阶段需要用关键词规则和轻量模型联合做一次快速拦截。虽然规则不可能覆盖所有变体,但能拦住90%的固定模式,剩下的交给L4的兜底。

第四类:其他业务串扰。很多Agent是嵌入在业务系统里的,客服渠道可能混入订单查询、投诉、闲聊等。有些输入并不是给当前Agent的,而是用户误发。这类输入可以用业务前缀、槽位关键词等简单特征识别。

L1的输出一定要带"原因码",比如format_error、duplicate、blocked、out_of_scope。原因是后续监控和排障都依赖这些原因码的分布。没有原因码,你就只知道被拦了很多,但不知道被谁拦的、为什么拦,查一次线上故障会痛不欲生。

2.3 L2:轻量粗筛,追求高召回、少误杀

L2的核心目标不是"分类准确",而是"别漏掉有用的东西"。它要解决的核心矛盾是:L3的深度模型很贵,不能什么事都丢给它做,但用户输入又千奇百怪,规则很难覆盖所有写法。

我的做法是组合三路信号:

  1. 规则与词典。先枚举高频意图的触发词、同义词、别名,组合成前缀或包含规则。比如"查一下天气"命中"天气"词典,"明天杭州会下雨吗"也能通过同义改写命中。这一路的优点是快、可控、可解释。

  2. 轻量分类模型。用FastText或者带Bigram的线性模型,在离线标注数据上训练一个多标签意图分类器。这个模型不追求超高的精细度,但要在有限的意图类别上做到高召回。模型输出的粗置信度是后续决策的重要参考。

  3. 相似度检索。维护一个历史意图样本库,每个意图存若干典型话术的向量,每次输入计算和库中样本的相似度,把Top-K相似意图及其得分作为候选。这样能覆盖很多规则写不出来的长尾说法。向量库早期用暴力搜索就行,数据量大了再上FAISS或Milvus,别一开始就把架构搞重。

L2最终的输出是一个候选意图列表,而不是唯一意图。每个候选带一个粗置信度。我会设置一个"保留阈值":粗置信度高于保留阈值,继续进L3;全部低于某个下限值,则直接进入L4走兜底,比如转人工。这个阈值不要追求精确,宁可放更多样本进L3,也不要在这里把真正有价值的需求误杀。漏掉一个用户的真实诉求,比多花几分钱推理费严重得多。

2.4 L3:深度语义精判,做最后的裁判

经过L1和L2的过滤,进入L3的输入只占全量的一部分,这时候的花费才"值得"。L3要做的事有四个:

一是对候选意图做精细判定,输出每个候选意图的细置信度。 二是抽取意图相关的关键实体与槽位,比如查天气的地点、日期,查账单的账期、金额。 三是结合多轮对话上下文,修正单轮无法判定的歧义。比如用户只说了"明天呢",单看文本无法判定意图,但结合上一轮提到的"天气"或"会议",就能确定是追问还是改期。 四是输出解释性信息,比如关键Token、注意力分数,便于审计与排查。

在模型选型上,我实测过两种路线。一种是用预算内的BERT类小模型精调,比如6层左右的轻量模型,速度很快,30到50毫秒就能出结果,适合对延迟敏感的场景。另一种是用大模型做Few-shot,效果上限更高,但延迟和成本都上了一个量级,更适合离线批量处理或非实时入口。工业级场景通常把两者结合:先跑轻量精调模型,如果置信度落在模糊区间,再调用大模型复核一次。

L3绝对不能直接输出最终答案。它应该输出一个结构化的决策对象,包含意图标签、置信度、实体列表、触发原因。这样后面L4才能做灵活的决策,也方便做效果分析。这个习惯我是在踩了几次坑后才养成的,最开始L3直接返回一个字符串标签,导致L4想加冲突检测都没法做,只能返工。

2.5 L4:决策兜底,决定走哪条路

L4这一层经常被忽略,但在我看来它才是漏斗价值的集中体现。L4做的事情不是识别,而是"决策与路由"。它拿到L3的输出后,根据业务规则做最终决定:

  • 高置信度命中某意图:直接路由到对应的Agent技能或工具链。
  • 中置信度且缺少必要实体:触发明细澄清,让用户补充信息。
  • 低置信度且多意图冲突:发起一次"确认式提问",把模糊问题变成选择题。
  • 全部低置信度:走人工兜底或者友好拒识,同时记录失败样本。

L4还负责一个核心动作:置信度校准。模型的原始置信度并不等价于实际正确概率,直接拿0.8当阈值,往往线上表现会偏离预期。我通常会在离线数据集上做一次校准映射,把模型得分映射成更接近真实正确概率的值,再根据业务容忍度设置分档阈值。常用的方法有Platt Scaling和Temperature Scaling,实现都不复杂,但效果差异很大,值得单独花时间调。

这里放一个实际业务中常见的分档策略作为参考:

决策行为条件后续动作
直接路由校准置信度≥0.85 且 实体完整立即交给对应Agent执行
澄清补充校准置信度≥0.6 且 实体缺失追问缺失槽位,最多2轮
确认式提问0.95>校准置信度≥0.6 且 多意图冲突给出候选,让用户选择
拒识/转人工校准置信度<0.4兜底人工,沉淀Badcase

注意表里的分档条件不是拍脑袋定的,是从真实验证集上画阈值曲线后选出来的工作点。不同业务、不同风险等级,阈值差异会很大,后面我会讲怎么算。

3. 关键参数计算与落地实现细节

3.1 先算账:漏斗要筛掉多少流量

设计漏斗的第一步不是选模型,而是算账。先确定几个数值:全量日请求量、单次深度推理的成本、单次浅层推理的成本、目标延迟、可承受的算力预算,然后倒推每一层的最大通过率。

以我的一个真实项目为例。全量日请求量100万条,如果每条都走大模型精判,一次推理成本折合约0.015元,一天光意图识别就是1.5万元,一个月45万元,这是任何业务都扛不住的。于是我决定,真正的深度推理日处理量控制在10万条以内,也就是通过率不超过10%。

当时的分配是:L1拦截约20%,剩下80万条;L2只保留粗置信度相对高的40%,也就是32万条;L3对32万条做深度精判,但其中大约60%会被高置信直接路由,只有40%左右需要调用更贵的大模型复核。于是真正落在大模型复核上的只有13万条左右,再通过阈值控制到10万以下。从100万到10万,漏斗做到了九成的流量压缩。

这样算下来,一天的深度推理成本降到1500元左右,加上浅层计算和向量检索,总成本大约是原来不分层方案的十分之一,而延迟反而更低,因为大部分请求在L1和L2就返回了,根本不需要排队等大模型。

3.2 阈值用数据定,不用感觉定

阈值的设定方法,我总结了一套可操作的流程:

  1. 准备一份带标注的验证集,覆盖高频意图和长尾意图。
  2. 在每个可能的阈值下,计算误拒绝率和误纳率。
  3. 画出阈值曲线,观察两个指标的变化趋势。
  4. 结合业务成本和用户影响,选定一个最合适的工作点。

先解释两个指标,因为很多人搞混。误拒绝率是"本该路由到某个意图的输入被挡掉了",比如用户明明是想查天气,系统却说"我不理解你",用户直接流失。误纳率是"不该路由到某个意图的输入被放进去了",比如用户查的是退货运费,系统却误判成"申请退款"并执行了退款流程,这会造成资损。

两个指标是跷跷板关系。阈值调高,误纳率降了,但误拒绝率升了;阈值调低,误拒绝率降了,但误纳率上来了。业务上怎么权衡呢?我举一个实际例子:

某个意图"查天气",在验证集上取阈值0.7时,误纳率是1.8%,误拒绝率是2.3%;取0.8时,误纳率降到0.9%,但误拒绝率涨到5.6%。对于查天气这种低风险意图,我选择0.7,因为误拒一次用户可能就骂骂咧咧走了;对于"退款申请"这种高影响意图,我选择0.9,避免把别的意图误路由到退款流程,代价是有一部分用户需要多走一次澄清。高风险动作要压低误纳,低风险动作要压低误拒,这就是阈值选择的核心逻辑。

3.3 每层具体怎么实现:一个可复用的伪代码流程

理论说了这么多,直接上一份可复用的分层伪代码。这个结构不绑定特定框架,换成LangChain也好、自研引擎也好,都能照搬。

# 分层漏斗意图识别主入口 pseudo code def intent_funnel(raw_text: str, context: dict) -> RoutingDecision: # L1 入口守卫 guard = l1_guard(raw_text) if not guard.passed: return RoutingDecision(action="reject", reason=guard.reason_code) # L2 轻量粗筛 coarse = l2_coarse(guard.clean_text) if coarse.keep_score < KEEP_LOWER_BOUND: return RoutingDecision(action="fallback_human", reason="low_coarse_confidence") # L3 深度精判 detailed = l3_deep(guard.clean_text, context, candidates=coarse.candidates) # L4 决策兜底:阈值路由 + 澄清/确认/拒识 return l4_decide(detailed, business_policy=context.policy)

每个函数内部的关键动作,我再拆开说一下:

l1_guard里先做文本标准化,包括全角转半角、繁体转简体、去掉连续空白;再用正则判断长度、语种、特殊字符;然后通过归一化哈希做短时窗口去重;最后跑一遍敏感词规则。

l2_coarse用FastText加载提前训练好的模型,输出候选意图和粗置信度。与此同时并行跑词典命中和向量检索,三者结果做一次简单的加权融合。不需要复杂网络,线性加权加一个阈值就够。

l3_deep用ONNX Runtime跑轻量BERT模型,动态加载上下文窗口,输出细置信度和槽位。如果细置信度落在模糊区间,比如0.4到0.7之间,再同步调用一次大模型接口做复核。

l4_decide读业务策略表,根据校准后的置信度和实体完整度,返回一个结构化的RoutingDecision对象。重点是把决策逻辑从模型代码里拆出来,用规则表配置,这样产品和运营也能直接调整策略,不用每次改代码。

3.4 性能与资源控制的几个实操点

第一,并发与异步。L1和L2是无状态且计算轻量的,可以做成高并发同步服务;L3因为有模型推理,建议用异步批量或流水线,避免阻塞入口。如果业务流量有波峰,比如每天早上9点到11点咨询量暴增,要提前做容量评估和弹性扩容。

第二,缓存策略。同一个用户、相似文本在短时间内的意图结果可以缓存,尤其是高频操作。我做过统计,线上大概15%的流量是重复查询,命中缓存后直接跳过L3,能省下不少算力。缓存键用归一化文本加用户维度的组合,过期时间根据业务场景设,一般5到10分钟。

第三,量化与加速。L3的BERT类模型用INT8量化后,在CPU上可以跑到20到40毫秒,基本够用;GPU部署时用动态Batch能显著提升吞吐。这块我建议优先做,因为量化是投入产出比最高的优化手段。

第四,熔断与降级。当大模型服务超时或不可用,L3要能自动退化为L2的粗置信度结果,而不是让整个链路挂掉。这个降级思维在工业级场景里特别重要。线上大模型服务一旦抖动,如果没有降级机制,整个意图识别模块会连带崩溃,最后所有用户请求都转人工,那才是灾难。

4. 上线后踩过的坑与排查经验

4.1 线上和离线指标差异大,问题出在数据分布

最常见的坑:离线验证效果很好,上线后准确率下降。原因出在数据分布漂移。离线测试集里正常中文语句偏多,线上却充满口语、错别字、中英混合、Emoji、断句问题,而且很多输入是残缺的,比如用户话说到一半就发送了。

解决办法没有捷径,只能上线前先做"真实流量回放":把历史线上日志按时间比例抽出来,作为压测和验证集,模型效果以回放结果为准。这个习惯我后来一直保留,每次模型迭代都要先过一遍回放数据集,否则不敢发布。

另外我踩过一个更隐蔽的坑:离线评估指标用"识别准确率",但线上真正要关注的是"路由正确率"。识别准确率看着不错,但低频高影响意图错了几个,业务损失就很大。我后来在指标设计上增加了按意图加权的评估方式,高危意图的权重远高于高频意图,这样模型调优才有方向。

4.2 意图漂移与长尾输入,需要回流机制

业务上线后,用户的表达方式会不断变化。比如某个促销活动上线后,"怎么参加活动""优惠券怎么领"这类话术暴增,而训练集里几乎没有。如果不处理,这部分请求会全部落到低置信度区间,大量转人工,客服成本直线上升。

应对措施有两个。一是建立Badcase回流机制:每周把转人工和拒识样本聚出来,让标注团队增量标注,定期增量更新L2的词典、规则和L3的训练数据。这个机制要跑成常态,而不是偶尔做一次。二是改动L3的输出层,接入"意图热词漂移检测":对新的高频未识别片段做聚类,如果发现某个簇的量级异常增长,就自动告警,提示可能出现了新的意图或话术变体。有一次我就是靠这个告警发现新上线功能被用户用了一个团队完全没想到的说法,提前补了规则,避免了客服被打爆。

4.3 排查一个线上误判的思路

讲一次典型的排查过程。线上有个"删除订单"的操作,偶尔会把"删除聊天记录"误判成"删除订单",导致用户被路由到危险操作。这个Case非常典型,我想拆开讲。

我的排查步骤是:

  1. 先看L4的决策日志,确认误判发生在哪一层。查日志发现L2粗筛阶段就没区分这两个意图,把候选都给了"删除订单"。
  2. 再看L3,发现"聊天记录"这个词不在训练样本里,模型对"删除订单"的置信度反而更高。
  3. 最终修复不是只加一条规则,而是做三点修复:L2词典增加"聊天记录""清空消息"等词;L3补充30条"删除聊天记录"的样本;L4增加一条疑似冲突判定:如果文本同时命中"订单"和"记录",必须走确认式提问。

这个案例说明,排查一定要看分层日志,而不是盯着最终结果猜。每一层的结果只要留了结构化日志,很快就能定位问题出在哪一层。我建议日志里至少记录:当前层、输入摘要、输出结果、置信度、命中规则或模型版本、耗时。有了这些,线上排障就是按图索骥。

4.4 分层漏斗的维护节奏

最后说说维护节奏。L1规则可以随时小改,不需要走模型发布流程;L2词典建议每周更新,配合线上高频词统计;L3模型每两周到一个月做一次增量训练,具体频率看业务变化速度;L4的策略表则按业务需求随时调整,但每次调整要留变更记录,方便回溯。

每一层分开迭代,最大的好处是避免"改一个规则需要重新训练整个模型"的尴尬。我见过很多团队把意图识别当单模型维护,每一次需求变化都要重新训练、重新评估、重新发布,周期动辄一周,根本跟不上业务节奏。分层之后,很多调整可以当天完成,只有涉及语义理解的部分才需要动模型,迭代效率提升非常明显。

另外,分层漏斗上线后一定要建一个全局可视化大盘,把每一层的通过率、拦截率、原因码分布、决策分布、平均延迟和成本全部展示出来。这个大盘既是监控工具,也是优化工具。每次想动某一层的参数,先在大盘上看一周的数据,确认变化趋势,再动手。不要凭感觉调参,线上系统经不起拍脑袋。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/1 11:34:55

Rust GUI框架Iced核心库Size结构体源码深度解析

作为常年跟 Rust GUI 打交道的人&#xff0c;我越来越觉得 Iced 是个值得反复读源码的框架&#xff0c;尤其它拆出来的core库&#xff0c;简直是一套“零依赖也能用的 2D 几何 布局基础类型集”。很多教程上来就让你写应用&#xff0c;但真正卡人的往往不是业务逻辑&#xff0…

作者头像 李华
网站建设 2026/10/16 17:30:28

Qt组播实战:解决多网卡绑定与收发指定IP的完整方案

在实际的项目开发里&#xff0c;组播往往扮演着一个“不起眼但关键时刻掉链子”的角色。无论是工控机之间的设备状态同步、AGV调度系统里的车辆发现&#xff0c;还是多媒体推流、集群节点心跳&#xff0c;组播都能把一份数据高效地发给一组接收方&#xff0c;比单播挨个发省带宽…

作者头像 李华
网站建设 2026/10/16 19:59:23

二维拓扑优化Matlab源码:SIMP变密度法与OC准则实践

搞结构设计的朋友基本都遇到过这样的场景&#xff1a;给定一块设计域&#xff0c;要在满足一定材料用量约束的前提下&#xff0c;让结构尽量“硬”——这时候无脑加厚显然不行&#xff0c;你得知道材料放哪里最值。这就是二维拓扑优化的核心价值。我近期整理了一套基于四边形单…

作者头像 李华
网站建设 2026/10/16 19:59:20

光伏-混合储能-并网微电网一体仿真:建模要点与控制策略全复盘

最近在做微电网仿真时&#xff0c;搭了一套“光伏-混合储能-并网-直流负载-控制模块一体仿真”模型&#xff0c;前前后后踩了不少坑&#xff0c;也趟出了一些经验。起初我以为这活儿就是把光伏板、电池、逆变器在仿真软件里连起来就算完事&#xff0c;真正动手才发现&#xff0…

作者头像 李华
网站建设 2026/10/19 2:17:25

AI返回的JSON总是解析失败?一套可复用的多层清洗管道解法

先说个真实场景。上一周我在处理项目里的大模型报表配置&#xff0c;提示词里白纸黑字写了“只输出JSON&#xff0c;不要加任何解释”&#xff0c;结果模型还是把JSON塞进了Markdown代码块里&#xff0c;前后各带一句“好的&#xff0c;这是你要的配置”。我把整段返回当作json…

作者头像 李华