@arcaneorion/pi-provider-manager
Visual config panel for models.json + roundrobin failover engine + provider health stats for pi coding agent
Package details
Install @arcaneorion/pi-provider-manager from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@arcaneorion/pi-provider-manager- Package
@arcaneorion/pi-provider-manager- Version
0.4.3- Published
- Aug 20, 2026
- Downloads
- 1,057/mo · 21/wk
- Author
- arcaneorion
- License
- MIT
- Types
- package
- Size
- 270.9 KB
- Dependencies
- 0 dependencies · 2 peers
Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-provider-manager
给 pi 编程助手 加上可视化配置面板、智能轮询故障转移引擎与渠道健康统计——一个扩展读懂、调优你所有大模型渠道。
一个 pi 扩展,三项核心能力:
- 可视化配置面板:
/providers打开本地网页面板,图形化编辑models.json与轮询配置,拖拽排序、在线发现上游模型、一键保存即热重载。 - 轮询故障转移引擎:把多个真实渠道/模型注册成一个虚拟
roundrobin模型,请求时自动故障转移——首个候选超时或出错就切下一个,全程你无感。可选开启测速排序,让最快的渠道永远排在前面。 - 渠道健康统计:7 天滚动成功率、首字延迟(TTFT)、总延迟,被动采集、跨 CLI 共享,面板一目了然。
特性一览
🎛 可视化配置面板
/providers 启动本地网页服务器(仅 127.0.0.1),浏览器打开两个标签页:
- 模型配置:逐字段编辑
~/.pi/agent/models.json——provider、模型、API Key、请求头、compat 字段、thinking level map 全覆盖。支持从上游端点拉取可用模型、与已配置模型 diff、一键增删。 - 轮询配置:编辑
~/.pi/agent/roundrobin/config.json,配置虚拟模型元数据、候选池、超时/冷却、测速排序策略,保存即热重载。支持预设组(多套候选组合一键切换)。
面板走固定端口 17890 + 持久化 token:多终端共用同一实例——第二个 CLI 跑 /providers 检测到端口已占用,直接打开已有面板而非重启。面板闲置 5 分钟自动关闭;前台打开时每 3 秒轮询健康数据顺带保活,活跃面板永不超时;由本进程启动的 server 在 pi 会话退出时一并关闭。
🔄 轮询故障转移引擎
把多个真实渠道注册成一个虚拟模型 roundrobin/<组名>,用 /model 选中它,之后所有请求走故障转移引擎:
- 请求按顺序试候选,首个成功的就粘住(sticky 策略)。
- 当前候选在首响应阶段超时或出错 → 自动切下一个候选。
- 单候选原地重试
maxRetriesPerCandidate次(指数退避)后才换渠道;耗尽则进冷却。 - 整轮全炸:测速关闭时清冷却重试 + 等最早冷却结束;测速开启时重测排序再战。
- 流中途出错(内容已吐出)直接返回不重放——避免内容/工具调用乱序;该候选仍记一次失败 + 进冷却(让 7 天统计与 smart 排序能看到“常吐一半断”的渠道)。
真正发生故障转移时,TUI 弹一次 toast:↔ 轮询故障转移到 XXX——纯提示不进对话历史,不污染 LLM 上下文。
请求隔离:请求
glm组绝不会测速/影响到deepseek组——按组名严格隔离。
⚡ 测速排序(可选,强烈推荐)
开启后,引擎不再只会被动重试——它会主动测量每个候选的真实速度并重新排队。
怎么测:给每个候选发一个极简真实对话(普通候选 maxTokens=16,reasoning 候选抬到 2048——Anthropic 类 API 开 thinking 时要求 max_tokens > thinking budget 最小 1024,16 会被 400 拒绝导致误杀),默认 prompt 欧拉函数的意义?,复用面板模型测试的同一套 streamSimple 内核——看起来就是正常聊天流量,不会被当成探活封号。测量首字延迟(TTFT)与总延迟。
TTFT 兑底(v0.4.2):TTFT 优先认首个
text_delta(真正首字);reasoning 模型小 maxTokens 可能全花在 thinking 上、永不产text_delta,此时退而认首个thinking_delta(模型开始产出的信号)作为 TTFT 兑底,避免该候选被当“拿不到首字”直接垫底。测速失败自动重试:单次测速失败且非 abort/鉴权问题 → 退避 1.5s 重试,最多共 3 次尝试(首试 + 重试 2 次)。三连败才判
✗+ recordFailure 进冷却。这是为了不把“基本可用但暂时抖动”的渠道一次判死——一个 86% 成功率的渠道,三连败概率仅 ≈0.3%,抖动几乎必能救回;真挂的渠道三次都败判死正确,多花的只是注定失败的请求(不耗 token)。另:只要测速最终 ok=true,该候选就排在所有失败候选之前(“能用”本身就是兑底);ttft/latency 缺失时用组内中位数参与排序(v0.4.2 起)。真实请求路径的maxRetriesPerCandidate原地重试哲学同样适用于测速路径。
四种排序键:
| 排序键 | 算法 | 适用场景 |
|---|---|---|
ttft |
首字延迟(默认) | 追求交互体感,首字快=响应快 |
latency |
总延迟 | 追求整轮最快 |
hybrid |
0.7×ttft + 0.3×latency 加权和 |
兼顾首字与总延迟 |
smart |
0.5×ttft_norm + 0.3×(1−reliability) + 0.2×latency_norm 三维加权 |
又快又稳,可靠性差的候选被压下去 |
smart 的可靠性兜底:reliability 用贝叶斯平滑从 7 天历史算:
(success + 2.5) / (total + 5)(先验 = 5 次 50% 成功率)。
- 全新候选(0 次)→ 0.5 中性,给机会但不越过高可靠老候选
- 单次成功(1/0)→ 0.583,往中性拉回,不被单次结果带偏
- 10 次全成 → 0.833,高但留余地;10 次全败 → 0.167 沉底,新候选能排它前面
速度维度做组内 min-max 归一化(0=最快),加权后分数越小越好,升序排序。测速失败的候选进冷却排末尾。
中位数兑底(v0.4.2):ttft/latency 缺失(null)的可用候选,用组内实测中位数参与排序(语义:该维度未知 → 假设中等水平),而非直接垫底 Infinity。不污染持久化字段——health.jsonl 仍存真实 null,面板仍显示“无首字”,仅排序时用中位数代理。全组都没测出该维度才退回 Infinity。
测速后:成功候选清冷却上前、失败候选进冷却沉底、currentIndex 强制归 0(放弃旧 sticky 位置——实测速度是更强的实时信号)。排序结果保持到下次测速。
何时触发:
- 首次请求某组(新增,懒触发)——本会话内首次请求一个开启了测速的轮询组时,同步跑一次测速排序:等排完再发首请求(首次就享受排序,代价是首请求多等几秒到几十秒)。不同组独立判定“首次”(
lastSpeedTestAt===0),面板保存配置不再重置这个状态。v0.4.1 改动:取代了原先“session_start / 面板保存就狂测所有组”的行为——现在启动后不测,用到哪个组才测哪个。 - 请求整轮全炸——所有候选试过 + 重试耗尽 + 全冷却,触发重测重排。受
minIntervalMs(默认 60s)节流,避免持续故障时疯狂烧 token。 - 手动——
/rr-speedtest [组名]命令,或面板 ⚡ 按钮,绕过节流立即重测。/rr-speedtest还会在终端上方打印详细结果表(逐候选 TTFT/延迟/排序/失败原因,见下文「手动测速」),30s 后自动消失。
⏱ 动态请求超时(测速开启时自动启用)
测速关闭时,单候选首响应超时 = 静态 timeoutMs(默认 30s)。测速开启后,超时变动态:
动态超时 = max(timeoutMs, min(120000, round(实测 ttft × 2.0))) // 下限 = 你配的 timeoutMs(面板可调); 上限 120s 防病态样本(曾测出 ttft 327s)把超时抬到很大。中转站波动大就调大 timeoutMs 兼容临时劣化
这个动态值同时守护两处:
- 首响应——首个流事件到达前的等待上限(替代静态
timeoutMs)。 - 流中空闲——start 之后任意两个 chunk 之间的停顿上限。v0.4.0 新增:以前流一旦 start 就再无超时保护,候选首字几秒到达后慢慢吐几十秒,pi 一直干等——这就是"卡死"的根因。现在流中卡顿超过动态超时立即 abort。
没测出首字的候选(ttft=null,测速时就没拿到首字)回退静态 timeoutMs(默认 30s),null 回退双保险,不会被动态超时误杀。
超时后:当作普通流前失败处理——消耗一次 maxRetriesPerCandidate(不是立即换渠道),退避后重试同一候选,耗尽才进冷却换渠道。想"超时即换"就把 maxRetriesPerCandidate 设 0。内容已转发后的流中空闲超时属 terminal(不能重放,直接终止);该候选仍记一次失败 + 进冷却(与前述流中途出错一致,让 health/smart 看到质量问题)。
面板轮询 tab 每个候选显示计算出的动态超时值("超时 26s"),一眼看清每个候选当前的有效超时。
📊 渠道健康统计
每个渠道的 7 天滚动统计显示在面板(历史均值,非仅当前进程):
- 成功/失败次数与成功率
- 平均首字延迟(TTFT)
- 平均总延迟
每次请求追加一行到 ~/.pi/agent/roundrobin/health.jsonl,多 CLI 共享(无文件锁,best-effort)。启动时自动剪除 7 天外旧事件。TTFT 取首个 text_delta 到达时刻,Latency = message.timestamp 到 message_end——不依赖队列配对,中断/取消不污染延迟统计。
轮询候选不再双计数(第四轮审计修复):早期版本里轮询获胜候选会在
health.jsonl被双写(引擎recordSuccess/Failure一次 + 全局message_end被动采集又一次),总数×2。现已修复:成功转发的 done 事件改写为虚拟模型 provider,message_end钩子不再重复记录,每候选只落一条。
🔧 手动测速
- 在 pi 里:
/rr-speedtest(所有开启测速的组)或/rr-speedtest <组名>(指定组,支持 Tab 补全组名)。测完在终端编辑器上方打印逐候选详细表:每个候选一行,按排序顺序显示#排名 TTFT 延迟(可用)或✗ 失败原因(不可用),30s 后自动消失,同时弹 toast 摘要。 - 在面板里:轮询 tab "测速排序" 行的 ⚡ 按钮,调用
POST /api/rr/manual-speedtest。
两者都绕过 minIntervalMs 节流,但尊重 speedTestRunning 锁(不会对正在测速的组重复测)。
安装
pi install npm:@arcaneorion/pi-provider-manager
使用
/providers启动面板,在轮询 tab 添加候选(从已配置模型里选)、保存配置。/model选择roundrobin/<组名>。- 之后所有请求走故障转移引擎。
保存即热重载——轮询引擎立即 pickup 新候选,无需重启。
跨 CLI 局限:热重载只对当前面板所属的 CLI 进程即时生效。若你有多个 pi 终端在跑,其他终端的轮询组不会自动重载(显示的候选/配置仍是旧的),需重启该终端或在其内重新触发加载。
配置
models.json
标准 pi models.json,面板支持完整 schema 编辑。
roundrobin/config.json
{
"virtualModel": {
"id": "roundrobin",
"name": "Model Round Robin",
"reasoning": true,
"input": ["text", "image"],
"contextWindow": 200000
},
"candidates": [
{ "provider": "my-openai", "model": "gpt-4o" },
{ "provider": "my-anthropic", "model": "claude-sonnet-4-20250514" }
],
"log": true,
"timeoutMs": 30000,
"cooldownMs": 60000,
"strategy": "sticky",
"maxRetriesPerCandidate": 2,
"speedTest": {
"enabled": false,
"sortKey": "smart",
"prompt": "欧拉函数的意义?",
"timeoutMs": 60000,
"concurrency": 5,
"minIntervalMs": 60000
}
}
字段说明
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
virtualModel |
object | — | 虚拟模型元数据(id/name/reasoning/input/contextWindow/maxTokens/thinkingLevelMap/compat)。id 在加载时强制为组名;maxTokens 未设默认 16384,contextWindow 未设默认 200000。 |
candidates |
array | [] |
[{ "provider": "...", "model": "..." }, ...]。未知组合会被跳过;解析后为空则禁用该组。 |
log |
boolean | true |
追加到 ~/.pi/agent/roundrobin/roundrobin.log。 |
timeoutMs |
number | 30000 |
单候选首响应超时;测速开启且拿到 ttft 时作为动态超时下限 max(timeoutMs, min(120000, ttft×2.0)),没有 ttft 时回退它本身。中转站波动大就调大(如 45000=45s),给临时劣化更多恢复时间。 |
cooldownMs |
number | 60000 |
候选失败(重试耗尽)后的冷却窗口。 |
strategy |
"sticky" |
"sticky" |
成功后候选推进策略:sticky=黏住当前候选直到失败,冷却后回首选;round-robin=成功后指向下一个候选(均分流量);primary=恒回首选(0),仅首选冷却/失败时用备选。 |
maxRetriesPerCandidate |
number | 2 |
单候选原地重试次数(不含首试),指数退避。设 0 = 超时/失败即换渠道。 |
speedTest.enabled |
boolean | false |
开启测速排序(见上方测速章节)。 |
speedTest.sortKey |
ttft/latency/hybrid/smart |
ttft |
排序键。hybrid = 0.7×ttft+0.3×latency;smart = 三维加权含贝叶斯平滑的 7 日成功率。 |
speedTest.prompt |
string | "欧拉函数的意义?" |
测速 prompt,空则用默认。复用面板模型测试同一真实对话内核。 |
speedTest.timeoutMs |
number | 60000 |
单次测速尝试的超时(≥1000)。失败后退避 1.5s 重试,最多 3 次尝试(retries 默认 2)。太短会误判慢但可用的候选。 |
speedTest.concurrency |
integer | 5 |
并行测速 worker 数(≥1)。设为候选数 = 全组并行测。 |
speedTest.minIntervalMs |
number | 60000 |
自动测速节流间隔(≥0)。手动 /rr-speedtest 与 ⚡ 按钮绕过此节流。 |
speedTest.retries |
integer | 2 |
单次测速失败后重试次数(不含首试),退避 1.5s。设 0 = 失败即判 ✗(快但无抖动容错)。面板可编辑。 |
安全
- 仅监听
127.0.0.1:本机回环,不暴露到网络。 - 192-bit token 鉴权:首次启动生成(
randomBytes(24))。为支持多 CLI 无缝复用同一面板,token 持久化到~/.pi/agent/roundrobin/.panel-token,跨会话/跨终端复用,非每次随机;所有/api/*路由必须带X-Config-Token头。 - 同机威胁模型:本地
127.0.0.1only,同机其他进程理论上能读到 token 文件或访问端口——这是用「固定 token 换多 CLI 复用」的明确取舍。介意可删~/.pi/agent/roundrobin/.panel-token强制重置。 - API Key 服务端解析:支持
$ENV_VAR(如$OPENAI_API_KEY,运行时从环境变量取值),浏览器永远拿不到解析后的明文。 - 保存自动备份:每次写
models.json/config.json前先复制一份带时间戳的.bak,误改可回退。
开发
仓库含 9 个 vitest 测试套件(tests/),覆盖配置解析、健康存储、轮询故障转移、前后端联动等。
许可证
MIT