Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

pi-cache-check

Diagnose and fix prompt-cache failures when Pi talks to an OpenAI-compatible relay API. Three commands: /cache-check, /cache-fix, /cache-restore.

用第三方中转 API(聚合站、网关、自建代理)跑 Pi 时,账单经常比预期高好几倍, 原因几乎总是同一个:prompt cache 没生效,每一轮都在按全价重算前缀。

难点不在于修,在于定位是谁的问题——配置写错了?Pi 没发缓存标记?还是上游不认? 这三件事的修法完全不同,靠猜会绕很久。这个包把它们拆成三层,一条命令一次答完。

── L1 有效配置 ───────────────────────────
  ✓ api = openai-responses(自动前缀缓存 + prompt_cache_key)
  ✓ 缓存 TTL    long (ttl:"1h")
── L2 线上请求 ───────────────────────────
  ✓ 请求里有 3 个 cache_control 断点(pi 原生): system[0], user[4], tools[3]
── L3 本会话实际用量 ─────────────────────
  input     485   cacheRead    17,920   命中 97.4%
✓ 缓存生效

安装

pi install git:github.com/Song-ic/pi-cache-check

先试不装(临时加载,只对本次运行生效):

pi -e git:github.com/Song-ic/pi-cache-check

典型流程

/cache-check        →  L1 ✗ 配置不对
/cache-fix          →  看改动 → 确认 → 自动备份 → 热生效(不用重启 pi)
(说句话,触发一次请求)
/cache-check        →  L2 出现断点 / L3 的 cacheRead > 0  →  ✓ 缓存生效

先搞清楚:两条缓存策略

同一个 OpenAI 兼容上游,缓存有两条完全不同的路,别把其中一条当成唯一答案

A. openai-responses(先试这条) B. openai-completions + 断点
机制 服务端自动前缀缓存 显式 cache_control 断点
prompt_cache_key Pi 恒发 默认不发(仅官方直连或 TTL=long)
需要 compat 不需要 需要 cacheControlFormat: "anthropic"
多后端账号池下 key 把同一会话粘到同一后端 每轮随机分发,命中率随池子大小塌方

prompt_cache_key 是决定性差异:聚合站/中转商背后往往是多个后端账号轮询, 缓存写在 A 号、下一轮被派到 B 号就 miss。实测同一上游同一时段: A 路 93.9%(6 轮全中,82 秒空闲后仍命中),B 路 16~39%(间歇性 miss/hit)。

排查顺序:上游支持 responses 就走 A;上游不支持 responses 或不对它做自动缓存时, 才落到 B(/cache-fix 修的就是 B 路的配置)。/cache-check 认得这两条路, 不会在 A 路上要求断点,/cache-fix 也绝不会把 A 路降级成 B 路。

命令

/cache-check —— 诊断

不发额外请求、不花额外 token,搭你正在跑的请求的车。三层各自指向不同责任方:

看什么 红了代表 该找谁
L1 有效配置 api / compat / TTL 档位 / 磁盘与会话是否一致 配置不对、被外部工具覆写、或改了还没重启 配置/cache-fix
L2 线上请求 真实 payload 里的 cache_control 断点(数量、位置、谁打的) 配置对但 Pi 没发 Pi 变了 → 查版本/源码
L3 实际用量 本会话每轮 cacheRead / cacheWrite 请求发对了但上游不认 上游 → 找中转商

L2 是外部脚本做不到的一层:它直接看线上请求长什么样,而不是从结果反推 (靠 Pi 的 before_provider_request 钩子;诊断本身只读,不改 payload)。

判定是四态的,两条设计原则都是从真实翻车里学来的:

  • ✓ 生效 / ✗ 未生效 / ⚠ 能用但易失效(如 TTL 太短)/ ? 尚未验证
  • 没有证据 ≠ 通过:L2/L3 没数据时报"尚未验证",不假绿
  • 会失效 ≠ 没生效:TTL 短报"易失效",不假红;第 1 轮 cacheRead=0 是建缓存,不算失败

失败时的提示按层级给方向:配置有问题 → /cache-fix;配置全对仍不命中 → 指向换策略或找上游,不会让你去"修"一个没毛病的配置

L2/L3 需要本会话发过至少一次请求。刚进 pi 就跑的话,说句话再跑一次。

/cache-fix —— 一键修复

/cache-check 的 L1 红了就跑它。修两样东西,全部先展示改动 → 等确认 → 先备份 → 热生效

① provider 配置(B 路时)—— api 设为 openai-completionscompat.cacheControlFormat 设为 anthropic。之后 Pi 用官方机制在 system prompt / 最后一个 tool / 最后一条对话消息三处打 cache_control 断点。 走 A 路(responses)的 provider 本身就是对的,不做任何改动

② 缓存 TTL 档位 —— 写进 cache-check.json,立即生效且每次启动自动应用。

TTL 为什么和 compat 一样要紧。 实测某中转上游默认 TTL 只有约 30 秒, 且从写入起算、读取不续期。背靠背连打能看到 97% 命中,但真实写码时你会看输出、 会思考,两轮间隔一两分钟是常态——每次都跨过 TTL,命中率塌回 30% 上下。 PI_CACHE_RETENTION=long 让断点带上 ttl:"1h";对照实验(同 provider、同协议、 同样静默 45 秒,只差这一个变量):不开 → 全量重发;开 → 97.1% 命中。

