自动路由
Spark Work 自动路由(AutoRouter)完整指南:一个 router = 1 个分流器模型 + N 个按强度(high / balanced / low)配置的执行模型,宿主在轮次级把 provider 与 model 换成选中档位的执行模型。覆盖真实落库形态(provider_profiles 的 provider_type = auto-router)、AutoRouterConfig 全字段与默认值、分流决策的强制 JSON schema、强度粘性与最近两轮消息摘要、五类降级阶段(timeout / http / schema / rule / no_executor)、规则兜底分类器的判定顺序、确定性 4xx 不重试与 429 保留一次重试、引擎不匹配回退,以及界面上的路由决策展示与排查表。
自动路由(AutoRouter)解决的是一个很具体的问题:你不该为了「翻译一句话」去启动最强的模型,也不该为了「重构整个模块」省那点 token。 它的做法是给会话配一个分流器模型,让它每一轮先看一眼这轮任务有多难,然后把这轮直接交给对应强度档位的执行模型去跑——不是让执行模型自己去调用工具,而是宿主在轮次开始前就把 provider 和 model 换掉了。
这一页讲清楚四件事:AutoRouter 在数据里是什么、一个 router 由哪些配置组成、一轮任务是怎么被分流的、分流失败时会怎么降级。字段名、默认值、错误后缀都来自应用仓库的真实代码。
先记住这四句话
- 一个 router = 1 个分流器 + N 个执行模型,执行模型按
high / balanced / low三档配置。- 分流是轮次级替换:宿主在跑这一轮之前把 provider/model 换成选中的执行模型,执行模型本身不需要知道 AutoRouter 的存在。
- 分流失败从不抛错中断,一律降级(超时 → 规则兜底 → 兜底强度),本轮照常执行。
- 确定性错误(鉴权、权限、模型不存在、请求非法)不重试,避免白付一次调用。
1. 它在数据里是什么
AutoRouter 不是一个虚拟的伪 Provider。每个 router 就是 provider_profiles 表里一行真实记录,provider_type = 'auto-router',config_json 存的就是下面的 AutoRouterConfig。
早期实现用过 claude-auto-router / codex-auto-router 两个魔法 provider id,已于 2026-09 重构下线。代码里保留这两个常量只为识别存量会话引用并回退默认渠道,不会再用于新建任何行。
| 项 | 值 |
|---|---|
| 落库位置 | provider_profiles,provider_type = 'auto-router' |
| 配置字段 | config_json = AutoRouterConfig(kind: 'auto-router',version: 1) |
| 绑定引擎 | adapter: 'claude' | 'codex'——claude 走 anthropic 系渠道,codex 走 openai 系渠道 |
| 强度枚举 | high / balanced / low(ROUTER_INTENSITIES) |
| 界面入口 | 渠道管理 → 工具栏「自动路由」(创建与管理:分流器 + 强度分级执行模型) |
2. 一个 router 由什么组成
interface AutoRouterConfig {
kind: 'auto-router'
version: 1
adapter: 'claude' | 'codex'
dispatcher: { providerProfileId: string; modelId: string; timeoutMs: number }
executors: Array<{
id: string
providerProfileId: string
modelId: string
intensity: 'high' | 'balanced' | 'low'
enabled: boolean
reasoningEffort?: 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max' | null
}>
fallbackIntensity: 'high' | 'balanced' | 'low'
allowDecomposition: boolean
maxConcurrentSubtasks: number
subagentIntensityMapping: boolean
}
| 字段 | 默认值 | 说明 |
|---|---|---|
dispatcher.timeoutMs | 30000 | 分流决策超时上限,最大 120000。它是故障兜底而非正常耗时:分流器快返回时不会产生等待,只有真超时才等满然后走规则降级 |
executors | [] | 每条含渠道 + 模型 + 强度档位;同一强度可以配多条,取第一个有效条目。条目的 enabled 关掉即不参与候选 |
executors[].reasoningEffort | null(跟随会话) | 这是模型的思考深度,与条目的任务强度档位是两件不同的事。显式配置后,经该执行器执行的轮次以它为准,覆盖会话与 Agent 级配置 |
fallbackIntensity | 'balanced' | 分流失败时的兜底强度 |
allowDecomposition | true | 是否允许分流器把这轮拆成子任务 |
maxConcurrentSubtasks | 3(范围 1–10) | 子任务并发上限,仍受会话每轮派发预算约束 |
subagentIntensityMapping | true | 把强度档位映射注入引擎子代理环境变量,仅 claude 引擎生效,让 SDK 原生 Task 子代理也按强度分级 |
分流器怎么选:它只做轻量分类,所以优先选快且便宜的非思考模型。如果选了带思考时间的模型,请按实际思考耗时放宽 timeoutMs(例如 60s),否则会频繁走到规则兜底。
执行模型怎么选:三档各自独立选渠道与模型,例如 high 给最强模型、balanced 给主力模型、low 给最快最便宜的模型。每条还能单独指定推理强度,让「档位」和「思考深度」正交组合。
3. 一轮任务是怎么被分流的
用户发消息
→ 加载 router 配置,筛出 enabled 的执行模型
→ 调用分流器 LLM(带上当轮消息 + 会话事件数 + 估算 token + 最近两轮用户消息摘要)
→ zod 严格解析决策 JSON
→ 强度粘性判定(上轮同强度且执行器未变 → 沿用,避免来回横跳)
→ 选中该强度的执行器,宿主替换本轮 provider / model
→ 落一条 auto_router_decision 事件,轮次开始执行
分流器的输出有强制 schema,不是自由文本:
{
intensity: 'high' | 'balanced' | 'low',
decompose: boolean, // 默认 false
subtasks: Array<{ summary: string; intensity: ...; parallelizable: boolean }>, // 最多 10 条
reason: string, // 最多 300 字符
}
三个值得注意的实现细节:
- 最近两轮用户消息摘要会被一并送进分流器,专门用来解决「继续」这类消息的误判——单看「继续」两个字只能判成低强度。
- 强度粘性:如果这轮的决策强度与上轮相同且执行器没换,会命中
keptPrevIntensity,避免相邻轮次在档位间反复横跳、导致模型频繁切换。 - 轮次取消可中断分流:分流期间如果这一轮被取消,会中止分流的 HTTP 请求并标记
cancelled,不会留下挂着的请求。
你在界面上能看到什么
分流结果会落成 auto_router_decision 事件并持久化,所以重进会话或换设备都还在,展示有两种形态:
| 形态 | 出现条件 | 内容 |
|---|---|---|
| 轮次边界提示条 | 仅当本轮实际模型与上一轮不同时渲染 | ⚙ 已路由 → ●高强度 <模型名> · <分流理由>;降级或引擎回退时图标换成 ⚠,文案变成「分流降级」/「引擎不匹配回退」 |
| 轮次 meta 行小标识 | 每轮常驻 | 强度色点 + 模型名 |
| 悬浮提示 | 悬停提示条 | 路由器名、本轮强度、上轮强度、是否兜底、兜底落在哪个阶段 |
输入框上方的模型悬浮卡也会把 router 展开成「分流器 + 各档执行模型」的行列表,并显示引擎标签与错误信息——不用打开设置页就能确认这个 router 现在派给谁。
4. 分流失败时发生什么
核心约定:分流环节的任何失败都不抛异常、不中断本轮。 所有异常路径都收敛成 fallbackStage 五个值之一,再由兜底强度选执行器把这一轮跑完。
fallbackStage | 触发原因 | 行为 |
|---|---|---|
timeout | 分流调用超时或请求被 abort | 走规则兜底分类器 |
http | 分流调用返回错误 | 走规则兜底分类器 |
schema | 返回内容不是合法 JSON / 不满足 schema | 走规则兜底分类器 |
rule | 规则兜底路径本身(含引擎不匹配回退) | 用规则或 fallbackIntensity 选执行器 |
no_executor | 筛选后一个可用执行器都没有 | ok: false,本轮无法执行 |
规则兜底是怎么判的
规则分类器只在分流 LLM 失败时使用,逻辑是「token 阈值 + 收敛正则」:
- 估算 token >
30_000→ 直接high; - 命中低强度正则(
好的 / ok / 可以 / 继续 / go on / continue这类整句、翻译、错别字、改个名、格式化、总结一句)→low; - 命中高强度正则(
重构、架构、设计…方案/系统/实现、多文件/多模块/多步、全链路、排查…问题/故障/根因、性能优化/调优、implement / refactor / architect / redesign)→high; - 都不命中 →
balanced。
顺序很重要:token 阈值在正则之前判定,所以一条很长的「总结一句」消息仍然会被判成 high。规则兜底只是降级路径,不是主逻辑。
重试策略
分流调用失败后最多重试一次,且区分错误类型:
- 确定性 4xx(鉴权失败、权限不足、模型不存在、请求非法)→ 立即收口,不再重试。重试必然同样失败,只会白付一次调用。
- 429 → 保留那次重试。代码注释说明了取舍:套餐权限类 429 重试确实是白费,但通用上无法与瞬时限流区分,所以按「可能瞬时限流」处理。
引擎不匹配(adapterMismatch)
如果 router.adapter 与会话当前引擎不一致(例如 router 声明 claude,会话在 codex 引擎跑),不会报错:宿主按兜底强度找一个匹配会话引擎的渠道,找不到就回退默认渠道,并把 adapterMismatch: true 标记出来,界面上显示为「引擎不匹配回退」而不是「已路由」。这样存量会话换引擎后不会突然跑不动。
一个执行器都没有
如果筛完可用执行器为空,本轮会直接报错并给出可操作文案:
自动路由「<router 名>」没有可用执行模型:<原因>。请在渠道管理 → 自动路由中检查配置
5. 在界面里怎么配
- 打开渠道管理,点工具栏上的「自动路由」入口,打开管理弹窗。
- 左侧是 router 列表,右侧是编辑表单。首次进入恢复上次选中的 router。
- 配分流器模型:选渠道 + 模型,按需调整「决策超时 (ms)」。旁边有**「测试分流器」按钮,会对当前草稿**(无需先保存)发一个最小请求并显示延迟。
- 配执行模型(按强度):每行是一个档位条目,选渠道、模型、推理强度(留空 = 跟随会话),可「添加执行模型」多配几条。
- 配兜底强度:分流失败时用哪一档。
- 按需打开高级项:子任务拆分与并发上限、子代理档位映射。
- 保存后,在会话的模型选择器里选中这个 router 即可生效。
渠道管理页的卡片网格里,AutoRouter 会以独立类别出现。它的编辑入口是自动路由弹层,不是普通渠道编辑面板——后者会把
provider_type改掉,留下脏数据。
速查表
| 项目 | 内容 |
|---|---|
| 落库 | provider_profiles(provider_type = 'auto-router')+ config_json |
| 组件构成 | 1 个分流器 + N 个执行模型(按 high/balanced/low) |
| 强度枚举 | high、balanced、low |
| 分流器超时默认 | 30000 ms(上限 120000) |
| 兜底强度默认 | balanced |
| 子任务并发默认 | 3(范围 1–10) |
| 决策 schema 强制项 | intensity;可选 decompose、subtasks(≤10)、reason(≤300 字符) |
| 降级阶段 | timeout / http / schema / rule / no_executor |
| 重试次数 | 最多一次;确定性 4xx 立即收口,429 保留一次 |
| 规则兜底优先级 | token > 30000 → high;低强度正则 → low;高强度正则 → high;否则 balanced |
| 决策事件 | auto_router_decision(持久化,重进会话仍可见) |
| 无执行器时的行为 | 抛错并提示到渠道管理 → 自动路由检查配置 |
排查表
| 现象 | 原因 | 怎么办 |
|---|---|---|
| 每轮都显示「分流降级」 | 分流器调用超时或返回不合法 JSON | 放宽 timeoutMs、换更快的分流器模型 |
| 提示条显示「引擎不匹配回退」 | router.adapter 与会话引擎不一致 | 按会话引擎重建 router,或确认这是预期回退 |
| 本轮直接报「没有可用执行模型」 | 筛完可用执行器为空 | 补全条目渠道/模型,或启用被关掉的条目 |
| 简单任务被判成高强度 | 消息很长(token > 30000)优先于低强度正则 | 拆短消息,或接受这一档 |
| 相邻轮次模型来回横跳 | 分流器对同类任务给出不同强度 | 强度粘性只在同强度且执行器未变时生效;可减少档位差异 |
| 子代理没按强度分级 | subagentIntensityMapping 关闭,或引擎不是 claude | 打开该开关,并确认会话在 claude 引擎上 |
| 改了配置但当前轮没变化 | 分流是轮次级替换 | 等下一轮;必要时重发消息 |
| 分流器测不通 | 渠道凭据/端点问题 | 在「测试分流器」里看错误原文,先回渠道设置修凭据 |
| 重进会话看不到路由记录了 | 该轮不是经 AutoRouter 跑的 | 只有经 router 的轮次才有 auto_router_decision 事件 |
| 旧会话切换到新引擎后跑不动 | 存量会话仍引用已下线的魔法 provider id | 代码会自动回退默认渠道;建议把会话模型改到新建的 router |
常见问题
分流器会不会拖慢每一轮?
会有一点点额外延迟,但不占用执行模型的额度。想压缩这点延迟,就把 timeoutMs 收紧、并把分流器换成快模型——注意别换成带思考的模型,否则会频繁超时走规则兜底。
为什么这一轮用的模型和我选的不一样?
因为会话选中的是 router 而不是具体模型,本轮 provider/model 被宿主替换成了分流结果对应的执行模型。轮次边界提示条会写明「已路由 → 具体模型」,悬浮提示里有分流器的判断理由。
为什么界面上写「分流降级」而不是「已路由」?
说明分流环节失败了,本轮走的是规则兜底或兜底强度。悬浮提示会给出具体阶段(timeout / http / schema / rule)。如果这个前缀频繁出现,通常是分流器渠道不稳定或 timeoutMs 太紧。
执行模型的"推理强度"和"任务强度档位"是一回事吗?
不是。档位(intensity)决定派给哪个模型,推理强度(reasoningEffort)决定该模型的思考深度。两者正交:你可以让 low 档的执行模型也开高推理强度。
为什么改了 router 配置,正在跑的会话没变?
分流是轮次级的。已开始的轮次不会被改写,下一轮才会用新配置。
报「没有可用执行模型」怎么办?
检查三件事:执行模型条目是否都补全了渠道与模型、条目的 enabled 是否被关掉、adapter 与会话引擎是否匹配。任意一档都没有可用条目时本轮会直接失败。
分流器和执行模型可以用同一个模型吗?
可以,但基本没有收益。分流的价值在于「用便宜模型做分类、把贵模型留给难任务」;如果分流器和执行模型是同一个,你等于每轮多付一次调用,却没有拿到任何成本优化。
为什么同一档配了多条执行模型,实际只用其中一条?
同一强度允许多条,但取的是第一个有效条目(enabled 为真的第一条)。想让另一条生效,就把排在前面那条关掉,或者调整条目顺序。