Quay lại blog

API & SDKHướng dẫn

Theo dõi chi phí token của AI agent trong Python: usage, ngân sách, dừng

Ghi log trường usage của mọi yêu cầu, cộng dồn theo từng nhiệm vụ và dừng agent tại một ngân sách đô-la. Ví dụ Python hoạt động được cho API tương thích OpenAI.

Bởi CheapTokens TeamĐã xuất bản 7 phút đọc

Thẻ code kiểu terminal: log usage mỗi bước và kiểm tra ngân sách dừng một AI agent
Trên trang này
  1. Các con số nằm ở đâu: đối tượng usage
  2. Một đồng hồ đo dừng agent
  3. Một lần chạy 40 bước cộng lại thành gì
  4. Khi model đang suy nghĩ
  5. Streaming: usage đến cuối cùng
  6. Nếu các con số trông sai

Agent của bạn chạy mười phút và hoá đơn còn lớn hơn cả nhiệm vụ. Để hiểu tại sao, hãy đọc đối tượng usage trong mỗi phản hồi API, cộng dồn theo từng nhiệm vụ rồi nhân với giá của model trên mỗi 1M token. Để giới hạn nó, hãy so sánh tổng đang chạy đó với ngân sách sau mỗi bước và dừng vòng lặp khi vượt qua. Dưới đây là một lớp Python nhỏ làm đúng việc này.

Ví dụ dùng endpoint tương thích OpenAI https://api.cheaptokens.dev/v1 và model id deepseek-ai/DeepSeek-V4-Flash-0731 (ngữ cảnh 380K, tối đa 32.768 token đầu ra mỗi câu trả lời). Nếu bạn muốn ước lượng một lần chạy trước khi bắt đầu, đó là việc khác, và Cách ước lượng chi phí của một AI agent đề cập đến nó. Bài này nói về việc đo lường và giới hạn trong khi lần chạy đang diễn ra.

Các con số nằm ở đâu: đối tượng usage#

Mọi chat completion không streaming đều trả về một đối tượng usage. Tài liệu tham khảo API của DeepSeek (kiểm tra ngày 26 tháng 10 năm 2026) liệt kê các trường sau:

  • prompt_tokens — mọi thứ bạn đã gửi lần này: system prompt, lịch sử, kết quả công cụ.
  • completion_tokens — những gì model viết trả lại.
  • total_tokens — hai cái cộng lại với nhau.

DeepSeek cũng ghi nhận các trường bổ sung tuỳ chọn: prompt_cache_hit_tokens và prompt_cache_miss_tokens (chúng cộng lại thành prompt_tokens), và completion_tokens_details.reasoning_tokens cho các model suy luận. Trang này không hiển thị ngày xuất bản, và những trường bổ sung này là phần mở rộng của định dạng OpenAI, nên đoạn code dưới đây coi chúng là tuỳ chọn và chỉ dựa vào hai trường đầu tiên.

Tại sao ghi log mọi bước mà không chỉ bước cuối? Vì một agent gửi lại ngữ cảnh đang lớn dần của nó mỗi lần. Bước 1 có thể gửi 2.000 token đầu vào, bước 30 có thể gửi 9.000, và hoá đơn là tổng của tất cả chúng. Lý do được giải thích trong tại sao một cuộc trò chuyện dài tốn hơn, và sự phân chia đầu vào/đầu ra trong token đầu vào so với đầu ra.

Một đồng hồ đo dừng agent#

Đây là toàn bộ: một đồng hồ đo cộng dồn token, in một dòng mỗi bước và ném lỗi khi vượt ngân sách, cùng một vòng lặp agent tối giản bao quanh nó. Cài SDK bằng pip install openai và đặt khoá của bạn vào CHEAPTOKENS_API_KEY.

python
import os
from openai import OpenAI

client = OpenAI(
    base_url='https://api.cheaptokens.dev/v1',
    api_key=os.environ['CHEAPTOKENS_API_KEY'],
)
MODEL = 'deepseek-ai/DeepSeek-V4-Flash-0731'

# example prices, USD per 1M tokens: use the current ones from the pricing table
PRICE_IN = 0.10
PRICE_OUT = 0.40


class BudgetExceeded(Exception):
    pass


