先让“自动选择”跑起来。
第一版用 difficulty、urgency、固定 role chain、quota 与 cooldown 决定模型;随后加上 OpenAI-compatible proxy,让现有客户端无需改代码即可接入。这一步证明了路由可以进入真实调用链,但也把任务理解压缩得太简单。
复杂任务被归纳成难度和紧急度。规则能命中熟悉表达,却无法稳定表达 coding、image、batch 等能力差异。
保留兼容层,但不再把 role chain 当成新架构的核心。
模型选择不是界面偏好
它决定一个任务是否可做、何时完成、怎样失败
以及这个决定能否被复盘。
最初只是一个按角色和额度选模型的小工具。
真正的架构来自后续几轮失败
误判任务、错误冷却、量纲混乱、并发丢数据
以及名义上存在却没有真正执行的 fallback。
下面不是发布功能列表,而是每一轮实践如何改变下一轮判断。
第一版用 difficulty、urgency、固定 role chain、quota 与 cooldown 决定模型;随后加上 OpenAI-compatible proxy,让现有客户端无需改代码即可接入。这一步证明了路由可以进入真实调用链,但也把任务理解压缩得太简单。
复杂任务被归纳成难度和紧急度。规则能命中熟悉表达,却无法稳定表达 coding、image、batch 等能力差异。
保留兼容层,但不再把 role chain 当成新架构的核心。
项目从 router 变成“任务 + 模型 + 时间窗口”的调度器:加入 Task、状态机、JSON 持久化、defer、Executor 和最小看板。设计重点不是更多字段,而是让 task 只描述做什么与何时做,执行细节留给 Executor。
代理层曾把所有 HTTP ≥ 400 都视为模型故障并进入 cooldown;400 参数错、401 凭据错也会错误惩罚模型。
把 request/auth、rate limit、server error 与 timeout 分类,只有真正的瞬态故障进入冷却或重试。
Utility 引入质量、成本、延迟、失败风险、额度压力和截止压力六个维度;Hard Constraints 先排除不可能的候选,breakdown 和 why 留下选择依据。Policy Compiler 再把“便宜一点”“尽快”等意图翻译成约束与权重。
第一版直接把不同量纲乘权重后相加。balanced 模式下,成本绝对值甚至可能反压明显的质量优势。
增加候选集内 min-max 相对归一化,再应用权重;单候选则明确标注没有相对参照。
StateStore 加入 SQLite,benchmark 开始对比 utility、role chain 和 round-robin。但最终审查与真实使用连续暴露了基础问题:多进程写入丢数据、并发 tick 竞态、quota 哨兵误判,以及纯文本模型被派去执行图片任务。
两进程各写 30 条任务,预期 60 条,实际只剩 31 条;合成 benchmark 也只能比较策略,不能证明生产并发能力。
改用 SQLite BEGIN IMMEDIATE 原子更新,补真实线程/进程压测,并公开 quota record_call 约 102 QPS 的已知瓶颈;image/vision 能力不匹配改为硬过滤。
项目更名为 agent-model-router,明确它不只是时间调度器。随后一次接管审查指出:多 scheduler claim 不原子、running 没有 lease/heartbeat、旧 worker 可以迟到回写,而降级矩阵当时只记录 action 字符串,并没有真正执行 fallback。
进程崩溃会留下永久 running;重复 worker 可能执行同一任务;“fallback”只是记录了一个词,不能证明模型真的切换。
加入 SQLite CAS claim、worker/attempt ownership、lease、heartbeat、stale recovery 与 owner-safe completion。只有 Executor.prepare_fallback() 真正成功,任务才重新排队;否则 fail closed。
这是结构真实但完全虚构的示例决策记录。选择下方的硬约束,查看它如何改变候选与理由。
先让不可能的候选退出,再讨论谁更合适。
这些不是边角 bug,而是曾经成立、后来被证据推翻的设计假设。
早期矩阵只把 action_taken 写进 last_error。执行器没有准备下一个候选,任务却可能被描述成已经 fallback。
后来:prepare_fallback() 成功才重排;否则 fail closed。真正的问题跨越 load 与 save:两个进程都从旧快照出发,后写入者覆盖先写入者。
后来:整个读—改—写放进 BEGIN IMMEDIATE 原子事务。它只能在同一套假设下比较三种策略,无法证明真实锁竞争、写吞吐或 provider 表现。
后来:分开标注合成策略对比与真实并发压测,并公开已知瓶颈。image 缺配置时退化为 ALL_TIERS,成本和延迟评分可以把纯文本模型推到图片任务前面。
后来:能力缺失返回 0.0,宁可无候选,也不假装能做。库只负责可解释选择与可恢复任务语义。任务理解、provider 传输与凭据仍属于接入层。
Executor 执行真实 provider 调用。fallback 也必须由 Executor 真正准备;调度器不解析私有 payload,也不会凭一个状态字符串假装完成了切换。
清晰的边界让路由库可以专注于决策与调度,而不会假装替每个接入系统完成它自己的职责。
库不让 LLM 猜测任务类型;由调用方显式传入,或由接入层负责推断。
不实现 provider SDK、凭据或网络传输;这些留给具体的执行与接入层。
不承诺“永远最优”的模型选择;它提供可检查的依据、约束与失败处理语义。