目的 :为 SkillLite Assistant 设计一套类似「Auto」体验的本地优先 LLM 路由能力。
范围 :先讨论 聊天/Agent/UI 路由层 ,不讨论云端计费池、统一托管 API、训练型策略系统。
当前状态:本地场景路由 + 非流式场景的失败自动切换(MVP-A)已落地;启发式复杂度路由、健康度评分、流式聊天的中途切换尚未实现。
1. 设计目标
这套能力不是为了"看起来聪明",而是为了解决三个实际问题:
- 主链路不要轻易挂掉
- 主对话、Agent、多工具执行时,模型或 provider 出错不能直接让用户卡死。
- 辅助链路尽量省钱
followup、后台状态、轻量摘要/命名等场景不必默认使用最贵模型。
- 决策可解释、可回退
- 用户与开发者都应能回答:这次为什么选这个模型?失败后切到了哪里?
2. 价值取向
第一原则:稳定优先于便宜
若只能二选一,优先级应为:
- 尽量别挂
- 再去省钱
原因:
- 用户对"完全失败"的负面感受,通常强于"这次多花了一点 token"。
- 对复杂 Agent 任务使用过弱模型,看似省钱,常会引发更多重试与上下文膨胀,最终反而更贵。
第二原则:主链路保守,边缘链路优化
- 核心链路 :主对话、工具调用、代码修改、长上下文任务
- 优先用更稳定的主模型,并提供 fallback。
- 边缘链路 :猜你想问、轻量状态同步、标题/摘要、简单分类
- 优先使用更便宜/更快的小模型。
第三原则:先规则,再启发式,再智能分类
演进顺序建议:
- 固定规则
- 失败自动切换
- 输入复杂度启发式
- 小模型分类 / 健康度打分
不要一开始就做成黑盒决策引擎。
2.1 我的判断过程
这份设计不是从"如何做得最聪明"出发,而是从"如何先解决最痛的问题"出发。
我是怎么判断优先级的
我对 LLM 路由问题的判断顺序是:
- 先看失败成本
- 主对话、Agent、多工具执行,一旦失败,用户体验会直接断裂。
- 再看省钱空间
followup、标题、轻量摘要、后台状态这类任务,即使质量略降,也通常能接受。
- 最后才看自动化程度
- "更智能"不一定比"更稳定"更有价值。
因此我的结论不是"先做最智能的 Auto",而是:
- 先做不会让系统挂掉的自动兜底
- 再把低风险任务迁到更便宜模型
- 最后才逐步增加真正的智能决策
为什么不是一上来就做 Cursor 式 Auto
因为 SkillLite 当前更接近:
- 本地优先
- BYOK(用户自己配 key)
- 多 provider 但未统一托管
这种形态下,如果一开始就追求完全自动分流,会同时引入:
- 配置复杂度
- 运行时状态复杂度
- 失败回退复杂度
- 可解释性复杂度
- UI 理解成本
这会让"一个增强功能"迅速膨胀成"一个路由子系统"。
2.2 复杂度判断:会增加,但可以分层控制
结论先说:
这个设计一定会增加项目复杂度,但前两阶段的复杂度增加是可控且值得的;
真正危险的是过早引入"智能分流黑盒"。
复杂度会增加在哪里
| 维度 | 现在 | 增加 Auto 后 |
|---|---|---|
| 配置 | 主模型 / 场景到 profile | 主模型、fallback、cooldown、tier、启发式 |
| 运行时 | 直接选定后请求 | 先决策,再请求,失败后切换,再记录原因 |
| UI | 用户知道自己选了什么 | 需要解释"为什么这次系统换了模型" |
| 测试 | 请求是否成功 | 还要覆盖 fallback、cooldown、回退、提示 |
为什么前两阶段仍然值得做
Phase 1 的复杂度:中等,但收益最大
- 新增内容:
- fallback 列表
- 错误分类
- cooldown
- 切换记录
- 带来的收益:
- 主链路失败率下降
- "别挂"能力显著增强
这是典型的 复杂度上升,但价值足够覆盖成本 的阶段。
Phase 2 的复杂度:低到中等,收益稳定
- 新增内容:
- 一些固定低风险场景的成本分层
- 带来的收益:
- 成本下降
- 几乎不影响主链路稳定性
这一步本质上是 静态分层优化,比智能决策要简单很多。
哪一步开始危险
真正复杂度快速爆炸的是:
- 动态健康度评分
- 小模型预分类
- 多因子综合打分
- 看实时输入语义做模型选择
因为从这一层开始,系统会从"路由规则"演进成"路由引擎"。
3. 不做什么
第一阶段明确 不做:
- 不做云端托管路由。
- 不做跨用户统一策略下发。
- 不做"完全自动理解所有自然语言意图再选模型"。
- 不做复杂打分系统(成功率、延迟、成本、健康度同时建模)。
- 不做隐藏式静默切换到陌生 provider 而不留痕。
这类能力后面可演进,但不应成为 MVP 前提。
4. 路由对象与术语
4.1 已有基础
llmProfiles: 用户保存的一组模型配置(provider + model + apiBase + apiKey)- 当前已有:固定场景到已保存 profile 的本地映射
4.2 建议新增概念
- Route Tier :按用途抽象出的路由层级,而不是直接写死某模型名
- 例如:
cheap/balanced/strong/vision
- 例如:
- Primary Profile:某条链路的首选 profile
- Fallback Profiles:主 profile 失败后的候补链
- Routing Decision:一次请求最终选出的 profile 与原因
- Provider Cooldown:某条失败链路在一段时间内不优先再试
4.2.1 两层结构:先配置绑定,再运行时决策
这套设计最容易讲清楚的方式,不是直接说"Auto 会帮你选模型",而是把它拆成两层:
第一层:设置里的配置绑定
这一层解决的是:
不同任务类型,原则上应该使用哪类模型 / 哪条 profile。
也就是说,用户先在设置里把绑定关系配好,例如:
- 主对话 / Agent ->
balanced或某条稳定 profile followup->cheap或某条便宜 profile- 图片任务 ->
vision或某条支持多模态的 profile - 自进化分析 ->
strong或某条更强的 reasoning profile
这一层的本质是:
- 场景 -> profile
- 或 场景 -> tier -> profile
所以从产品表达上说,这套系统的底座首先是 设置驱动的绑定表。
第二层:运行时规则决策
这一层解决的是:
当前请求来了之后,应该怎么使用前面已经绑定好的配置。
运行时系统做的事情不是"随便猜一个模型",而是:
- 判断当前属于哪个场景
- 读取设置里为该场景绑定的 profile 或 tier
- 按规则尝试主模型
- 若失败,则进入 fallback
- 若命中 cooldown,则跳过坏线路
- 最终记录本次实际选择与原因
所以"自动"的含义其实是:
- 绑定关系由设置给出
- 执行策略由运行时规则负责
为什么这两层划分很重要
因为它能把系统复杂度控制住:
- 没有第一层,系统会变成不可控黑盒
- 没有第二层,系统又只是静态映射,谈不上 Auto
换句话说:
这不是一个纯配置系统,也不是一个纯智能系统,
而是一个"设置绑定 + 运行时规则"的混合设计。
一句最直白的话
如果要用最短的话解释这套方案,可以直接写成:
不同任务可以绑定不同模型,而自动路由做的事情,是在运行时按规则使用这些绑定,并在失败时自动切到备用模型。
4.3 系统视角:从请求到路由决策
下面这张图描述的是我建议的 本地优先 Auto 路由 的基本流程。
主对话 / Agent
followup / lifePulse / 轻任务
有
无
是
否
否
是
有
无
用户发起请求
调用场景是什么?
读取该场景主配置
读取该场景默认档位
构建候选链: primary + fallbacks
是否有 cooldown 中的 provider?
跳过冷却中的候选
直接尝试首个候选
请求成功?
记录 Routing Decision 并返回结果
错误是否可 fallback?
保留错误并返回
切到下一个候选
还有候选吗?
这个流程的重点不在"聪明",而在:
- 先有一条稳定主链
- 失败后能往后切
- 切换有据可查
4.4 两阶段路线为什么适合作为文章主线
如果要写成文章,我建议把核心叙事写成下面这个结构:
- 先承认现实
- SkillLite 不是 Cursor 那种托管 Auto,也不是 OmO 那种完整路由编排系统。
- 先解决最痛的问题
- 最痛的是"请求直接挂掉",不是"少花 5% token"。
- 把成本优化放到低风险任务
- 这样既能省钱,又不把主链路质量压坏。
- 刻意延后真正的黑盒智能
- 因为那一层最贵、最难测、最难解释。
这会让文章更有说服力,因为它讲的是 为什么这样演进,而不只是"我想加一个 Auto 功能"。
5. 推荐演进路线
Phase 0:当前能力(已具备)
目标 :让用户手动把不同固定场景映射到不同 profile。
特点 :简单、透明、成本低。
限制:
- 不看输入内容
- 不看 API 实时可用性
- 不会自动 fallback
这是一个合适的起点,但不是"Auto"。
Phase 1:可靠性优先的自动 fallback(MVP-A 已落地)
实现状态:
followup/lifePulse/ 自进化状态与触发等 非流式 invoke 已接入;流式skilllite_chat_stream当前仅尊重场景映射,不在流中途切换。详见tasks/TASK-2026-038-llm-scenario-fallback。
目标
让关键请求在主模型失败时 自动切换到备用模型,先解决"别挂"问题。
建议能力
- 每个关键场景支持:
primary_profile_idfallback_profile_ids[]
- 触发 fallback 的条件:
- HTTP
429 - HTTP
5xx - 连接错误 / provider 不可达
- 请求超时
- HTTP
- 加
cooldown_seconds- 一条坏链路短时间内不要反复重试
- 可选 UI 提示:
- "主模型不可用,已切换到备用模型"
为什么先做这个
- 最贴近用户价值
- 不需要复杂分类系统
- 可以在现有
llmProfiles与场景映射之上增量实现
Phase 1 流程图
是
否: 429 / 5xx / timeout / network
是
否
主对话请求
选择 primary profile
成功?
返回结果
写失败记录
切到 fallback profile
成功?
返回结果 + 标记已降级
继续尝试下一个 fallback 或报错
Phase 2:低风险场景省钱
目标
在不影响主交互体验的情况下,系统性地降低辅助链路成本。
推荐做法
followup固定走cheaplifePulse/ 后台轻状态同步固定走cheap- 简单摘要、标题、轻量结构化生成优先走
cheap或balanced - 主对话 / Agent 默认走
balanced或strong
核心原则
省钱应优先发生在:
- 失败代价低
- 对推理深度要求低
- 可容忍偶发质量波动
的场景,而不是直接压主对话。
Phase 2 流程图
低风险: followup / title / status
中风险: 普通主对话
高风险: 复杂 Agent / 长上下文
收到请求
场景风险等级
走 cheap tier
走 balanced tier
走 strong tier
若失败可升到 balanced
若失败切同档位或升 strong
若失败切同能力不同 provider
Phase 3:输入复杂度启发式路由
目标
让系统不只看"调用点",还初步看"这次请求像不像复杂任务"。
可用的轻量启发式
- 是否带图片
- 输入长度 / 上下文长度
- 是否进入 Agent/tool-heavy 流程
- 是否包含代码修改请求
- 是否请求分析架构、排障、长文总结
路由方向
- 简单请求 ->
cheap - 中等请求 ->
balanced - 复杂请求 ->
strong - 视觉请求 ->
vision
注意
这一步依然应以 规则 为主,而不是先加二次 LLM 分类。
规则虽然"笨",但足够可解释,也更容易调参。
Phase 4:受控的"类 Auto"决策
目标
开始接近 Cursor Auto / OmO 那类体验,但保持本地优先和可解释。
可能引入的能力
- 小模型预分类:
- 先判断请求属于
simple / normal / complex / visual / risky
- 先判断请求属于
- 动态健康度:
- 最近 N 分钟内某 provider 的失败率、超时率、降级次数
- 决策理由枚举:
manualscenario-defaultcomplexity-upgraderuntime-fallbackprovider-cooldown-avoidance
风险
- 每次请求前若都额外分类,会增加延迟和 token 成本
- 黑盒程度上升,排障变难
- 用户可能不理解"为什么这次不是我选的那个模型"
因此这一步必须在前几阶段稳定后再考虑。
6. MVP 建议(最小可行版)
如果只做一版最值得上线的能力,建议是:
MVP-A:可靠性版 Auto
只包含:
- 保留现有固定场景映射
- 为关键场景增加 fallback 列表
- 在常见错误下自动切换
- 本地记录"本次切换原因"
MVP-A 为什么是最合理的起点
因为它同时满足四个条件:
- 解决真实问题
- 用户感知最强的是"挂了没有"和"能不能自动恢复"。
- 不需要重新定义整个配置系统
- 仍然以现有
llmProfiles为中心。
- 仍然以现有
- 可以最小化 UI 学习成本
- 用户只需要理解"主模型 + 备用模型"。
- 容易做成文章里的清晰故事
- 从"固定路由"升级到"稳定优先 Auto",叙事非常自然。
MVP-A 的收益
- 用户感知直接
- 技术风险低
- 与现有结构最兼容
- 最能回答"为什么这次没挂"
MVP-A 不做
- 不做智能分类
- 不做健康度全局评分
- 不做云端调度
MVP-A 代码示意
下面是一个文章里可以直接使用的 伪代码 / TypeScript 风格示意,表达的不是最终实现细节,而是设计思想:
ts
type RouteScenario = "agent" | "followup" | "lifePulse" | "evolution";
type RouteAttemptReason =
| "primary"
| "runtime-fallback"
| "cooldown-skip";
interface SavedProfileRef {
id: string;
provider: string;
model: string;
apiBase: string;
}
interface ScenarioRouteConfig {
primaryProfileId: string;
fallbackProfileIds: string[];
cooldownSeconds: number;
}
interface RoutingDecision {
scenario: RouteScenario;
selectedProfileId: string;
reason: RouteAttemptReason;
triedProfileIds: string[];
}
async function runWithFallback(
scenario: RouteScenario,
route: ScenarioRouteConfig,
request: unknown
): Promise<{ decision: RoutingDecision; result: unknown }> {
const candidates = [route.primaryProfileId, ...route.fallbackProfileIds];
const tried: string[] = [];
for (const profileId of candidates) {
if (isCoolingDown(profileId)) {
continue;
}
tried.push(profileId);
try {
const result = await callLlm(profileId, request);
return {
decision: {
scenario,
selectedProfileId: profileId,
reason: profileId === route.primaryProfileId ? "primary" : "runtime-fallback",
triedProfileIds: tried,
},
result,
};
} catch (err) {
if (!isRetryableLlmError(err)) {
throw err;
}
markCooldown(profileId, route.cooldownSeconds);
}
}
throw new Error("All route candidates failed");
}
这段代码传达的核心理念是:
- 决策不复杂
- fallback 条件明确
- 错误可分类
- 切换可追踪
- 行为可解释
6.1 再往后走时,代码形态会怎么变化
如果继续做 Phase 3 / Phase 4,代码会从"按场景查配置"逐步变成"先做决策,再执行请求":
ts
interface RequestSignals {
scenario: RouteScenario;
hasImages: boolean;
inputChars: number;
estimatedContextChars: number;
toolHeavy: boolean;
}
type RouteTier = "cheap" | "balanced" | "strong" | "vision";
function pickTierBySignals(s: RequestSignals): RouteTier {
if (s.hasImages) return "vision";
if (s.toolHeavy) return "strong";
if (s.estimatedContextChars > 120_000) return "strong";
if (s.inputChars < 400 && !s.toolHeavy) return "cheap";
return "balanced";
}
注意:这一步虽然还是规则,但已经明显比 Phase 1 更复杂;
再往后如果引入小模型预分类,就会从"规则系统"进入"半智能决策系统"。
7. 推荐的默认策略
7.1 场景默认档位
| 场景 | 默认档位 | 理由 |
|---|---|---|
| 主对话 / Agent | balanced |
保持质量与成本平衡 |
| 长上下文复杂问题 | strong |
降低误判、返工和重试 |
| 猜你想问 / followup | cheap |
容错高、结果短 |
| Life Pulse / 轻后台任务 | cheap |
成本敏感,失败影响低 |
| 图片 / 多模态 | vision |
明确能力要求 |
| 手动触发自进化 / 审核类复杂分析 | strong |
更偏复杂 reasoning |
7.2 fallback 原则
cheap失败后可升到balancedbalanced失败后可切同档位其他 provider,或升到strongstrong失败后先切同能力不同 provider,再考虑降级- 不建议从
strong直接降到明显弱很多的模型继续执行高风险 Agent 流程
8. 配置理念
建议继续走 本地优先:
- 用户保存多个
llmProfiles - 在 UI 中为不同档位或场景选择 profile
- 自动策略只决定"用哪一档 / 哪一条",不代替用户保存密钥
为什么不先上云端
- SkillLite 当前更适合 BYOK 与本地自治
- 云端路由会引入:
- 统一鉴权
- 配额协调
- 策略托管
- 隐私边界问题
这会让问题从"产品优化"升级为"平台建设"。
8.1 一个适合文章表达的架构分层
如果要对外发布文章,我建议把架构分成这四层来讲:
第 1 层:用户配置层
- 用户保存多个
llmProfiles - 为场景或档位绑定 profile
第 2 层:路由决策层
- 根据场景、规则、错误状态选择候选链
第 3 层:执行与回退层
- 调用 LLM
- 识别错误
- 自动 fallback
- 维护 cooldown
第 4 层:可观测与解释层
- 记录这次为什么这样选
- 告诉用户是否发生切换
这样写比直接堆功能点更像一篇成熟的技术文章。
用户配置层
路由决策层
执行与回退层
可观测与解释层
9. 可观测性要求
如果要做自动路由,必须留痕。
至少建议记录:
- 请求场景
- 原始首选 profile
- 最终实际使用 profile
- 是否发生 fallback
- fallback 触发原因
- 是否命中 cooldown
UI 最少要可见什么
- 当前启用的是手动路由还是自动路由
- 最近一次自动切换是否发生
- 出错时是否切到备用模型
否则用户会觉得系统在"乱跳模型"。
10. 判断是否值得继续演进的指标
在没有云端大盘的前提下,也可以先观察本地与开发期数据:
- 主对话请求失败率是否下降
- 429 / 超时后的恢复率是否上升
followup/ 后台任务的平均成本是否下降- 用户是否更少手动切模型
- 是否出现更多"为什么换模型"的困惑反馈
若失败恢复率提升明显,而成本也下降,再继续做启发式与分类层。
一句话总结:
SkillLite 的 Auto 路由应当是一个"本地优先、稳定优先、可解释、可渐进增强"的系统,
而不是一开始就做成一个不可见、不可调、不可回退的黑盒模型调度器。
项目地址:Github