---
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 验证
