Anthropic 官方博客分享如何通过任务分解、上下文管理和工具调用策略最大化 Claude Code 产出。
在任务之间运行 /clear。这可以防止之前不相关的上下文被发送回模型,从而减少 token 使用量。
在开始之前设置好模型和努力级别(effort level)。在对话中途更改任何一个都会破坏提示缓存,这会增加 token 成本。
使用 @-mention 文件而不是直接命名文件。文件会直接附加到你的消息中,这样可以节省一次 Read 调用,如果 Claude 不得不去搜索文件的话还能节省一次搜索。
给有噪音的命令添加 quiet 标志,或者在子代理中运行。命令输出和文件一样会被添加到对话中,并保留在会话的剩余时间里。
在新的会话中运行一次 /context。它会显示已加载的内容(CLAUDE.md、MCP 工具定义),这样你就可以删掉任何不必要的部分。
在离开键盘休息之前运行 /compact。提示缓存在一小时后过期,趁对话还在缓存中时总结,成本要低得多。
直到最近,你编写代码所使用的工具都是固定费用(或免费的)。无论你那天下午修复了一个测试还是五十个,你的编辑器成本都是一样的,所以单个任务本身并没有真正的价格。
但使用像 Claude Code 这样的代理编程工具就不同了。相同的已完成任务也可能花费不同,取决于你如何使用它。
在一个会话中,Claude 读取测试和它覆盖的文件,进行编辑,然后在几个回合内完成。在另一个会话中,它先在仓库里 grep,找到同一文件的路上读取了十几个文件,而且每一个回合都携带着那天早上以来读取的所有其他内容。

同样的修复,但你花在了上面,但整个过程中模型还不得不同时考虑它不需要的十个文件。
高效使用 token 并不意味着总体上使用更少的 token。而是确保你使用的那些 token 用在了你真正要求的事情上。
所以让我们先看看什么决定了 token 的价格,然后再看什么决定了会话发送的 token 数量,以及在这个过程中,这对你如何运行一个会话意味着什么。
你按 token 付费,但你实际支付的是推理:GPU(或 TPU,或模型恰好运行所在的任何硬件)对你的 token 运行模型所花费的时间。
三件事决定了一个 token 花费多少时间:你运行的是哪个模型,它是输入 token(进入)还是输出 token(出去),以及它是否被缓存了。
更大的模型对输入和输出 token 做更多的工作。哪个模型值得用于哪种工作是一个独立的话题,我们在《在 Claude Code 中选择 Claude 模型和努力级别》中已经介绍过了。
对于这篇文章,你需要知道的是,接下来我们要介绍的所有内容都会乘以模型的价格:当问题确实困难或模糊时,使用更大的模型;当工作是例行的时候,使用更小的模型。
曲线仅用于说明目的,不代表真实基准数据。

一个请求通过 GPU 经历两个阶段,它们的成本不同。
首先,在预填充(prefill)阶段,模型读取你的请求和上下文:系统提示、你的 CLAUDE.md、你的消息,以及自那之后添加到对话中的所有内容(Claude 读取的文件和它运行的命令输出)。这些是你的输入 token。
然后,在解码(decode)阶段,它写出输出 token:它的思考、它进行的工具调用,以及你看到的文本。这个过程一次只出一个 token;一个 200 token 的响应是模型的 200 次运行,一次接一次。每个 token,解码让 GPU 保持繁忙的时间要长得多,这就是为什么输出定价大约是输入的 5 倍。

