Agent đốt 437.000 token cho một câu hỏi: chẩn đoán từ đâu
Hai chỗ token biến mất mà dashboard không báo: tool call kéo theo lịch sử, và prefix prompt tự phá cache. Cách đo bằng trace và ba field telemetry.
Hóa đơn API tháng này gấp ba lần tháng trước, nhưng số request gần như không đổi. Bạn mở dashboard hạ tầng: CPU 12%, queue rỗng, không lỗi nào — mọi thứ trông bình thường.
Có hai chỗ token biến mất mà dashboard hạ tầng không bao giờ báo. Dưới đây là cách phân biệt hai chỗ đó bằng trace và ba field telemetry, kèm thứ tự sửa.
Chỗ thứ nhất: mỗi tool call kéo theo toàn bộ lịch sử
Đội FoxNose trỏ agent thật vào MCP server production của họ rồi ghi lại trace. Một lần chạy tốn 437.000 input token cho đúng một câu hỏi. Theo bài viết, đó "not a hard question either" — thứ một support engineer trả lời trong một câu — nhưng agent đi qua hai mươi tool call, "each one dragging every earlier answer along behind it".
Chi tiết đáng chú ý: server không hỏng lúc đó. Bài ghi rõ server "answered initialize correctly, spoke the 2025-03-26 revision, returned valid JSON-RPC to everything we threw at it", và tất cả những điều đó "turned out to be beside the point". Health check xanh không nói gì về chi phí token.
Quy mô thí nghiệm nhỏ vừa đủ để bạn lặp lại: "18 scenarios, two vendors, a 5 dollar budget that we topped up once". Server của họ expose "seven tools regardless of how many collections exist, five read and two optional write" — số tool cố định, không phình theo số collection.
Nguyên nhân agent đi lòng vòng, theo bài, nằm ở phần mô tả dữ liệu chứ không ở protocol: "a badly described collection is effectively invisible". Agent không nhận ra chỗ cần tra nên nó thử tiếp, và mỗi lần thử lại nhân context lên một lần nữa. Bài cũng nói rõ agent "inherits exactly the rights of the API key it connects with", nên phần quyền không phải chỗ bạn cần soi khi truy chi phí.
Đo trace, đừng đo câu trả lời
Đây là phần đáng copy nhất về cách kiểm thử. Harness của họ "do not look at the answer text", vì "model output moves between runs, and a suite that asserts on wording is a suite you quietly stop trusting".
Thay vào đó, check chạy trên trace: "which tools ran, in what order, with what arguments, which errors came back, how many tokens the whole thing burned". Mỗi check là một predicate nhỏ trên run. Ví dụ trong bài:
any_of(
no_tool_errors(),
recovered_after("unknown_resource", then="search_records"),
)
Phân tích: nếu mục tiêu của bạn là chi phí chứ không phải chất lượng câu trả lời, đây là dạng eval rẻ nhất để dựng. Nó không cần label, không cần người chấm, và nó trả về đúng con số bạn đang bị tính tiền.
Chỗ thứ hai: prefix prompt tự phá cache
Trường hợp còn lại nhìn giống hệt trên dashboard: CPU thấp, queue rỗng, nhưng token đầu vào leo đều. Một bài drill riêng — có disclosure rằng nội dung được chuẩn bị trong khuôn khổ product outreach của MonkeyCode — mô tả cơ chế: "Idle CPU is not proof of spare capacity. It often hides a prefill tax on every call."
Thủ phạm nhỏ đến mức dễ trôi qua code review: "A timestamp in the system prompt is enough. A request id in the tools preamble is enough. The prefix hash then changes on every call." Hệ quả trực tiếp, theo bài: "You pay input tokens for the same instructions. You wait for prefill on text you already sent."
Bài tự dán nhãn các con số dưới đây là điều kiện lab, không phải benchmark sản phẩm — "These figures are labels for the drill only" — nên hãy đọc chúng như hình dạng bài toán:
- Workload: "40 serial jobs with an eight second deadline"
- Prefix: "about 1,200 tokens of stable instructions"
- Phần câu hỏi: "about 80 tokens of unique user text"
- Noise: "an ISO-8601 clock field inside the prefix"
- Cache: "SHA-256 prefix digest in SQLite, fifteen minute TTL"
Tỷ lệ mới là chỗ đau. Trong hình dạng đó, phần chỉ dẫn lặp lại (khoảng 1.200 token) lớn hơn hẳn phần văn bản thực sự thay đổi mỗi call (khoảng 80 token). Một field clock đặt trong prefix biến toàn bộ phần lặp lại đó thành khoản trả lại từ đầu, ở mọi request.
Ba field nói cho bạn biết đang gặp chuyện gì
Bài drill đưa ra bảng quyết định để dán vào runbook, đọc trước khi chỉnh replica count. Nguyên văn các cột:
| Observed fields | Inference (labeled as inference) | Action |
|---|---|---|
| queue_age_ms low, cpu_pct low, prompt tokens rising | Cache-busting prefix, not saturation | Reject or strip the clock |
| queue_age_ms high, cpu_pct high | True saturation on the node | Shed load; skip the free path |
| queue_age_ms high, cpu_pct low | Blocked IO or a stuck worker | Debug the server; do not enqueue |
| deadline_slack_ms under 1,500 | The job cannot finish in time | Reject; do not spend tokens |
Dòng đầu là dòng hay bị đọc sai nhất. Bài mô tả đúng cái bẫy: "That pattern is not a scale-out signal. It is an admission and reject signal." Thêm replica vào một node chưa bão hòa compute không sửa được prefix hash.
Kèm theo là một quy tắc log đáng áp dụng rộng hơn phạm vi bài: "Separate observation from inference in every log line. The first column stays observed telemetry only."
Sửa: fingerprint phần prefix ổn định
Thứ tự trong bài rất gọn: "You cannot meter what you cannot name yet. Hash only the stable instruction prefix today. Drop clocks, UUIDs, and trace ids first."
Nói cách khác, bạn chuẩn hóa prefix trước khi hash, để phần chỉ dẫn giống nhau luôn cho ra cùng một fingerprint. Đoạn dưới là ví dụ minh họa dạng chuẩn hóa đó, không phải thư viện production:
# ví dụ minh họa — không phải code production
CLOCK = re.compile(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?")
IDS = re.compile(r"\b(?:req|trace|run|job)[-_][A-Za-z0-9-]{6,}\b", re.I)
def stabilize(text):
text = CLOCK.sub("", text)
text = IDS.sub("", text)
return " ".join(text.split())
Quy tắc chặn của drill đứng trước cả việc tối ưu: "Pass rule: reject noisy prefixes before tokens move", và với job sắp trễ hạn thì "Reject; do not spend tokens". Từ chối sớm rẻ hơn chạy xong rồi bỏ kết quả.
Làm gì trong tuần này
- Log
prompt_tokenstheo từng run và theo từng tool call. Không có số này thì mọi tranh luận về chi phí agent chỉ là cảm giác. - Grep system prompt và tools preamble tìm timestamp, request id, trace id. Đây là thứ sửa được trong một commit.
- Đối chiếu ba field
queue_age_ms,cpu_pct,prompt_tokens_totaltheo bảng trên trước khi tăng replica. - Viết một check dạng predicate trên trace cho luồng agent đắt nhất, thay vì assert vào nội dung câu trả lời.
- Đọc lại mô tả collection hoặc resource mà agent phải chọn giữa: mô tả kém khiến agent thử nhiều vòng, và mỗi vòng đều tính tiền.
Điều cần theo dõi tiếp: cả hai bài đều là quan sát của người vận hành, không phải số liệu do nhà cung cấp model công bố. Hãy đo lại trên chính traffic của bạn trước khi rút kết luận về mức tiết kiệm.
Không spam, hủy đăng ký bất kỳ lúc nào.
