SSD Nodes Learn
Hướng dẫn Matt ConnorBởi Matt Connor

Cách kiểm soát chi phí AI agent trên VPS

Tránh mất tiền oan do agent chạy loop vô tận trên VPS. Bạn cần dùng prompt caching, giới hạn token và log usage để kiểm soát chi phí API hiệu quả.

Cách để giữ một AI agent chạy liên tục không làm tăng hóa đơn

Kiểm soát chi phí AI agent trên một VPS (virtual private server) là về các mức trần (ceilings) mà bạn thiết lập trước khi agent bắt đầu chạy, vì không có ai canh chừng đồng hồ đo khi nó đang chạy cả. Hãy giới hạn mỗi response bằng max_tokens, giới hạn số vòng lặp (loop iterations) trong code của chính bạn, cache phần prompt không bao giờ thay đổi, và log mọi số liệu usage của mỗi response để see job nào đang tiêu tốn tiền. Tiền thuê server là một mức giá cố định hàng tháng. Model API được tính phí theo token, và một vòng lặp không người giám sát (unattended loop) rất dễ tiêu tốn token một cách âm thầm.

Điều này giả định một agent đã tồn tại và gọi Messages API từ một máy chủ mà bạn sở hữu. Building an AI agent with Claude on a VPS sẽ đề cập đến chính các thành phần máy móc đó.

Tại sao một agent không người giám sát có cấu trúc chi phí khác biệt

Một session tương tác luôn có con người tham gia. Khi model đi sai hướng hoặc đọc một file log dài 40,000 dòng, người đang theo dõi sẽ dừng nó lại. Một agent không người giám sát không có phanh như vậy: nó chạy cho đến khi vòng lặp kết thúc, sau đó một timer sẽ khởi động lại nó.

Tần suất (frequency) là hệ số nhân mà mọi người thường bỏ lỡ. Một job chạy theo lịch 5 phút sẽ chạy 288 lần một ngày và khoảng 8,640 lần một tháng. Bất kể một lần chạy tốn bao nhiêu, đó chính là con số bạn cần nhân lên. Nhiều agent "always-on" thực tế không cần phải luôn bật. Chúng chỉ cần trả lời trong vòng một số phút nhất định, đó chính là một lịch trình (schedule).

Một agent cũng trả tiền cho những thứ mà một cửa sổ chat không làm.

  • Tool definitions đi kèm trong mọi request. System prompt của tool-use tốn 290 tokens trên Claude Opus 4.8 với tool_choice của auto hoặc none, và 410 với any hoặc tool. Tool bash cộng thêm 325 tokens nữa. Mỗi MCP server bạn gắn vào sẽ thêm schema của nó vào trọng lượng đó, MCP là model context protocol.
  • Tool results là input tokens. Một lệnh in ra 8,000 dòng sẽ đưa 8,000 dòng đó vào request tiếp theo, và vào mọi request sau đó trong turn đó.
  • Các trang web được fetch là input tokens. Một trang web trung bình 10 kB tương đương khoảng 2,500 tokens và một file PDF nghiên cứu 500 kB tương đương khoảng 125,000 tokens. max_content_tokens chỉ cắt bớt (truncate) các phần văn bản, vì nó "áp dụng cho nội dung văn bản, không áp dụng cho nội dung binary như PDF". Hãy dùng max_usesallowed_domains để giới hạn PDF.
  • Web search được tính phí theo mỗi lượt search, với giá $10 cho mỗi 1,000 search, bất kể có bao nhiêu kết quả trả về. Một search bị lỗi thì không bị tính phí.

Không có cái nào trong số đó là đắt nếu chỉ chạy một lần. Tất cả sẽ trở nên đắt đỏ khi chạy 8,640 lần.

Hard ceilings và soft ceilings giải quyết các vấn đề khác nhau

max_tokens được thực thi (enforced). Đây là mức trần cứng (hard cap) cho tổng output của một request, bao gồm cả text suy nghĩ (thinking) và text phản hồi. Claude không bao giờ tạo quá mức này, và model không thể thấy con số đó. Nếu chạm mức này, bạn sẽ nhận được stop_reason: "max_tokens" và một câu trả lời bị cắt cụt. Điểm yếu đối với agent: mỗi request trong một vòng lặp tool-use đều mang theo max_tokens riêng, vì vậy nó giới hạn một response chứ không phải toàn bộ task. Mười lần gọi tool ở mức 4,000 sẽ là mức trần 40,000-token cho một turn.