会话中大量输出 token 是思考 token,而模型每个回合做多少思考是由努力级别控制的。和模型一样,你用 /effort 设置的级别会成为下次会话的默认设置。
如果一个请求以与服务器刚刚看到的请求完全相同的 token 开头,那么这个共享开头的状态输出也是相同的,所以服务器可以从上次保留它,只需要预填充它后面的部分。这被称为提示缓存(prompt caching)。
从缓存中读取的成本是输入价格的 0.1 倍,因为服务器加载的是状态而不是计算它。将 token 写入缓存的成本比普通输入略高,最高可达 2 倍,因为服务器之后还需要保留该状态。但写入每个 token 只需要一次,而 0.1 倍的读取在之后的每个回合都会发生。
Claude Code 在每个请求上管理提示缓存,没有什么东西需要开启。但你可能会破坏它,所以了解如何避免这些成本飙升很重要。
假设我们输入"fix the failing test in utils.test.ts"。以下是 Claude Code 为此发送的内容:
Claude Code 将第一次请求组装在一起:系统提示(包括工具定义)、你的 CLAUDE.md 和你的消息,然后发送出去(输入 token)。缓存中还没有任何东西,所以所有内容都被预填充并写入缓存。
模型无法修复它没有看到的测试,所以它思考了一下,然后用 Read 调用响应以获取 utils.test.ts(输出 token)。Claude Code 读取该文件,将其附加到对话中,然后再次发送所有内容(输入 token)。这次,请求 1 中的所有内容都以十分之一的价格从缓存中读回,唯一以全价预填充的新内容是:Read 调用和该文件。
现在模型想要被测试的文件(输出)。又一次 Read,又一次追加,所有内容再次发出:请求 1 和 2 从缓存中读取,第二个文件以全价读取(输入)。
模型用 Edit 响应(输出)。Claude Code 应用它,追加结果,然后再次发送所有内容。同样的情况:Edit 及其结果是新的,它们之前的所有内容都是缓存读取(输入)。
模型运行 npm test(输出)。Claude Code 追加测试输出并再次发送所有内容,测试输出是唯一的新部分(输入)。
测试通过,模型用简短的总结响应(输出)。没有工具调用意味着没有东西需要追加,也不会有请求 6,所以我们完成了。
对于一个小修复,这是五个请求,而且每个请求都包含到那时为止的整个对话。一个典型的回合是失衡的:数万个 token 进入,数百个出来。但只有该回合中的新内容以全价预填充。
这就是每个回合的完整账单:历史记录的缓存读取、以全价输入的新内容,以及响应上的输出价格。
缓存必须从头开始完全匹配,而且请求总是按相同顺序发出:工具定义,然后是系统提示,然后是对话(CLAUDE.md 放在最前面)。
如果这个前缀中的任何内容发生变化,它后面的所有内容都会重新预填充。追加到对话末尾的工具结果是理想情况,因为后面没有任何东西。会破坏缓存的是任何在更靠前的位置改变请求的东西,或者改变缓存键的东西:
/model:每个模型都有自己的缓存,所以在下一个回合,整个对话会以全价重新预填充。(这也包括 opusplan,每次你进入或退出计划模式时都会切换模型。)
/effort:努力级别也是缓存键的一部分,所以情况相同。这就是为什么 /model 和 /effort 都会在对话中途切换时要求你确认。
快速模式(Fast mode):也是键的一部分,重新预填充以快速模式价格发生,所以如果你要打开它,就在开始时打开。(再次关闭它,缓存方面是免费的。)
/compact:对话被替换为更短的版本,所以其中没有任何内容再匹配(前面的系统提示保留)。只要旧对话仍在缓存中,写入摘要本身就很便宜,所以在长时间休息之前做这件事比之后做要便宜得多。
时间:每个回合都会重置时钟,但缓存在订阅后一小时或 API 密钥后五分钟过期(ENABLE_PROMPT_CACHING_1H=1 可以使其延长到一小时)。超过这个时间才回来,下一个回合就会重新预填充整个对话。恢复旧会话几乎总是如此:缓存通常那时已经消失了,系统提示在启动时也会重建。
这些并不意味着你永远不应该切换模型或努力级别。这意味着有一些便宜的时刻可以这样做——会话开始时或 /clear 之后——也有一些昂贵的时刻——长时间对话中途。
这里需要知道的主要是,没有任何东西只发送一次。凡是最终出现在对话中的内容——Claude 读取的文件或它运行的命令输出——在之后的每个回合都会再次发送。
它被缓存了,所以每次重发都很便宜,但便宜不等于零,而且它也在占用模型每个回合必须思考的上下文空间。
这确实是会话的完整成本模型:上下文中最终有多少 token,它们停留多少回合,以及你同时运行多少个上下文。
上下文中有一部分在你输入任何内容之前就已经在了:工具定义、系统提示、CLAUDE.md,以及启动时加载的任何其他内容。
会话期间添加的几乎所有其他东西都是工具结果:Claude 读取的文件,以及它运行的命令输出。
Claude 读取多少内容主要取决于它必须自己弄清楚多少。如果你只说"测试失败了",它首先必须找出是哪些测试:一两次 grep,读取几个文件看看哪个是相关的,所有这些结果都会在上下文保留很久之后才变得有用。
"Fix the failing test in utils.test.ts" 跳过了搜索,只花一次 Read 调用读取该文件,而"Fix the failing test in @utils.test.ts" 连 Read 调用都不需要。

另一个填满上下文的东西是 Claude 运行的命令输出。每次它运行你的测试、构建或 git log,无论它打印了什么都会被追加到对话中,就像它读取的文件一样,并且在相同数量的回合中保留在那里。
真正的大输出实际上没问题:超过 30,000 个字符后,Claude Code 会将输出写入文件,只在对话中放一个简短的预览和路径(如果你想更改,使用 BASH_MAX_OUTPUT_LENGTH)。
问题是低于那个的所有东西。一个测试运行器逐行打印 400 个通过测试,低于限制,但这 400 行现在成了每个剩余回合的一部分。
Claude 通常会用标志和 tail 来处理这个问题,如果你不想把这件事交给 Claude,文档中有一个小钩子可以在命令运行前重写它们,这样只有重要的行会回来。
一次长会话的成本比相同工作分散在几次短会话中的成本更高,而且高得超出你的想象,因为第 40 回合也会重读它之前的 39 个回合。你希望会话中的上下文简短且相关,所以不要把一个任务的上下文带到下一个任务:开始新任务时用 /clear,同一任务的早期部分完成后用 /compact。

同时也要注意在你没有输入时发生的回合。/loop 在你设置它的会话中作为完整回合触发,每次都带着整个对话;如果距离上次回合已经超过一小时,那还有一次缓存未命中。在另一个终端启动一个新会话并从那里运行 loop。
另一种让某些东西远离你的上下文的方法是让它在不同的上下文中发生,这就是子代理的用途。子代理获得自己的上下文窗口,有自己的系统提示、工具和你的 CLAUDE.md,但没有你的对话。它运行自己的回合,而唯一回到主会话的是它的答案。一旦完成,其他所有东西都被丢弃。
没有你的对话的缺点是,子代理有时不得不重新读取主会话已经拥有的东西,而且它在这样做的时候也在为自己的回合付费。对于小任务来说这只是开销。
当一项工作产生大量你不需要保留的输出时,这就值得了,比如浏览日志。Claude 经常会在这种情况下主动使用子代理,当你没有主动要求时你也可以直接要求它("go through this log in a subagent")。请记住,主会话只能取回子代理选择报告的内容。

在以上所有内容中,有四件事值得密切关注,大致按成本顺序:
