意图分数出来了,该查哪个库、查多少条
开篇引言
上一篇讲了歧义引导——用户问退货政策,3C、家电和服装三个品类都举手了,系统停下来让用户选。handleGuidance 检测到歧义后短路输出引导文案,Pipeline 直接 return,不走检索。
歧义引导走完了——要么用户被问了一次做出了选择,要么意图本身明确不需要引导。不管哪种情况,到这一步意图分类的结果已经确定了。每个子问题手里拿着的意图列表是板上钉钉的:可能是一个 KB 意图(清关流程,score=0.92),可能是一个 MCP 意图(订单查询,score=0.90),也可能是一个 SYSTEM 意图(欢迎与问候,score=0.88)。
现在摆在面前的问题是:这些意图分数怎么翻译成实际的动作?
不同类型的意图走完全不同的路径——KB 意图要去向量数据库的某个 Collection 做向量检索,MCP 意图要调订单系统的 API 拿实时数据,SYSTEM 意图压根不用查任何东西直接回复就行。 而且分数的高低还会影响检索策略——高置信度走定向检索就够了,低置信度还得兜底搜全库给 Reranker 更多候选。
本篇是意图识别子系列的第 5 篇也是收尾篇。前四篇解决了怎么知道用户想问什么——树结构(第 5 篇)→ 打分(第 6 篇)→ 封顶(第 7 篇)→ 歧义引导(第 8 篇),本篇补上最后一环:知道了之后怎么做。
用电商客服的四个典型问题来跑一遍:
| 用户问题 | 命中意图 | 类型 | 接下来该做什么 |
|---|---|---|---|
| 你好 | 欢迎与问候(0.88) | SYSTEM | 直接回复,不走检索 |
| 帮我查一下订单 2024112801 的物流进度 | 订单查询(0.90) | MCP | 调工具,不走向量检索 |
| 跨境包裹清关一般要多久? | 清关流程(0.92) | KB | 去对应 Collection 做定向检索 |
| 退货规则是什么 | 清关流程(0.55) | KB | 分数低于置信度阈值,定向 + 全局兜底检索 |
四个问题,四条路径,本篇逐个拆解。
总览:从意图到动作的分发地图
1. Pipeline 中的位置
先回到 StreamChatPipeline 的 execute() 方法,定位本篇涉及的阶段:
public void execute(StreamChatContext ctx) {
loadMemory(ctx); // 第 2 篇
rewriteQuery(ctx); // 第 4 篇
resolveIntents(ctx); // 第 5~7 篇
if (handleGuidance(ctx)) {
return; // 第 8 篇:歧义引导短路
}
if (handleSystemOnly(ctx)) {
return; // ← 本篇重点:SYSTEM 意图短路
}
RetrievalContext retrievalCtx = retrieve(ctx); // ← 本篇重点:KB/MCP 分流入口
if (handleEmptyRetrieval(ctx, retrievalCtx)) {
return;
}
streamRagResponse(ctx, retrievalCtx);
}
上一篇讲到 handleGuidance 返回 false(没有歧义或者引导完成了),Pipeline 继续往下走。接下来就是本篇的两个核心位置:
handleSystemOnly:SYSTEM 意图的短路分支,不走检索直接回复retrieve:KB 意图和 MCP 意图的分流入口,委托给RetrievalEngine处理
2. 三条路径一张图

用一张表汇总三种意图类型的处理方式:
| 意图类型 | 处理方式 | 关键代码入口 | 是否走向量检索 | 备注 |
|---|---|---|---|---|
| SYSTEM | 短路直接回复 | handleSystemOnly() | 否 | 可配自定义 Prompt,Temperature=0.7 |
| MCP | 工具调用 | executeMcpTools() | 否 | LLM 提取参数 + 工具执行 |
| KB | 定向 / 全局检索 | retrieveAndRerank() | 是 | 走多通道检索引擎,可能两个通道同时激活 |
SYSTEM 意图:不查库,直接回复
1. 什么场景走这条路
用户发了一句“你好”。意图分类命中了 系统交互 > 欢迎与问候 节点(kind=SYSTEM,score=0.88)。
这种问题去向量库搜 chunk 纯属浪费——答案不在任何知识库文档里,而是系统自己该有的社交应答能力。搜出来的 chunk 不但没用,还可能干扰 LLM 的回复(比如搜到一段退货政策的文档片段,LLM 莫名其妙地在问好回复里塞进一句退货相关的话)。
最合理的做法就是跳过检索,让 LLM 直接根据会话历史和系统提示词回复。
2. handleSystemOnly 的判定条件
private boolean handleSystemOnly(StreamChatContext ctx) {
List<SubQuestionIntent> subIntents = ctx.getSubIntents();
boolean allSystemOnly = subIntents.stream()
.allMatch(si -> intentResolver.isSystemOnly(si.nodeScores()));
if (!allSystemOnly) {
return false;
}
// ... 短路处理
return true;
}
判定的核心是 allMatch——必须所有子问题都是 SYSTEM-only 才短路。
isSystemOnly 的定义也很严格:
public boolean isSystemOnly(List<NodeScore> nodeScores) {
return nodeScores.size() == 1
&& nodeScores.get(0).getNode() != null
&& nodeScores.get(0).getNode().getKind() == SYSTEM;
}
恰好只有一个意图,且 kind 是 SYSTEM,才算 SYSTEM-only。
为什么这么谨慎?看两个场景:
| 场景 | 子问题意图 | 是否短路 | 原因 |
|---|---|---|---|
| 用户说“你好” | [欢迎与问候(SYSTEM,0.88)] | ✅ 短路 | 唯一意图是 SYSTEM |
| 用户说“你好,顺便帮我查一下退货政策” | 子问题 1:[欢迎与问候(SYSTEM,0.85)] 子问题 2:[退货政策(KB,0.78)] | ❌ 不短路 | 子问题 2 不是 SYSTEM-only |
| 用户说“谢谢” | [情感反馈(SYSTEM,0.82)] | ✅ 短路 | 唯一意图是 SYSTEM |
第二个场景很典型——用户打了个招呼顺带问了个问题,查询改写把它拆成两个子问题。虽然打招呼那个子问题是 SYSTEM,但退货政策那个子问题是 KB,allMatch 不满足,走正常流程。这样退货政策的问题不会被跳过。