第 9 章:意图识别与路由:可信的语义分派
9.1 工程问题的本质:语义分派的失败成本
路由解决"这条输入该由谁处理"。语义分派的失败成本并不均衡:把"查库存"路由到下单,会把一次查询变成一次业务操作;把"下单"路由到知识问答,则业务请求悬空。分派错误的代价远高于分派延迟的代价——这一不对称性决定了路由设计的优先级排序。
设计原理: 路由必须同时满足三个性质——可训练(分派规则随业务演化)、可降级(任何输入都有出口)、可审计(分派结果可追溯)。三者缺一,路由就会成为系统中"说不清为什么走这条路"的黑洞。
9.2 上下文隔离:分派的正确性基础
路由正确性的基础是上下文隔离:每个智能体只能看到它该看到的数据。深拷贝保证无共享可变状态;按目标智能体类型裁剪敏感字段(非财务智能体不看到财务明细、非内审智能体不看到内审数据),实现最小权限可见。
原理: 上下文隔离不是"防御性编程",而是信任边界的实现。协作系统的信任不能建立在"每个智能体都很自觉"上,而必须建立在"结构上拿不到"上。隔离之后的显式注入(intent / slots / 流程实例)则是信任的授出——授出哪些、何时授出,由路由层统一决定。
9.3 路由表:可训练与可降级
路由决策分三层:DB 规则路由(intent_rules 表,target_agent 可训练)→ 内置映射(降级)→ 正则表(再降级)。
原理: 三层结构回答了两个问题。可训练:业务调整路由时改库即可,训练后 reload 生效,无需发版;可降级:数据库不可用时逐级回退,语义层永不因依赖缺失而失去出口。路由本身的治理(谁改、改什么)复用于规则审批链,与第 14 章的配置治理一致。
9.4 识别链路:规则优先、模型兜底
意图识别采用四阶段链路:
输入归一化 → DB 高优先级训练规则 → 确定性短语白名单 → LLM+训练样本 → 正则兜底
原理: 四阶段是按"确定性递减、泛化性递增"排列的。规则层命中是零延迟、零成本、完全确定的事实;模型层命中是秒级、消耗 token、有随机性的推断。将规则层置于模型层之前,不是轻视模型,而是让确定的东西先说话——只有当确定的手段无法裁决时,才把不确定性引入。
这一顺序还保证了训练修正的即时性:DB 高优先级规则(priority<10)位于链路第一步,用户训练的正确意图永远优先于白名单与模型。学习的成果(第 14 章)因此能立刻改变行为。
9.5 槽位与参数:事实提取的确定性
意图之后的参数提取同样遵循"确定性优先":槽位定义(正则、排除规则、单位换算、值映射)存于可训练配置,intent 层与智能体层共用同一提取链路,避免双份正则漂移。查询附加词("库存大于 100""近 7 天")由参数解析器提取为结构化条件,规则无法解析的模糊片段才交模型补全,且字段名与操作符均经白名单校验。
原理: 参数是业务事实,事实提取不容许"这次提取到、下次提取不到"的随机性。确定性提取 + 白名单校验,使"用户说什么"到"系统执行什么"的转换链条中,只有语义理解环节允许概率性——参数本身必须是确定的事实。
9.6 消歧:业务策略的编码
多规则命中时按语义优先级链裁决(质量动作优先于质量查询、动作意图优先于查询意图等)。消歧规则集的顺序本身就是业务策略的编码。
原理: 消歧不是技术细节,而是业务风险偏好的形式化。当两条规则冲突时,先满足谁,反映系统对两类错误(漏执行 vs 误执行)的不同容忍度。动作优先于查询,意味着"该执行的操作不能被查询词吞掉"比"查询被误判为操作"更值得避免。
9.7 识别质量度量与经典理论对照
9.7.1 识别质量的度量体系
识别器与路由的质量必须可度量,否则优化无从谈起。四类指标构成最小评估集,见表 9-1:
表 9-1 识别质量的度量指标
| 指标 | 定义 | 优化手段 |
|---|---|---|
| 意图识别准确率 | 命中意图 / 总输入 | 规则补全、few-shot 增强、训练样本 |
| 路由正确率 | 到达正确 Agent / 总输入 | 消歧规则、DB 路由训练 |
| 规则命中率 | 规则层命中 / 总输入 | 高频句式规则化(零延迟收益) |
| 参数提取召回 | 正确提取槽位 / 应提取槽位 | 槽位定义、排除规则、单位换算 |
原理: 度量驱动的改进遵循"先度量、再归因、后优化"——一次误路由的归因链是:输入 → 识别(哪一层命中)→ 消歧(哪条规则优先)→ 路由(哪张表)→ 执行。任一环都可能出错,度量必须能区分"识别错了"与"路由错了",否则优化会打在错误的环节上。
9.7.2 冲突消解策略:经典规则引擎理论的对照
多规则命中时的消歧,与经典规则引擎(如基于 RETE 算法的产生式系统)的冲突消解策略(conflict resolution strategy)是同构问题。经典策略有三类,本框架的消歧链可对照其中两类,见表 9-2:
表 9-2 经典冲突消解策略与框架对应
| 经典策略 | 语义 | 框架中的对应 |
|---|---|---|
| 特殊性优先(specificity) | 条件更具体的规则优先 | 训练规则 priority<10 高于内置规则 |
| 新鲜度优先(recency) | 最近更新的规则优先 | 训练数据动态白名单(新样本优先收录) |
| 显著性优先(salience) | 显式声明的优先级 | 消歧链规则 1~9 的显式顺序 |
对照价值: 这一对照给出一个设计检验:规则冲突的解决必须是确定性、可解释、可训练的。确定性(同样的输入永远同样的结果)靠固定优先级链;可解释性靠"命中哪条规则、为何优先"可追溯;可训练性靠优先级与分类集合存配置(DISAMBIG-CFG)。三者缺一,消歧就会退化为"说不清为什么"的黑盒。
9.7.3 few-shot 与结构化输出的机理
模型识别层以 few-shot + 函数调用(function calling)产出结构化意图。其可靠性来源有三层,缺一不可:
- schema 约束:模型被要求输出符合意图 schema 的结构化结果,格式错误即失败——结构化输出把"自由文本"的风险前移到解析层;
- 白名单校验:解析结果经已知意图白名单校验,意图名不在名单内一律降级 unknown——模型"发明意图"被拒绝;
- 参数合并:规则提取的参数优先,模型提取的补缺失字段——模型不覆盖确定性事实。
原理: 结构化输出的可靠性是"约束叠加"而非"模型自觉":schema 约束格式、白名单约束内容、合并约束事实。每一层都是"模型可能错,但结构上错不出去"的实现。
9.8 失败模式与权衡
表 9-3 意图识别的失败模式与权衡
| 失败形态 | 根因 | 设计应对 |
|---|---|---|
| 查询被路由成操作 | 查询词触发动作意图 | 动作/查询分类集合可训练(DISAMBIG-CFG) |
| 主谓宾歧义误判 | 子串匹配("查看下订单"含"下订单") | 白名单整句精确匹配,歧义保留给模型 |
| 识别延迟拖垮会话 | 每轮都走强模型 | 规则预检 + 多轮补充信息零 LLM(见 17.3 节) |
| 跨智能体数据泄露 | 共享上下文 | 深拷贝 + 敏感字段按类型裁剪 |
权衡: 规则优先牺牲了泛化能力(新颖表达须走模型),换取延迟、成本与确定性;可训练的消歧集合在"稳定"与"灵活"之间给了业务方调节权——默认行为保守(规则优先),业务需要时训练放开。
9.9 实现剖析:上下文隔离、指代消解与参数确定性
本节从 runtime/coordinator.py(4,262 行)与 runtime/intent_recognition.py(3,861 行)摘取三段,印证 9.2 节的上下文隔离、9.2 节的指代消解与 9.5 节的参数提取确定性。
9.9.1 上下文隔离:深拷贝 + 按 Agent 类型裁剪
def _isolate_context(self, user_context):
"""为目标 Agent 构造隔离的上下文副本(深拷贝并裁剪敏感字段)"""
import copy
isolated = copy.deepcopy(user_context) if user_context else {} # ★ 无共享可变状态
isolated.setdefault("user", {})
isolated.setdefault("history", [])
isolated.setdefault("slots", {})
isolated.setdefault("data", {})
agent_type = isolated.get("agent_type", "")
if agent_type != "audit": # 非内审 Agent 不应看到内审数据
isolated.pop("audit_data", None)
if agent_type != "finance": # 非财务 Agent 不应看到财务明细
isolated.pop("financial_data", None)
return isolated
这段代码印证 9.2 节:隔离靠"结构上拿不到",而非"自觉不去看"。
copy.deepcopy 切断共享可变状态,杜绝"A 智能体改了上下文、B 智能体看到脏数据";按 agent_type 裁剪则是最小权限可见的实现——audit_data 只对 audit 可见、financial_data 只对 finance 可见。裁剪发生在分发之前,因此下游智能体即使被提示注入诱导,也拿不到无权字段。
9.9.2 指代消解:把"它/那个"还原成上一轮的实体
def _resolve_pronouns(self, user_input, session_context):
"""多轮指代消解:检测代词并用会话上下文中的实体替换"""
if not session_context or not isinstance(session_context, dict):
return user_input
pronouns = ["它", "那个", "这个", "上面的", "刚才的", "之前那个", "该"]
if not any(p in user_input for p in pronouns):
return user_input # 无代词,原样返回
slots = session_context.get("slots", {})
if not slots: # 回退:从历史轮次里取最近实体
for turn in reversed(session_context.get("history", [])):
if isinstance(turn, dict) and turn.get("slots"):
slots = turn["slots"]
break
if not slots:
return user_input
replacements = []
for key in ("product_code", "order_id", "work_order_id", "po_id",
"customer_name", "supplier"): # ★ 实体优先级固定
if slots.get(key):
replacements.append((key, str(slots[key])))
if not replacements:
return user_input
resolved = user_input
for pronoun in pronouns:
if pronoun in resolved:
resolved = resolved.replace(pronoun, replacements[0][1], 1)
break # 只替换第一个匹配的代词
return resolved
这段代码印证 9.2 节与第 10 章的交界:指代消解是"上下文隔离"之后的一次显式授出。隔离保证智能体拿不到多余数据;消解则把上一轮槽位中的实体,按固定优先级(产品 > 订单 > 工单 > 采购单 > 客户 > 供应商)注入本轮输入。优先级的固定是关键——否则"它"指向哪个实体将随会话状态漂移,路由结果不可复现。代词表与优先级都是确定性规则,不含概率成分。
9.9.3 参数确定性:合并优先级 + exclude_regexes 后过滤
@staticmethod
def _merge_params(base, extra):
"""合并参数槽位:base 优先,extra 仅补充 base 缺失的键"""
merged = dict(base or {})
for k, v in (extra or {}).items():
if v is not None and merged.get(k) is None: # None 不覆盖已有值
merged[k] = v
return merged
@staticmethod
def _filter_by_exclude_regexes(params):
"""用 SLOT-DEFS.exclude_regexes 过滤合并后的槽位值"""
defs = get_slot_defs(use_cache=True) # 槽位定义存 DB,可训练热生效
filtered = {}
for k, v in params.items():
if v is None:
continue
excludes = (defs.get(k) or {}).get("exclude_regexes") or []
if not excludes:
filtered[k] = v
continue
if not any(re.search(pat, str(v)) for pat in excludes):
filtered[k] = v # ★ 命中排除规则的槽位被丢弃
return filtered
这段代码印证 9.5 节:参数提取的确定性由"明确的合并优先级 + 可训练的排除规则"共同保证。_merge_params 定义"谁覆盖谁"——调用约定为 LLM 结构化输出优先、正则提取补缺,且 None 不覆盖已有值(避免 LLM 显式 null 抹掉正则结果)。
_filter_by_exclude_regexes 则解决"属性定语被误当实体名"的问题(如"状态为待发布"被当作 product_name):无论值来自 LLM 还是正则,只要命中该槽位的 exclude_regexes 即丢弃。
关键是 exclude_regexes 存于 SLOT-DEFS(数据库、可训练),因此口径修正不需要改代码,与第 14 章的配置治理一脉相承。
9.10 本章小结
- 分派错误的代价远高于分派延迟,决定路由的优先级排序;
- 上下文隔离是信任边界的实现,而非防御性编程;
- 路由三层结构同时满足可训练、可降级、有出口;
- 识别链路按确定性递减排列,规则先说话、模型兜底;
- 参数提取保持确定性,只有语义理解环节允许概率性。