·

Ở bài trước, chúng ta đã hiểu token là gì và cách LLM (large language model) "nhìn" văn bản dưới dạng các đơn vị nhỏ. Bài này đi sâu vào phần mà mọi kỹ sư, QA, và product manager làm việc với AI agent sớm muộn cũng phải đối mặt: hóa đơn thực tế. Khi một agent chạy 20-30 vòng lặp để hoàn thành một task, số token tiêu thụ không tuyến tính với "độ dài câu trả lời cuối cùng" mà người dùng nhìn thấy — nó tuyến tính (thậm chí siêu tuyến tính) với số lượt gọi API, độ dài context được gửi đi lặp lại, và cách bạn thiết kế input/output.

Hiểu đúng kinh tế học của token — input token vs output token, prompt caching, extended thinking, và cách thiết lập ngân sách (token budget) ở cấp agent/team — là kỹ năng phân biệt một hệ thống agentic chạy ổn định với chi phí dự đoán được, và một hệ thống "cháy tiền" âm thầm mỗi khi traffic tăng. Nội dung dưới đây dùng bảng giá thực tế của các model Claude hiện tại (Claude Opus 4.8, Claude Sonnet 5, Claude Haiku 4.5) để tính toán cụ thể — không phải lý thuyết suông.

Giải phẫu một Hóa đơn Token của AI Agent

Khi bạn nhìn vào dashboard billing của một ứng dụng AI agent sau một tháng vận hành, con số tổng thường gây bất ngờ so với kỳ vọng ban đầu. Lý do nằm ở cấu trúc của một agentic loop (vòng lặp agentic) — khác hoàn toàn với một lệnh gọi API đơn lẻ.

Với một chatbot đơn giản, mỗi câu hỏi của người dùng tương ứng với đúng một request: input là câu hỏi, output là câu trả lời. Nhưng với một AI agent thực thi task nhiều bước — sửa bug, viết báo cáo tài chính, review code — mỗi "task" của người dùng thực chất là một chuỗi nhiều request, và mỗi request trong chuỗi đó phải gửi lại toàn bộ lịch sử hội thoại từ đầu (vì API của LLM là stateless — không có trạng thái lưu ở server giữa các lượt gọi). Đây là điểm mấu chốt khiến hóa đơn token của agent khác biệt hoàn toàn so với ứng dụng chat truyền thống.

Hãy phân tách một request đơn lẻ trong vòng lặp agentic thành các thành phần token:

Thành phần Mô tả Đặc điểm chi phí
System prompt Hướng dẫn vai trò, quy tắc, ràng buộc cho agent Cố định, lặp lại mỗi request
Tool definitions Schema JSON của toàn bộ tool agent có thể gọi Cố định, lặp lại mỗi request, thường bị đánh giá thấp về kích thước
Lịch sử hội thoại (messages) Toàn bộ các lượt user/assistant/tool_result trước đó Tăng dần theo số vòng lặp
Tool result mới Kết quả tool gọi ở vòng trước (log, nội dung file, output lệnh bash...) Có thể rất lớn, khó kiểm soát (output của bash, grep không giới hạn)
Extended thinking / reasoning token Token agent dùng để "suy nghĩ" trước khi trả lời hoặc gọi tool Tính giá theo output token, nhưng không hiển thị cho người dùng
Output cuối cùng Văn bản/tool_use mà model trả về ở vòng này Giá output token — luôn cao hơn input

Vấn đề nằm ở dòng thứ ba và thứ tư: system prompt + tool definitions + lịch sử hội thoại được gửi lại toàn bộ ở mỗi vòng lặp, không chỉ phần mới. Một agent chạy 15 vòng lặp với system prompt 2.000 token và tool definitions 3.000 token sẽ trả tiền cho 5.000 token đó 15 lần, cộng thêm lịch sử hội thoại phình to dần theo từng vòng. Đây chính là lý do tại sao một "task" trông đơn giản trên UI — ví dụ "sửa giúp tôi lỗi này" — có thể tiêu tốn 50.000-200.000 token thực tế phía sau, gấp 20-40 lần so với những gì hiển thị trên màn hình.

Một ví dụ cụ thể: agent lập trình xử lý một bug report, cần đọc 4 file, chạy test 2 lần, và sửa 1 file. Chuỗi vòng lặp có thể là: đọc file 1 → đọc file 2 → chạy grep tìm định nghĩa hàm → đọc file 3 → chạy test (fail) → đọc file 4 → sửa file → chạy test (pass) → tóm tắt cho người dùng. Đó là 9 vòng lặp. Nếu system prompt + tool schema là 4.000 token và output của các lệnh read/grep/bash trung bình 800 token mỗi lần, tổng token input tích lũy qua 9 vòng đã vượt 60.000 token — trước cả khi tính lịch sử hội thoại phình to.

Mẹo

  • Luôn log usage.input_tokens, usage.output_tokens, usage.cache_creation_input_tokens, usage.cache_read_input_tokens cho từng vòng lặp, không chỉ tổng cuối task. Nhìn biểu đồ token theo từng bước sẽ lộ ra ngay vòng lặp nào "ăn" nhiều token nhất (thường là các lệnh đọc file/lệnh bash trả về output khổng lồ).
  • Đặt giới hạn độ dài cho output của tool (ví dụ giới hạn số dòng grep trả về, cắt log dài) — tool result không kiểm soát là nguồn "rò token" phổ biến nhất trong agent thực tế, phổ biến hơn cả output của model.
  • Trước khi launch một agent mới, hãy chạy thử 3-5 task điển hình và tính tổng token thực tế tiêu thụ trên toàn bộ vòng lặp, không chỉ ước lượng dựa trên 1 lệnh gọi mẫu — sai lệch giữa hai con số này thường là 10-30 lần.

Input Tokens vs. Output Tokens — Kinh tế học Bất đối xứng

Đây là điểm dễ bị bỏ qua nhất khi tối ưu chi phí agent: input token và output token không có giá ngang nhau — và độ lệch giá rất lớn. Với các model Claude hiện tại:

Model Giá input (mỗi 1M token) Giá output (mỗi 1M token) Tỷ lệ output/input
Claude Opus 4.8 $5.00 $25.00
Claude Sonnet 5 $3.00 $15.00
Claude Haiku 4.5 $1.00 $5.00

Ở mọi tier model, output token đắt gấp 5 lần input token. Điều này có ý nghĩa thiết kế cực kỳ quan trọng: nếu agent của bạn tạo ra output dài dòng, đầy văn xuôi giải thích, lặp lại thông tin đã có trong input — bạn đang trả tiền ở mức giá cao nhất cho những token ít giá trị nhất. Ngược lại, đầu tư thêm token vào input để cấu trúc hóa yêu cầu chặt chẽ hơn — ép model trả lời ngắn, đúng format — thường mang lại lợi ích ròng rất lớn, dù input dài hơn.

Đây là gốc của một nguyên tắc thiết kế cốt lõi trong tối ưu token cho agent: "input rẻ, output đắt" — nên đầu tư token vào input (schema, ví dụ, ràng buộc format) để ép output ngắn và có cấu trúc, thay vì để model tự do viết output dài rồi mới parse lại.

Anti-pattern: input tối giản, output dài dòng

response = client.messages.create(
    model="claude-opus-4-8",
    max_tokens=2000,
    messages=[{
        "role": "user",
        "content": "Phân tích log lỗi này và cho tôi biết nguyên nhân với cách sửa."
                    "\n\n" + error_log,
    }],
)

Pattern tối ưu: input giàu cấu trúc, output bị ràng buộc chặt

response = client.messages.create(
    model="claude-opus-4-8",
    max_tokens=400,
    output_config={
        "format": {
            "type": "json_schema",
            "schema": {
                "type": "object",
                "properties": {
                    "root_cause": {"type": "string", "description": "1 câu, tối đa 25 từ"},
                    "fix": {"type": "string", "description": "Mô tả bản sửa, tối đa 40 từ"},
                    "affected_file": {"type": "string"},
                    "confidence": {"type": "string", "enum": ["high", "medium", "low"]},
                },
                "required": ["root_cause", "fix", "affected_file", "confidence"],
                "additionalProperties": False,
            },
        },
    },
    messages=[{
        "role": "user",
        "content": (
            "Bạn là công cụ chẩn đoán lỗi tự động. Chỉ trả về JSON đúng schema, "
            "không giải thích thêm, không lặp lại log gốc.\n\n"
            f"LOG:\n{error_log}"
        ),
    }],
)

Bảng chi phí thực tế: verbose output vs structured output

Giả sử một agent chẩn đoán lỗi xử lý 375 request/tháng (khoảng 12 request/ngày — mức tải hợp lý cho một agent nội bộ chuyên biệt), dùng Claude Opus 4.8 ($5/$25 mỗi 1M token):

Pattern Input/request Output/request Chi phí input/tháng Chi phí output/tháng Tổng/tháng
Anti-pattern (verbose output) ~40 token (gần như không cấu trúc) 800 token ~$0.00 (bỏ qua vì quá nhỏ) 375 × 800 × $25/1M = $7.50 $7.50
Optimized (structured input + constrained output) 786 token (schema + hướng dẫn + ví dụ) 150 token 375 × 786 × $5/1M = $1.47 375 × 150 × $25/1M = $1.41 $2.88

Tiết kiệm: $4.62/tháng, tương đương giảm 61,6% — dù input ở pattern tối ưu lớn hơn gần 20 lần so với anti-pattern. Đây chính là minh chứng cho tính bất đối xứng: dồn token sang input (rẻ) để cắt token ở output (đắt) luôn có lợi về tổng chi phí, miễn là bạn cắt được đủ nhiều output.

Ở quy mô một agent nội bộ 375 request/tháng, $4.62 nghe có vẻ nhỏ — nhưng hãy nhân với hệ số thực tế: một agent production xử lý 50.000-500.000 request/tháng, hoặc một tổ chức chạy hàng chục agent khác nhau, con số tiết kiệm 61,6% sẽ nhanh chóng lên đến hàng nghìn, hàng chục nghìn đô-la mỗi tháng. Và quan trọng hơn chi phí thuần: output ngắn, có cấu trúc, dễ parse hơn, ít lỗi runtime hơn, và độ trễ (latency) thấp hơn — vì output token cũng là thành phần chiếm nhiều thời gian sinh (generate) nhất.

Mẹo

  • Không bao giờ đánh giá "input dài = tốn kém" một cách máy móc. Luôn tính tổng chi phí quy đổi (input × giá_input + output × giá_output), vì với tỷ lệ giá 1:5, một input dài thêm 500 token vẫn rẻ hơn một output dài thêm 100 token.
  • Dùng output_config.format (structured outputs) hoặc strict: true trên tool definition bất cứ khi nào output cần được máy đọc tiếp (JSON, danh sách có cấu trúc) — đây là cách rẻ nhất và đáng tin cậy nhất để ràng buộc độ dài output, rẻ hơn nhiều so với việc dựa vào system prompt kiểu "hãy trả lời ngắn gọn" (không đảm bảo).
  • Với các use case chat/tư vấn cần văn xuôi tự nhiên (không thể ép JSON), vẫn có thể giảm output bằng cách nêu rõ giới hạn độ dài trong system prompt ("trả lời tối đa 3 câu, không lặp lại thông tin người dùng đã cung cấp") — hiệu quả kém structured output nhưng vẫn tốt hơn không làm gì.

Thiết kế một Token Budget Framework

Khi một tổ chức chạy nhiều AI agent phục vụ nhiều team, "cứ để chạy rồi xem hóa đơn cuối tháng" không phải là một chiến lược quản trị chi phí bền vững. Bạn cần một token budget framework (khung ngân sách token) — một lớp kiểm soát nằm giữa lời gọi API và logic nghiệp vụ, cho phép:

  1. Giới hạn cứng (hard cap) — chặn hoàn toàn khi vượt ngưỡng, tránh một agent lỗi (rơi vào loop vô hạn) đốt hết ngân sách cả tháng trong vài giờ.
  2. Cảnh báo mềm (soft alert) — thông báo khi đạt 70-90% ngân sách, cho phép con người can thiệp trước khi bị chặn cứng.
  3. Phân bổ theo nhiều lớp — ngân sách per-request (giới hạn 1 lượt gọi), per-task (giới hạn 1 vòng lặp agentic hoàn chỉnh), per-team/per-agent (giới hạn theo ngày/tháng), và per-organization (tổng ngân sách toàn công ty).
  4. Gán chi phí (cost attribution) — biết chính xác team nào, agent nào, feature nào đang tiêu tiền, để tối ưu đúng chỗ thay vì cắt giảm dàn trải.

Về mặt API, có ba cơ chế bạn cần phân biệt rõ, vì chúng vận hành ở các tầng khác nhau:

Cơ chế Tầng kiểm soát Model có "biết" giới hạn này không?
max_tokens Giới hạn cứng cho một lần sinh output Không — chỉ là giới hạn cắt cứng phía server
output_config.task_budget (beta) Giới hạn tổng token cho toàn bộ agentic loop — model thấy bộ đếm ngược và tự điều chỉnh nhịp làm việc
Token budget framework của bạn Giới hạn theo team/agent/tháng, nằm ngoài API Không — đây là logic ứng dụng tự viết

task_budget (tối thiểu 20.000 token, hiện có beta trên Claude Fable 5 / Claude Sonnet 5 / Claude Opus 4.8 / 4.7) là công cụ rất hữu ích cho tầng thứ hai — nó không chặn cứng như max_tokens, mà cho model "biết" nó còn bao nhiêu token để hoàn thành công việc, giúp model tự rút ngắn các bước thăm dò không cần thiết khi ngân sách sắp hết, thay vì bị cắt ngang giữa câu.

Tầng thứ ba — token budget framework cấp team/tổ chức — là phần bạn phải tự xây dựng. Dưới đây là một bộ khung (allocator) tối giản minh họa nguyên lý:

"""
token_budget.py — bộ khung phân bổ và theo dõi token budget theo agent/team.
Đây là lớp trung gian đặt TRƯỚC lời gọi client.messages.create() thực tế.
"""

import time
from dataclasses import dataclass, field
from threading import Lock


class BudgetExceededError(Exception):
    """Ném ra khi một agent/team vượt ngân sách cứng đã cấp."""


@dataclass
class BudgetWindow:
    """Một cửa sổ ngân sách (ví dụ: theo ngày hoặc theo tháng)."""
    limit_tokens: int
    used_tokens: int = 0
    window_start: float = field(default_factory=time.time)
    window_seconds: int = 86400  # mặc định: ngân sách theo ngày

    def maybe_reset(self) -> None:
        if time.time() - self.window_start >= self.window_seconds:
            self.used_tokens = 0
            self.window_start = time.time()

    @property
    def remaining(self) -> int:
        return max(self.limit_tokens - self.used_tokens, 0)

    @property
    def usage_ratio(self) -> float:
        return self.used_tokens / self.limit_tokens if self.limit_tokens else 0.0


class TokenBudgetAllocator:
    """
    Quản lý ngân sách token theo từng agent, với ngưỡng cảnh báo mềm
    và chặn cứng khi vượt giới hạn. Thread-safe cho môi trường nhiều worker.
    """

    def __init__(self, alert_threshold: float = 0.8):
        self._windows: dict[str, BudgetWindow] = {}
        self._lock = Lock()
        self.alert_threshold = alert_threshold

    def register_agent(self, agent_id: str, daily_limit_tokens: int) -> None:
        self._windows[agent_id] = BudgetWindow(limit_tokens=daily_limit_tokens)

    def check_and_reserve(self, agent_id: str, estimated_tokens: int) -> None:
        """Gọi TRƯỚC khi thực hiện request — chặn nếu vượt ngân sách."""
        with self._lock:
            window = self._windows[agent_id]
            window.maybe_reset()
            if window.used_tokens + estimated_tokens > window.limit_tokens:
                raise BudgetExceededError(
                    f"Agent '{agent_id}' sẽ vượt ngân sách ngày "
                    f"({window.used_tokens}/{window.limit_tokens} token đã dùng, "
                    f"cần thêm ~{estimated_tokens})."
                )

    def record_usage(self, agent_id: str, actual_input: int, actual_output: int) -> None:
        """Gọi SAU khi nhận response — ghi nhận token thực tế đã tiêu."""
        with self._lock:
            window = self._windows[agent_id]
            window.used_tokens += actual_input + actual_output
            if window.usage_ratio >= self.alert_threshold:
                self._raise_soft_alert(agent_id, window)

    def _raise_soft_alert(self, agent_id: str, window: BudgetWindow) -> None:
        # Trong hệ thống thật: gửi vào Slack/PagerDuty/observability pipeline.
        print(
            f"[CẢNH BÁO NGÂN SÁCH] Agent '{agent_id}' đã dùng "
            f"{window.usage_ratio:.0%} ngân sách ngày "
            f"({window.used_tokens}/{window.limit_tokens} token)."
        )


allocator = TokenBudgetAllocator(alert_threshold=0.8)
allocator.register_agent("support-triage-agent", daily_limit_tokens=2_000_000)


def call_agent_with_budget(agent_id: str, client, **request_kwargs):
    # Ước lượng thô trước khi gọi (dựa trên độ dài prompt, có thể dùng
    # client.messages.count_tokens() để ước lượng chính xác hơn).
    estimated = len(str(request_kwargs.get("messages", ""))) // 3
    allocator.check_and_reserve(agent_id, estimated)

    response = client.messages.create(**request_kwargs)

    allocator.record_usage(
        agent_id,
        actual_input=response.usage.input_tokens,
        actual_output=response.usage.output_tokens,
    )
    return response

Khung này cố tình tối giản để bạn thấy rõ ba bước cốt lõi: ước lượng trước (check_and_reserve), gọi API, và ghi nhận thực tế sau (record_usage). Trong hệ thống production, bạn sẽ muốn: lưu trạng thái ngân sách vào Redis/database thay vì in-memory (để chịu được restart và scale nhiều instance), tách ngân sách theo nhiều chiều (agent × team × model × môi trường dev/staging/prod), và tích hợp record_usage với usage.cache_read_input_tokens/cache_creation_input_tokens để tính đúng chi phí thực (không phải chỉ đếm token thô — token đọc từ cache rẻ hơn nhiều, xem phần tiếp theo).

Mẹo

  • Đặt ngân sách theo ngày, không chỉ theo tháng. Ngân sách tháng dễ bị "đốt" hết trong vài ngày đầu bởi một lỗi logic (agent lặp vô hạn), và bạn chỉ phát hiện ra vào cuối tháng khi xem báo cáo billing.
  • Luôn tách check_and_reserve (ước lượng, chặn sớm) khỏi record_usage (ghi nhận thực tế) — ước lượng trước giúp tránh gọi API rồi mới phát hiện vượt ngân sách (lúc đó tiền đã mất).
  • Với các agentic loop dài, ưu tiên dùng output_config.task_budget của API (khi model hỗ trợ) thay vì chỉ dựa vào max_tokens cứng — vì model biết về ngân sách và có thể tự "wrap up" công việc gọn gàng thay vì bị cắt ngang giữa một bước quan trọng.

Kinh tế học Prompt Caching — Hệ số Nhân của Input Token

Nếu input token đã rẻ hơn output token 5 lần, prompt caching (bộ nhớ đệm cho prompt) còn đẩy sự bất đối xứng đó xa hơn nữa — nó biến một phần input token thành gần như miễn phí. Đây là công cụ tối ưu chi phí có tác động lớn nhất cho bất kỳ agent nào có system prompt và tool definitions lớn, ổn định, được gửi lại ở mỗi vòng lặp.

Nguyên lý: prompt caching hoạt động theo cơ chế khớp tiền tố (prefix match) — phần đầu của prompt (thường là system prompt + tool definitions, vì thứ tự render luôn là toolssystemmessages) được lưu lại phía server sau lần gọi đầu. Các lần gọi sau, nếu phần tiền tố đó giống hệt byte-for-byte, model đọc từ cache thay vì xử lý lại từ đầu — với hai mức giá khác biệt hoàn toàn so với giá input gốc:

  • Cache write (lần đầu ghi vào cache): ~1,25× giá input gốc (TTL 5 phút) hoặc ~2× (TTL 1 giờ).
  • Cache read (các lần đọc lại từ cache): chỉ ~0,1× giá input gốc — tức rẻ hơn 10 lần.

Với một agent gọi lại cùng một system prompt + bộ tool definitions ở mỗi vòng lặp (điều gần như luôn đúng với agent), phần token cố định đó chỉ cần trả giá cao (cache write) một lần, sau đó toàn bộ các lần gọi tiếp theo trả giá cực rẻ (cache read). Nói cách khác, prompt caching biến chi phí input token cố định từ tuyến tính theo số vòng lặp thành gần như phẳng.

Ví dụ tính toán thực tế

Giả sử một agent hỗ trợ khách hàng có system prompt + tool definitions cố định là 3.200 token, xử lý 50 request/ngày, dùng Claude Sonnet 5 (giá input $3,00/1M token):

Không dùng caching — mỗi request trả full giá cho 3.200 token cố định:

50 request/ngày × 3.200 token × $3,00/1.000.000 = $0,4800/ngày

Có dùng caching — phần lớn request đọc từ cache (~10% giá gốc), chỉ một phần nhỏ phải viết mới (~125% giá gốc) khi cache hết hạn hoặc chưa được khởi tạo. Với tỷ lệ đọc/viết thực tế của một agent chạy liên tục trong giờ làm việc, giá hiệu dụng bình quân rơi vào khoảng 11% giá gốc:

$0,4800 × 0,11 ≈ $0,0528/ngày
Không caching Có caching Tiết kiệm
Chi phí/ngày $0,4800 $0,0528 $0,4272
% giảm 89,0%

Quy ra tháng (30 ngày): từ $14,40/tháng xuống $1,58/tháng — chỉ với một dòng cấu hình cache_control. Đây không phải là con số cường điệu: bất kỳ agent nào gửi lại system prompt + tool schema cố định ở mỗi vòng lặp (tức là gần như mọi agent) đều đủ điều kiện để đạt mức tiết kiệm 80-90% này trên phần input cố định.

Cách triển khai

response = client.messages.create(
    model="claude-sonnet-5",
    max_tokens=4096,
    # System prompt + mô tả nghiệp vụ ổn định, ít thay đổi giữa các request
    system=[{
        "type": "text",
        "text": LARGE_STABLE_SYSTEM_PROMPT,  # ~3.200 token, không đổi qua các lượt
        "cache_control": {"type": "ephemeral"},  # đánh dấu breakpoint cache
    }],
    tools=STABLE_TOOL_DEFINITIONS,  # render TRƯỚC system — cũng được cache theo
    messages=conversation_history + [{"role": "user", "content": new_user_message}],
)

print("cache_creation_input_tokens:", response.usage.cache_creation_input_tokens)
print("cache_read_input_tokens:", response.usage.cache_read_input_tokens)
print("input_tokens (không cache):", response.usage.input_tokens)

Một lỗi rất phổ biến khiến caching "âm thầm không hoạt động" (không lỗi, nhưng cache_read_input_tokens luôn bằng 0): chèn timestamp động (datetime.now()), UUID ngẫu nhiên, hoặc dữ liệu người dùng thay đổi liên tục vào trong system prompt — bất kỳ byte nào thay đổi ở phần tiền tố sẽ vô hiệu hóa toàn bộ cache phía sau nó. Nguyên tắc: giữ system prompt và tool list hoàn toàn tĩnh; đưa mọi nội dung động (ngày giờ hiện tại, ID người dùng, trạng thái phiên) xuống cuối messages, sau breakpoint cache.

Mẹo

  • Luôn log cả ba trường cache_creation_input_tokens, cache_read_input_tokens, và input_tokens — nếu cache_read_input_tokens bằng 0 qua nhiều request liên tiếp có cùng tiền tố, chắc chắn có thứ gì đó đang "làm bẩn" tiền tố (thường là timestamp hoặc JSON serialize không ổn định thứ tự khóa).
  • Không thay đổi model, không thêm/xóa/sắp xếp lại tool ngay giữa một hội thoại — cả hai hành động này đều làm mất toàn bộ cache (cache được khóa theo model, và tool render ở vị trí đầu tiên trong prompt).
  • Với hệ thống có traffic ngắt quãng (không liên tục trong 5 phút), hãy dùng TTL 1 giờ (ttl: "1h") thay vì mặc định 5 phút, hoặc chủ động "làm nóng" cache bằng một request max_tokens: 0 định kỳ để tránh cache hết hạn giữa các lượt request thưa.

Extended Thinking và Reasoning Token — Mục Ngân sách Cao cấp

Extended thinking (suy luận mở rộng, còn gọi là reasoning token — token dùng cho quá trình suy luận nội bộ trước khi model đưa ra câu trả lời cuối) là một hạng mục chi phí đặc biệt cần được xem như một "mục ngân sách cao cấp" riêng biệt trong mọi kế hoạch tối ưu token. Về mặt tính giá, reasoning token được tính theo giá output token (tức đắt gấp 5 lần input) — nhưng khác với output thông thường, phần lớn nội dung suy luận đó không hiển thị cho người dùng cuối. Bạn đang trả tiền ở mức giá cao nhất cho một quá trình mà giá trị hữu hình chỉ là kết luận cuối cùng.

Điều này không có nghĩa là extended thinking "lãng phí" — ngược lại, với các quyết định phức tạp (thiết kế kiến trúc hệ thống, debug logic nhiều lớp, lập kế hoạch nhiều bước), việc cho model "suy nghĩ" trước khi trả lời thường cải thiện chất lượng đáng kể, và có thể giảm tổng chi phí bằng cách tránh việc phải gọi lại nhiều lần để sửa một câu trả lời sai. Vấn đề là bạn cần chủ động kiểm soát khi nào và bao nhiêu model được phép suy luận, thay vì để nó luôn chạy ở mức cao nhất một cách mặc định.

Cơ chế điều khiển hiện tại trên các model Claude thế hệ mới (Claude Opus 4.8, Claude Sonnet 5, Claude Fable 5) là adaptive thinking kết hợp với tham số effort — Claude tự quyết định có cần suy luận hay không và bao nhiêu, còn effort (low / medium / high / xhigh / max) là cần gạt bạn dùng để điều chỉnh độ sâu suy luận tổng thể theo độ khó của task. Cách tiếp cận cũ dùng budget_tokens cố định đã bị loại bỏ trên các model này — không nên dùng cho code mới.

Ví dụ: dùng extended thinking cho một quyết định kiến trúc phức tạp

import anthropic

client = anthropic.Anthropic()

architecture_question = """
Chúng tôi đang có một hệ thống xử lý đơn hàng monolith, 40.000 request/phút
giờ cao điểm, đang gặp vấn đề về độ trễ khi ghi vào database chính.
Hãy đánh giá 3 phương án: (1) tách read/write database, (2) thêm message
queue (Kafka) để xử lý ghi bất đồng bộ, (3) chuyển sang event-sourcing.
Phân tích trade-off về độ phức tạp vận hành, chi phí hạ tầng, và rủi ro
migration cho từng phương án, rồi đưa ra khuyến nghị cụ thể.
"""

