跳到主要内容

路由层:一句话怎么变成“选哪个 agent”

本章讲:AgenticSeek 怎么不靠 LLM、而靠两个小型分类器把你的请求派给最合适的 agent。

3.1 它要解决的小问题

用户说一句话,系统要决定两件事:

  1. 该哪个 agent 接?(talk / code / files / web / mcp)
  2. 这事多复杂?(LOW 交单个 agent;HIGH 交给 planner 拆多步)

难点:本地跑的小模型(几 B 参数)让它“自己决定该谁接”不可靠——会乱选、输出格式不稳。所以 AgenticSeek 把路由从 LLM 手里拿走,交给专门的文本分类器。

3.2 思路/直觉

不用一个分类器,而是 两个模型投票,取长补短:

  • BART zero-shotfacebook/bart-large-mnli):不需训练,给它一组候选标签就能打分,胜在泛化。
  • 自适应分类器AdaptiveClassifier,加载本地 llm_router/ 权重):用各一百余条手写 few-shot 例子现学,胜在贴合本项目的真实请求措辞。

两者各出一个标签 + 置信度,归一化后比高者胜

复杂度则是 另一个独立的自适应分类器(标签 LOW/HIGH),同样靠 few-shot 例子现学。

3.3 图示:select_agent 的完整流程

怎么读:从上到下顺序执行;HIGH 复杂度是个“提前退出”的分叉。

query 进来
|
v
只有 1 个 agent?--yes--> 直接返回它
| no
v
检语种 detect_language() langid
v
取第一句 find_first_sentence()
v
翻译成英文 translate() Helsinki-NLP MarianMT
v
估复杂度 estimate_complexity()
|--HIGH--> find_planner_agent() -> 返回 planner,结束
| LOW
v
router_vote(text, labels) BART vs 自适应分类器投票
v
按 best_agent == agent.role 找到 agent -> 返回

主函数在 router.py:441-471AgentRouter.select_agent)。注意两个小细节:

  • 只拿第一句去分类(find_first_sentencerouter.py:392-399)——避免长句干扰分类器。
  • 先翻译成英文再分类router.py:452-454)——因为 few-shot 例子都是英文,中/法文请求先翻才能准。

3.4 原理演示:两模型投票

这段演示 router_vote 的“归一化置信度、高者胜”逻辑:

# 示意,非源码
# bart 和 llm_router 各给一个 (标签, 置信度)
bart, conf_bart = "web", 0.42
llm, conf_llm = "code", 0.55

# 把两个置信度归一化到同一尺度(除以总和)
score_bart = conf_bart / (conf_bart + conf_llm) # 0.43
score_llm = conf_llm / (conf_bart + conf_llm) # 0.57

final = bart if score_bart > score_llm else llm # -> "code"

重点看:两个模型原始置信度不在同一量纲,归一化后才能公平比较。

3.5 真实实现

投票router.py:370-390router_vote):

  • 超短文本(<= 8 字符)直接当“talk”(router.py:379-380)——“hi”这种不必跑模型。
  • BART:self.pipelines['bart'](text, labels)router.py:381)。
  • 自适应分类器:llm_router(text)router.py:359-368),返回前过滤掉 HIGH/LOW 标签(这个分类器同时学了任务和复杂度两套标签)。

复杂度估算router.py:401-426estimate_complexity):

预测 -> 按置信度排序 -> 取 top1
若置信度 < 0.5 -> 返回 HIGH(保守:拿不准就交给 planner)
若 == HIGH -> HIGH
若 == LOW -> LOW

这里有个设计取舍:低置信时倒向 HIGHrouter.py:418-420)——宁可多走一道 planner 拆解,也不要让单个 agent 接下自己搭不住的复杂任务。

few-shot 怎么学router.py:203-357):learn_few_shots_tasks(任务标签,约 144 条)/ learn_few_shots_complexity(复杂度标签,约 122 条)里是各一百余条手写 (文本, 标签)random.shuffle 后一次性 add_examples 灌进自适应分类器。这是路由准不准的根。

3.6 关键细节/坑

  • 标签来自 agent.role。 select_agentlabels = [agent.role for agent in self.agents]router.py:455),再用返回的 role 字符串反查 agent。所以每个 agent 的 role(如 "code""web""files""talk""mcp")必须跟 few-shot 标签一致,否则永远选不中。
  • planner 的 role 是 "planification"planner_agent.py:31),不参与投票,只能通过复杂度 HIGH 路径拿到。
  • 路由模型要预下载。 llm_router/ 里的 safetensors 要跑 dl_safetensors.sh,否则 load_llm_router 报错(router.py:54-58)。

3.7 代码地图

主题文件符号
路由主入口sources/router.pyAgentRouter.select_agent
两模型投票sources/router.pyrouter_vote
自适应分类器推理sources/router.pyllm_router
复杂度估算sources/router.pyestimate_complexity
few-shot 学习sources/router.pylearn_few_shots_taskslearn_few_shots_complexity
模型加载sources/router.pyload_pipelinesload_llm_router
语言检测/翻译sources/language.pyLanguageUtility.detect_languagetranslate