Bỏ qua để đến nội dung

Theo dõi chi phí và usage

Bài viết được dịch tự động từ bài viết gốc, chưa được kiểm tra lại bởi con người. Chỉ những bài viết có dấu tick xanh cạnh tiêu đề là đã được kiểm tra.

Claude Agent SDK cung cấp thông tin token usage chi tiết cho mỗi lượt tương tác với Claude. Hướng dẫn này giải thích cách theo dõi usage đúng cách và hiểu cost reporting, đặc biệt khi làm việc với parallel tool use và hội thoại nhiều bước.

Để xem tài liệu API đầy đủ, xem TypeScript SDK reference và Python SDK reference.

TypeScript SDK và Python SDK expose cùng một dữ liệu usage nhưng dùng tên trường khác nhau:

  • TypeScript cung cấp chi tiết token theo từng step trên mỗi assistant message (message.message.id, message.message.usage), chi phí theo từng model qua modelUsage trên result message, và tổng cộng dồn trên result message.
  • Python cung cấp chi tiết token theo từng step trên mỗi assistant message (message.usage, message.message_id), chi phí theo từng model qua model_usage trên result message, và tổng tích luỹ trên result message (total_cost_usd và dict usage).

Cả hai SDK dùng chung một cost model bên dưới và expose cùng mức độ chi tiết. Khác biệt nằm ở cách đặt tên trường và vị trí lồng usage theo từng step.

Việc theo dõi chi phí phụ thuộc vào việc hiểu SDK phân định phạm vi dữ liệu usage thế nào:

  • Lời gọi query(): một lần gọi hàm query() của SDK. Một lời gọi có thể bao gồm nhiều step (Claude trả lời, dùng tool, nhận kết quả, trả lời tiếp). Mỗi lời gọi tạo ra một message result khi kết thúc.
  • Step: một chu kỳ request/response trong một lời gọi query(). Mỗi step tạo ra assistant message kèm token usage.
  • Session: một chuỗi lời gọi query() liên kết bởi session ID (dùng option resume). Mỗi lời gọi query() trong một session báo cáo chi phí của riêng nó, độc lập với các lời gọi khác.

Sơ đồ sau cho thấy message stream từ một lời gọi query() duy nhất, với token usage được báo cáo ở mỗi step và ước tính tích luỹ ở cuối:

Diagram showing a query producing two steps of messages. Step 1 has four assistant messages sharing the same ID and usage (count once), Step 2 has one assistant message with a new ID, and the final result message shows the estimated total_cost_usd.

Khi Claude trả lời, nó gửi một hoặc nhiều assistant message. Trong TypeScript, mỗi assistant message chứa một BetaMessage lồng bên trong (truy cập qua message.message) với một id và một object usage chứa số lượng token (input_tokens, output_tokens). Trong Python, dataclass AssistantMessage expose cùng dữ liệu trực tiếp qua message.usagemessage.message_id. Khi Claude dùng nhiều tool trong cùng một turn, mọi message trong turn đó dùng chung một ID, nên bạn cần loại trùng theo ID để tránh đếm hai lần.

Result message cung cấp ước tính tích luỹ

Phần tiêu đề “Result message cung cấp ước tính tích luỹ”

Khi lời gọi query() hoàn tất, SDK phát ra một result message kèm total_cost_usdusage tích luỹ. Trường này có mặt trong cả TypeScript (SDKResultMessage) và Python (ResultMessage). Nếu bạn thực hiện nhiều lời gọi query() (ví dụ, trong một session nhiều lượt), mỗi result chỉ phản ánh chi phí của riêng lời gọi đó. Nếu bạn chỉ cần tổng ước tính, bạn có thể bỏ qua usage theo từng step và chỉ đọc giá trị duy nhất này.

Result message (TypeScript, Python) đánh dấu kết thúc agent loop cho một lời gọi query(). Nó bao gồm total_cost_usd, chi phí ước tính tích luỹ qua tất cả step trong lời gọi đó. Điều này đúng cho cả kết quả thành công lẫn lỗi. Nếu bạn dùng session để thực hiện nhiều lời gọi query(), mỗi result chỉ phản ánh chi phí của riêng lời gọi đó.

Ba trường ở cấp result khác nhau ở chỗ chúng tính gì khi agent sinh ra subagent. Dùng modelUsage, hoặc model_usage trong Python, để tính token cho toàn bộ cây (whole-tree); trường usage sẽ đếm thiếu ngay khi có sự lồng nhau (nesting).

TrườngHoạt động của subagent
usageKhông tính. Chỉ đếm agent loop cấp cao nhất, nên token tiêu thụ bên trong subagent không được cộng vào
total_cost_usdCó tính. Đếm request của subagent cùng với vòng lặp cấp cao nhất
modelUsage / model_usageCó tính. Đếm request của subagent cùng với vòng lặp cấp cao nhất, chia theo từng model

Các ví dụ sau lặp qua message stream từ một lời gọi query() và in ra tổng chi phí khi message result xuất hiện:

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) {
// Một lời gọi query() single-shot throw lỗi sau khi yield result lỗi. Nếu
// lỗi đến từ result lỗi, nhánh trên đã chạy rồi và vẫn mang total_cost_usd;
// lỗi kết nối hoặc process không yield result message nào cả.
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:
# Một lời gọi query() single-shot raise lỗi sau khi yield result lỗi. Nếu
# lỗi đến từ result lỗi, nhánh trên đã chạy rồi và vẫn mang total_cost_usd;
# lỗi kết nối hoặc process không yield result message nào cả.
print(f"Session ended with an error: {error}")
asyncio.run(main())

Các ví dụ trong phần này dùng tên trường TypeScript. Trong Python, các trường tương ứng là AssistantMessage.usageAssistantMessage.message_id cho usage theo từng step, và ResultMessage.model_usage cho phân tích theo từng model.

Mỗi assistant message chứa một BetaMessage lồng bên trong (truy cập qua message.message) với một id và object usage chứa số lượng token. Khi Claude dùng tool song song, nhiều message dùng chung một id với dữ liệu usage giống hệt nhau. Theo dõi những ID bạn đã đếm và bỏ qua bản trùng để tránh tổng bị thổi phồng.

Ví dụ sau cộng dồn input và output token qua tất cả step, chỉ đếm mỗi message ID duy nhất một lần:

import { query } from "@anthropic-ai/claude-agent-sdk";
const seenIds = new Set<string>();
let totalInputTokens = 0;
let totalOutputTokens = 0;
try {
for await (const message of query({ prompt: "Summarize this project" })) {
if (message.type === "assistant") {
const msgId = message.message.id;
// Tool call song song dùng chung một ID, chỉ đếm một lần
if (!seenIds.has(msgId)) {
seenIds.add(msgId);
totalInputTokens += message.message.usage.input_tokens;
totalOutputTokens += message.message.usage.output_tokens;
}
}
}
} catch (error) {
// Một lời gọi query() single-shot throw lỗi sau khi yield result lỗi, nên
// các tổng bên dưới vẫn phản ánh các step đã chạy trước khi lỗi xảy ra.
console.error(`Session ended with an error: ${error}`);
}
console.log(`Steps: ${seenIds.size}`);
console.log(`Input tokens: ${totalInputTokens}`);
console.log(`Output tokens: ${totalOutputTokens}`);

Result message bao gồm modelUsage, một map từ tên model tới số lượng token và chi phí theo từng model. Điều này hữu ích khi bạn chạy nhiều model (ví dụ, Haiku cho subagent và Opus cho agent chính) và muốn xem token đang tiêu tốn ở đâu.

Ví dụ sau chạy một query và in ra chi phí cùng phân tích token cho từng model được dùng:

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) {
// Một lời gọi query() single-shot throw lỗi sau khi yield result lỗi. Nếu
// lỗi đến từ result lỗi, phân tích theo từng model ở trên đã in xong;
// lỗi kết nối hoặc process không yield result message nào cả.
console.error(`Session ended with an error: ${error}`);
}

Mỗi lời gọi query() trả về total_cost_usd của riêng nó. SDK không cung cấp tổng ở cấp session, nên nếu ứng dụng của bạn thực hiện nhiều lời gọi query() (ví dụ, trong một session nhiều lượt hoặc qua nhiều người dùng khác nhau), bạn phải tự cộng dồn tổng.

Các ví dụ sau chạy tuần tự hai lời gọi query(), cộng total_cost_usd của mỗi lời gọi vào một tổng đang chạy, và in ra cả chi phí từng lời gọi lẫn tổng kết hợp:

import { query } from "@anthropic-ai/claude-agent-sdk";
// Theo dõi chi phí tích luỹ qua nhiều lời gọi query()
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) {
// Một lời gọi query() single-shot throw lỗi sau khi yield result lỗi. Nếu
// lỗi đến từ result lỗi, chi phí của lời gọi này đã được đếm rồi;
// lỗi kết nối hoặc process không yield result message nào cả. Tiếp tục
// với prompt kế tiếp.
console.error(`Call failed: ${error}`);
}
}
console.log(`Total spend: $${totalSpend.toFixed(4)}`);
from claude_agent_sdk import query, ResultMessage
import asyncio
async def main():
# Theo dõi chi phí tích luỹ qua nhiều lời gọi query()
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:
# Một lời gọi query() single-shot raise lỗi sau khi yield result lỗi.
# Nếu lỗi đến từ result lỗi, chi phí của lời gọi này đã được đếm
# rồi; lỗi kết nối hoặc process không yield result message nào cả.
# Tiếp tục với prompt kế tiếp.
print(f"Call failed: {error}")
print(f"Total spend: ${total_spend:.4f}")
asyncio.run(main())

Để theo dõi chi phí chính xác, hãy tính đến hội thoại thất bại, giá cache token, và một số sai lệch báo cáo hiếm gặp.

Trong một số trường hợp hiếm, bạn có thể thấy giá trị output_tokens khác nhau cho các message cùng ID. Khi việc này xảy ra:

  1. Dùng giá trị cao nhất: message cuối cùng trong một nhóm thường chứa tổng chính xác.
  2. Ưu tiên result message: total_cost_usd trong result message phản ánh ước tính tích luỹ của SDK qua tất cả step, nên đáng tin hơn là tự cộng các giá trị theo từng step. Nó vẫn là ước tính và có thể khác với hoá đơn thực tế của bạn.
  3. Báo cáo sai lệch: tạo issue tại Claude Code GitHub repository.

Theo dõi chi phí trên hội thoại thất bại

Phần tiêu đề “Theo dõi chi phí trên hội thoại thất bại”

Cả result message thành công lẫn lỗi đều bao gồm usagetotal_cost_usd. Nếu một hội thoại thất bại giữa chừng, bạn vẫn đã tiêu tốn token tính đến thời điểm thất bại. Luôn đọc dữ liệu chi phí từ result message bất kể subtype của nó là gì.

Agent SDK tự động dùng prompt caching để giảm chi phí trên nội dung lặp lại. Bạn không cần tự cấu hình caching. Object usage bao gồm hai trường bổ sung để theo dõi cache:

  • cache_creation_input_tokens: token dùng để tạo entry cache mới (tính giá cao hơn input token thông thường).
  • cache_read_input_tokens: token đọc từ entry cache có sẵn (tính giá thấp hơn).

Theo dõi các trường này riêng biệt với input_tokens để hiểu mức tiết kiệm từ caching. Trong TypeScript, các trường này có kiểu trên object Usage. Trong Python, chúng xuất hiện dưới dạng key trong dict ResultMessage.usage (ví dụ, message.usage.get("cache_read_input_tokens", 0)).

Entry cache do SDK ghi dùng TTL 5 phút mặc định khi bạn xác thực bằng API key hoặc chạy trên Amazon Bedrock, Google Cloud’s Agent Platform, hoặc Microsoft Foundry. Nếu workload của bạn chạy nhiều session ngắn trên cùng một system prompt và context với khoảng cách dài hơn 5 phút giữa chúng, cache hết hạn giữa các session và mỗi session mới phải trả full input price.

Để yêu cầu TTL 1 giờ trên cache write, đặt biến môi trường ENABLE_PROMPT_CACHING_1H. Bạn có thể export nó trong shell hoặc container environment, hoặc truyền qua options.env.

Ví dụ sau bật TTL 1 giờ cho một agent chạy trên Amazon Bedrock. Vì nó đặt CLAUDE_CODE_USE_BEDROCK, nó cần AWS credentials hoạt động cho Amazon Bedrock; nếu không có, query sẽ thất bại.

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);
}

Cache write với TTL 1 giờ được tính giá cao hơn write TTL 5 phút, nên bật tính năng này là đánh đổi chi phí write cao hơn để lấy nhiều cache read hơn. Xem giá prompt caching để biết chi tiết. Người dùng Claude subscription đã tự động nhận TTL 1 giờ và không cần đặt biến này.

  • TypeScript SDK Reference - Tài liệu API đầy đủ
  • SDK Overview - Bắt đầu với SDK
  • SDK Permissions - Quản lý tool permission