深度 3:意图路由——"模糊的 query"到底怎么判断
这是「Agent 工程化」系列的深度篇。前面几篇讲了 Agent 的骨架:编排、工具、记忆、护栏。但有一个所有生产 Agent 都绕不开、却很少有人讲透的细节——意图路由:用户一句话进来,系统怎么知道它想干嘛?更难的追问是:怎么知道这句话说不清它想干嘛?
开场:一句"没接住"的话
用户抛过来一句话:
“这个服务怎么老出问题?”
你的意图路由看了一眼,没发现任何强关键词——没有"503"、没有"报错"、没有"日志"。按规则它应该走哪条路?
答案是通用兜底(general)。于是 Agent 用最泛化的知识回了一段"可能是服务有问题,建议检查日志"。
但用户真正的意图是诊断——他想让你查环境、看日志、定位根因。一条本可以精准响应的请求,因为路由判断失误,变成了一次低质量的通用回答。
问题出在哪?路由系统把"这句 query 是清晰的还是模糊的"判断错了——它根本没判断,只是"没命中就当清晰"。
这就是意图路由里最容易被忽略、也最影响体验的一环:模糊度判断。
意图路由的骨架:分类 → 路由 → 执行
先看全貌。一个生产 Agent 的意图路由通常长这样:
#mermaid-svg-d9bstgJ8Vb1Wh1OU{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-d9bstgJ8Vb1Wh1OU .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-d9bstgJ8Vb1Wh1OU .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-d9bstgJ8Vb1Wh1OU .error-icon{fill:#552222;}#mermaid-svg-d9bstgJ8Vb1Wh1OU .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-d9bstgJ8Vb1Wh1OU .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-d9bstgJ8Vb1Wh1OU .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-d9bstgJ8Vb1Wh1OU .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-d9bstgJ8Vb1Wh1OU .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-d9bstgJ8Vb1Wh1OU .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-d9bstgJ8Vb1Wh1OU .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-d9bstgJ8Vb1Wh1OU .marker{fill:#333333;stroke:#333333;}#mermaid-svg-d9bstgJ8Vb1Wh1OU .marker.cross{stroke:#333333;}#mermaid-svg-d9bstgJ8Vb1Wh1OU svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-d9bstgJ8Vb1Wh1OU p{margin:0;}#mermaid-svg-d9bstgJ8Vb1Wh1OU .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-d9bstgJ8Vb1Wh1OU .cluster-label text{fill:#333;}#mermaid-svg-d9bstgJ8Vb1Wh1OU .cluster-label span{color:#333;}#mermaid-svg-d9bstgJ8Vb1Wh1OU .cluster-label span p{background-color:transparent;}#mermaid-svg-d9bstgJ8Vb1Wh1OU .label text,#mermaid-svg-d9bstgJ8Vb1Wh1OU span{fill:#333;color:#333;}#mermaid-svg-d9bstgJ8Vb1Wh1OU .node rect,#mermaid-svg-d9bstgJ8Vb1Wh1OU .node circle,#mermaid-svg-d9bstgJ8Vb1Wh1OU .node ellipse,#mermaid-svg-d9bstgJ8Vb1Wh1OU .node polygon,#mermaid-svg-d9bstgJ8Vb1Wh1OU .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-d9bstgJ8Vb1Wh1OU .rough-node .label text,#mermaid-svg-d9bstgJ8Vb1Wh1OU .node .label text,#mermaid-svg-d9bstgJ8Vb1Wh1OU .image-shape .label,#mermaid-svg-d9bstgJ8Vb1Wh1OU .icon-shape .label{text-anchor:middle;}#mermaid-svg-d9bstgJ8Vb1Wh1OU .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-d9bstgJ8Vb1Wh1OU .rough-node .label,#mermaid-svg-d9bstgJ8Vb1Wh1OU .node .label,#mermaid-svg-d9bstgJ8Vb1Wh1OU .image-shape .label,#mermaid-svg-d9bstgJ8Vb1Wh1OU .icon-shape .label{text-align:center;}#mermaid-svg-d9bstgJ8Vb1Wh1OU .node.clickable{cursor:pointer;}#mermaid-svg-d9bstgJ8Vb1Wh1OU .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-d9bstgJ8Vb1Wh1OU .arrowheadPath{fill:#333333;}#mermaid-svg-d9bstgJ8Vb1Wh1OU .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-d9bstgJ8Vb1Wh1OU .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-d9bstgJ8Vb1Wh1OU .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-d9bstgJ8Vb1Wh1OU .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-d9bstgJ8Vb1Wh1OU .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-d9bstgJ8Vb1Wh1OU .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-d9bstgJ8Vb1Wh1OU .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-d9bstgJ8Vb1Wh1OU .cluster text{fill:#333;}#mermaid-svg-d9bstgJ8Vb1Wh1OU .cluster span{color:#333;}#mermaid-svg-d9bstgJ8Vb1Wh1OU div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-d9bstgJ8Vb1Wh1OU .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-d9bstgJ8Vb1Wh1OU rect.text{fill:none;stroke-width:0;}#mermaid-svg-d9bstgJ8Vb1Wh1OU .icon-shape,#mermaid-svg-d9bstgJ8Vb1Wh1OU .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-d9bstgJ8Vb1Wh1OU .icon-shape p,#mermaid-svg-d9bstgJ8Vb1Wh1OU .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-d9bstgJ8Vb1Wh1OU .icon-shape .label rect,#mermaid-svg-d9bstgJ8Vb1Wh1OU .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-d9bstgJ8Vb1Wh1OU .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-d9bstgJ8Vb1Wh1OU .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-d9bstgJ8Vb1Wh1OU :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
高置信
低于阈值
用户 query
意图分类
得分情况
命中某条路由
模糊兜底
执行该路由的工具集
LLM 二次分类或通用回答
关键就在 B 这一步:意图分类的结果不是一个,而是两个——“归哪类” + “归得多确信”。 绝大多数实现只做了前者,漏了后者。
"归得多确信"就是模糊度。它决定了一条 query 是直接走规则,还是值得花更多成本(调 LLM)去确认。
为什么"模糊"这么难判断
三个现实困难:
1. 模糊没有天然标签。
query 就是一句话,没有任何元数据告诉你它清不清晰。"帮我看看"和"帮我看看 order-service 的 OOM 日志"看起来都像诊断意图,但后者信息完整、前者处处是坑。
2. 模糊是相对的。
"这台机器内存溢出了"对 diagnose 路由是清晰的,但对 code 路由就是模糊的。模糊不是 query 的属性,是 query 与路由之间匹配程度的属性——所以必须按路由分别评估。
3. 规则只能覆盖"说得像"的。
规则的强项是命中已知表达(“503”、“报错"这类强词),弱项是处理"没见过的说法”——"这个服务怎么老出问题"这种口语化表达,规则永远覆盖不到。
三种方案:从规则到小模型
方案一:规则打分(最基础,当前实现)
核心思想:别用"是否命中"二值判断,用加权打分量化匹配程度。
INTENT_WORDS = {
"diagnose": ["503", "404", "报错", "OOM", "失败", "超时", "挂了", "日志"],
"code": ["源码", "实现", "在哪改", "代码", "函数", "写法"],
"pipeline": ["流水线", "构建", "发版", "部署", "Jenkins", "CI"],
}
def score_intent(query: str, intent: str) –> tuple[int, list[str]]:
"""给 query 对某个意图的匹配程度打分"""
score = 0
hits = [w for w in INTENT_WORDS[intent] if w in query]
score += len(hits) * 2 # 强词命中,每个 +2
if len(query) < 4:
score -= 1 # query 过短,降权
return score, hits
def classify(query: str, threshold: int = 3) –> tuple[str, int]:
"""分类 + 置信度:低于阈值判为模糊"""
scores = {i: score_intent(query, i)[0] for i in INTENT_WORDS}
best = max(scores, key=scores.get)
if scores[best] < threshold:
return "ambiguous", scores[best] # 模糊 → 交给 LLM 兜底
return best, scores[best]
三个设计细节:
规则的代价:只能覆盖约 80% 说得明确的 query,剩下 20% 的口语化表达要靠兜底。
方案二:规则 + LLM 兜底(过渡形态)
模糊的 query 进 LLM,把四个路由的定义、工具、能力范围写成提示词,让模型做二次分类。
优点:能处理任意表达。缺点:每一条模糊 query 都多一次大模型调用——延迟和成本都不可控。而且"什么时候算模糊"依然依赖规则打分,规则不准,LLM 兜底的门槛就失守。
方案三:3B 小意图模型(演进方向)
意图分类本质是个文本分类任务——不是推理任务。所以它根本不需要大模型:
- 模型:Qwen2.5-3B / Llama-3.2-3B 级别
- 训练:几百条标注样本(各意图的正例 + 边界负例)LoRA 微调
- 部署:vLLM 本地部署,延迟毫秒级
- 为什么 3B 够:分类任务简单,小模型参数足够;比规则鲁棒(没见过的说法也能分);比大模型便宜快一个数量级
#mermaid-svg-bb43R2NCMmFheYli{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bb43R2NCMmFheYli .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bb43R2NCMmFheYli .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bb43R2NCMmFheYli .error-icon{fill:#552222;}#mermaid-svg-bb43R2NCMmFheYli .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bb43R2NCMmFheYli .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bb43R2NCMmFheYli .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bb43R2NCMmFheYli .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bb43R2NCMmFheYli .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bb43R2NCMmFheYli .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bb43R2NCMmFheYli .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bb43R2NCMmFheYli .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bb43R2NCMmFheYli .marker.cross{stroke:#333333;}#mermaid-svg-bb43R2NCMmFheYli svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bb43R2NCMmFheYli p{margin:0;}#mermaid-svg-bb43R2NCMmFheYli .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-bb43R2NCMmFheYli .cluster-label text{fill:#333;}#mermaid-svg-bb43R2NCMmFheYli .cluster-label span{color:#333;}#mermaid-svg-bb43R2NCMmFheYli .cluster-label span p{background-color:transparent;}#mermaid-svg-bb43R2NCMmFheYli .label text,#mermaid-svg-bb43R2NCMmFheYli span{fill:#333;color:#333;}#mermaid-svg-bb43R2NCMmFheYli .node rect,#mermaid-svg-bb43R2NCMmFheYli .node circle,#mermaid-svg-bb43R2NCMmFheYli .node ellipse,#mermaid-svg-bb43R2NCMmFheYli .node polygon,#mermaid-svg-bb43R2NCMmFheYli .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-bb43R2NCMmFheYli .rough-node .label text,#mermaid-svg-bb43R2NCMmFheYli .node .label text,#mermaid-svg-bb43R2NCMmFheYli .image-shape .label,#mermaid-svg-bb43R2NCMmFheYli .icon-shape .label{text-anchor:middle;}#mermaid-svg-bb43R2NCMmFheYli .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-bb43R2NCMmFheYli .rough-node .label,#mermaid-svg-bb43R2NCMmFheYli .node .label,#mermaid-svg-bb43R2NCMmFheYli .image-shape .label,#mermaid-svg-bb43R2NCMmFheYli .icon-shape .label{text-align:center;}#mermaid-svg-bb43R2NCMmFheYli .node.clickable{cursor:pointer;}#mermaid-svg-bb43R2NCMmFheYli .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-bb43R2NCMmFheYli .arrowheadPath{fill:#333333;}#mermaid-svg-bb43R2NCMmFheYli .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-bb43R2NCMmFheYli .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-bb43R2NCMmFheYli .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bb43R2NCMmFheYli .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-bb43R2NCMmFheYli .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bb43R2NCMmFheYli .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-bb43R2NCMmFheYli .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-bb43R2NCMmFheYli .cluster text{fill:#333;}#mermaid-svg-bb43R2NCMmFheYli .cluster span{color:#333;}#mermaid-svg-bb43R2NCMmFheYli div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-bb43R2NCMmFheYli .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-bb43R2NCMmFheYli rect.text{fill:none;stroke-width:0;}#mermaid-svg-bb43R2NCMmFheYli .icon-shape,#mermaid-svg-bb43R2NCMmFheYli .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bb43R2NCMmFheYli .icon-shape p,#mermaid-svg-bb43R2NCMmFheYli .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-bb43R2NCMmFheYli .icon-shape .label rect,#mermaid-svg-bb43R2NCMmFheYli .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bb43R2NCMmFheYli .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-bb43R2NCMmFheYli .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-bb43R2NCMmFheYli :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
高
低
用户 query
3B 意图模型
置信度
直接路由
大模型二次确认或人工兜底
关键升级:置信度由模型原生给出(softmax 概率),不再靠人工写打分规则。规则维护成本从"改代码"变成"加样本"。
三方案对比
| 延迟 | 最快(毫秒内) | 模糊时慢(多一次大模型) | 快(本地毫秒级) |
| 成本 | 几乎为零 | 模糊越多越贵 | 低(自部署) |
| 鲁棒性 | 差(没见过的说法不认) | 好 | 好 |
| 覆盖 | ~80% 明确 query | ~100%(兜底兜住) | ~95%(边界仍需兜底) |
| 维护 | 改代码加词 | 改代码 + 调 prompt | 加标注样本 |
| 适用阶段 | 起步期 | 过渡期 | 规模化期 |
演进路线:别一步到位
生产里的正确做法是渐进式,别上来就训模型:
第一步:规则打分 + 阈值兜底 → 上线观察边界误路由
第二步:模糊 query 进 LLM 二次分类 → 把 LLM 的分支结果沉淀成样本
第三步:用样本训 3B 小模型 → 替换规则,模型给原生置信度
第 2 步到第 3 步之间有个隐藏红利:LLM 兜底期间跑过的每一条模糊 query,都是天然的训练数据(query + LLM 分类结果,人工抽检后进训练集)。过渡形态不白花钱,它在给最终形态攒弹药。
三个最容易踩的坑
坑一:没有阈值,全量进 LLM。
把"模糊度判断"省了,所有 query 先问一遍大模型——延迟、成本、稳定性全崩。规则存在的意义就是让 80% 的请求不经过 LLM。
坑二:阈值拍脑袋。
阈值定得太高,明确的 query 被误判模糊,白白多花钱;定得太低,模糊的 query 走错路由。正确做法:上线后拉真实 query 分布调阈值——统计各意图得分分布,把阈值放在"明确与模糊"的天然分界处。
坑三:只测"说得像的",不测边界。
评测路由时只测"503 报错"这种标准说法,永远发现不了"这个服务怎么老出问题"会走错路。评测集必须包含边界 query——口语化、短句、指代不清的,这才是路由系统真正的薄弱点。
常见疑问
问:路由分类和 RAG 检索是一回事吗?
不是。路由是"判断用户想干嘛、走哪条流程",RAG 是"在知识库里找资料"。但可以联动:路由命中 diagnose 后,诊断流程里再用 RAG 查排障文档。分类在先,检索在后。
问:为什么不用大模型做意图分类?
能做,但不划算。文本分类是简单任务,3B 小模型就够;大模型在这里是"杀鸡用牛刀",而且每次调用都有延迟和成本。大模型的正确用途是模糊兜底和复杂推理,不是批量分类。
问:规则和模型的边界到底在哪?
经验法则是:能确定的地方用规则,不确定的地方用模型。规则管"说得很像"的 80%,模型管"说不清"的 20%。边界本身(模糊度)可以用规则打分估算,也可以用小模型置信度原生获得。
总结:一句话记住它
意图路由的完整答案是"分类 + 置信度":加权打分算匹配程度,低于阈值判模糊进兜底。规则管得住 ~80% 明确 query,模糊的交给 LLM,规模上来后训 3B 小模型拿原生置信度——分类是简单任务,用大模型是浪费。千万别省掉模糊度判断,那是路由系统防误判的最后一道闸。
小结
- 路由 = 分类 + 置信度:只分类不判确信度,模糊 query 会被静默误路由
- 模糊是相对的:query 与路由的匹配程度,要按路由分别评估
- 三方案:规则打分(快、覆盖 80%)、规则+LLM(全、贵)、3B 小模型(鲁棒、置信度原生)
- 渐进演进:规则 → 规则+LLM → 小模型,LLM 兜底期间的 query 是天然训练数据
- 三坑:无阈值全量进 LLM、阈值拍脑袋、评测不测边界 query
下一篇深度篇:Skill 与 Tool——剧本和演员的边界,以及 Skill 到底存哪。“Skill 是知识还是执行”“存数据库还是融进 Agent”——把这两个最容易讲含糊的点一次说清。
本系列路线(从 0 到 1): Agent 是什么 → 手写最小 ReAct → Function Calling 与工具设计 → 上下文管理 → Memory 记忆系统 → RAG 知识库 → Skill 自学习 → 编排模式与多 Agent → 给 Agent 装护栏 → 生产部署与可观测 → 评测与回归 → 番外:框架横评 → LangGraph 深挖 → 框架选型决策 → 深度篇:Reflection 反思 → 记忆压缩 → 意图路由 → Skill 辨析 → 更多
网硕互联帮助中心

评论前必须登录!
注册