fischer-agentkit/docs/plans/2026-06-15-003-feat-router-...

16 KiB
Raw Permalink Blame History

title status superseded_by superseded_reason closed origin
feat: 路由智能化优化 — 复杂度校准、意图消歧、质量门控增强 superseded 2026-06-16-005-refactor-routing-architecture-plan SimpleRouter 已替代 CostAwareRouter 的 4 层路由架构。IntentRouter 多候选评分U2和 QualityGate 技能匹配验证U3属于被删除的旧路由层组件不再需要实现。U1 HeuristicClassifier 测试仅对向后兼容有价值。 2026-06-16 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_CN17个词_LOW_COMPLEXITY_HINTS_EN14个词命中时基础分数为 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.72个高复杂度词命中
    • "执行部署脚本并重启服务" → 复杂度 > 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

    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_matchpassed 也变为 False,导致整体 passed=False

  5. 保持向后兼容:skill_context 为 None 或缺少 intent_keywords 时,行为与原来完全一致(四维度检查)。

Patterns to follow: 现有四维度检查模式(gate.py L50-114QualityCheck 数据类

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%
    • 技能路由 F166.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 验证