pi-model-auto-router
v0.4.0
Published
Pi extension that exposes virtual models and routes requests across real provider/model targets with load balancing and failover.
Maintainers
Readme
pi-model-auto-router
Pi 扩展:将虚拟路由模型注册为 model-auto-router provider,把请求按策略分发到真实 provider/model 目标,内置负载均衡、故障切换(failover)、重试退避、冷却机制。
安装
bun add -g pi-model-auto-router # 或按 Pi 插件方式安装到 ~/.pi/agent需要 @earendil-works/pi-ai >= 0.84.0、@earendil-works/pi-coding-agent >= 0.84.0。
配置
配置文件(按优先级查找第一个存在的):
| 路径 | 说明 |
|---|---|
| .pi/model-auto-router.routes.json | 项目级配置 |
| ~/.pi/agent/extensions/model-auto-router.routes.json | 全局配置 |
支持 JSONC(注释 + 尾逗号)。修改后执行 /auto-router reload 生效,或直接使用 TUI /auto-router config 可视化编辑(保存即生效)。
完整字段说明
{
// ═══ 重试与冷却(可选,缺省用默认值;也可在 TUI 的 "⚙️ 重试与冷却设置" 中配置)═══
"retry": {
"maxRetries": 4, // 所有目标瞬态失败后的整轮重试次数,0 = 禁用重试(默认 3)
"backoffBaseMs": 2000, // 退避起始间隔 ms,每轮翻倍(默认 2000)
"backoffMaxMs": 30000, // 退避等待上限 ms(默认 30000)
"transientCooldownMs": 60000, // 瞬态失败(限流/超时/网络)冷却 ms(默认 60000 = 1m)
"longCooldownMs": 43200000, // quota/config 类失败冷却 ms(默认 43200000 = 12h)
"perTargetRetries": 2, // 单目标重试:瞬态失败先在原目标重试 N 次再 failover(默认 0 = 立即切换)
"perTargetBackoffMs": 1500, // 单目标重试退避起始间隔 ms,每次翻倍,上限沿用 backoffMaxMs(默认 1500)
"retryEmptyResponses": true // 结束检测:响应无任何内容时视为失败并 failover/重试(默认 true)
},
// ═══ 路由分组 ═══
"routes": {
"default": { // 路由 id 即模型 id,在 provider model-auto-router 下选择
"strategy": "least-loaded", // least-loaded(默认) | round-robin | cache-first
"targets": [
{
"provider": "ducky", // 对应 ~/.pi/agent/models.json 中的 provider id
"model": "qwen3.8-max", // 模型 id
"weight": 2, // 负载均衡权重(least-loaded 按 active/weight 计分,默认 1)
"maxConcurrency": 3, // 该目标最大并发,超过则跳过(可选)
"enabled": true, // 设为 false 临时绕过该目标,不参与选择(可选,默认 true)
"api": "openai-completions", // 覆盖 api(可选)
"baseUrl": "https://...", // 覆盖 baseUrl(可选)
"contextWindow": 200000, // 覆盖窗口(可选,路由取各目标最小值)
"maxTokens": 8192, // 覆盖 maxTokens(可选)
"compat": { "supportsDeveloperRole": false } // 追加 compat(可选)
}
]
}
},
// ═══ 隐藏 provider(可选)═══
// 下列 provider 会被注册为空模型来"隐藏",避免 Pi 直接列出目标 provider。
// 路由里出现的 provider 会自动隐藏;show 可豁免 hide。
"hide": ["anthropic"],
"show": []
}优先级
- 重试:
routes.json retry.*> 环境变量MODEL_AUTO_ROUTER_MAX_RETRIES> 内置默认 - API Key:
<PROVIDER>_AUTH_TOKEN/<PROVIDER>_API_KEY环境变量 > models.json 中 provider 的apiKey(支持${ENV_VAR}展开) - baseUrl:目标
baseUrl> models.json provider.baseUrl><PROVIDER>_BASE_URLenv > provider id
支持的环境变量
| 变量 | 默认 | 说明 |
|---|---|---|
| MODEL_AUTO_ROUTER_MAX_RETRIES | 3 | 最大重试轮数(被 routes.json retry.maxRetries 覆盖) |
| MODEL_AUTO_ROUTER_STALL_TIMEOUT_MS | 90000 | 目标流超过该时长无任何事件判定为挂起,强制终止(90s) |
| MODEL_AUTO_ROUTER_RETRY_EMPTY | on | 设为 off 关闭空响应 failover/重试(routes.json retry.retryEmptyResponses 优先) |
| MODEL_AUTO_ROUTER_STALL_CHECK_MS | 5000 | 挂起检查的间隔(测试可调小) |
| MODEL_AUTO_ROUTER_LOG | 开 | 设为 off 关闭日志 |
| MODEL_AUTO_ROUTER_LOG_PATH | ~/.pi/agent/model-auto-router.log | 日志文件路径 |
注意:重试完全由 auto-router 统一控制(
retry.maxRetries+ 退避)。透传给底层 provider 时已剥离maxRetries/maxRetryDelayMs,避免 pi-ai 的 provider 层(OpenAI/Anthropic SDK 风格,指数退避 0.5s→8s 封顶)在路由重试之上再叠加一层不可见的重试。
TUI 配置
运行 /auto-router config 打开可视化配置:
- + 添加分组:输入名字 → 选策略 → 添加目标模型(从注册表挑选 provider/model,可设权重)
- 编辑分组:改名称 / 改策略 / 管理目标(增删、改权重)/ 删除分组
- ⚙️ 重试与冷却设置:最大重试轮数、单目标重试次数、单目标重试退避、退避起始间隔、退避上限、瞬态失败冷却、严重失败冷却、空响应自动重试开关
- 时长输入支持
5/30s/2m/1h,留空恢复默认,可一键全部恢复默认
- 时长输入支持
命令
| 命令 | 说明 |
|---|---|
| /auto-router status | 查看路由、目标负载、冷却、失败统计 |
| /auto-router log [N] | 最近 N 条路由/切换/重试事件(默认 20) |
| /auto-router reset | 清空冷却和运行时计数 |
| /auto-router reload | 重新加载 routes 与隐藏 provider |
| /auto-router config | 打开 TUI 配置 |
| /auto-router debug | 列出注册表可用模型 |
路由策略
- least-loaded(默认):按
active/weight + failures*0.05计分选最低,兼顾当前并发与历史失败 - round-robin:按累计被选次数轮询
- cache-first:固定优先第一个可用目标,失败才切换
状态行会实时显示:api-wait → streaming → target-retry … retry=n/N(单目标重试退避) / retry pass=x/y / last=served failovers=n。
失败分类与重试机制
错误按类型处理:
| 分类 | 判定(关键字,大小写不敏感) | 行为 |
|---|---|---|
| transient | 429、rate limit、timeout、502/503/504、overloaded、网络错误等 | 单目标重试(可配,见下)→ failover 到下一目标;全部失败后整轮退避重试(2s 起指数翻倍,上限 30s,可配);结束后目标冷却 1m(可配) |
| quota | 402、insufficient balance、credits exhausted 等 | failover;目标冷却 12h(可配) |
| config | model not found、404、401/403、invalid key、not allowed for this account(账号无权访问该模型)、not supported for this model(模型不支持请求能力/参数)等 | failover;目标冷却 12h(可配) |
| fatal | 其他未知错误 | 立即终止,不再重试 |
账号级/模型级不可用属于目标级问题,不是 fatal:如网关返回 400
Access to Anthropic models is not allowed for this account.(该账号无权访问此模型)或"thinking.type.enabled" is not supported for this model(模型不支持请求参数),此类错误只冷却当前目标并切换到下一目标,路由内其余目标继续可用;只有所有目标都失败后才把聚合错误暴露给用户。failover 事件会在model-auto-router.log中记录class与命中的marker(/auto-router log同样展示),便于排查是哪条规则命中的。
单目标重试(per-target retry)
瞬态错误(如 429 限流)默认立即 failover 到下一目标。若希望高优先级模型被短暂限流时先原地等待恢复,而不是直接被切换掉,可配置 retry.perTargetRetries:
{
"retry": {
"perTargetRetries": 2, // 每个目标瞬态失败后先原地重试 2 次
"perTargetBackoffMs": 1000 // 退避起始 1s,翻倍(1s → 2s),上限沿用 backoffMaxMs
}
}- 期望链路:
opus (429) → 等 1s → 重试 opus (429) → 等 2s → 重试 opus ✅ served(恢复则无需 failover) - 重试额度耗尽后进入原有 failover 流程;状态行显示
target-retry {elapsed}/{delay} target=… retry=n/N - 仅作用于
transient类错误;config/quota/fatal不做单目标重试 - 默认
perTargetRetries: 0保持原有立即 failover 行为;不影响整轮重试(maxRetries)逻辑 - 空响应(结束检测)不做单目标重试,仍直接 failover
结束检测(空响应)
流式结束(done)时检查响应结构:若 content 为空或所有块都是空文本/空思考(无 toolCall),判定为空响应,视为 transient 失败 → failover 到下一目标,全部失败后进入重试退避(受 retry.maxRetries 控制)。
- 默认开启,TUI 中可关闭(
⚙️ 重试与冷却设置 → 空响应自动重试),或配置retry.retryEmptyResponses: false/MODEL_AUTO_ROUTER_RETRY_EMPTY=off - 只在尚未输出任何内容时生效(此时切换目标对用户无感知、可干净重试);已输出内容后的错误仍按 mid-stream 处理(原样透传,由 Pi 的 agent 重试机制接管)
流式输出中途(已提交内容后)出现瞬态错误时,只能原样透传错误给前端,无法回滚已输出的内容。
目标流长时间无事件(默认 90s,MODEL_AUTO_ROUTER_STALL_TIMEOUT_MS 可调)或用户中止(Esc)时,即使底层流卡死,也会立即清理 streaming 状态、下发错误并结束请求,不会一直停留在 streaming。
开发
bun run build # tsc 编译到 dist/
bun test # 运行 e2e 测试(含重试配置覆盖测试)