pi-cache-stats1.5.0
Pi Agent extension for prompt-cache observation and prefix diagnostics
Untested on omp
This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.
Reports 0
No reports yet.
Readme
pi-cache-stats
English · 中文文档
Pi Agent 扩展:观测 prompt cache 命中率并提供只读前缀变化诊断。不会冻结、重排或删减 system prompt 与请求 payload。
Pi Agent 的事件系统与扩展 API 已经可用。服务端缓存仍取决于渲染前缀稳定性、路由、TTL 和工作负载。本扩展按 Pi 净输入口径记录 usage,并可在 TUI 显示页脚。它不会在 provider 本身没有缓存时“创造”缓存,也不会静默丢掉新的系统规则、技能或 project_instructions 边界。
Development conventions
详细的开发与协作规范维护于以下文档:
- AGENTS.md:Agent 协作铁律、真实构建/测试命令与仓库索引。
- .agents/notes/README.md:决策笔记触发条件、豁免清单与维护规范。
核心机制
1. 安全观测(默认)
每个 factory 实例持有自己的统计、启停、footer 句柄和未分类 400 列表。没有模块级 golden prompt,也不会回滚 systemPrompt。
每轮都原样保留传入的 systemPrompt。新工具、资源发现、前序扩展规则和后序扩展改写都会到达模型。
扩展不会插入或覆盖 prompt_cache_key,不会改 tools/messages/system 的 TTL,也不会在导入、enable、disable 时改 PI_CACHE_RETENTION。
session_start(new / resume / fork / switch / reload)只重置本实例当前运行区间的计数,不回放历史 custom entry。
2. 未分类 400
只有 status/headers 的 400 记为 unknown,不改变后续缓存策略。扩展 API 不暴露响应正文,因此不会猜测被拒绝的字段。
3. 缓存统计
启用期间记录每轮 cacheRead / cacheWrite / input。/cache-stats 查看合计。Pi 的 usage.input 在所有 API 下都是净输入,命中率为 cacheRead / (input + cacheRead + cacheWrite)。多轮聚合为 sum(cacheRead) / sum(分母)。逐轮明细有界,累计合计不丢。
/cache-stats 同时打印两个口径,避免与 footer 数字打架:
Aggregate hit ... (current run; ...)— 本进程自session_start以来的区间(state.snapshot口径,逐轮报告用它)。Session aggregate ... (all session, footer scope / post-reset window; ...)— 完整会话条目(或/cache-stats reset之后的窗口),与 footer◆累计项同一算法。reset 后 footer 只统计 reset 后追加的条目,标签会注明这一点。
逐轮明细显示每轮的实测命中率。若扫描的会话窗口从未出现缓存交互,Per-turn 显示 n/a(绝不伪造 0%)。一旦会话中曾出现缓存交互,denom > 0 但无缓存交互的轮次显示 0%。无 usage 或分母为零的轮次仍显示 n/a。
诊断里的字符串长度是字符数,不是 UTF-8 字节或 token。
4. 前缀变化诊断(默认开启,只读)
在本扩展的 before_provider_request hook 上,对当前 payload 快照与同一实例、会话、provider、API、模型、endpoint 的上一份摘要做比较。endpoint 隔离会在内存中对完整 baseUrl 字符串做实例内带盐指纹(协议、host/端口、路径、query、userinfo 的差异会保留,但不会打印原文)。该指纹不能证明真实后端路由。只报告固定类别(system / developer / instructions / tools)、状态(baseline / stable / changed / unknown / skipped / disabled)以及长度或数量。空区段与缺失、超限区段分开;缺失或超限不会标成稳定。工具数组按发送顺序比较(不会为诊断排序)。不可信 payload 只读取自有数据属性:不求值访问器,用 Node util.types.isProxy 识别并跳过 Proxy(不跑陷阱),也不执行 payload 自带的 Symbol.iterator / toJSON。诊断 system / developer 时不读取历史 user / assistant / tool 正文(只看必要的 role/类型/位置元数据)。宿主 ctx / model / sessionManager.getSessionId 按契约调用;宿主抛错则这次观察 skipped,不改请求、不泄漏异常文本。进入遍历、指纹、拼接或 scope 组成的可变字符串(含字段名)会在昂贵处理前计入明确的长度/总量预算;超限或不安全观察不会标成 stable。这是 JSON-like 数据的观测上限,不是对任意恶意 JS 的沙箱。
必要身份必须是长度上限内的非空字符串(ctx、model 的 provider/api/id、session id、baseUrl)。任一不可信则这次观察为 skipped,不写入任何 scope 摘要,并清除已有比较链(最多 8 条),下一次有效上下文从 baseline 开始。缺失/空串/非字符串不会降成共享空 key 或 endpoint:none。已知 scope 里 payload 超限/不支持仍只按该 scope 断链。
区段指纹保留请求形状、string 与 block-array 表示、目标消息数量/顺序/原数组位置与角色、内容块类型/边界/顺序。每个 chunk 在字符之前混入固定宽度的 32 位 UTF-16 长度(低 16 位再高 16 位,高位为 0 也编码);分隔仍是 mix(31)。该摘要只作内部非认证用途:修掉长度同构不等于有限 hash 再无碰撞,也不会持久化或当 cache key。已识别的 shape(openai-chat / openai-responses / anthropic)作为有界原语留在比较快照里:同一 scope 下两个可比较快照 shape 不同则为 changed。shape 不塞进 scopeKey,也不会靠改写 ctx.model.api 制造新 scope。合并后的纯文本不是比较键。在最后一个目标消息之后追加历史、或只改非目标正文,不会因此把指令标成 changed。互斥自有字段混合(messages 与 input 或 instructions 同时存在,含 input 为字符串/空串/null/自有 undefined)标 unknown,不会丢掉字段后局部 stable。Anthropic 的 system+messages、Responses 的 instructions+input(数组或字符串)仍合法。unknown / skipped 不按 shape changed 比较,下一次有效上下文为 baseline。不会用厂商名或模型名猜协议。
这不是缓存失效结论、命中率或省 token 数字。它不等于最终 HTTP body(后序扩展仍可改 payload),也不等于服务端 token 前缀或缓存状态。字符数不是 token。可比较字段稳定并不表示全文被冻结。离线 CPU 改进也不等于缓存命中或费用收益。
已知形状按本机 Pi SDK 实际使用的 payload 结构识别(OpenAI chat 的 messages system/developer、OpenAI Responses 的 instructions / input system/developer、Anthropic 的 system + tools)。历史对话、reasoning、工具结果本轮不采集。若能在已提取的 system 文本里识别恰好一块形态完整且唯一的 Pi 标签块,会额外给出长度摘要。支持的 tag-like 形式仅限:精确 <project_instructions> / </project_instructions>,或 <project_instructions 后接 ASCII 空白(空格/制表/LF/CR)再跟属性并以 > 结束;以及精确 <available_skills> / </available_skills>(不带属性)。扫描要求单一开标记再闭标记的次序:首部/块间/尾部的孤立闭标记、第二处开标记、嵌套、截断、或不受支持的名字后缀(. / : / 非 ASCII / 其它)保持 unidentified,不能冒称完整目标块。这不是通用 XML。extras 扫描是单调前进的线性扫描,不是回溯正则,也不会改写输入。
用 PI_CACHE_STATS_PREFIX_DIAGNOSTICS=0 关闭。/cache-stats disable 以及 reset / session_start 也会停止更新并清理比较状态。未知或不安全的 payload 原样放行。观测不会插入 cache key / TTL / retention,也不会改请求对象。未分类 400 与统计里来自 provider/model 的展示片段会做有长度上限的控制字符净化,只作用于展示,不用清洗后的显示名当内部身份键。
仍以 Pi 原生行为为先,未知端点尊重宿主/用户配置。本树不硬编码厂商能力表;离线测试通过不等于服务端已支持缓存,也不等于实测省钱。
5. 实时 TUI 页脚(默认开启)
在 TUI 模式下,footer 采用与 Pi 官方内置 footer 一致的「渲染时实时计算」方式,不再依赖事件快照:
- 渲染时实时取数:命中率在
render()执行时动态遍历sessionManager.getEntries()实时汇总与提取最新轮次,上下文窗口占用直接调用getContextUsage()实时获取。 - 命中率双显示:命中率采用双菱形图标先累计、后实时的格式:
◆ <累计>% ◇ <实时>%(例如◆ 87% ◇ 95%)。其中◆累计项保持常规文本颜色,◇实时最新一轮项使用 warning 警示色。扫描窗口内从未出现缓存交互时(所有条目 cacheRead=0 且 cacheWrite=0,例如不支持 prompt caching 的 provider)两项都显示n/a,绝不伪造 0%。而当窗口内曾出现缓存交互后,最新一轮本身没有缓存交互时显示◇ 0%(该轮已被测量,只是没走缓存);若最新一轮完全没有usage字段(中断/报错/注入消息),则显示n/a而不是残留上一轮数值。◆累计口径不变,且另需分母可计算。 - 上下文占用分色:
▲上下文段仿官方阈值——>90%用 error 色,>70%用 warning 色,其余保持默认文本色;其它 footer 段颜色不受影响。 - 最近一轮生成速度(
▸ t/s):在▲上下文与■模型之间显示最近一次 provider 生成的估算速度,格式▸ <n> t/s(例如▸ 45 t/s)。TPS 采用流式解码窗口口径(从message_update首个非空 delta 到done/message_end,剔除 TTFT 首字预填充与工具执行耗时;同时具备思考与正文 delta 且usage.reasoning > 0时剥离思考窗口,并经四重合理性守卫校验),无流式 delta 事件时自动回退至before_provider_request -> turn_end端到端口径。无打点、无 assistantusage、output非有限正数、任一守卫未通过或尚无完成轮次时显示 dim 的n/a;reset/session_start会清零,不跨会话残留。 - 刷新时机与节流:高频事件(
turn_end、tool_execution_start、tool_execution_end)触发 16ms TUI 渲染节流刷新,低频事件(agent_end、model_select、thinking_level_select、/cache-stats reset)保持 force 强制重绘。 - 脏检查缓存:实时命中率扫描带脏检查缓存,仅在条目数变化时重算;
/cache-stats reset与会话切换会使缓存失效(reset 会记录水位标,footer 只统计 reset 后追加的条目,无新条目则显示n/a;若条目列表比水位标还短,走安全的n/a回退)。上下文占用每次渲染都实时获取,不做缓存。 - 单入口累加:token usage 在
turn_end单入口累加至liveRun缓冲区并于agent_end结算,命令端统计与实时 footer 各司其职且不双计。
安装
npm(推荐)
pi install npm:pi-cache-stats
Pi 自动安装并加载,无需设置变更。
直接复制
git clone https://github.com/dorokuma/pi-cache-stats.git
cd pi-cache-stats
bash integrations/pi/install.sh # 或:pnpm sync
install.sh 幂等:部署副本与仓库源 md5 一致时报告 unchanged,不重写目标(mtime 不变)。失败显式而非静默:md5sum 缺失、HOME 未设置(仅在未指定 PI_EXT_DEST 时校验)、目标非绝对路径或为目录,均向 stderr 打印 install.sh FAILED: ... 并非零退出。目标可用 PI_EXT_DEST=/abs/path/to/cache-stats.ts 覆盖。pi 仅在会话启动时加载扩展——之后需 /reload 或新会话生效。本脚本只覆盖「仓库源文件 → 本机扩展目录」这条链路;上方的 pi install npm:pi-cache-stats 装的是 npm 发布版,不经此脚本。
环境变量
| 变量 | 默认值 | 说明 |
|---|---|---|
PI_CACHE_STATS_VERBOSE |
0 |
即使当轮无缓存交互也写入 custom turn 条目 |
PI_CACHE_STATS_FOOTER |
启用 | TUI 下默认启用自定义 footer(长任务期间实时刷新);0 / false 关闭 |
PI_CACHE_STATS_PREFIX_DIAGNOSTICS |
启用 | 本扩展 hook 上的只读前缀变化快照;0 / false / off / no 关闭 |
说明:
compactionCacheLoss字段已移除。Pi 扩展 API 没有可靠的 compaction 事件来累计该值。
命令
/cache-stats # 显示缓存统计(当前运行区间 + 全会话两个口径,含一行前缀状态摘要)
/cache-stats prefix # 查看最近一次前缀变化快照(只含类别/状态/长度)
/cache-stats disable # 禁用本实例(不再写统计、不再装 footer、不更新前缀诊断)
/cache-stats enable # 重新启用(仅在 TUI 且 FOOTER 开启时装 footer)
/cache-stats reset # 重置当前运行区间统计、未分类 400 列表和前缀比较状态;将 footer 实时视图重置为只统计 reset 之后追加的条目,并刷新 footer
测试
npm test
npm test 完全离线:加载被测扩展、驱动 Pi 事件、跑隔离的 AgentSession 工具集路径,并自检 comparison 辅助逻辑。不读取 ~/.pi/agent、用户凭证,也不调用模型 API。
若要测本机宿主 SDK 而不是项目锁定的 0.84.2:
PI_CACHE_STATS_TEST_SDK=/path/to/@earendil-works/pi-coding-agent npm test
真实网络 A/B 必须显式选择(不在 npm test 中):
PI_CACHE_STATS_BENCH=1 \
PI_CACHE_STATS_BENCH_PROVIDER=... \
PI_CACHE_STATS_BENCH_MODEL=... \
PI_CACHE_STATS_BENCH_AGENT_DIR=/explicit/isolated/agent-dir \
node test/comparison.mjs
详见 docs/testing-methodology.md 与 docs/audit-remediation.md。
参考
- OpenAI Prompt caching:前缀匹配、路由 key、保留期与成本
- Anthropic Prompt caching:缓存块、TTL、成本与顺序约束
- DeepSeek Context caching:自动前缀缓存(尽力而为)