代价:长 TTL 的缓存写入通常更贵(Anthropic 官方口径 2x vs 1.25x 默认档)。 读取折扣一般能盖过它,但你的中转商如何计价请核对账单——所以这一项会单独告知并等你确认

不碰 apiKey、不碰 models、不碰其他 provider。幂等——已经对了就说"无需修改"退出。 需要交互确认,非 TUI 模式下拒绝执行而不是默默改文件。

/cache-fix                 # 修当前 provider
/cache-fix "My Provider"   # 修指定 provider

/cache-restore —— 回滚

用最近一次 /cache-fix 的备份覆盖当前 models.json,同样要确认,同样热生效。

兜底注入(默认开启,B 路专用)

配置修好了也可能被外部工具(cc-switch 这类 API 切换器)重写回去;而且 Pi 的 compat 是 per-provider 的,每新增一个 provider 都得重配一次

所以本包在 before_provider_request 里兜底:走 chat/completions 且请求里一个断点 都没有时,自动补上(位置与 Pi 原生一致)。新 provider 不配也能命中。三条硬约束:

  1. 只处理 chat/completions——responses 的 input[] 里放 cache_control 会被上游 400 拒绝(实测),宁可不缓存也不能把请求打挂
  2. 已有断点就不碰,绝不重复打
  3. 熔断——注入过的请求收到 400 → 立即停止注入并告知

兜底不掩盖根因:L2 会明确区分断点是 pi 原生还是本插件补的,靠补的会被记成 "尚未验证"并提醒去修配置。关掉:PI_CACHE_CHECK_ENFORCE=0

本包写的文件

文件 内容 谁写
~/.pi/agent/models.json provider 的 api / compat /cache-fix(改前备份)
~/.pi/agent/models.json.bak-cachefix-* 备份 /cache-fix
~/.pi/agent/cache-check.json {"cacheRetention":"long"} /cache-fix

TTL 改回默认:删掉 cache-check.json。回滚 models.json/cache-restore。 显式设过 PI_CACHE_RETENTION 环境变量的,优先级高于偏好文件,本包不覆盖。

什么时候用不上这个包

  • 直连 OpenAI 官方 API——自动前缀缓存本来就工作,prompt_cache_key 也自动带
  • 上游对 cache_control 字段直接报 400 且不支持 responses——客户端改不了,得找中转商 (/cache-check 仍能帮你出一份 L1/L2 绿 + L3 红的证据)

常见坑(实战踩过的)

改了配置,/cache-check 还是红。 配置是会话启动时解析的,/reload 只重载扩展、换不掉已选定的模型对象; resume 恢复旧会话同样带着当时解析的配置。L1 的"磁盘配置与本会话不一致" 提示就是在抓这个。解法:会话内 /model 重选一次同款模型(触发重新解析,不丢会话), 或者开新会话。

敲了 /cache-check 没反应,或模型自己回了一份"缓存报告"。 说明命令没注册,斜杠命令被当普通文本发给了 LLM——它可能凭空编一份像模像样的报告, 甚至用它的文件工具替你"执行"命令(实测发生过:模型收到 /cache-restore 后 自己找到备份把 models.json 回滚了)。两种常见原因: ① 包没装上(检查 pi config / settings.jsonpackages); ② 装了两份(比如 pi install 过本地路径又用 -e 加载了一份)——pi 会给重名命令 加 :1/:2 后缀,裸名消失。删掉一份即可。

在 API 切换器(如 cc-switch)里切分组后配置被打回原形。 这类工具给每个 provider 存的是完整快照,切换时整个写回。把它存的快照也更新成 正确配置才能根治;在那之前,本包的兜底注入能保住 B 路的命中(L2 会标注是谁打的断点)。

模型自称 GPT-5 / 别的型号。 模型不知道自己的型号,自我介绍来自训练数据或宿主的 system prompt,不是证据—— 既不能证明中转商换了模型,也不能证明没换。验货看行为,别问名字。

一个诚实的提醒

给这类上游做过合成 HTTP 探针的都知道:探针结果可能和真实客户端完全相反 (同一路径探针 0% 命中、真 Pi 97%——TLS 指纹、路由、请求形状都可能不同)。 本包因此坚持只用两种证据:真实请求的 payload(L2)和真实会话的用量(L3)。 如果你打算自己排查,别信 curl 出来的数字。

开发

npm test    # 45 个用例,Node ≥22.18 原生剥类型,无构建步骤

src/analyze.ts 是纯函数层(payload 解析、配置检查、四态判定、修复计划、过期检测、 断点注入),不 import 任何 Pi 运行时模块,测试直接跑。 extensions/cache-check.ts 只负责注册命令、事件钩子和读写文件。

License

MIT

About

Diagnose and fix prompt-cache failures when Pi talks to OpenAI-compatible relay APIs — /cache-check, /cache-fix, /cache-restore

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages