本页目录15
- total_cost_usd 和 costUSD 是客户端本地估算值,并非权威账单数据,权威计费请用 Usage and Cost API 或 Claude Console
- 并行工具调用时多条 assistant 消息共享同一个消息 id,需按 id 去重后再累加 input_tokens,避免重复计数
- assistant 消息上的 output_tokens 只是 message_start 时的占位值,真实输出 token 数要从 result 消息的 usage 或 modelUsage 中读取
- 当 agent 派生 subagent 时,usage 字段只统计顶层主循环,会漏算子代理消耗;total_cost_usd 和 modelUsage/model_usage 才包含子代理请求
- 流式输入模式下一次 query() 调用可能包含多个用户回合,每个回合各自产生一个 result 消息,应读取最新 result 而非逐条相加,且 /clear、/reset、/new 会重置累计值
- 设置环境变量 ENABLE_PROMPT_CACHING_1H 可将默认 5 分钟的缓存 TTL 延长到 1 小时,但写入价格更高
本文是对官方 Agent SDK 某页的中文整理,完整与最新内容以原文为准:https://code.claude.com/docs/en/agent-sdk/cost-tracking
Claude Agent SDK 会为每次与 Claude 的交互提供详细的 token 用量信息。本指南说明如何正确跟踪用量、理解成本报告,尤其是在涉及并行工具调用和多步对话的场景下。
完整 API 文档参见 TypeScript SDK reference 和 Python SDK reference。
警告:
total_cost_usd和costUSD字段是客户端本地估算值,并非权威账单数据。SDK 是根据构建时内置的价格表在本地计算得出的,因此在以下情况下可能与实际账单存在偏差:
- 价格发生变化
- 已安装的 SDK 版本不认识某个模型
- 适用了客户端无法建模的计费规则
这些字段仅用于开发阶段的洞察和粗略预算估算。若需要权威账单数据,请使用 Usage and Cost API 或 Claude Console 的 Usage 页面。不要据此向终端用户计费或触发财务决策。
理解 Token 用量
TypeScript 和 Python SDK 暴露的是同一份用量数据,只是字段命名不同:
- TypeScript:在每条 assistant 消息上提供逐步 token 明细(
message.message.id、message.message.usage),在 result 消息上通过modelUsage提供按模型划分的成本,并在 result 消息上提供累计总量。 - Python:在每条 assistant 消息上以
message.usage和message.message_id提供逐步 token 明细,在 result 消息上通过model_usage提供按模型划分的成本,累计总量为 result 消息上的total_cost_usd。
两个 SDK 使用相同的底层成本模型,暴露相同粒度的信息,区别只在于字段命名和逐步用量的嵌套位置。
成本跟踪依赖于理解 SDK 对用量数据的作用域划分:
query()调用:SDKquery()函数的一次调用。一次调用中可能包含多个步骤:Claude 回复、使用工具、获得工具结果、再次回复。每次调用结束时产生一条result消息,但在流式输入模式下例外——此时一次query()调用可携带多个用户回合,每个回合各自产生一条result消息。- 步骤(Step):一次
query()调用内的单次请求/响应循环。每个步骤会产生带有 token 用量的 assistant 消息。 - 会话(Session):通过
resume选项以同一会话 ID 关联起来的一系列query()调用。会话内每次query()调用各自独立报告其成本。
消息流程
- 每个步骤产生 assistant 消息:Claude 回复时会发送一条或多条 assistant 消息。在 TypeScript 中,每条 assistant 消息包含一个嵌套的
BetaMessage(通过message.message访问),带有id和一个含 token 计数(input_tokens、output_tokens)的usage对象。在 Python 中,AssistantMessagedataclass 直接通过message.usage和message.message_id暴露相同数据。当 Claude 在一个回合内使用多个工具时,该回合内的所有消息共享同一个id,因此需按 id 去重以避免重复计数。 - result 消息提供累计估算值:
query()调用完成时,SDK 会发出一条带有total_cost_usd和累计usage的 result 消息,在 TypeScript 中类型为SDKResultMessage,在 Python 中类型为ResultMessage。如果你进行多次query()调用(例如多轮会话),每个 result 只反映该次调用自身的成本。如果只需要估算总额,可以忽略逐步用量,只读取这一个值。在流式输入模式下,每个回合各自产生一条 result 消息,具体读取方式见下一节。
在流式输入模式下跟踪成本
在流式输入模式下,一次 query() 调用携带多个用户回合,每个回合各自产生一条 result 消息。result 字段的统计范围有所不同:
usage:仅覆盖该回合,且只统计主 agent 循环,不含它运行的任何子代理。total_cost_usd与modelUsage(Python 中为model_usage):携带到目前为止整次调用的累计总量。
在应用从未发送 /clear、/reset 或 /new 的一次调用中,应读取最新的 result 来获取调用总量,而不是把各个 result 相加。
这些累计总量会在应用每次发送上述三条命令之一时重新归零,在一次 query() 调用内部,除此之外没有其他因素会重置它们。以下三种 result 与统计相关:
/clear回合自身的 result:只覆盖重置以来发生的内容,并携带一个新的session_id。- 之后的每个 result:从该次重置开始继续计数。
- 每次
/clear之前的最后一个 result:保存自上次重置以来各回合的总量。
要统计整次调用的总量,应将每次 /clear 之前的最后一个 result 与调用的最终 result 相加;其他所有 result(包括 /clear 回合自身的 result)都会被后续的 result 覆盖取代。
在 TypeScript 中,SDK 还会在每次重置时发出一条 SDKConversationResetMessage,因此可以从消息流中检测重置事件;Python 中同样会发出一条 ConversationResetMessage。在 Python SDK v0.2.137 之前,Python 的迭代器会丢弃该消息,因此在那些版本上需要自行根据应用发送的 /clear 回合来统计重置次数。
maxBudgetUsd(Python 中为 max_budget_usd)是与同一个累计总量做比较的,因此 /clear 同样会重置预算计数。
获取一次查询的总成本
result 消息(TypeScript 中类型为 SDKResultMessage,Python 中类型为 ResultMessage)标志着一次 query() 调用的 agent 循环结束。它包含 total_cost_usd,即该次调用中所有步骤的累计估算成本。在 Python 中该字段类型是可选的(optional),读取前需先检查它是否为 None。成功和错误的 result 都会携带该字段,不过会话崩溃后的最终 result 可能携带的是被清零的值。
如果你通过会话进行多次 query() 调用,每个 result 只反映该次调用自身的成本。在流式输入模式下,请按照上一节所述方式读取调用总量。
当 agent 派生子代理(subagents)时,以下三个 result 级字段对子代理活动的统计方式有所不同。请使用 modelUsage(Python 中为 model_usage)来统计整棵调用树的 token;一旦出现嵌套,usage 字段就会低估实际消耗。
| 字段 | 对子代理活动的统计 |
|---|---|
usage | 不包含。只统计顶层 agent 循环,子代理内消耗的 token 不会被计入 |
total_cost_usd | 包含。将子代理请求与顶层循环一并统计 |
modelUsage / model_usage | 包含。将子代理请求与顶层循环一并统计,并按模型划分 |
以下示例遍历一次 query() 调用的消息流,并在 result 消息到达时打印总成本:
import { query } from "@anthropic-ai/claude-agent-sdk";
try {
for await (const message of query({ prompt: "Summarize this project" })) {
if (message.type === "result") {
console.log(`Total cost: $${message.total_cost_usd}`);
}
}
} catch (error) {
// A single-shot query() throws after yielding an error result. If the
// failure was an error result, it still carried total_cost_usd and the
// branch above has already run; connection or process failures yield
// no result message.
console.error(`Session ended with an error: ${error}`);
}
from claude_agent_sdk import query, ResultMessage
import asyncio
async def main():
try:
async for message in query(prompt="Summarize this project"):
if isinstance(message, ResultMessage):
print(f"Total cost: ${message.total_cost_usd or 0}")
except Exception as error:
# A single-shot query() raises after yielding an error result. If the
# failure was an error result, the branch above has already run;
# connection or process failures yield no result message.
print(f"Session ended with an error: {error}")
asyncio.run(main())
要限制子代理能对 total_cost_usd 增加多少,可以在 query 上设置深度、并发和花费限制。
跟踪逐步与逐模型用量
本节示例使用 TypeScript 字段名。在 Python 中,对应字段为逐步用量的 AssistantMessage.usage 和 AssistantMessage.message_id,以及按模型划分的 ResultMessage.model_usage。
跟踪逐步用量
每条 assistant 消息包含一个嵌套的 BetaMessage(通过 message.message 访问),带有 id 和含 token 计数的 usage 对象。当 Claude 并行使用工具时,多条消息会共享同一个 id 且用量数据相同。应记录已计数过的 id 并跳过重复项,以避免统计结果虚高。
警告:去重后的逐步值对于 input 和 cache token 是准确的。逐步
output_tokens只是一个占位值,因此应从 result 消息中读取输出 token。
以下示例在所有步骤上累加 input token,每个唯一的主循环消息 id 只计一次并跳过子代理消息,输出 token 总量则从 result 消息(覆盖主循环)中读取:
import { query } from "@anthropic-ai/claude-agent-sdk";
const seenIds = new Set<string>();
let totalInputTokens = 0;
let resultOutputTokens = 0;
try {
for await (const message of query({ prompt: "Summarize this project" })) {
if (message.type === "assistant" && !message.parent_tool_use_id) {
const msgId = message.message.id;
// Parallel tool calls share the same ID, only count once
if (!seenIds.has(msgId)) {
seenIds.add(msgId);
totalInputTokens += message.message.usage.input_tokens;
}
}
if (message.type === "result") {
// Per-step output_tokens is a placeholder; the result message
// carries the accumulated output total.
resultOutputTokens = message.usage.output_tokens;
}
}
} catch (error) {
// A single-shot query() throws after yielding an error result, so the
// input total below still reflects the steps that ran before the failure.
console.error(`Session ended with an error: ${error}`);
}
console.log(`Steps: ${seenIds.size}`);
console.log(`Input tokens: ${totalInputTokens}`);
console.log(`Output tokens: ${resultOutputTokens}`);
按模型拆分用量
result 消息包含 modelUsage,即模型名称到逐模型 token 计数和成本的映射。当你运行多个模型(例如子代理使用 Haiku、主代理使用 Opus)并想了解 token 消耗去向时,这个字段很有用。
以下示例运行一次查询,并打印每个所用模型的成本和 token 明细:
import { query } from "@anthropic-ai/claude-agent-sdk";
try {
for await (const message of query({ prompt: "Summarize this project" })) {
if (message.type !== "result") continue;
for (const [modelName, usage] of Object.entries(message.modelUsage)) {
console.log(`${modelName}: $${usage.costUSD.toFixed(4)}`);
console.log(` Input tokens: ${usage.inputTokens}`);
console.log(` Output tokens: ${usage.outputTokens}`);
console.log(` Cache read: ${usage.cacheReadInputTokens}`);
console.log(` Cache creation: ${usage.cacheCreationInputTokens}`);
}
}
} catch (error) {
// A single-shot query() throws after yielding an error result. If the
// failure was an error result, the per-model breakdown above has already
// printed; connection or process failures yield no result message.
console.error(`Session ended with an error: ${error}`);
}
modelUsage 中每个模型条目包含的字段:
| 字段 | 说明 |
|---|---|
costUSD | 该模型的估算成本(美元) |
inputTokens | 输入 token 数 |
outputTokens | 输出 token 数 |
cacheReadInputTokens | 从缓存读取的 token 数 |
cacheCreationInputTokens | 用于创建缓存的 token 数 |
累计多次调用的成本
每次 query() 调用返回各自的 total_cost_usd。SDK 不提供会话级别的总量,因此如果应用要进行多次 query() 调用(例如多轮会话或跨不同用户),需要自行累加总量。流式输入模式下,请按上文所述方式读取每次调用的总量。对于以崩溃结束的调用,参见下文「会话崩溃后恢复统计」。
以下示例依次运行两次 query() 调用,将每次调用的 total_cost_usd 加到累计总量中,并打印每次调用及合计的成本:
import { query } from "@anthropic-ai/claude-agent-sdk";
// Track cumulative cost across multiple query() calls
let totalSpend = 0;
const prompts = [
"Read the files in src/ and summarize the architecture",
"List all exported functions in src/auth.ts"
];
for (const prompt of prompts) {
try {
for await (const message of query({ prompt })) {
if (message.type === "result") {
totalSpend += message.total_cost_usd;
console.log(`This call: $${message.total_cost_usd}`);
}
}
} catch (error) {
// A single-shot query() throws after yielding an error result. If the
// failure was an error result, this call's cost was already counted;
// connection or process failures yield no result message. Continue
// with the next prompt.
console.error(`Call failed: ${error}`);
}
}
console.log(`Total spend: $${totalSpend.toFixed(4)}`);
from claude_agent_sdk import query, ResultMessage
import asyncio
async def main():
# Track cumulative cost across multiple query() calls
total_spend = 0.0
prompts = [
"Read the files in src/ and summarize the architecture",
"List all exported functions in src/auth.ts",
]
for prompt in prompts:
try:
async for message in query(prompt=prompt):
if isinstance(message, ResultMessage):
cost = message.total_cost_usd or 0
total_spend += cost
print(f"This call: ${cost}")
except Exception as error:
# A single-shot query() raises after yielding an error result. If
# the failure was an error result, this call's cost was already
# counted; connection or process failures yield no result message.
# Continue with the next prompt.
print(f"Call failed: {error}")
print(f"Total spend: ${total_spend:.4f}")
asyncio.run(main())
处理错误、缓存与输出 token 计数
为了准确跟踪成本,需要考虑 assistant 消息上的占位输出计数、失败对话所消耗的 token,以及缓存 token 的计价方式。
从 result 消息读取输出 token
Claude Code 是根据响应开始时 API 报告的用量来构建每条 assistant 消息的,因此消息的 output_tokens 只是 API 在 message_start 时报告的计数——也就是在响应实际生成之前的值。一次 API 响应可能产生多条 assistant 消息,而每一条都携带这同一个占位值。
API 会在响应结束时报告真实的输出计数,Claude Code 会把它加到 result 消息中。应从 result 的 usage 中读取输出 token,或从 modelUsage 中读取按模型划分的明细。
若想在响应流式生成过程中实时观察输出计数的增长,可设置 includePartialMessages(Python 中为 include_partial_messages),并从每个 message_delta 流事件中读取 usage,该事件在 TypeScript 中类型为 SDKPartialAssistantMessage,在 Python 中类型为 StreamEvent。
跟踪失败对话的成本
成功和错误的 result 消息都包含 usage 和 total_cost_usd;在 Python 中这两个字段都是可选类型,读取前需检查它们是否为 None。
如果对话中途失败,在失败之前所消耗的 token 依然是真实消耗。应从每一条 result 消息中读取成本数据,无论其 subtype 是 success 还是某个错误子类型。在部分错误 result 上,usage 报告的数值会低于实际调用消耗:
- 会话崩溃后的
error_during_execution:所有成本字段都可能被清零。 error_max_budget_usd:usage不包含导致超出预算的那次响应,而total_cost_usd和modelUsage包含它。
在可以选择的情况下,应优先根据 total_cost_usd 或 modelUsage 来统计,而不是 usage。
会话崩溃后恢复统计
当 Claude Code 进程崩溃时,无论是单次模式还是流式输入模式,都会发出一条最终的 error_during_execution result 后退出。该 result 可能携带被清零的 usage、total_cost_usd 和 modelUsage,因此需要从崩溃之前到达的消息中恢复该次调用的统计。步骤 1 只要存在更早的 result 就能恢复完整统计;步骤 2 的兜底方案只能恢复主循环的 input 和 cache token。
- 使用崩溃前一个回合的 result。在流式输入模式下,它保存的是自调用开始或自上次
/clear以来的累计总量。如果出现以下情况,该 result 无法帮到你,请转到步骤 2:- 该调用是单次模式,不存在更早的 result;
- 崩溃发生在第一个回合;
- 崩溃前的那个回合恰好就是
/clear本身,其 result 只覆盖了这次重置。
- 改为对 assistant 消息上的
usage求和,每个 API 响应只计一次,做法与「跟踪逐步用量」示例相同。单次模式下对全部消息求和;流式输入模式下只对最后一个 result 之后到达的消息求和。这样能得到主循环的 input 和 cache token。子代理的用量无法用这种方式恢复,输出 token 和美元成本也不能,因为逐步output_tokens只是一个占位值。
跟踪缓存 token
Agent SDK 会自动使用提示缓存(prompt caching)来降低重复内容的成本,无需自行配置缓存。usage 对象包含两个额外的缓存跟踪字段:
| 字段 | 说明 |
|---|---|
cache_creation_input_tokens | 用于创建新缓存条目的 token 数(计价高于标准 input token) |
cache_read_input_tokens | 从已有缓存条目中读取的 token 数(计价低于标准 input token) |
应将它们与 input_tokens 分开跟踪,以了解缓存带来的节省。在 TypeScript 中,这些字段定义在 Usage 对象上;在 Python 中,它们是 ResultMessage.usage 这个 dict 里的键(例如 message.usage.get("cache_read_input_tokens", 0))。
将提示缓存 TTL 延长到一小时
当使用 API key 认证,或运行在 Amazon Bedrock、Google Cloud 的 Agent Platform、Microsoft Foundry 或 Claude Platform on AWS 上时,SDK 写入的缓存条目默认 TTL 为 5 分钟。如果你的工作负载是针对同一个 system prompt 和上下文频繁运行许多短会话,且会话之间的间隔超过 5 分钟,缓存就会在会话之间过期,每个新会话都要按完整 input 价格计费。
要为缓存写入请求 1 小时 TTL,可设置 ENABLE_PROMPT_CACHING_1H 环境变量。你可以在 shell 或容器环境中导出它,也可以通过 options.env 传入。
以下示例为运行在 Amazon Bedrock 上的 agent 启用 1 小时 TTL。由于它设置了 CLAUDE_CODE_USE_BEDROCK,需要具备可用的 AWS 凭证(参见 Amazon Bedrock),否则查询会失败。
from claude_agent_sdk import ClaudeAgentOptions, query
import asyncio
async def main():
options = ClaudeAgentOptions(
env={
"CLAUDE_CODE_USE_BEDROCK": "1",
"ENABLE_PROMPT_CACHING_1H": "1",
},
)
async for message in query(prompt="Summarize this project", options=options):
print(message)
asyncio.run(main())
import { query } from "@anthropic-ai/claude-agent-sdk";
const options = {
env: {
...process.env,
CLAUDE_CODE_USE_BEDROCK: "1",
ENABLE_PROMPT_CACHING_1H: "1",
},
};
for await (const message of query({ prompt: "Summarize this project", options })) {
console.log(message);
}
1 小时 TTL 的缓存写入价格高于 5 分钟写入,因此启用该选项相当于用更高的写入成本换取更多的缓存命中。详情参见提示缓存价格说明。使用 Claude 订阅版且在包含额度范围内的用户会自动获得 1 小时 TTL,无需设置该变量。当消耗的是额外使用额度(usage credits)时,除非设置了 ENABLE_PROMPT_CACHING_1H,SDK 会降级为 5 分钟 TTL。
相关文档
- TypeScript SDK Reference — 完整 API 文档
- SDK Overview — SDK 入门
- SDK Permissions — 工具权限管理