Kiểm soát chi phí AI agent trên VPS luôn chạy
Agent chạy nền có thể âm thầm tính phí mỗi vòng lặp. Dùng hard cap, ngân sách task, prompt caching, batching và log usage để biết job nào tốn token.
Cách ngăn AI agent chạy liên tục làm tăng chi phí
Kiểm soát chi phí AI agent trên VPS (virtual private server) là đặt các giới hạn trước khi agent khởi chạy, vì không có ai theo dõi đồng hồ chi phí trong lúc agent chạy. Giới hạn mỗi response bằng max_tokens, giới hạn số vòng lặp trong code của bạn, cache phần prompt không thay đổi và ghi log các số liệu usage của mọi response để biết job nào tiêu tốn nhiều nhất. Chi phí thuê server là mức cố định hằng tháng. Model API tính phí theo token, còn một vòng lặp không có người giám sát rất dễ âm thầm tiêu tốn token.
Phần này giả định bạn đã có một agent gọi Messages API từ một máy bạn sở hữu. Xây dựng AI agent với Claude trên VPS trình bày phần triển khai agent.
Tại sao agent không có giám sát có cấu trúc chi phí khác
Một session tương tác có người tham gia. Khi model đi sai hướng hoặc đọc một log dài 40,000 dòng, người đang theo dõi sẽ dừng nó lại. Agent không có giám sát không có cơ chế phanh như vậy: nó chạy đến khi vòng lặp kết thúc, sau đó timer lại khởi động nó.
Tần suất là hệ số nhân mà nhiều người bỏ qua. Một job chạy theo lịch 5 phút sẽ chạy 288 lần mỗi ngày và khoảng 8,640 lần mỗi tháng. Dù mỗi lần chạy tốn bao nhiêu, bạn phải nhân chi phí đó lên. Nhiều agent "luôn bật" thực ra không cần phải chạy liên tục. Chúng chỉ cần phản hồi trong một số phút nhất định. Đó là một schedule.
Agent cũng phát sinh chi phí cho những thành phần mà cửa sổ chat không có.
- Tool definition được gửi kèm trong mọi request. System prompt cho việc sử dụng tool tốn 290 token trên Claude Opus 4.8 với
tool_choicecủaautohoặcnone, và 410 vớianyhoặctool. Bash tool cộng thêm 325 token. Mỗi MCP server bạn gắn vào đều thêm schema của nó vào phần này; MCP là viết tắt của model context protocol. - Kết quả từ tool là input token. Một command in ra 8,000 dòng sẽ đưa 8,000 dòng đó vào request tiếp theo và mọi request sau đó trong lượt chạy đó.
- Các trang được fetch là input token. Một trang web trung bình 10 kB tương đương khoảng 2,500 token, còn một research PDF 500 kB tương đương khoảng 125,000 token.
max_content_tokenschỉ truncate phần text, vì nó "áp dụng cho nội dung text, không áp dụng cho nội dung nhị phân như PDF". Thay vào đó, hãy giới hạn PDF bằngmax_usesvàallowed_domains. - Web search được tính phí theo từng search, ở mức $10 cho 1,000 search, bất kể có bao nhiêu kết quả trả về. Search bị lỗi không bị tính phí.
Không khoản nào trong số này đắt nếu chỉ phát sinh một lần. Tất cả đều đắt khi lặp lại 8,640 lần.
Giới hạn cứng và giới hạn mềm giải quyết các vấn đề khác nhau
max_tokens được áp dụng bắt buộc. Đây là giới hạn cứng cho tổng output của một request, gồm cả phần suy luận và nội dung phản hồi. Claude không bao giờ tạo output vượt quá giới hạn này, và model không nhìn thấy giá trị giới hạn. Khi chạm giới hạn, request trả về stop_reason: "max_tokens" và câu trả lời bị cắt ngắn. Với agent, điểm cần lưu ý là mỗi request trong vòng lặp sử dụng tool có max_tokens riêng. Vì vậy, giới hạn này chỉ áp dụng cho một phản hồi, không áp dụng cho toàn bộ task. Mười lần gọi tool với giới hạn 4,000 token tạo thành giới hạn 40,000 token cho lượt đó.
Task budget chỉ mang tính khuyến nghị. task_budget nằm trong output_config và cho model biết số token được phép dùng cho toàn bộ vòng lặp agent, bao gồm phần suy luận, các lần gọi tool, kết quả từ tool 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 budget là gợi ý mềm, không phải giới hạn cứng." Claude có thể vượt qua giới hạn này trong một hành động, còn giới hạn bắt buộc đối với output vẫn là max_tokens. "Chỉ model nhìn thấy đồng hồ đếm ngược", và phản hồi không có trường cho biết ngân sách còn lại. Giá trị task_budget.total tối thiểu được chấp nhận là 20,000 token. Giá trị thấp hơn sẽ trả về lỗi 400. Nếu budget quá nhỏ so với công việc, model có thể hoạt động như đang từ chối: thu hẹp phạm vi task hoặc dừng sớm.
Có một chi tiết làm tăng chi phí thay vì tiết kiệm. Nếu client giảm task_budget.remaining trong mỗi request tiếp theo, giá trị đã thay đổi sẽ làm invalid mọi cached prefix chứa giá trị đó. Hãy đặt giá trị này một lần, trong request đầu tiên.
Task budget đang ở giai đoạn beta trên Claude Fable 5.1, Claude Fable 5, Claude Opus 5.5, Claude Opus 5, Claude Opus 4.8, Claude Opus 4.7, Claude Sonnet 5.5 và Claude Haiku 5.5. Claude Sonnet 5 và Claude Haiku 4.5 được liệt kê là Not supported. Task budget không áp dụng cho Claude Code, vì vậy một session Claude Code được tách chạy trong tmux phụ thuộc vào việc quản lý session đúng cách.
Giới hạn thứ ba nằm trong Claude Console: cấp cho agent một workspace riêng, sau đó đặt giới hạn chi tiêu hàng tháng và giới hạn rate theo phút cho workspace đó. "Bạn không thể đặt giới hạn trên Default Workspace", và "Giới hạn cấp tổ chức luôn được áp dụng, ngay cả khi tổng các giới hạn của workspace cao hơn". Hãy thêm thông báo chi tiêu để nhận cảnh báo khi đạt ngưỡng, trước khi chạm giới hạn.
Lựa chọn model theo từng job và effort thực sự thay đổi điều gì
Lựa chọn model là quyết định riêng cho từng job. Tính đến tháng 10 năm 2026, giá cho mỗi triệu token, lần lượt là input rồi output: Claude Fable 5.1 và Fable 5 ở mức $10 và $50, Claude Opus 5.5 ở mức $4 và $20, Claude Opus 5, Opus 4.8 và Opus 4.7 ở mức $5 và $25, Claude Sonnet 5.5 và Sonnet 5 ở mức $2 và $10, Claude Haiku 5.5 ở mức $0.10 và $0.50 cho prompt tối đa 100,000 token ($0.50 và $2.50 khi vượt mức đó; token được cache vẫn được tính), và Claude Haiku 4.5 ở mức $1 và $5. Ban đầu Anthropic bán Sonnet 5 với mức giá đó như một giá mở bán, sau đó giữ đây là giá tiêu chuẩn và hủy kế hoạch tăng lên $3 và $15. Một bước chỉ phân loại các dòng log không cần Opus.
Cũng không nên dựa vào usage miễn phí để xử lý một lịch chạy dày. Ngoài khoản credit nhỏ mà Claude API cấp khi đăng ký, allowance định kỳ duy nhất là credit API hàng tháng đi kèm các plan Claude Max và Team: $100 với Max 5x, $200 với Max 20x và tối đa $500 dùng chung với Team; allowance được cấp lại sau mỗi chu kỳ thanh toán. Bạn có thể nhận khoản này sau 7 ngày sử dụng một plan đủ điều kiện và không cần thêm phương thức thanh toán. Credit này dùng được cho Claude API, Managed Agents, Agent SDK và playground, nhưng không dùng được cho Claude Code. Credit hết hạn khi chu kỳ kết thúc và không được chuyển sang chu kỳ sau. Khoản đã dùng được tính vào spend cap. Khi credit cạn, request sẽ dừng trừ khi tổ chức cũng có credit đã mua.
Effort là đòn bẩy thứ hai. output_config.effort chấp nhận low, medium, high, xhigh và max, nhưng không phải model nào cũng hỗ trợ mọi level. Mặc định là high trên hầu hết model và medium trên Claude Opus 5.5 và Claude Haiku 5.5. Đặt rõ default của một model cũng tương đương với việc bỏ qua tham số này. Effort thấp hơn không chỉ rút ngắn quá trình reasoning. Tài liệu cho biết nó khiến Claude gọi ít tool hơn và gộp nhiều thao tác vào ít call hơn. Với agent, đây là khoản tiết kiệm lớn hơn, vì bỏ được một tool call nghĩa là cả một request không cần thực hiện.
Điểm dễ mắc bẫy là effort ảnh hưởng đến cache. Thay đổi giá trị cấp cao nhất giữa các request sẽ làm prompt caching mất hiệu lực. Trong ví dụ được tài liệu hóa trên Claude Opus 5.5, request 2 báo cache_read_input_tokens: 3546. Request 3 đổi effort từ medium, là giá trị mặc định của model đó, sang low và báo cache_creation_input_tokens trên tổng số 3546, cùng với cache_read_input_tokens trên tổng số 0. Vì vậy, hãy thay đổi effort cấp cao nhất giữa các workload, không thay đổi nó trong cùng một cuộc hội thoại đã được cache. Có 2 cách thay đổi độ sâu mà không làm hỏng cache. Trên Claude Fable 5.1, Opus 5.5, Opus 5, Sonnet 5.5 và Haiku 5.5, thay đổi effort theo từng message (beta, header mid-conversation-output-config-2026-07-01) sẽ đặt level mới nhưng giữ nguyên prefix đã cache. Với mọi model, bạn cũng có thể điều khiển bằng prompt: một dòng như "Trả lời trực tiếp mà không cần suy xét." trong user message mới nhất sẽ giữ nguyên các breakpoint trước đó. Tuy nhiên, trên Claude Haiku 5.5, câu này không giảm thinking một cách đáng tin cậy.
Thinking token được tính tiền theo mức giá output và tính vào max_tokens. Vì vậy, câu trả lời bị cắt thường là do thinking đã dùng hết budget. Đọc usage.output_tokens_details.thinking_tokens để biết số lượng. Điều gì thực sự làm tăng hóa đơn token của Claude phân tích chi tiết cách bộ đếm này hoạt động.
Cache prefix ổn định và tránh vô tình làm hỏng nó
Ghi cache có chi phí bằng 1.25 lần giá input cơ bản với cache năm phút và 2 lần với cache một giờ. Trên hầu hết model, đọc cache có chi phí bằng 0.1 lần. Vì vậy, “cache bắt đầu có lợi sau một lần đọc cache với thời lượng 5 phút (ghi ở mức 1.25x), hoặc sau hai lần đọc cache với thời lượng 1 giờ (ghi ở mức 2x)”. Các model mới nhất còn có giá đọc thấp hơn: 0.05 lần trên Claude Opus 5.5 và Claude Sonnet 5.5, và 0.025 lần trên Claude Fable 5.1.
Một dòng giải thích vì sao cách này phù hợp với agent luôn chạy: “Cache được refresh mà không phát sinh thêm chi phí mỗi lần nội dung đã cache được sử dụng.” Một job chạy mỗi hai phút với cache năm phút sẽ giữ prefix luôn nóng cả ngày chỉ với một lần ghi.
Có ba cách làm mất cache mà không nhận ra.
Prefix thay đổi. “Cache prefix được tạo theo thứ tự sau: tools, system, rồi messages.” Bất kỳ byte nào thay đổi ở vị trí trước đó đều làm mất hiệu lực của toàn bộ phần sau, còn việc chỉnh sửa tool definition sẽ làm mất toàn bộ cache. Lỗi tự gây ra phổ biến là đưa timestamp hoặc run id vào system prompt: khi đó mỗi request đều có prefix khác nhau, ghi một entry mới ở mức 1.25x và không đọc lại được gì. Dấu hiệu là usage.cache_read_input_tokens luôn bằng 0 trong các lần gọi trông giống hệt nhau. Hãy chuyển phần thường xuyên thay đổi vào user message mới nhất.
Prefix quá ngắn. Mỗi model có độ dài tối thiểu được phép cache. Nếu ngắn hơn mức này, request sẽ được xử lý mà không cache và “không trả về lỗi”. Các mức gồm 512 token trên Claude Fable 5.1, Fable 5, Opus 5.5, Opus 5, Sonnet 5.5 và Haiku 5.5; 1,024 trên Claude Opus 4.8 và Claude Sonnet 5; và 4,096 trên Claude Haiku 4.5. Vì vậy, chuyển một job từ Sonnet 5 sang Haiku 4.5 có thể khiến caching bị tắt mà không có cảnh báo.
Conversation vượt quá lookback. “Cửa sổ lookback có 20 block.” Hệ thống kiểm tra tối đa 20 vị trí tại mỗi breakpoint rồi dừng. Trong ví dụ được tài liệu hóa, một turn chứa 35 block và có breakpoint tại block 35 sẽ kiểm tra từ block 35 đến block 16. Entry của turn trước tại block 15 nằm ngoài cửa sổ, nên không có cache hit. Một agent thêm nhiều block tool-use và tool-result trong mỗi turn sẽ vượt quá 20 block chỉ sau hai hoặc ba turn. Mỗi request có bốn breakpoint, vì vậy hãy dành một breakpoint cho các message gần nhất.
Chuyển mọi việc có thể chờ sang Batches API
"Toàn bộ usage được tính bằng 50% giá API tiêu chuẩn", áp dụng cho cả input và output. Xử lý batch là bất đồng bộ, "phần lớn batch hoàn tất trong chưa đến 1 giờ", và có kết quả khi mọi request đã hoàn tất hoặc sau 24 giờ, tùy điều kiện nào đến trước. Đây là thời gian thông thường, không phải cam kết.
Poll processing_status cho đến khi giá trị là ended. Các request trả về errored, canceled hoặc expired không bị tính phí. Nếu bạn đặt giới hạn chi tiêu, cần lưu ý rằng "batch có thể vượt nhẹ giới hạn chi tiêu đã cấu hình cho Workspace".
Các mức giảm giá được cộng dồn. Vì một batch có thể chạy lâu hơn 5 phút, tài liệu khuyến nghị dùng cache 1 giờ cho các batch dùng chung context. Hãy tách luồng xử lý: mọi việc mà người dùng hoặc webhook phải chờ thì giữ trên live path; digest chạy hằng đêm hoặc việc phân loại log của ngày hôm qua có thể đưa vào batch với chi phí bằng một nửa.
Ghi log các trường usage của mọi response vào kho dữ liệu riêng
Bạn không thể phân bổ khoản chi phí mà mình chưa ghi lại. Mỗi response đều cho 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 tệp JSON-lines và gắn tên job. Sau một tuần, bạn có thể biết job nào tiêu tốn chi phí và job nào chỉ tạo ra nhiều hoạt động. Hãy theo dõi cache_read: một cột toàn số 0 là lỗi tính chi phí phổ biến nhất trong agent tự host.
Một trường rất dễ bị đọc sai. input_tokens chỉ đếm số token sau cache breakpoint cuối cùng, nên kích thước prompt thực tế là total_input_tokens = cache_read_input_tokens + cache_creation_input_tokens + input_tokens. Agent báo input_tokens: 400 trên một prompt lớn không có nghĩa là nó rẻ: phần còn lại đến từ cache.
Hãy đếm trước khi gửi. Việc đếm token là miễn phí và có rate limit riêng với việc tạo message, nên hãy dùng count_tokens để từ chối attachment quá lớn thay vì trả phí rồi mới phát hiện. Kết quả chỉ là ước tính, vì vậy hãy đo lại cho từng model và không dùng lại kết quả đếm từ tokenizer của vendor khác. Các model Claude 4.7 trở lên, trong đó có Claude Fable 5 và Claude Sonnet 5, dùng tokenizer mới hơn và “tạo ra nhiều hơn khoảng 30% token cho cùng một văn bản”. Claude Sonnet 4.6 trở về trước, trong đó có Claude Haiku 4.5, dùng tokenizer cũ.
Để xem dữ liệu chính thức, 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 nhận admin key (sk-ant-admin01-...) làm x-api-key: $ANTHROPIC_ADMIN_KEY cùng với anthropic-version: 2023-06-01. Báo cáo usage chấp nhận bucket_width=1d, group_by[]=model và api_key_ids[]=. Báo cáo cost chỉ có dữ liệu theo ngày và nhóm theo workspace_id hoặc description. Một hạn chế là: “Admin API không khả dụng cho tài khoản cá nhân.”
Tham số api_key_ids[] là một cách đơn giản để phân bổ chi phí: cấp cho mỗi job một API key riêng, lọc báo cáo usage bằng api_key_ids[], rồi tách dữ liệu theo từng key bằng group_by[]=api_key_id. Bộ lọc dùng dạng số nhiều, còn chiều nhóm dùng dạng số ít. Lưu các key trong environment thay vì trong code, giống như ứng dụng Claude API đầu tiên trên VPS quản lý chúng.
Giới hạn vòng lặp, vì không có gì khác tự làm việc đó
Số lần lặp tối đa là bắt buộc ở đây. Vòng lặp do bạn viết, nên bộ đếm cũng do bạn quản lý:
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)Cả hai giới hạn ở trên đều không tự làm việc đó: max_tokens chỉ giới hạn một response, còn model chỉ được thông báo về ngân sách của task. Một sản phẩm được host sẽ dừng tại đây, giống như giới hạn số lần gọi tool của Claude trong một lượt kết thúc session đã gọi quá nhiều lần. Nhưng vòng lặp do bạn tự viết không có cơ chế dừng tương tự cho đến khi bạn thêm vào.
Hãy đặt thêm một cơ chế dừng bên ngoài process. Chạy job bằng systemd timer thay vì một process chạy liên tục, đồng thời đặt giới hạn thời gian thực cho service unit. Một timer job thường là Type=oneshot, và RuntimeMaxSec= “không có tác dụng với service Type=oneshot”, nên hãy đặt TimeoutStartSec=600 tại đó. Chỉ dùng RuntimeMaxSec=600 trên unit Type=simple. Dù theo cách nào, một lần chạy bị treo cũng sẽ bị kill sau 10 phút thay vì tiếp tục chạy cho đến khi bạn phát hiện. Chạy một chương trình dưới dạng systemd service và timer trình bày chi tiết các unit file. Dùng journalctl -u triage-agent.service --since "1 hour ago" để xem lần chạy đã thực hiện những gì.
Cũng giới hạn số lần retry, vì một handler retry vô hạn sẽ tính phí cho mọi lần thử. Lỗi 429 hoặc 500 nên được thử lại vài lần với backoff. Lỗi 400 thì không nên retry, vì cùng một request sẽ tiếp tục fail theo cùng một cách.
Kiểm soát chi phí AI agent bắt đầu bằng 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 chạy sẽ tốn bao nhiêu, vì chi phí bằng số token mỗi lần chạy nhân với số lần chạy mỗi ngày, và cả hai yếu tố này đều do bạn quyết định. Hãy chạy agent một lần, đọc dòng usage bạn đã ghi lại rồi nhân với lịch chạy của mình. Kiểm tra báo cáo chi phí sau 2 ngày và đối chiếu với phép tính đó. Khi hai con số không khớp, nguyên nhân gần như luôn là cache bị lỗi hoặc một loop chạy lâu hơn bạn dự tính.
Phần này giả định bạn dùng 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ủa riêng bạn, gói Claude nào phù hợp với cách bạn làm việc trình bày phần subscription. Mọi mức giá và giới hạn ở đây đã được đối chiếu với tài liệu của Anthropic vào tháng 10 năm 2026, vì vậy hãy đọc lại trang pricing trước khi lập ngân sách.
FAQ
Chi phí chạy một AI agent luôn hoạt động trên VPS là bao nhiêu?
Có 2 khoản chi phí và chỉ 1 khoản có thể dự đoán trước. Máy chủ có giá cố định hằng tháng. Model API tính phí theo token, nên chi phí bằng số token một lần chạy tiêu thụ nhân với tần suất chạy. Anthropic không công bố con số nào cho agent luôn hoạt động được self-host, vì vậy hãy xem mọi con số được trích dẫn chỉ là ước tính. Ghi lại usage từ một lần chạy thực tế rồi nhân với lịch chạy của bạn.
Sự khác nhau giữa max_tokens và ngân sách tác vụ là gì?
max_tokens được áp dụng bắt buộc và model không nhìn thấy giá trị này. Nó giới hạn output của một request, bao gồm cả phần suy nghĩ, và khi chạm giới hạn sẽ trả về stop_reason: "max_tokens". Ngân sách tác vụ thì ngược lại: model được thông báo con số này và điều chỉnh vòng lặp agent theo nó, nhưng “Ngân sách tác vụ chỉ là gợi ý mềm, không phải giới hạn cứng” và giới hạn được áp dụng vẫn là max_tokens.
Tại sao cache_read_input_tokens luôn bằng 0 với agent của tôi?
Vì prefix thay đổi giữa các lần gọi hoặc quá ngắn để được cache. Nguyên nhân thường gặp là timestamp hoặc run id được chèn vào system prompt: cache dựa trên prefix, nên bất kỳ byte nào thay đổi cũng làm mất hiệu lực của toàn bộ phần đứng sau nó. Thay đổi định nghĩa tool hoặc giá trị effort cấp cao nhất cũng gây ra kết quả tương tự. Nếu không, nguyên nhân là kích thước, vì prompt ngắn hơn sẽ không được cache và hệ thống không trả về lỗi.
Làm cách nào để ngăn AI agent lặp vô hạn?
Đếm số vòng lặp trong code của bạn và dừng ở một giá trị tối đa cố định, vì max_tokens chỉ giới hạn 1 response còn agent thực hiện nhiều response. Thêm giới hạn thời gian thực ngoài process: khởi chạy job bằng systemd timer và đặt TimeoutStartSec= cho oneshot unit, hoặc RuntimeMaxSec= cho simple unit, để run bị treo sẽ bị kill đúng lịch. Đồng thời giới hạn số lần retry, vì mỗi lần retry đều phát sinh phí.
Tôi có thể đặt giới hạn chi tiêu cho một Claude API key riêng lẻ không?
Giới hạn chi tiêu được tài liệu hóa áp dụng theo workspace thay vì theo key, vì vậy hãy cấp cho agent một workspace riêng và đặt giới hạn chi tiêu hằng tháng tại đó. “Bạn không thể đặt giới hạn cho Default Workspace”. Thêm thông báo chi tiêu để nhận cảnh báo khi đạt ngưỡng trước. Để phân bổ chi phí, cấp cho mỗi job một key riêng, sau đó nhóm báo cáo sử dụng bằng group_by[]=api_key_id.