资讯动态

Claude Code 工程笔记:用 TaoToken 统一 Key 打通 Prompt Caching 优先的 Agent Harness(defer_loading、Plan Mode 与 Com

发布时间:2026/9/26 20:04:22 来源:尧图企业网站定制
1. 为什么 Claude Code 的 Agent Harness 必须围绕 Prompt Caching 来搭如果你正在用 Claude Code 跑长会话 Agent大概率遇到过两个现象一是聊到二三十轮之后首 token 延迟TTFT肉眼可见地变长二是账单里 input token 的数量远超你肉眼估算的对话长度。原因不复杂——每一轮请求都要把 tools 定义、system 指令、历史消息重新送进模型模型侧要重新算一遍这些前缀的注意力状态。Prompt Caching 要解决的就是这件事对匹配的 prompt 前缀复用已经算好的状态断点之后的内容才按未缓存输入计费。前缀按 tools → system → messages 的顺序形成任何更早一层发生变化后面全部失配。Claude Code 团队在工程复盘里把结论写得很直接——整套 harness 围绕 Prompt Caching 来建命中率掉了就按事故处理。这篇笔记聚焦三件事defer_loading、Plan Mode、Compaction 如何与缓存协作以及怎么用 TaoToken 统一 Key 把这条链路在本地跑通并观测。适合已经在写自建 Agent、或者正在用 Claude Code 做长期编码任务的人。读完你应该能独立完成说清 automatic / explicit 两种缓存启用方式从 usage 里读出 cache_creation_input_tokens 与 cache_read_input_tokens解释为什么中途增删工具会打穿缓存在自建 harness 里复现三条约束。2. 用 TaoToken 统一 Key 打通 API 通道在动手改 harness 之前先把 API 通道固定下来。多模型、多工具、多会话混跑时最怕的是 Key 散落在各处、base_url 一会儿一个联调时根本分不清是哪条通道出的问题。TaoToken 在这里的作用是提供一个统一的 Key 和 API 入口让 Claude Code 与自建脚本走同一条通道缓存行为才好对比。官网入口在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 基址是 https://taotoken.net/api 这个不加 UTM。你需要先去控制台拿 Key再把它写进环境变量避免硬编码进仓库。拿 Key 的路径是控制台里的 API Keys 页面对应 deep link 是 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面写了 base_url 与鉴权头的写法。如果你后面要跑长期编码或 Agent 任务可以看 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。环境变量这样设Linux / macOS 用 exportWindows 用 setxexport TAOTOKEN_API_KEYsk-你的key export ANTHROPIC_BASE_URLhttps://taotoken.net/api export ANTHROPIC_API_KEY$TAOTOKEN_API_KEY注意 base_url 不要带末尾斜杠SDK 拼接路径时容易出双斜杠。设完之后用一条最小请求验证通道是否通再往下做缓存实验否则缓存读写为 0 时你分不清是通道问题还是断点问题。3. 可复制的 settings.json 骨架与缓存优先布局Claude Code 的配置入口是 settings.json缓存相关的关键不在某个开关而在「静态段与动态段怎么排」。先把骨架钉死全局稳定的 system 与 tools 最前项目级约定比如 CLAUDE.md其次会话上下文再次真正轮次的 messages 最后。这样跨会话、跨用户也能尽量共享最前面的前缀。{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的key }, cache: { mode: automatic, ttl: 5m }, context: { staticSystemFirst: true, projectRulesFile: CLAUDE.md, dynamicInfoChannel: messages }, tools: { freezeOrder: true, deferLoadingStubs: true } }字段名以你实际使用的版本为准这里表达的是布局约束而不是某个版本的完整 schema。核心是三条staticSystemFirst 保证静态段在前freezeOrder 保证工具集合与顺序在整个会话生命周期内字节级一致dynamicInfoChannel 把日期、当前文件列表这类变化信息赶到 messages 里不要塞进静态 system。官方文档里的常见反例值得记一下系统上下文块 1-5后面跟一个带时间戳的用户块块 6却把 cache_control 放在块 6。每轮哈希都不同lookback 也找不到更早的写入点结果是每轮都在写、几乎不读。修法是把断点钉在最后一块跨请求不变的内容上。lookback 找的是「此前请求在断点处写下的条目」不是替你自动缓存断点前面看起来稳定的内容。4. 三条 harness 约束的落地写法4.1 Plan Mode用工具建模而不是换工具集「进入 Plan Mode 就换成只读工具」看起来干净但会改 tools 前缀整段会话缓存作废。Claude Code 的做法是工具定义始终在场EnterPlanMode / ExitPlanMode 本身就是工具。进入后靠系统侧注入的说明约束「只探索、不改文件」退出时再交计划。TOOLS [ { name: read_file, description: Read a text file from the workspace., input_schema: { type: object, properties: {path: {type: string}}, required: [path], }, }, { name: EnterPlanMode, description: Enter plan mode: explore only, no edits., input_schema: {type: object, properties: {}}, }, { name: ExitPlanMode, description: Exit plan mode after a written plan exists., input_schema: { type: object, properties: {plan: {type: string}}, required: [plan], }, }, ]附带收益是模型可以自己调用 EnterPlanMode 处理难题不必由宿主改请求体。同类模式可以推广到「只读审查」「发布冻结」等状态用进入/退出工具表达状态机执行策略写在消息里工具清单保持恒定。4.2 defer_loading短桩代替删除 MCP 工具MCP 一多每轮携带完整 schema 很贵中途删工具又会打穿前缀。defer_loading 的思路是请求里始终放同一批短桩通常先给名称并标 defer_loading: true需要时再通过 tool search 拉完整定义。短桩集合与顺序保持不变缓存前缀就稳。mcp_stubs [ { name: jira_search, description: Search Jira issues (full schema via tool search)., input_schema: {type: object, properties: {}}, defer_loading: True, }, { name: github_get_pr, description: Fetch a pull request by number., input_schema: {type: object, properties: {}}, defer_loading: True, }, ] tools TOOLS mcp_stubs需要某工具时由 tool search 把完整 schema 注入后续消息而不是改顶层 tools 数组。验收时盯两件事短桩集合在整个会话生命周期内是否字节级一致真正加载完整 schema 时是否只通过消息/工具结果通道进入而没有回头改 tools。4.3 Compaction复用父会话前缀提示放在最后一条 user上下文将满时要先把长历史送给模型做摘要。若另开请求、换一套「请摘要」system、还不带 tools前缀从第一个 token 就与父会话分叉长历史按全额未缓存输入计费。会话越长这次「为了省上下文」的调用越贵。COMPACT_PROMPT ( Summarize the conversation for handoff. Keep goals, decisions, open todos, and file paths. Omit prose fluff. ) def compact(parent_messages): messages list(parent_messages) [ {role: user, content: COMPACT_PROMPT} ] return turn(messages)从 API 视角这次请求几乎等于「父会话上一轮再多一条 user」因此可以吃到已有前缀缓存。关键是与父会话使用完全相同的 system、tools 定义和 cache_control不要另起「摘要专用」system。5. 验证请求与成功结果配置改完必须验证否则你只是在猜。下面这段脚本用 automatic 模式跑两轮观察 usage 字段的变化。import anthropic client anthropic.Anthropic() SYSTEM ( You are a coding agent. Prefer small, reversible edits. Do not invent file contents you have not read. ) def turn(messages, *, ttlNone): cache_control {type: ephemeral} if ttl: cache_control[ttl] ttl resp client.messages.create( modelclaude-opus-5, max_tokens1024, cache_controlcache_control, system[ { type: text, text: SYSTEM, cache_control: {type: ephemeral}, } ], toolsTOOLS, messagesmessages, ) u resp.usage print( { cache_write: u.cache_creation_input_tokens, cache_read: u.cache_read_input_tokens, input: u.input_tokens, output: u.output_tokens, } ) return resp history [{role: user, content: 先只读梳理 src/auth 的登录入口。}] r1 turn(history) history.append({role: assistant, content: r1.content}) history.append({role: user, content: 继续列出相关测试文件路径。}) r2 turn(history)第一轮常见形态是 cache_creation_input_tokens 0同前缀的后续轮次应看到 cache_read_input_tokens 上升。若读写都是 0先查最小可缓存长度与断点是否落在变化块上。总量约等于 cache_read cache_creation input 三者之和别把 input_tokens 当成总输入否则会出现「usage 很小但账单不小」的错觉。定价倍率按官方表核对5 分钟 cache write 1.25×1 小时 write 2×cache read 通常 0.1×。TTL 默认 5 分钟使用时刷新且不另收费从写入/读取请求开始时计时流式生成耗时也算进窗口。6. 本篇常见错排查断点钉在变化块上。时间戳、请求 ID、本轮用户原文若落在断点所在块lookback 找不到稳定写入点。断点应钉在跨请求不变的最后一块。中途增删或重排 tools。工具层在前缀最前任何改动使 tools/system/messages 整链失配。Plan Mode 与 MCP 都应绕开「改 tools 数组」。静态 system 里塞深度时间戳。一次看起来无害的时间注入就能让全局缓存失效。Compaction 另起炉灶。不同 system、空 tools 的摘要调用按未缓存全量计费。必须复用父前缀提示放在末尾 user。为省钱中途换模型。缓存按模型隔离十万 token 级会话切到小模型可能要重建整段前缀账单未必更低。JSON 键序不稳定。某些语言在序列化 tool_use 等结构时会打乱键顺序前缀哈希随之变化。序列化层要固定键序联调时用原始请求体做字节对比。并发首请求全 miss。缓存条目要等第一次响应开始之后才可被后续请求读到。若一上来就并行打多条同前缀请求可能全部 miss、全部写。预热或串行首请求更稳妥官方也提供 max_tokens: 0 的预热写法预热请求的 thinking / effort 配置要与正式流量一致。排障时如果怀疑是通道问题先回 API Keys 页面确认 Key 状态 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 再对照接入文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 检查 base_url 与鉴权头。想单独验证某个模型的行为可以用模型对话页面快速试一条请求 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 。长期跑编码或 Agent 任务走 Coding Plan 更省心 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。落地时用三条验收线就够同一会话第二轮起 cache_read_input_tokens 是否稳定上升切换 Plan Mode / 加载 MCP 时 tools 数组是否仍字节一致Compaction 请求的 system tools 是否与父会话相同。把缓存命中率当成和 uptime 同级的指标harness 才会从文档参数变成账单和 TTFT 上稳定可测的改善。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