alxsuv/pino

GitHub: alxsuv/pino

一个专为 Claude Code 设计的本地反向代理,通过自动注入缓存断点、升级 TTL 和裁剪冗余工具,将 Anthropic API 的输入成本降低约 90%。

Stars: 3 | Forks: 1

Pino proxy # Pino proxy [![License](https://img.shields.io/github/license/alxsuv/pino)](./LICENSE) [![Node](https://img.shields.io/badge/node-%E2%89%A5%2020-43853d?logo=node.js&logoColor=white)](https://nodejs.org) [![Zero dependencies](https://img.shields.io/badge/dependencies-0-brightgreen)](./package.json) [![GitHub stars](https://img.shields.io/github/stars/alxsuv/pino?style=social)](https://github.com/alxsuv/pino/stargazers) [![Saves ~90% on Claude Code API](https://img.shields.io/badge/Claude%20Code%20API-~90%25%20saved-blueviolet?style=for-the-badge&logo=anthropic&logoColor=white)](#savings-math)
一个运行在 `api.anthropic.com` 前端的微型本地 HTTP 反向代理。它会原封不动地转发所有请求到上游,**除了**针对 `/v1/messages` 的请求,它会选择性进行以下操作: - **自动注入 prompt-cache 断点**,作用于 Claude Code 未缓存的数据块 —— 最重要的是 `tools`(约 24k tokens,开箱即有 0 个断点)以及 `messages[0]` 中的静态提醒块。 - **将 TTL 升级至 1 小时**,作用于不常变化的可缓存内容。Claude Code 确实在 system prompt 上设置了 `cache_control: {type: "ephemeral"}`,但**省略了 `ttl` 字段** —— 这会静默回退到新的 5 分钟默认值,因此一次需要几分钟阅读的深思熟虑的交互,就可能在下一个交互时超出缓存窗口并重新支付 1.25 倍的写入费用。该代理将每个 ephemeral 断点重写为 `ttl: "1h"`(除了滚动尾部,它被刻意保留为 5 分钟,以免您为每个交互都在移动的断点支付过高的 2.0 倍写入倍数)。 - **丢弃未使用的工具**,并从系统提醒中清除它们的名称,从而缩减请求大小。 - **剥离 ANSI 转义码**,使工具结果中的终端输出能被干净地缓存。 - **使 system prompt 可编辑** —— 请求体的任何部分都可以通过用户自定义的 `transform(body)` 钩子进行重写。没有内置的 system prompt 修改开箱即用;该钩子是扩展点。 主要为 **Claude Code** 设计。在 Claude Code 中,相同的约 24k token 工具目录在每个交互中未缓存发送,而约 8k token 的系统提示使用的是脆弱的 5 分钟计时器 —— Claude Code 的 `cache_control` 省略了 `ttl` 并静默回退到 5 分钟默认值,单次深思熟虑的交互(长生成、慢工具调用、用户阅读输出)就足以让缓存过期。 ### 真实会话证明 通过 pino-proxy 代理的两个连续 API 请求。数字是 Anthropic API 响应中原始的 `usage` 字段: ``` # Turn N # Turn N+1 input_tokens: 6 input_tokens: 6 cache_read_input_tokens: 83_324 cache_read_input_tokens: 83_910 ← the previous tail, cached cache_creation: cache_creation: ephemeral_5m_input_tokens: 586 ephemeral_5m_input_tokens: 252 ← only the moving delta written ephemeral_1h_input_tokens: 0 ephemeral_1h_input_tokens: 0 output_tokens: 195 output_tokens: 802 ``` 此配对的 Opus 定价:**使用代理约 $0.34 vs 不使用约 $2.60**(输入 + 输出,根据上面的 `usage` 数字计算) —— 5 分钟的滚动尾部加上对 tools / system / reminders 的 1 小时缓存发挥了主要作用。有关详细明细,请参阅[节省金额计算](#savings-math)。 ## 快速开始 ### 1. 克隆并安装 ``` git clone https://github.com/alxsuv/pino cd pino ``` 不需要 `npm install` —— 零运行时依赖。需要 Node >= 20。 ### 2. 启动代理 **Linux / macOS (bash/zsh):** ``` # 在 :8787 上纯透传 npm start # 典型 dev 设置:auto-cache + transforms + logs AUTO_CACHE=1 \ TRANSFORM_FILE=./src/transforms/default.js \ DROP_TOOLS=NotebookEdit,CronCreate,CronDelete,CronList,RemoteTrigger,PushNotification,Monitor \ LOG_BODIES=1 \ npm start # 或直接调用 bin node bin/pino-proxy.js ``` **Windows (PowerShell):** ``` # 在 :8787 上纯透传 npm start # 典型 dev 设置:auto-cache + transforms + logs $env:AUTO_CACHE=1 $env:TRANSFORM_FILE="./src/transforms/default.js" $env:DROP_TOOLS="NotebookEdit,CronCreate,CronDelete,CronList" $env:LOG_BODIES=1 npm start # 或直接调用 bin node bin/pino-proxy.js ``` **Windows (cmd.exe):** ``` :: pure pass-through on :8787 npm start :: typical dev setup: auto-cache + transforms + logs set AUTO_CACHE=1 set TRANSFORM_FILE=./src/transforms/default.js set DROP_TOOLS=NotebookEdit,CronCreate,CronDelete,CronList set LOG_BODIES=1 npm start ``` ### 3. 将您的客户端指向它 **Linux / macOS:** ``` export ANTHROPIC_BASE_URL=http://127.0.0.1:8787 ``` **Windows (PowerShell):** ``` $env:ANTHROPIC_BASE_URL="http://127.0.0.1:8787" ``` **Windows (cmd.exe):** ``` set ANTHROPIC_BASE_URL=http://127.0.0.1:8787 ``` ## 如何验证 (确凿证据) 您不必只听我的一面之词。您可以在 60 秒内看到 Claude Code 实际发送的内容: 1. **以直通模式启动代理**(捕获日志但暂不改变任何内容): LOG_BODIES=1 npm start 2. **在 Claude Code 中运行任意命令**(例如,`claude "hi"`)。 3. **检查捕获的请求**: # 1. Tools:零个 cache_control 条目。约 24k token 的工具目录未被缓存。 jq '[.body.tools[] | select(.cache_control)] | length' logs/*.req.json # 2. System:cache_control 已设置,但缺少 ttl → 静默默认为 5 分钟。 jq '.body.system[]? | select(.cache_control) | {len: (.text|length), cache_control}' logs/*.req.json 预期输出:tools 计数为 `0`,并且每个 system `cache_control` 都是纯粹的 `{"type":"ephemeral"}`,没有 `ttl` 键。这种纯粹的形式意味着 **5 分钟** —— 这个时间长得足以让一次深思熟虑的交互使缓存过期,并在下一个交互时强制重写。 4. **现在,启用修复**: 使用 `AUTO_CACHE=1` 重启代理: AUTO_CACHE=1 LOG_BODIES=1 npm start 对新的 `logs/*.req.json` 重新运行相同的 `jq` 查询,您将看到断点被添加到 `tools`,每个 ephemeral 被重写为 `ttl: "1h"`(除了滚动尾部),并且 `anthropic-beta` 带有 `extended-cache-ttl-2025-04-11`。 5. **运行另一个命令并观察命中情况**: 您的 Anthropic API 仪表板(或响应中的 `usage` 字段)现在将显示 `cache_read_input_tokens` 捕获了约 90% 的输入费用。 ## 缓存工作原理 Anthropic API 允许每个请求最多 **4 个缓存断点**。每个断点告诉 API “缓存至此(包含此块)的所有内容”,这样具有相同前缀的后续请求将以 0.1 倍的基础输入价格命中缓存,而不是 1 倍。 此代理在(4 个槽位的限制内)如下放置它们: 1. **最后一个 `tools` 条目** → 1 小时 TTL。Claude Code 在 `tools` 上发送 **零个** 断点,因此整个约 24k token 的目录在每个交互中都会按完整输入价格重新计费。这是最大的一项收益。 2. **最后一个 `system` 块** → 1 小时 TTL。Claude Code 的 system prompt 大约是 8k tokens,并且在数小时内保持稳定。Claude Code 确实在 system 块上设置了 `cache_control`,但没有 `ttl`,因此会回退到 5 分钟的默认值 —— 这个时间长得足以让一次深思熟虑的交互使窗口过期,并在下一个交互时对所有约 8k tokens 强制收取 1.25 倍的重写费用。代理将其重写为 1 小时(并去除小于 500 个字符的 system 块上浪费的断点 —— Claude Code 在一个约 57 个字符的块上放置了一个,消耗了一整个槽位只为缓存约 15 个 tokens)。 3. **`messages[0]` 的最后一个可缓存块** → 1 小时 TTL。Claude Code 将静态提醒(CLAUDE.md、技能目录、延迟工具列表 —— 约 5k tokens)塞入第一个用户消息。每个会话缓存一次。 4. **滚动尾部** → 默认 5 分钟 TTL(可通过 `TAIL_TTL=1h` 配置)。所有消息中最后一个 `text` / `tool_result` / `image` 块。每个交互都会移动,因此每个新交互都会从缓存中读取上一个交互的前缀,并仅为增量部分支付基础价格。5 分钟是合适的默认值,因为尾部很少能在重复使用中存活一小时;为不断移动的断点支付 2.0 倍的写入倍数是浪费的。 在注入之前,代理还会 **剥离客户端在小于 500 个字符的 system 块上发送的断点** —— 缓存约 125 个 tokens 会消耗一整个槽位,而这个槽位更好地花在 `messages[0]` 提醒上。 ## 节省金额计算 Anthropic 定价乘数(均相对于基础输入): | 操作 | 乘数 | |-----------------------------------|------------| | 基础输入 (未缓存) | 1.0× | | 缓存写入, 5 分钟 TTL | 1.25× | | 缓存写入, 1 小时 TTL | 2.0× | | 缓存读取 (命中) | 0.1× | ### 每次 API 调用的节省 (Claude Sonnet,每百万输入/输出 token $3 / $15) 下面的“交互”是指**一次到 `api.anthropic.com` 的 HTTP 往返**,而不是一条用户消息。(一条用户消息会扩展为许多次往返 —— 参见下面的[往返倍数](#round-trip-multiplier-the-bigger-win))。数字反映了典型的 Claude Code 请求,即会话中期的稳定状态: | 数据块 | Tokens | 无代理 | 有代理 (缓存命中) | 节省 | |------------------------------------------------------|---------|---------------|------------------------|--------------------| | `tools` | 24,400 | $0.0732 | $0.00732 | **$0.0659** | | `system` | 8,200 | $0.0246 | $0.00246 | **$0.0221** | | `messages[0]` 提醒 | 5,000 | $0.0150 | $0.00150 | **$0.0135** | | 上一轮历史记录 (滚动尾部) | ~15,000 | $0.0450 | $0.00450 | **$0.0405** | | **每次调用总计 (输入)** | ~52,600 | **$0.158** | **$0.0158** | **约 $0.142 (−90%)** | 首次交互需支付**缓存写入附加费**:`(24.4k + 8.2k + 5k) × $3 × (2.0 − 1.0) / 1M = $0.113` 额外费用。在**第 2 次交互**时回本;之后的每次交互都是纯利润。 ### 往返倍数 (更大的收益) 上面每次调用的节省看起来已经很不错了 —— 但在实践中,它们会乘以每条用户消息的往返次数,而这个数字才是让账单变得吓人的原因。 Anthropic API 是**无状态**的:每个 HTTP 请求都带有完整的对话上下文(system + tools + 先前消息 + 先前工具结果)。Claude Code 的循环也会**每次工具调用进行一次往返** —— Read、Grep、Edit、Bash、另一个 Read 等等。因此,像“修复这个 bug”这样的单个用户提示通常会扩展为: - 小任务约 10-20 次往返 - 典型的调试或功能实现消息约 30-50 次 - 长时间自主任务或 `/plan` 风格的多阶段运行 100 次以上 这些往返中的每一次都会以完整的输入价格(无断点)重新发送约 24k token 的工具目录,并冒着 system prompt 无 TTL 的 5 分钟窗口过期的风险(1.25 倍重写)。 **在三种往返量级下的实际费用**(上面表格中的每次调用总计 × N,包括一次性的缓存写入附加费): | 量级 | 定价层 | 无代理 | 有代理 | 节省 | |-----------------------------------------------------|---------------------|---------------|------------|--------------| | 10 次往返 (小任务) | Sonnet ($3/M input) | ~$1.58 | ~$0.27 | **约 $1.31** | | | Opus ($15/M input) | ~$7.90 | ~$1.36 | **约 $6.54** | | 30 次往返 (典型用户消息) | Sonnet | ~$4.74 | ~$0.59 | **约 $4.15** | | | Opus | ~$23.70 | ~$2.94 | **约 $20.76** | | 100 次往返 (重度会话) | Sonnet | ~$15.80 | ~$1.69 | **约 $14.11** | | | Opus | ~$79.00 | ~$8.46 | **约 $70.54** | 数字仅为输入费用;输出费用不变(输出不被缓存)。缓存写入附加费(Sonnet 约 $0.11 / Opus 约 $0.57)在每个缓存窗口只支付一次,而不是每次往返支付一次 —— 因此在保持在 1 小时 TTL 内的会话中,您只需在开始时支付一次,随后的每次往返都是纯粹的 0.1 倍缓存读取。 **为什么缓存胜过裁剪。** 裁剪工具目录(例如,`DROP_TOOLS=…` 削减约 3k tokens)可以在没有缓存的情况下为您节省 `3k × N 往返` 的费用。有了缓存,它只能为您节省 `3k × 1 次缓存写入 + 3k × (N−1 次缓存读取` —— 收益大约小了一个数量级。缓存是承重的优化;裁剪是在缓存到位后有用的附加项。 ### 请求大小节省 (`DROP_TOOLS` + ANSI 剥离) 独立于缓存,请求体修改减少了网络传输大小: - `DROP_TOOLS=NotebookEdit,CronCreate,CronDelete,CronList,RemoteTrigger,PushNotification,Monitor` → 每次交互丢弃约 **3,300 tokens**。 - `STRIP_ANSI=1` → 从 `/context` 输出、终端颜色等中剥离 SGR 转义码。在受影响的交互中,大致将 `tool_result` 大小减半(约 500-2,000 tokens)。 - `TRIM_BASH_GIT=1` → 删除 Bash 工具描述的 git-commit 和 PR-creation 子部分。如果您不在 Claude Code 中使用 git,则可节省约 1,800 tokens。 ### Claude Code 工具丢弃参考 Claude Code 附带了一个庞大的工具目录。并非每个会话都使用每个工具,您丢弃的每一个工具都会在每次交互中从工具 schema 中削减约 100-800 个 tokens。下面是一份实用的速查表,说明每个工具的作用以及通常是否可以安全丢弃。 图例:🟢 如果不使用可安全丢弃 · 🟡 有附加条件地丢弃(功能会消失) · 🔴 不要丢弃(没有它 Claude Code 会出错) | 工具 | 作用 | 是否丢弃? | |------------------------------------------------------|------------------------------------------------------------------|-------------------------------------------------------------------------------------| | `Bash` | 运行 shell 命令。 | 🔴 保留。几乎所有操作的核心。 | | `Read` | 读取本地文件(文本、图像、PDF、笔记本)。 | 🔴 保留。 | | `Edit` | 在现有文件中进行精确字符串编辑。 | 🔴 保留。 | | `Write` | 创建或覆盖文件。 | 🔴 保留。 | | `Glob` | 按模式查找文件。 | 🔴 保留。比通过 Bash 使用 `find` 便宜得多。 | | `Grep` | 搜索文件内容 (ripgrep 封装)。 | 🔴 保留。比通过 Bash 使用 `grep -r` 便宜得多。 | | `TaskCreate` / `TaskUpdate` / `TaskList` / `TaskGet` | 在持久任务列表中跟踪多步骤工作。 | 🟡 如果您不想要任务跟踪则可丢弃 —— Claude 也会停止通过 todos 进行规划。 | | `TaskOutput` / `TaskStop` | 检查/终止后台 `run_in_background` 任务。 | 🟡 仅当您也不运行长时间后台命令时才丢弃。 | | `AskUserQuestion` | 带预览的结构化多项选择题。 | 🟡 丢弃以强制使用自由文本进行澄清。 | | `EnterPlanMode` / `ExitPlanMode` | 规划模式工作流(先设计后实现)。 | 🟡 如果您从不使用 `/plan` 则丢弃。Claude 将以纯文本形式进行规划。 | | `EnterWorktree` / `ExitWorktree` | 为隔离工作创建/退出 git worktrees。 | 🟢 除非您积极使用 worktrees 否则丢弃。 | | `NotebookEdit` | 编辑 Jupyter `.ipynb` 单元格。 | 🟢 除非您使用笔记本否则丢弃。 | | `WebFetch` | 获取 URL 并总结其内容。 | 🟡 如果您从不需要网络查找则丢弃 —— 会破坏文档获取。 | | `WebSearch` | 搜索网络 (仅限美国)。 | 🟡 如果您不需要实时网络信息则丢弃。 | | `CronCreate` / `CronDelete` / `CronList` | 在 cron 上调度提示;默认仅限会话内。 | 🟢 除非您使用会话内调度否则丢弃。 | | `Monitor` | 后台事件流观察器(尾部日志、轮询 API)。 | 🟢 除非您需要实时监控否则丢弃。 | | `PushNotification` | 通过 Remote Control 推送桌面/移动通知。 | 🟢 丢弃。很少需要。 | | `RemoteTrigger` | 调用 claude.ai remote-trigger API (例程/计划)。 | 🟢 除非您管理计划的远程代理否则丢弃。 | | `Skill` | 调用命名技能 (`/skills`)。 | 🟡 仅当您从不使用技能或斜杠命令时才丢弃。 | | `mcp__ide__getDiagnostics` | 拉取 IDE 诊断(仅在附加了 IDE 扩展时出现)。 | 🟢 如果您不使用 IDE 扩展则丢弃。 | 保守的起步设置:`DROP_TOOLS=NotebookEdit,CronCreate,CronDelete,CronList,RemoteTrigger,PushNotification,Monitor,EnterWorktree,ExitWorktree`。 在一次交互后检查 `logs/*.req.json`,看看您的客户端实际发送了哪些工具 —— 目录因 Claude Code 版本和您加载的 MCP 服务器而异。 ## 环境变量 | 变量 | 默认值 | 作用 | |------------------|----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `PORT` | `8787` | 要绑定的本地端口(始终为 `127.0.0.1`)。 | | `AUTO_CACHE` | 关闭 | 启用缓存断点注入 + 1 小时 TTL 重写 + beta header。 | | `TAIL_TTL` | `5m` | 滚动尾部断点的 TTL。`5m`(推荐)或 `1h`。其他槽位始终为 1 小时。 | | `TRANSFORM_FILE` | — | 导出 `transform(body)` 用于自定义请求体修改的 JS 模块的路径。 | | `DROP_TOOLS` | — | 从 `body.tools` 中移除的、逗号分隔的工具名称 *(需要 `TRANSFORM_FILE=./src/transforms/default.js`)*。 | | `STRIP_ANSI` | `1` | 从消息文本和工具结果中剥离 ANSI 转义序列。设置为 `0` 以禁用。 | | `TRIM_BASH_GIT` | `0` | 在“Committing changes”部分截断 Bash 工具描述。 | | `MODEL_OVERRIDE` | — | 对每个 `/v1/messages` 请求强制使用不同的模型(例如,`claude-opus-4-6`)。同时重写 `system` 块中的模型名称引用,以便模型的自我描述保持一致。 | | `LOG_BODIES` | 关闭 | 将修改后的请求 JSON + 原始响应字节转储到 `LOG_DIR`。 | | `LOG_DIR` | `./logs` | 写入请求体转储的位置。 | ## 30 秒架构概述 ``` bin/pino-proxy.js # CLI entry (shebang) src/server.js # HTTP server + request handler, exports startServer() src/config.js # env parsing, constants, transform loader src/cache.js # breakpoint inject/rewrite, beta header src/model.js # model-name rewrites for system-prompt self-description src/logger.js # timestamps, sanitizers, request/response log writers src/transforms/default.js # default body mutator (env-driven tools/ANSI stripping) ``` - `src/server.js` — 位于 `127.0.0.1:$PORT` 的 HTTP 服务器。缓冲请求体,解析匹配路径上的 JSON,运行 transform → inject → rewrite → beta-header 管道,流式传输响应。 - `src/transforms/default.js` — 默认的请求体修改器。处理工具丢弃、ANSI 剥离和上下文重构。 - 日志记录:`LOG_BODIES=1` 为每个请求写入 `.req.json`(修改后,凭据已隐去)+ `.resp.log`(原始响应)。 有关完整的内部机制、操作顺序、注意事项以及扩展转换管道的指南,请参见 [CLAUDE.md](./CLAUDE.md)。 ## 注意事项 - 代理仅绑定到 `127.0.0.1` —— 其他主机无法访问。 - Header 按原样透传:`x-api-key` / `authorization` 原封不动地发送到上游(仅在日志中隐去)。 - 节省金额计算是说明性的 —— 实际数字取决于您的使用模式、模型选择以及您的上下文在多次交互间的稳定性。
标签:AI大模型, ANSI转义码清理, Anthropic API, API成本优化, API流量管理, API网关, Claude Code, GNU通用公共许可证, HTTP代理, LLM, MITM代理, Node.js, Prompt缓存, Unmanaged PE, 中间人代理, 工具过滤, 数据清洗, 缓存注入, 网络安全, 自定义脚本, 请求转换, 逆向代理, 防御绕过, 隐私保护, 零依赖