--- title: "feat: 路由智能化优化 — 复杂度校准、意图消歧、质量门控增强" status: superseded superseded_by: "2026-06-16-005-refactor-routing-architecture-plan" superseded_reason: "SimpleRouter 已替代 CostAwareRouter 的 4 层路由架构。IntentRouter 多候选评分(U2)和 QualityGate 技能匹配验证(U3)属于被删除的旧路由层组件,不再需要实现。U1 HeuristicClassifier 测试仅对向后兼容有价值。" closed: 2026-06-16 origin: test-results/e2e/capability_report.txt (真实LLM回测分析报告) --- ## Summary 基于真实 LLM 回测分析报告暴露的三个核心根因,优化 CostAwareRouter 的路由智能化水平:修复 HeuristicClassifier 复杂度评分偏差(执行模式准确率从 9.09% 提升至 >30%),解决 IntentRouter 首次匹配导致的技能混淆(技能路由 F1 从 66.67% 提升至 >80%),增强 QualityGate 的技能匹配验证拦截错误路由。 **当前进度**: U1 代码已实现,待补单元测试;U2/U3 待实现;U4 待验证。 --- ## Problem Frame 真实 LLM 回测(74个观测)揭示三个核心问题: 1. **执行模式准确率 9.09%** — HeuristicClassifier 倾向高估复杂度,将简单问答(如"你好"、"你是谁")判为需要 REACT 而非 DIRECT_CHAT。40个执行模式判断错误中仅1次低估复杂度。 2. **keyword_match 召回率 0%** — 62个关键词匹配用例全部未路由到预期技能,真实 SkillRegistry 虽然加载了15个技能,但路由链路未能正确匹配。 3. **意图歧义** — plan_exec_agent 与 goal_driven_agent 的关键词重叠("规划"、"报告"子串),IntentRouter 首次匹配策略导致混淆。 --- ## Requirements - R1: HeuristicClassifier 复杂度评分校准 — 简单问答应得低分(<0.3),复杂任务应得高分(>0.7) - R2: IntentRouter 多候选评分排序 — 匹配多个技能时按得分排序选择最佳,而非首次匹配 - R3: QualityGate 技能匹配验证 — 拦截路由结果与技能能力不一致的输出 - R4: 回测验证 — 改进后执行模式准确率 >30%,技能路由 F1 >80% --- ## Key Technical Decisions ### KTD1: HeuristicClassifier 评分重构 — 增加低复杂度信号 **决策**: 在现有高/中复杂度关键词之外,增加低复杂度关键词列表和否定信号机制。当输入包含低复杂度信号(问候、闲聊、简单定义)时,直接降低基础分数;当高复杂度词出现在否定上下文("不要X"、"无需X")时,不增加分数。 **理由**: 当前分类器只有正向累加逻辑(命中高复杂度词→加分),没有负向扣减逻辑。这导致任何包含"分析"、"搜索"等常见动词的输入都被判为高复杂度,即使实际是简单问答。 **替代方案**: 用 LLM 替代规则分类器 — 延迟高(~500ms)、成本高(~100 tokens),且当前 merged_llm_classify 已在 0.3-0.7 区间使用 LLM,规则层应保持零成本。 **实现状态**: 代码已完成。`classify()` 方法已重写,包含低复杂度信号优先检测、否定上下文排除、阈值调整(0.15→0.10, 0.45→0.35)、短疑问句扣减。 ### KTD2: IntentRouter 多候选评分排序 **决策**: 修改 `_match_keywords()` 从"首次匹配返回"改为"收集所有匹配候选,按匹配关键词数量×关键词长度排序,返回最佳匹配"。 **理由**: 首次匹配依赖 skills 列表遍历顺序,不可控且不公平。多候选评分让匹配更多、更精确关键词的技能胜出。例如输入"规划一个调研报告"同时匹配 plan_exec_agent("规划"、"报告")和 goal_driven_agent("规划"、"调研"),但 goal_driven_agent 还匹配"生成报告"的子串"报告",匹配数相同则按关键词长度排序,更长的关键词("调研报告" > "报告")权重更高。 **替代方案**: 在技能配置中添加互斥关键词 — 需要逐对配置,维护成本高,且无法覆盖所有重叠场景。 **实现状态**: 待实现。当前 `_match_keywords()` 仍为首次匹配逻辑(`intent.py` L89-98)。 ### KTD3: QualityGate 技能匹配验证 — 轻量级路由一致性检查 **决策**: 在 QualityGate.validate() 中增加可选的 `skill_context` 参数,当提供时检查输出内容是否与路由到的技能的能力范围一致。使用规则检查(关键词覆盖度)而非 LLM 语义检查,保持零额外成本。 **理由**: 当前 QualityGate 只检查输出格式(必填字段、字数、Schema),不检查输出内容是否与路由技能匹配。3个用例虽然 HTTP 成功但路由到了错误技能,质量门控未能拦截。 **实现状态**: 待实现。当前 `validate()` 仅有四维度检查(`gate.py` L37-114)。 --- ## Scope Boundaries ### In Scope - HeuristicClassifier 评分逻辑优化(代码已完成,待补测试) - IntentRouter._match_keywords() 多候选评分排序 - QualityGate 增加技能匹配验证维度 - 更新回测基准数据集以反映新的评分逻辑 - 改进后重跑回测验证 ### Out of Scope - LLM 分类器优化(merged_llm_classify 和 _classify_with_llm 已有实现,不在本次优化范围) - SemanticRouter 优化(需要嵌入模型,属于独立优化方向) - ExpertTeamRouter 在服务器启动时的注入(已实现但未接入 create_app,属于部署配置问题) - 新增技能配置文件 ### Deferred to Follow-Up Work - 训练专用意图分类模型替代规则匹配(长期方向) - 构建复杂度校准数据集持续优化阈值 - 实现自动质量回归检测 CI 流水线 --- ## Implementation Units ### U1. HeuristicClassifier 复杂度评分校准 **Goal**: 修复复杂度评分偏差,使简单问答得低分、复杂任务得高分,提升执行模式准确率 **Requirements**: R1, R4 **Dependencies**: None **Files:** - `src/agentkit/chat/skill_routing.py` — HeuristicClassifier 类(**代码已完成**) - `tests/unit/chat/test_skill_routing.py` — 新增复杂度校准测试(**待编写**) **Approach:** 代码已实现以下改动: 1. 增加低复杂度关键词列表 `_LOW_COMPLEXITY_HINTS_CN`(17个词)和 `_LOW_COMPLEXITY_HINTS_EN`(14个词),命中时基础分数为 0.05,且不再累加高复杂度词分数。 2. 增加否定上下文检测 `_NEGATION_PATTERNS`,匹配"不要/无需/不用/don't/no need/without"后跟的词,该词不计入高复杂度匹配。 3. 调整基础分数阈值:无关键词命中时基础分 0.10(原 0.15),中等复杂度命中基础分 0.35(原 0.45)。 4. 增加短疑问句检测 `_SHORT_QUESTION_RE`:以"?"或"?"结尾且长度 <30 字符时,额外 -0.10。 **剩余工作**: 编写单元测试验证分类器行为。 **Patterns to follow:** 现有 `test_skill_routing.py` 中的测试类结构(`TestExpertTeamRouterCanHandle` 等) **Test scenarios:** - **低复杂度信号优先检测** - "你好" → 复杂度 < 0.3(命中 `_LOW_COMPLEXITY_HINTS_CN`) - "Hello" → 复杂度 < 0.3(命中 `_LOW_COMPLEXITY_HINTS_EN`) - "嗨,早上好" → 复杂度 < 0.3(多个低复杂度词命中) - "你好,请帮我分析一下这个数据" → 复杂度 < 0.15(低复杂度信号优先,不累加高复杂度词) - **身份查询** - "你是谁" → 复杂度 < 0.3 - "你叫什么" → 复杂度 < 0.3 - **否定上下文排除** - "不要搜索" → "搜索"不计入高复杂度匹配,复杂度 < 0.3 - "无需分析,直接告诉我答案" → "分析"被否定,复杂度 < 0.3 - "分析市场趋势,但不要搜索" → "搜索"被否定但"分析"未被否定,复杂度 > 0.5 - **阈值调整验证** - 无关键词的短消息("好的")→ 复杂度 ≤ 0.10 - 含中等复杂度词("如何使用Python?")→ 基础分 0.35 而非 0.45 - **短疑问句扣减** - "怎么用?" → 复杂度 < 0.3(短疑问句 -0.10) - "如何设计一个高可用的微服务架构?" → 复杂度 > 0.5(长疑问句不扣减) - **复杂任务高分** - "分析市场趋势并生成报告" → 复杂度 > 0.7(2个高复杂度词命中) - "执行部署脚本并重启服务" → 复杂度 > 0.7 - **边界条件** - 空字符串 → 复杂度 0.0 - 纯空格 → 复杂度 0.0 - 超长低复杂度消息(>200字符的问候)→ 复杂度 ≤ 0.10 **Verification:** `pytest tests/unit/chat/test_skill_routing.py -v`,所有 HeuristicClassifier 测试通过 --- ### U2. IntentRouter 多候选评分排序 **Goal**: 解决首次匹配导致的技能混淆,使匹配更精确的技能胜出 **Requirements**: R2, R4 **Dependencies**: None **Files:** - `src/agentkit/router/intent.py` — IntentRouter._match_keywords() - `tests/unit/router/test_intent.py` — 新建多候选排序测试 **Approach:** 1. 重写 `_match_keywords()` 方法(当前为 `intent.py` L75-99): 当前逻辑(首次匹配): ``` for skill in skills: for keyword in keywords: if keyword in combined_text: return RoutingResult(matched_skill=skill.name, ...) return None ``` 改为多候选评分: ``` candidates = [] for skill in skills: matched_kws = [kw for kw in skill.config.intent.keywords if kw.lower() in combined_text] if matched_kws: score = sum(len(kw) for kw in matched_kws) # 更长关键词权重更高 candidates.append((skill, matched_kws, score)) if not candidates: return None candidates.sort(key=lambda c: (-c[2], c[0].name)) # 得分降序,同名字母序 best_skill, best_kws, best_score = candidates[0] confidence = min(1.0, 0.5 + 0.1 * len(best_kws)) return RoutingResult(matched_skill=best_skill.name, method="keyword", confidence=confidence) ``` 2. 保持 `RoutingResult` 数据类接口不变,`method` 仍为 `"keyword"`。 3. 向后兼容:单候选时行为与原来一致(只有一个 skill 匹配时,排序无影响)。 4. 需要创建 `tests/unit/router/` 目录和 `__init__.py`。 **Patterns to follow:** 现有 `RoutingResult` 数据类结构;`_extract_string_values()` 的输入处理方式 **Test scenarios:** - **单候选匹配** — 输入只匹配一个 skill 的关键词,行为与原来一致,confidence=1.0 - **多候选匹配 — 得分不同** — 输入同时匹配 skill_a(关键词"规划"2字)和 skill_b(关键词"调研报告"4字),skill_b 得分更高应胜出 - **多候选匹配 — 得分相同** — 两个 skill 得分相同时,按名称字母序稳定排序 - **无匹配** — 无任何关键词命中,返回 None - **空关键词列表** — skill 的 intent.keywords 为空列表,不参与匹配 - **大小写不敏感** — 英文关键词 "Search" 应匹配 "search" - **子串匹配行为** — 中文关键词"报告"应匹配包含"报告"的输入(保持现有子串匹配语义) - **confidence 计算** — 匹配1个关键词 confidence=0.6,匹配3个 confidence=0.8,上限 1.0 **Verification:** `pytest tests/unit/router/test_intent.py -v`,多候选排序测试通过 --- ### U3. QualityGate 技能匹配验证 **Goal**: 增加路由一致性检查,拦截技能匹配错误的低质量输出 **Requirements**: R3, R4 **Dependencies:** None **Files:** - `src/agentkit/quality/gate.py` — QualityGate.validate() - `tests/unit/quality/test_gate.py` — 新建技能匹配验证测试 **Approach:** 1. 在 `QualityGate.validate()` 签名中增加可选参数 `skill_context: dict | None = None`: ```python async def validate( self, output: dict[str, Any], skill: Skill, skill_context: dict | None = None, # 新增 ) -> QualityResult: ``` 2. `skill_context` 结构:`{"skill_name": str, "intent_keywords": list[str]}` 3. 当 `skill_context` 提供且 `intent_keywords` 非空时,增加第五维度检查"技能匹配验证": - 将 output 中所有字符串值拼接 - 检查拼接文本是否包含至少一个 `intent_keywords` 中的关键词(子串匹配) - 如果 0 个关键词匹配 → `QualityCheck(name="skill_match", passed=True, message="Warning: output may not match routed skill")` — 警告但不拦截 - 如果 ≥ 1 个关键词匹配 → `QualityCheck(name="skill_match", passed=True)` — 静默通过 4. 警告升级为失败的组合逻辑:当 `skill_match` 警告存在且其他任何维度检查失败时,`skill_match` 的 `passed` 也变为 `False`,导致整体 `passed=False`。 5. 保持向后兼容:`skill_context` 为 None 或缺少 `intent_keywords` 时,行为与原来完全一致(四维度检查)。 **Patterns to follow:** 现有四维度检查模式(`gate.py` L50-114);`QualityCheck` 数据类 **Test scenarios:** - **无 skill_context** — 行为与原来一致,仅四维度检查 - **skill_context=None** — 等同于无 skill_context - **skill_context 缺少 intent_keywords** — 等同于无 skill_context - **有 skill_context 且输出包含关键词** — 通过,无警告消息 - **有 skill_context 且输出不包含任何关键词** — 通过但有警告消息 - **输出无关 + 其他维度失败** — skill_match passed=False,整体 passed=False - **输出无关 + 其他维度全部通过** — skill_match passed=True(仅警告),整体 passed=True - **空 intent_keywords 列表** — 跳过技能匹配检查 **Verification:** `pytest tests/unit/quality/test_gate.py -v`,技能匹配验证测试通过 --- ### U4. 回测验证与基准更新 **Goal**: 验证改进效果,更新基准数据集 **Requirements**: R4 **Dependencies:** U1, U2, U3 **Files:** - `tests/e2e/test_capability_router_direct.py` — 使用真实 LLM 回测 - `tests/e2e/benchmark_dataset.py` — 可能需要更新预期值 - `test-results/e2e/capability_report.txt` — 对比改进前后报告 **Approach:** 1. 运行完整回测:`python3 -m pytest tests/e2e/test_capability_router_direct.py -v` 2. 对比改进前后指标: - 执行模式准确率:9.09% → 目标 >30% - 技能路由 F1:66.67% → 目标 >80% - 任务成功率:100% → 保持 3. 如果基准数据集中的预期值因评分逻辑变化需要调整,更新 `benchmark_dataset.py` 4. 保存改进后报告为基线:`cp test-results/e2e/capability_report.json test-results/e2e/baseline_capability_report.json` **Test scenarios:** - 回测全部通过 - 执行模式准确率 >30% - 技能路由 F1 >80% - 无回归(任务成功率不下降) **Verification:** 运行回测并检查报告指标 --- ## Risks & Dependencies | 风险 | 影响 | 缓解 | |------|------|------| | 复杂度评分调整可能过度修正,导致复杂任务被判为简单 | 高复杂度任务路由到 DIRECT_CHAT,无法使用工具 | 保留 merged_llm_classify 兜底机制,0.3-0.7 区间仍由 LLM 二次确认 | | 多候选排序可能改变现有路由行为的兼容性 | 已有用户依赖的路由结果可能变化 | 排序逻辑仅在多候选时生效,单候选行为不变 | | QualityGate 技能匹配验证的"相关词"判断可能误报 | 正常输出被标记为警告 | 使用 warning 级别而非 error,不单独拦截 | | keyword_match 召回率 0% 的根因可能不仅是 IntentRouter | 即使修复多候选排序,仍可能因技能配置关键词不匹配而召回率低 | U4 回测后若仍低,需进一步分析技能配置与基准用例的对齐度 | --- ## Open Questions - 复杂度评分的具体阈值已在代码中设定初始值(0.05/0.10/0.35/0.65/0.80),需通过 U4 回测校准 - 否定上下文检测的正则模式覆盖度需在回测中验证,可能需要迭代补充 - keyword_match 召回率 0% 是否完全由 IntentRouter 首次匹配导致,还是技能配置关键词本身与基准用例不对齐 — 需 U2 实现后通过 U4 验证