class TaskMeter:
    def __init__(self, budget_usd):
        self.budget = budget_usd
        self.steps = 0
        self.tokens_in = 0
        self.tokens_out = 0
        self.reasoning = 0

    @property
    def cost(self):
        return (self.tokens_in * PRICE_IN + self.tokens_out * PRICE_OUT) / 1_000_000

    def add(self, usage):
        self.steps += 1
        self.tokens_in += usage.prompt_tokens
        self.tokens_out += usage.completion_tokens
        details = getattr(usage, 'completion_tokens_details', None)
        self.reasoning += getattr(details, 'reasoning_tokens', 0) or 0
        print(f'step {self.steps}: in={usage.prompt_tokens} '
              f'out={usage.completion_tokens} task=${self.cost:.4f}')
        if self.cost > self.budget:
            raise BudgetExceeded(
                f'budget ${self.budget:.2f} crossed at step {self.steps}: '
                f'${self.cost:.4f}')


def run_task(goal, budget_usd=0.50, max_steps=40):
    meter = TaskMeter(budget_usd)
    messages = [
        {'role': 'system', 'content': 'Work step by step. Reply DONE when finished.'},
        {'role': 'user', 'content': goal},
    ]
    for _ in range(max_steps):
        resp = client.chat.completions.create(
            model=MODEL, messages=messages, max_tokens=1024)
        meter.add(resp.usage)  # log first, then decide
        reply = resp.choices[0].message.content or ''
        messages.append({'role': 'assistant', 'content': reply})
        if 'DONE' in reply:
            break
        messages.append({'role': 'user', 'content': 'Continue.'})
    return meter


try:
    meter = run_task('Summarise the main risks of a database migration.')
    print(f'finished: {meter.steps} steps, ${meter.cost:.4f}')
except BudgetExceeded as err:
    print('stopped:', err)

Ba lựa chọn trong đó đáng để sao chép.

Ghi log trước khi quyết định. meter.add ghi lại bước trước rồi mới kiểm tra ngân sách, nên một lần chạy bị dừng vẫn để lại dấu vết đầy đủ. Việc kiểm tra diễn ra sau lời gọi đã vượt qua giới hạn, nên bạn có thể vượt quá nhiều nhất một bước. max_tokens=1024 giới hạn đầu ra của bước cuối đó.

Hai điểm dừng cứng, không phải một. max_steps bắt được một agent lặp mà không tiêu nhiều; ngân sách đô-la bắt được một agent tiêu nhiều mỗi bước. Bạn cần cả hai.

Giá nằm ở một chỗ. PRICE_IN và PRICE_OUT là các con số giữ chỗ. Sao chép giá trị hiện tại cho model của bạn từ trang giá; hiện tại DeepSeek-V4-Flash là $0.015 mỗi 1M token đầu vào và $0.10 mỗi 1M token đầu ra.

Một lần chạy 40 bước cộng lại thành gì#

Giả sử agent của bạn thực hiện 40 bước, và vì ngữ cảnh lớn dần, một bước trung bình gửi 6.000 token đầu vào và nhận lại 400 token. Đó là 40 × 6.000 = 240.000 token đầu vào và 40 × 400 = 16.000 token đầu ra cho nhiệm vụ.

Bước 1 gửi 2.000 token đầu vào, bước 30 gửi 9.000; tổng nhiệm vụ là tổng của tất cả các bước
Minh hoạ với các giả định của bài viết, không phải kết quả chạy thực tế

Trên DeepSeek-V4-Flash, chi phí là $0.0052. Bảng chạy cùng khối lượng công việc trên các model khác và đặt giá tham chiếu của OpenRouter cạnh giá của chúng tôi:

Mô hình Chi phí OpenRouter
GLM-5.3-Flash $0.0054 $0.0111
DeepSeek-V4-Flash $0.0052 $0.0139
MiniMax-M2.7 $0.0144 $0.0912

Tổng cộng 240.000 token đầu vào và 16.000 token đầu ra. Giá trên 1 triệu token là giá hiện hành và thay đổi cùng với biểu phí của chúng tôi; cột OpenRouter hiển thị giá tham chiếu cho cùng khối lượng công việc.

Tổng cộng, khi tính cả đầu vào và đầu ra, DeepSeek-V4-Flash rẻ hơn OpenRouter cho nhiệm vụ này. Cũng hãy để ý token nằm ở đâu: 240.000 trong số 256.000 là đầu vào. Ngữ cảnh mà agent của bạn kéo theo chính là hoá đơn, đó là lý do con số in= mỗi bước trong log của bạn là thứ cần theo dõi. Ngân sách $0,50 mỗi nhiệm vụ trong ví dụ trên là giới hạn cho một lần chạy mất kiểm soát, không phải mục tiêu.

Đổi model chỉ có nghĩa là đổi MODEL. Chọn id từ tài liệu CheapTokens: tên khác nhau giữa các nhà cung cấp, và một id ngắn lấy từ nhãn giá sẽ không hoạt động trong API. Với các vòng lặp agent dài, DeepSeek-V4-Flash có một mức giá vào bất kỳ giờ nào trong ngày, nên một nhiệm vụ tốn như nhau vào buổi trưa và 3 giờ sáng. Xem trang DeepSeek để biết các giới hạn của nó.

Khi model đang suy nghĩ#

Nếu một câu trả lời có số token dài hơn nhiều so với văn bản bạn thấy, hãy xem reasoning_tokens. Token suy luận được tính là đầu ra, nên một bước nặng về suy luận có thể tạo ra nhiều token đầu ra hơn hẳn so với văn bản hiển thị, và chúng bị tính phí như đầu ra. Đồng hồ đo ở trên thu chúng vào meter.reasoning khi API trả về trường này và để nó ở 0 khi không trả về, nên nó không bao giờ sập vì thiếu key. In nó cạnh out= nếu bạn muốn xem bao nhiêu phần đầu ra là suy luận. Mức độ suy luận và token suy luận giải thích tại sao.

Streaming: usage đến cuối cùng#

Nếu agent của bạn stream, không có usage trong các chunk thông thường. Trong định dạng OpenAI, bạn yêu cầu nó bằng stream_options={'include_usage': True}. Tài liệu tham khảo của DeepSeek (kiểm tra ngày 26 tháng 10 năm 2026) nói rằng tuỳ chọn này chỉ hoạt động cùng với stream=True (nếu không API trả về 400), rằng usage là null trong mọi chunk trừ một chunk, và rằng chunk cuối cùng trước data: [DONE] mang tổng cho toàn bộ yêu cầu. Vậy trong một vòng lặp streaming, hãy gọi meter.add một lần, trên chunk mà usage không rỗng. Vòng lặp không streaming ở trên là nơi đơn giản nhất để bắt đầu; streaming trong Python có chính vòng lặp đó.

Nếu các con số trông sai#

  • usage là None. Bạn đang stream mà không có include_usage. Hãy thêm nó, hoặc chuyển sang lời gọi không streaming.
  • Tổng thấp hơn mức giảm số dư. Kiểm tra rằng mọi lời gọi đều đi qua đồng hồ đo: các lần thử lại, một lời gọi tóm tắt hoặc một lời gọi chọn công cụ được thực hiện ngoài run_task rất dễ bị bỏ sót. Hãy bọc client.chat.completions.create trong một hàm và định tuyến mọi thứ qua đó.
  • Chi phí mỗi bước cứ tăng mãi. Điều đó là bình thường với một vòng lặp nối mọi thứ vào messages. Cắt bớt đầu ra công cụ cũ hoặc tóm tắt lịch sử; đầu ra công cụ ra khỏi ngữ cảnh cho thấy ý tưởng này.
  • BudgetExceeded kích hoạt ở bước 2. Ngân sách của bạn nhỏ hơn một bước. Kiểm tra rằng PRICE_IN là trên 1M token, không phải trên 1K.

Để lưu các con số, hãy ghi meter.steps, meter.tokens_in, meter.tokens_out và meter.cost thành một dòng JSON cho mỗi nhiệm vụ. Một tuần những dòng đó cho bạn biết số token thực tế mỗi bước cho agent của bạn, đó là con số cần đưa vào mọi ước lượng sau này. Model, giá và yêu cầu đầu tiên nằm trong tài liệu; các lời gọi công cụ được đề cập trong gọi hàm trong Python.