Task budget mang tính chất tham khảo. task_budget nằm trong output_config và cho model biết nó có bao nhiêu token cho toàn bộ vòng lặp agentic, tính cả thinking, tool calls, tool results và output.

resp = client.beta.messages.create(
    model="claude-opus-4-8",
    max_tokens=4096,
    betas=["task-budgets-2026-03-13"],
    output_config={"task_budget": {"type": "tokens", "total": 64000}},
    messages=messages,
)

"Task budgets là một gợi ý nhẹ (soft hint), không phải là một mức trần cứng (hard cap)." Claude có thể vượt quá một mức trong quá trình thực hiện, và giới hạn output bắt buộc vẫn là max_tokens. "Bộ đếm ngược chỉ hiển thị cho model", và các response không mang theo field số token còn lại. Mức task_budget.total tối thiểu được chấp nhận là 20,000 tokens, và mức thấp hơn sẽ trả về lỗi 400. Một budget quá nhỏ so với khối lượng công việc sẽ tạo ra hành vi giống như từ chối (refusal), do đó model sẽ thu hẹp phạm vi task hoặc dừng sớm.

Một chi tiết sẽ làm tốn tiền thay vì tiết kiệm. Nếu client của bạn giảm task_budget.remaining ở mỗi request tiếp theo, giá trị thay đổi đó sẽ làm mất hiệu lực của bất kỳ prefix nào đã được cache. Hãy thiết lập nó một lần duy nhất ở request đầu tiên.

Task budgets đang ở bản beta trên Claude Fable 5, Claude Opus 4.8 và Claude Opus 4.7. Claude Sonnet 5 và Claude Haiku 4.5 được liệt kê là Not supported, và task budgets không áp dụng cho Claude Code, vì vậy một Claude Code session tách rời trong tmux sẽ phụ thuộc vào việc quản lý session (session hygiene) thay thế.

Mức trần thứ ba nằm trong Claude Console: hãy cấp cho agent một workspace riêng, sau đó thiết lập hạn mức chi tiêu hàng tháng (monthly spend limit) và hạn mức rate limit mỗi phút cho nó. "Bạn không thể thiết lập giới hạn trên Default Workspace", và "Các giới hạn trên toàn Organization luôn được áp dụng, ngay cả khi tổng các giới hạn workspace lớn hơn". Hãy thêm thông báo chi tiêu (spend notifications) để nhận cảnh báo trước khi chạm mức trần.

Lựa chọn model theo từng job, và yếu tố nào thực sự thay đổi chi phí

Lựa chọn model là quyết định theo từng job. Tính đến tháng 7 năm 2026, tính trên mỗi triệu token, input rồi đến output: Claude Fable 5 là $10 và $50, Claude Opus 4.8 và Opus 4.7 là $5 và $25, Claude Sonnet 5 là $3 và $15, Claude Haiku 4.5 là $1 và $5. Sonnet 5 hiện đang có giá thấp hơn giá niêm yết, vì "Mức giá giới thiệu $2/$10 cho mỗi triệu token input/output đang có hiệu lực đến hết ngày 31 tháng 8 năm 2026". Một bước chỉ để phân loại các dòng log thì không cần dùng Opus.

Effort là đòn bẩy thứ hai. output_config.effort chấp nhận low, medium, high, xhighmax, và mặc định là high, nên việc thiết lập high một cách tường minh cũng giống như việc bỏ qua nó. Effort thấp giúp cắt giảm nhiều hơn là chỉ độ dài suy nghĩ: tài liệu nói rằng nó khiến Claude thực hiện ít tool calls hơn và kết hợp các thao tác thành một. Đối với một agent, đây là khoản tiết kiệm lớn hơn, vì một tool call được tránh được chính là một request không bao giờ xảy ra.

Cạm bẫy là effort sẽ xung đột với cache. Thay đổi giá trị giữa các request sẽ làm mất hiệu lực prompt caching. Trong ví dụ được tài liệu hóa, request 2 báo cáo cache_read_input_tokens: 3546; request 3, với effort thay đổi từ high sang medium, báo cáo cache_creation_input_tokens là 3546 và cache_read_input_tokens là 0. Vì vậy, hãy thay đổi effort giữa các workload, đừng thay đổi bên trong một conversation đã được cache. Để điều chỉnh độ sâu mà không làm hỏng cache, hãy thực hiện việc đó trong prompt: một dòng như "Trả lời trực tiếp không cần suy nghĩ sâu" ở message mới nhất của user sẽ giữ nguyên các breakpoint trước đó.