response = client.messages.create(
    model="claude-opus-4-8",
    max_tokens=8000,
    # Adaptive thinking: model tự quyết định mức độ suy luận cần thiết.
    # display="summarized" để có thể hiển thị tóm tắt quá trình suy luận
    # cho kỹ sư review — mặc định là "omitted" (rỗng, không hiển thị).
    thinking={"type": "adaptive", "display": "summarized"},
    # Task kiến trúc phức tạp, nhiều trade-off => xứng đáng dùng effort cao.
    # Ngược lại, các task đơn giản (phân loại, tóm tắt ngắn) nên dùng "low"
    # hoặc "medium" để tránh trả tiền cho suy luận không cần thiết.
    output_config={"effort": "high"},
    messages=[{"role": "user", "content": architecture_question}],
)

for block in response.content:
    if block.type == "thinking":
        print("[Quá trình suy luận — tóm tắt]:", block.thinking[:200], "...")
    elif block.type == "text":
        print("[Khuyến nghị cuối cùng]:", block.text)

print("Tổng output token (gồm reasoning):", response.usage.output_tokens)

Về ngân sách hóa việc dùng effort cao: nguyên tắc thực dụng là phân loại task theo mức độ ảnh hưởng của quyết định sai. Một lỗi chẩn đoán trong quyết định kiến trúc có thể gây thiệt hại hàng tuần công sức migration sai hướng — chi thêm vài nghìn token reasoning để giảm rủi ro đó là hoàn toàn hợp lý. Ngược lại, việc phân loại một email hỗ trợ vào 1 trong 5 category không cần — và không nên — tốn effort cao.

Loại task Effort đề xuất Lý do
Phân loại, trích xuất dữ liệu đơn giản, format lại văn bản low Task không cần suy luận nhiều bước; effort cao chỉ tốn thêm token vô ích
Workflow nghiệp vụ thông thường (trả lời hỗ trợ, tóm tắt tài liệu) medium Điểm cân bằng chất lượng/chi phí tốt nhất cho phần lớn use case
Coding agent, agentic loop nhiều bước, tìm kiếm/điều tra thông tin high hoặc xhigh Các model hiện tại gọi tool và tự kiểm chứng nhiều hơn đáng kể ở mức này
Quyết định kiến trúc, debug logic phức tạp nhiều lớp, phân tích có rủi ro cao high / xhigh / max Chi phí sai lầm vượt xa chi phí token; ưu tiên chất lượng suy luận

Mẹo

  • Không đặt effort: "max" làm mặc định toàn hệ thống — hãy coi effort là một tham số cần A/B test theo route/use case, giống như bạn test giữa các model khác nhau. high thường là điểm cân bằng tốt hơn max cho phần lớn công việc, kể cả agentic/coding.
  • Nếu bạn cần hiển thị quá trình suy luận cho người dùng/kỹ sư (để tăng tính minh bạch, dễ debug), luôn set rõ display: "summarized" — mặc định trên các model mới là "omitted" (rỗng), khiến giao diện trông như "đứng hình" một khoảng thời gian dài trước khi có output.
  • Với các subagent làm việc phụ trợ (tìm kiếm thông tin đơn giản, format lại dữ liệu), hạ effort xuống low để giảm số lượt gọi tool và độ dài văn bản trung gian — effort thấp thường đi kèm ít lời mở đầu (preamble) và ít lời xác nhận rườm rà hơn.

Quản trị Token Budget cho Team

Khi một tổ chức có nhiều team cùng xây dựng và vận hành AI agent, ngân sách token không thể chỉ nằm trong đầu một kỹ sư — nó cần được mã hóa thành chính sách (policy-as-code), version-control được, review được như bất kỳ thay đổi hạ tầng nào khác. Đây là lúc token budget chuyển từ một khái niệm kỹ thuật thành một vấn đề quản trị (governance) liên quan đến engineering lead, QA, và cả product manager — vì chính sách token budget ảnh hưởng trực tiếp đến trải nghiệm sản phẩm (agent bị chặn giữa task vì hết ngân sách là một lỗi UX nghiêm trọng, không chỉ là vấn đề kỹ thuật).

Một chính sách quản trị token budget hiệu quả cần trả lời rõ các câu hỏi: Team/agent nào được cấp bao nhiêu ngân sách mỗi ngày/tháng? Model tier nào được phép dùng cho từng loại agent (một agent phân loại email không nên có quyền gọi model đắt nhất)? Ai nhận cảnh báo khi nào? Điều gì xảy ra khi vượt ngân sách — chặn cứng, giảm xuống model rẻ hơn, hay chuyển sang chế độ giới hạn?

Dưới đây là ví dụ một file cấu hình agent-config.yaml thể hiện chính sách quản trị này — dạng cấu hình mà một team platform/nền tảng AI nội bộ thường duy trì trong repo, review qua pull request như mọi thay đổi hạ tầng khác:


version: 1

organization:
  monthly_hard_cap_usd: 15000          # Ngân sách cứng toàn công ty/tháng
  alert_channels:
    - slack: "#platform-ai-cost-alerts"
    - pagerduty: "ai-platform-oncall"

teams:
  - team: "customer-support"
    monthly_budget_usd: 4000
    alert_threshold_pct: 80             # Cảnh báo mềm khi dùng 80% ngân sách
    agents:
      - agent_id: "support-triage-agent"
        allowed_models:
          - "claude-haiku-4-5-20251001"          # Phân loại đơn giản -> model rẻ nhất
        daily_token_cap: 2000000
        default_effort: "low"
        require_approval_above_effort: "medium"   # Cần duyệt thủ công nếu muốn effort cao hơn
        on_budget_exceeded: "downgrade_model"      # Không chặn — tự hạ xuống Haiku giá thấp

      - agent_id: "support-escalation-agent"
        allowed_models:
          - "claude-sonnet-5"
          - "claude-opus-4-8"
        daily_token_cap: 5000000
        default_effort: "medium"
        require_approval_above_effort: "high"
        on_budget_exceeded: "hard_stop"            # Task nhạy cảm -> chặn cứng, cần người duyệt

  - team: "platform-engineering"
    monthly_budget_usd: 6000
    alert_threshold_pct: 75
    agents:
      - agent_id: "coding-agent"
        allowed_models:
          - "claude-opus-4-8"
        daily_token_cap: 10000000
        default_effort: "high"
        prompt_caching:
          enabled: true
          ttl: "1h"                    # Session dev thường có khoảng nghỉ dài -> TTL 1h
        task_budget:
          enabled: true
          total_tokens: 128000          # Ngân sách cho toàn bộ 1 agentic loop
        on_budget_exceeded: "hard_stop"

      - agent_id: "architecture-advisor-agent"
        allowed_models:
          - "claude-opus-4-8"
        daily_token_cap: 1000000
        default_effort: "high"
        require_approval_above_effort: "max"
        on_budget_exceeded: "hard_stop"
        notes: >
          Dùng cho các quyết định kiến trúc rủi ro cao — chi phí token thấp
          hơn nhiều so với chi phí một quyết định sai, nên KHÔNG hạ effort
          để tiết kiệm token ở agent này.

governance:
  cost_attribution_tags:
    - "team"
    - "agent_id"
    - "environment"        # dev / staging / production
  review_cadence: "weekly"           # Review báo cáo chi phí token hàng tuần
  budget_change_requires_pr: true    # Thay đổi ngân sách phải qua pull request

Vài điểm thiết kế đáng chú ý trong file trên: (1) mỗi agent bị giới hạn model tier rõ ràng — không phải agent nào cũng được quyền gọi model đắt nhất, đây là cách kiểm soát chi phí đơn giản và hiệu quả nhất; (2) on_budget_exceeded có hai chiến lược khác nhau tùy mức độ nhạy cảm của agent — downgrade_model cho agent ít rủi ro (chấp nhận chất lượng thấp hơn khi hết ngân sách, còn hơn dừng hẳn), hard_stop cho agent xử lý việc quan trọng (dừng hẳn, cần con người can thiệp thay vì tự động hạ chất lượng một cách âm thầm); (3) require_approval_above_effort ngăn một kỹ sư vô tình (hoặc cố ý) đẩy effort lên max cho mọi request, gây tốn kém không kiểm soát; (4) cost attribution tags đảm bảo mọi report chi phí sau này có thể lọc theo team/agent/environment — thiếu bước này, bạn sẽ không bao giờ biết ai đang tiêu tiền vào việc gì khi hóa đơn tăng vọt.

Mẹo

  • Coi agent-config.yaml như một artifact hạ tầng thực sự — mọi thay đổi ngân sách (tăng cap, đổi model, đổi effort mặc định) nên qua pull request và có ít nhất một người review, giống hệt như thay đổi Terraform hay Kubernetes manifest.
  • Đừng chỉ đặt cảnh báo ở mức tổ chức — cảnh báo ở cấp agent riêng lẻ giúp bạn phát hiện sớm agent nào đang có hành vi bất thường (ví dụ agent triage bỗng nhiên tiêu gấp 10 lần bình thường — dấu hiệu của một vòng lặp lỗi hoặc prompt injection khiến model bị dẫn dụ làm việc thừa).
  • Review chi phí token hàng tuần, không chỉ hàng tháng, đặc biệt trong giai đoạn agent mới ra mắt — hành vi tiêu token của agent thường thay đổi nhanh theo loại task người dùng thực tế đưa vào, khác xa với dữ liệu test nội bộ trước khi launch.