@arcaneorion/pi-provider-manager

Visual config panel for models.json + roundrobin failover engine + provider health stats for pi coding agent

Packages

Package details

package

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 选中它,之后所有请求走故障转移引擎:

  1. 请求按顺序试候选,首个成功的就粘住(sticky 策略)。
  2. 当前候选在首响应阶段超时或出错 → 自动切下一个候选。
  3. 单候选原地重试 maxRetriesPerCandidate 次(指数退避)后才换渠道;耗尽则进冷却。
  4. 整轮全炸:测速关闭时清冷却重试 + 等最早冷却结束;测速开启时重测排序再战
  5. 流中途出错(内容已吐出)直接返回不重放——避免内容/工具调用乱序;该候选仍记一次失败 + 进冷却(让 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 位置——实测速度是更强的实时信号)。排序结果保持到下次测速。

何时触发

  1. 首次请求某组(新增,懒触发)——本会话内首次请求一个开启了测速的轮询组时,同步跑一次测速排序:等排完再发首请求(首次就享受排序,代价是首请求多等几秒到几十秒)。不同组独立判定“首次”(lastSpeedTestAt===0),面板保存配置不再重置这个状态。v0.4.1 改动:取代了原先“session_start / 面板保存就狂测所有组”的行为——现在启动后不测,用到哪个组才测哪个。
  2. 请求整轮全炸——所有候选试过 + 重试耗尽 + 全冷却,触发重测重排。受 minIntervalMs(默认 60s)节流,避免持续故障时疯狂烧 token。
  3. 手动——/rr-speedtest [组名] 命令,或面板 ⚡ 按钮,绕过节流立即重测/rr-speedtest 还会在终端上方打印详细结果表(逐候选 TTFT/延迟/排序/失败原因,见下文「手动测速」),30s 后自动消失。

⏱ 动态请求超时(测速开启时自动启用)

测速关闭时,单候选首响应超时 = 静态 timeoutMs(默认 30s)。测速开启后,超时变动态

动态超时 = max(timeoutMs, min(120000, round(实测 ttft × 2.0)))     // 下限 = 你配的 timeoutMs(面板可调); 上限 120s 防病态样本(曾测出 ttft 327s)把超时抬到很大。中转站波动大就调大 timeoutMs 兼容临时劣化

这个动态值同时守护两处:

  1. 首响应——首个流事件到达前的等待上限(替代静态 timeoutMs)。
  2. 流中空闲——start 之后任意两个 chunk 之间的停顿上限。v0.4.0 新增:以前流一旦 start 就再无超时保护,候选首字几秒到达后慢慢吐几十秒,pi 一直干等——这就是"卡死"的根因。现在流中卡顿超过动态超时立即 abort。

没测出首字的候选(ttft=null,测速时就没拿到首字)回退静态 timeoutMs(默认 30s),null 回退双保险,不会被动态超时误杀。

超时后:当作普通流前失败处理——消耗一次 maxRetriesPerCandidate(不是立即换渠道),退避后重试同一候选,耗尽才进冷却换渠道。想"超时即换"就把 maxRetriesPerCandidate0。内容已转发后的流中空闲超时属 terminal(不能重放,直接终止);该候选仍记一次失败 + 进冷却(与前述流中途出错一致,让 health/smart 看到质量问题)。

面板轮询 tab 每个候选显示计算出的动态超时值("超时 26s"),一眼看清每个候选当前的有效超时。

📊 渠道健康统计

每个渠道的 7 天滚动统计显示在面板(历史均值,非仅当前进程):

  • 成功/失败次数与成功率
  • 平均首字延迟(TTFT)
  • 平均总延迟

每次请求追加一行到 ~/.pi/agent/roundrobin/health.jsonl,多 CLI 共享(无文件锁,best-effort)。启动时自动剪除 7 天外旧事件。TTFT 取首个 text_delta 到达时刻,Latency = message.timestampmessage_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

使用

  1. /providers 启动面板,在轮询 tab 添加候选(从已配置模型里选)、保存配置。
  2. /model 选择 roundrobin/<组名>
  3. 之后所有请求走故障转移引擎。

保存即热重载——轮询引擎立即 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 未设默认 16384contextWindow 未设默认 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×latencysmart = 三维加权含贝叶斯平滑的 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.1 only,同机其他进程理论上能读到 token 文件或访问端口——这是用「固定 token 换多 CLI 复用」的明确取舍。介意可删 ~/.pi/agent/roundrobin/.panel-token 强制重置。
  • API Key 服务端解析:支持 $ENV_VAR(如 $OPENAI_API_KEY,运行时从环境变量取值),浏览器永远拿不到解析后的明文。
  • 保存自动备份:每次写 models.json / config.json 前先复制一份带时间戳的 .bak,误改可回退。

开发

仓库含 9 个 vitest 测试套件(tests/),覆盖配置解析、健康存储、轮询故障转移、前后端联动等。

许可证

MIT