Thinking tokens được tính theo giá output và tính vào max_tokens, đó là lý do tại sao một câu trả lời bị cắt cụt thường có nghĩa là phần thinking đã ngốn hết budget. Đọc usage.output_tokens_details.thinking_tokens để biết con số cụ thể. What actually fills a Claude token bill sẽ phân tích chi tiết.

Cache prefix ổn định, và đừng làm hỏng nó một cách vô ý

Một lần ghi cache (cache write) tốn 1.25 lần giá input cơ bản trên cache 5 phút và 2 lần trên cache 1 giờ. Một lần đọc cache (cache read) chỉ tốn 0.1 lần, vì vậy "caching sẽ có lợi sau chỉ một lần đọc cache cho thời hạn 5 phút (1.25x write), hoặc sau hai lần đọc cache cho thời hạn 1 giờ (2x write)".

Một dòng giải thích tại sao điều này phù hợp với một agent luôn bật: "Cache được làm mới mà không tốn thêm chi phí mỗi khi nội dung đã cache được sử dụng." Một job chạy mỗi hai phút trên cache 5 phút sẽ giữ cho prefix của nó luôn "warm" cả ngày chỉ với một lần write.

Ba cách để mất cache mà không nhận ra.

Một prefix bị thay đổi. "Các cache prefixes được tạo theo thứ tự sau: tools, system, sau đó là messages." Bất kỳ thay đổi byte nào sớm hơn trong thứ tự đó đều làm mất hiệu lực mọi thứ sau đó, và việc chỉnh sửa tool definitions sẽ làm mất toàn bộ cache. Sai lầm kinh điển tự gây ra là đưa timestamp hoặc run id vào system prompt: khi đó mỗi request sẽ mang một prefix khác nhau, thực hiện một write mới ở mức 1.25x, và không đọc được gì từ cache. Dấu hiệu nhận biết là usage.cache_read_input_tokens bằng 0 trong các cuộc gọi trông có vẻ giống hệt nhau. Hãy chuyển văn bản biến đổi vào message mới nhất của user.

Một prefix quá ngắn. Mỗi model có một độ dài tối thiểu để có thể cache, và nếu dưới mức đó, request sẽ được xử lý mà không có caching và "không có lỗi nào được trả về". Các con số bao gồm 1,024 tokens trên Claude Opus 4.8 và Claude Sonnet 5, và 4,096 trên Claude Haiku 4.5, vì vậy việc chuyển một job từ Sonnet sang Haiku có thể khiến việc caching bị tắt một cách âm thầm.

Một conversation vượt quá cửa sổ nhìn lại (lookback). "Cửa sổ lookback là 20 blocks." Hệ thống kiểm tra tối đa 20 vị trí cho mỗi breakpoint, sau đó dừng lại. Trong ví dụ được tài liệu hóa, một turn giữ 35 blocks với một breakpoint ở block 35 sẽ kiểm tra từ block 35 xuống block 16, và entry của turn trước đó ở block 15 nằm ngoài cửa sổ, nên không có hit. Một agent chèn thêm nhiều block tool-use và tool-result mỗi turn sẽ vượt quá 20 chỉ sau hai hoặc ba turn. Bạn có bốn breakpoints cho mỗi request, vì vậy hãy dành một cái cho các messages gần nhất.

Gửi bất cứ thứ gì có thể chờ đợi vào Batches API

"Tất cả usage được tính ở mức 50% giá API tiêu chuẩn", cho cả input và output. Batch processing là bất đồng bộ, "với hầu hết các batch hoàn thành trong chưa đầy 1 giờ", kết quả sẽ có khi mọi request đã hoàn thành hoặc sau 24 giờ, tùy điều kiện nào đến trước. Đó là thông tin điển hình, không phải là cam kết.

Hãy poll processing_status cho đến khi nó đọc được ended. Các request trả về errored, canceled hoặc expired không bị tính phí. Một lưu ý nếu bạn dựa vào mức trần chi tiêu: "các batch có thể vượt quá một chút so với giới hạn chi tiêu đã cấu hình của Workspace của bạn."

Các mức giảm giá được cộng dồn, và vì một batch có thể mất nhiều hơn 5 phút, tài liệu khuyến nghị dùng cache 1 giờ cho các batch chia sẻ context. Vì vậy, hãy chia nhỏ công việc: bất cứ thứ gì con người hoặc webhook đang chờ đợi thì hãy để ở path trực tiếp, còn các bản tóm tắt hàng đêm hoặc phân loại log ngày hôm trước thì đưa vào batch với nửa giá.

Log mọi field usage của response vào kho lưu trữ riêng của bạn

Bạn không thể quy trách nhiệm chi tiêu cho những gì bạn chưa bao giờ ghi lại. Mỗi response cho bạn biết nó tốn bao nhiêu.

u = resp.usage
row = {
    "job": job_name,
    "model": resp.model,
    "uncached_input": u.input_tokens,
    "cache_write": u.cache_creation_input_tokens,
    "cache_read": u.cache_read_input_tokens,
    "output": u.output_tokens,
    "stop_reason": resp.stop_reason,
}

Thêm một dòng cho mỗi API call vào một file JSON-lines, được gắn tag với tên job của bạn. Một tuần sau, bạn có thể nói job nào đang tiêu tiền và job nào chỉ trông có vẻ bận rộn. Hãy chú ý cache_read: một cột toàn số 0 là lỗi chi phí phổ biến nhất trong một agent tự host.

Có một field rất dễ đọc nhầm. input_tokens chỉ đếm các token sau breakpoint cache cuối cùng, vì vậy kích thước prompt thực tế là total_input_tokens = cache_read_input_tokens + cache_creation_input_tokens + input_tokens. Một agent báo cáo input_tokens: 400 trên một prompt lớn không hề rẻ: phần còn lại đến từ cache.

Hãy đếm trước khi gửi. Đếm token là miễn phí và rate limit của nó tách biệt với việc tạo message, vì vậy hãy dùng count_tokens để từ chối một attachment quá lớn thay vì phải trả tiền để phát hiện ra nó. Kết quả trả về là một ước tính, vì vậy hãy đo lại theo từng model và đừng bao giờ dùng lại kết quả đếm từ tokenizer của nhà cung cấp khác. Claude Opus 4.7 và các model Opus sau đó, Claude Fable 5 và Claude Sonnet 5 sử dụng một tokenizer mới hơn "tạo ra nhiều hơn khoảng 30% token cho cùng một văn bản". Claude Sonnet 4.6 và các bản cũ hơn, bao gồm cả Claude Haiku 4.5, sử dụng loại cũ hơn.

Để có cái nhìn chuẩn xác nhất, Admin API báo cáo usage tại https://api.anthropic.com/v1/organizations/usage_report/messages và cost tại https://api.anthropic.com/v1/organizations/cost_report. Cả hai đều cần một admin key (sk-ant-admin01-...) dưới dạng x-api-key: $ANTHROPIC_ADMIN_KEY với anthropic-version: 2023-06-01, và chấp nhận bucket_width=1d, group_by[]=modelapi_key_ids[]=. Một hạn chế: "Admin API không khả dụng cho các tài khoản cá nhân."

Tham số cuối cùng đó là một mẹo quy trách nhiệm chi phí rẻ tiền: hãy cấp cho mỗi job một API key riêng, lọc với api_key_ids[], và chia báo cáo theo từng key với group_by[]=api_key_id. Filter là số nhiều, dimension grouping là số ít. Hãy giữ các key trong environment thay vì trong code, giống như cách a first Claude API app on a VPS xử lý chúng.

Giới hạn vòng lặp, vì không có gì khác làm được việc đó

Một số lượng lặp (iteration count) có giới hạn là điều bắt buộc ở đây. Vòng lặp là của bạn, nên bộ đếm cũng là của bạn:

for step in range(MAX_STEPS):          # MAX_STEPS = 12, never "while True"
    resp = client.messages.create(...)
    if resp.stop_reason != "tool_use":
        break
else:
    log.warning("job %s hit MAX_STEPS=%d, giving up", job_name, MAX_STEPS)

Không có mức trần nào ở trên làm việc này thay bạn: max_tokens giới hạn một response, và model chỉ được gợi ý về một task budget.

Hãy đặt một phanh thứ hai bên ngoài tiến trình. Hãy chạy job từ một systemd timer thay vì một process vĩnh viễn, và thiết lập RuntimeMaxSec= trên service unit của nó. Với RuntimeMaxSec=600, một lần chạy bị kẹt sẽ bị kill sau mười phút thay vì chạy vô tận cho đến khi bạn nhận ra. Running a program as a systemd service and timer sẽ hướng dẫn về các file unit. Hãy đọc những gì một lần chạy đã thực hiện với journalctl -u triage-agent.service --since "1 hour ago".

Hãy giới hạn cả số lần thử lại (retries), vì một handler thử lại mãi mãi sẽ tính phí cho mọi lần thử. Một lỗi 429 hoặc 500 xứng đáng với vài lần thử kèm theo backoff. Một lỗi 400 thì không xứng đáng với lần thử nào, vì cùng một request sẽ thất bại theo cùng một cách.

Kiểm soát chi phí AI agent bắt đầu từ việc đọc các con số của chính bạn

Không ai có thể cho bạn biết một agent luôn bật tốn bao nhiêu, vì chi phí là số token trên mỗi lần chạy nhân với số lần chạy mỗi ngày, và cả hai phần này đều thuộc về bạn. Hãy chạy nó một lần, đọc dòng usage bạn đã log, và nhân với lịch trình của bạn. Kiểm tra báo cáo chi phí hai ngày sau đó so với phép tính đó. Khi hai con số không khớp nhau, khoảng cách đó gần như luôn là do cache bị hỏng hoặc một vòng lặp chạy lâu hơn bạn dự tính.

Điều này giả định bạn có một API key, vì agent là chương trình của chính bạn gọi Messages API. Đối với công việc tương tác cá nhân, which Claude plan fits the way you work sẽ đề cập đến khía cạnh đăng ký thuê bao. Mọi mức giá và giới hạn ở đây đã được kiểm tra so với tài liệu của Anthropic vào tháng 7 năm 2026, vì vậy hãy đọc lại trang giá trước khi bạn lập ngân sách.

FAQ

Chi phí để chạy một AI agent luôn bật trên VPS là bao nhiêu?

Có hai hóa đơn và chỉ có một cái là có thể dự đoán được. Server là một mức giá cố định hàng tháng. Model API được tính phí theo token, vì vậy chi phí là những gì một lần chạy tiêu thụ nhân với tần suất nó chạy. Anthropic không công bố con số cho một agent tự host luôn bật, vì vậy hãy coi bất kỳ con số được trích dẫn nào là một con số ước tính. Hãy log usage từ một lần chạy thực tế và nhân với lịch trình của bạn.

Sự khác biệt giữa max_tokens và task budget là gì?

max_tokens được thực thi và không hiển thị cho model. Nó giới hạn output của một request, bao gồm cả thinking, và nếu chạm mức này sẽ nhận được stop_reason: "max_tokens". Task budget thì ngược lại: model được cho biết con số đó và điều tiết vòng lặp agentic dựa trên nó, nhưng "Task budgets là một gợi ý nhẹ, không phải là một mức trần cứng" và giới hạn bắt buộc vẫn là max_tokens.

Tại sao cache_read_input_tokens luôn bằng 0 đối với agent của tôi?

Bởi vì prefix thay đổi giữa các lần gọi, hoặc nó quá ngắn để có thể cache. Nguyên nhân thông thường là một timestamp hoặc một run id được chèn vào system prompt: cache được định danh dựa trên prefix, vì vậy bất kỳ thay đổi byte nào cũng làm mất hiệu lực mọi thứ sau đó. Thay đổi tool definitions hoặc giá trị effort cũng gây ra điều tương tự. Nếu không, đó là do kích thước, vì các prompt ngắn hơn không được cache và không có lỗi nào được trả về.

Làm thế nào để ngăn AI agent lặp vô tận?

Hãy đếm số lần lặp trong code vòng lặp của bạn và dừng lại ở một mức tối đa cố định, vì max_tokens chỉ giới hạn một response còn một agent thì thực hiện rất nhiều. Hãy thêm một giới hạn thời gian thực (wall-clock limit) bên ngoài tiến trình: hãy bắt đầu job từ một systemd timer với RuntimeMaxSec= đã được thiết lập, để một lần chạy bị kẹt sẽ bị kill đúng lịch trình. Hãy giới hạn cả số lần thử lại, vì một vòng lặp retry sẽ tính phí cho mọi lần thử.

Tôi có thể thiết lập hạn mức chi tiêu cho một Claude API key duy nhất không?

Hạn mức chi tiêu được tài liệu hóa là theo từng workspace chứ không phải theo từng key, vì vậy hãy cấp cho agent một workspace riêng và giới hạn chi tiêu hàng tháng của nó tại đó. "Bạn không thể thiết lập giới hạn trên Default Workspace". Hãy thêm thông báo chi tiêu để một ngưỡng cảnh báo sẽ báo cho bạn trước. Để quy trách nhiệm, hãy cấp cho mỗi job một key riêng, sau đó nhóm báo cáo usage bằng group_by[]=api_key_id.

#claude#ai#agents#api#cost