SSD Nodes Learn
指南 Matt Connor作者: Matt Connor · 更新于 2026-07-26

常驻 VPS 上 AI 代理成本控制方法

无人值守代理每次循环都在计费。介绍硬上限、任务预算、提示词缓存、批处理,以及用量字段如何暴露真实花费,避免 token 在无人察觉时悄悄消耗。

如何防止常驻 AI 代理产生高额账单

在 VPS(虚拟专用服务器)上控制 AI 代理成本,关键在于在代理启动前设定上限,因为运行时没有人盯着计费表。为每个响应设置 max_tokens 限制,在你自己的代码中限制循环迭代次数,缓存提示词中不变的部分,并记录每个响应的用量数据,以便查看哪个任务在花钱。服务器租赁是固定的月费。模型 API 按 token 计费,而无人值守的循环非常擅长悄无声息地消耗 token。

本文假设代理已经存在,并且从你拥有的服务器上调用 Messages API。在 VPS 上用 Claude 构建 AI 代理 介绍了相关机制本身。

为什么无人值守代理的成本结构不同

交互式会话中有人参与。当模型走错方向或读取 40,000 行的日志时,旁边的人会叫停它。无人值守代理没有这种刹车:它会一直运行,直到循环结束,然后定时器再次启动。

频率是人们容易忽略的乘数。一个每 5 分钟运行一次的任务,每天运行 288 次,每月大约运行 8,640 次。无论一次运行花费多少,这就是你要乘以的数字。许多"始终在线"的代理并不需要始终在线。它们需要在若干分钟内做出响应,这就是一个调度任务。

代理还要为聊天窗口不需要的东西付费。

  • 工具定义会附加在每个请求上。 在 Claude Opus 4.8 上,工具使用的系统提示消耗 290 个 token(使用 tool_choiceautonone),使用 anytool 时为 410 个。bash 工具再增加 325 个。每附加一个 MCP 服务器 都会将其 schema 加入到这个权重中,MCP 是模型上下文协议。
  • 工具结果是输入 token。 一个打印 8,000 行的命令会将 8,000 行放入下一个请求,以及该轮中之后的所有请求。
  • 抓取的页面是输入 token。 一个平均 10 kB 的网页大约是 2,500 个 token,一个 500 kB 的研究型 PDF 大约是 125,000 个 token。max_content_tokens 仅截断文本类型的页面,因为它"适用于文本内容,不适用于 PDF 等二进制内容"。请使用 max_usesallowed_domains 来限制 PDF。
  • Web 搜索按搜索次数计费,每 1,000 次搜索 10 美元,无论返回多少结果。搜索出错不收费。

这些开销单次都不贵。但乘以 8,640 次就很贵了。

硬上限与软上限解决不同的问题

max_tokens 会被强制执行。 它是对单次请求总输出(思考文本与响应文本合计)的硬上限。Claude 永远不会生成超过它的内容,并且模型看不到这个数字。达到上限会触发 stop_reason: "max_tokens" 并返回一个被截断的回答。对智能体来说有一个关键点:工具调用循环里的每一次请求都各自携带 max_tokens,因此它约束的是单次响应,而不是整个任务。十次工具调用、每次上限 4,000,意味着这一轮的总上限是 40,000 tokens。

任务预算是建议性的。 task_budget 位于 output_config 之内,用于告诉模型整个智能体循环一共可使用多少 tokens,其中包含思考、工具调用、工具结果以及最终输出。

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,
)

"任务预算是软性提示,而非硬性上限。" 模型在执行过程中可能会超过它,而对输出真正的强制限制仍然是 max_tokens。"倒计时仅对模型可见",响应中不会携带剩余预算字段。可接受的 task_budget.total 最小值为 20,000 tokens,低于此值会返回 400 错误。如果预算小于任务所需的工作量,会产生类似拒绝的行为,模型会缩小任务范围或提前停止。

有一个细节反而会消耗成本而不是节省成本:如果客户端在每次后续请求中递减 task_budget.remaining,改动后的值会使所有包含该字段的缓存前缀失效。请在第一次请求时就将其设置好。

任务预算功能在 Claude Fable 5、Claude Opus 4.8 以及 Claude Opus 4.7 上处于 beta 阶段。Claude Sonnet 5 与 Claude Haiku 4.5 被列为 Not supported,并且任务预算不适用于 Claude Code,因此一个 在 tmux 中分离的 Claude Code 会话 只能依赖会话管理来控制。

第三种上限位于 Claude Console:给智能体分配独立的工作区,然后在该工作区上设置月度花费上限和每分钟速率限制。"你无法在 Default Workspace 上设置限制",并且"组织级的限制始终生效,即使各工作区的限制相加更大也是如此"。请配置花费通知,以便在达到硬上限之前先由阈值告警提醒你。

按任务选择模型,以及 effort 参数实际影响什么

模型选择是按任务决定的。截至 2026 年 7 月,每百万 token 的价格为输入在前、输出在后:Claude Fable 5 为 $10 和 $50,Claude Opus 4.8 和 Opus 4.7 为 $5 和 $25,Claude Sonnet 5 为 $3 和 $15,Claude Haiku 4.5 为 $1 和 $5。Sonnet 5 目前的价格低于标价,因为"每百万输入/输出 token 的入门价格为 $2/$10,有效期至 2026 年 8 月 31 日"。仅用于分类日志行的步骤不需要 Opus。

effort 是第二个调节手段。output_config.effort 接受 lowmediumhighxhighmax,默认值为 high,因此显式设置 high 与省略它效果相同。降低 effort 节省的不只是推理长度:文档说明它会让 Claude 减少工具调用次数,并将多个操作合并为一次。在 agent 场景下这是更大的节省,因为避免一次工具调用就等于一次永远不会发生的请求。

陷阱在于 effort 与缓存相互冲突。在请求之间更改该值会使 prompt 缓存失效。在文档示例中,请求 2 报告了 cache_read_input_tokens: 3546;请求 3 将 effort 从 high 改为 medium 后,报告 cache_creation_input_tokens 为 3546,cache_read_input_tokens 为 0。因此应在不同工作负载之间变化 effort,而不要在同一个已缓存的会话内变化。如果要在不破坏缓存的前提下控制深度,可以在 prompt 中实现:在最新的用户消息中加入类似"Answer directly without deliberating."的语句,这样此前的断点保持不变。

思考 token 按输出费率计费,并计入 max_tokens,这就是为什么答案被截断通常意味着思考吃掉了预算。读取 usage.output_tokens_details.thinking_tokens 获取该数值。Claude token 账单的实际构成对该计量方式进行了拆解。

缓存稳定的前缀,避免意外破坏

缓存写入在 5 分钟缓存上花费基础输入价格的 1.25 倍,在 1 小时缓存上花费 2 倍。缓存读取花费 0.1 倍,因此"5 分钟缓存只需一次读取即可回本(写入 1.25 倍),1 小时缓存只需两次读取即可回本(写入 2 倍)"。

有一句话解释了为什么这适合常驻运行的代理:"每次使用已缓存内容时,缓存都会免费刷新。"一个每两分钟触发一次的任务针对 5 分钟缓存,只需一次写入就能让前缀全天保持温热。

三种不知不觉丢失缓存的方式。

前缀发生变化。"缓存前缀按以下顺序创建:toolssystem,然后 messages。"该顺序中靠前的任何字节变化都会使之后的所有内容失效,编辑工具定义会使整个缓存失效。典型的自伤行为是在系统提示中加入时间戳或运行 id:每次请求都会携带不同的前缀,以 1.25 倍的价格写入新条目,却读不到任何内容。识别特征是 usage.cache_read_input_tokens 在看似相同的调用中始终为 0。把易变文本移到最新的用户消息中。

前缀过短。每个模型都有最小可缓存长度,低于该长度的请求不会启用缓存处理,并且"不会返回错误"。相关数字包括 Claude Opus 4.8 和 Claude Sonnet 5 上的 1,024 个 token,以及 Claude Haiku 4.5 上的 4,096 个 token,因此把任务从 Sonnet 切换到 Haiku 可能悄无声息地关闭缓存。

会话超出回溯窗口。"回溯窗口为 20 个块。"系统每个断点最多检查 20 个位置,然后停止。在文档示例中,一个包含 35 个块、且断点在第 35 块的回合,会从第 35 块向下检查到第 16 块,而上一回合位于第 15 块的条目落在窗口之外,因此没有命中。一个代理每回合追加若干 tool-use 和 tool-result 块,两到三个回合就会超过 20 个。每个请求有四个断点,把其中一个用在最近的消息上。

把可以等待的任务都发送到 Batches API

"所有用量按标准 API 价格的 50% 计费",输入和输出都按此计算。批处理是异步的,"大多数批次在 1 小时内完成",结果在每个请求都完成时返回,或者在 24 小时后返回,以先到者为准。这是典型情况,不保证一定如此。

轮询 processing_status,直到其值为 ended。返回 erroredcanceledexpired 的请求不计费。如果你依赖支出上限,有一个注意事项:"批次可能会略微超出你 Workspace 配置的支出限额。"

折扣可以叠加,并且由于批次可能耗时超过五分钟,文档建议对共享上下文的批次使用一小时的缓存。因此要拆分工作:人员或 webhook 等待的内容保留在实时路径上,每晚的摘要或昨天的日志分类则以半价放入批次中。

将每次响应的 usage 字段记录到你自己的存储中

你无法归集从未记录过的支出。每一次响应都会告诉你它的成本。

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,
}

将每一次 API 调用作为一行追加到 JSON-lines 文件,并附上你的任务名称。一周后,你就能分辨哪些任务真正产生了开销,哪些只是看起来很忙。注意 cache_read:一整列零是最常见的自托管代理成本错误。

有一个字段容易被误读。input_tokens 只统计最后一次缓存断点之后的 token,因此真实的提示词大小是 total_input_tokens = cache_read_input_tokens + cache_creation_input_tokens + input_tokens。一个代理在大型提示词上上报 input_tokens: 400 并不便宜:其余部分来自缓存。

发送前先计数。token 计数是免费的,且它的速率限制与消息创建相互独立,所以使用 count_tokens 来拒绝过大的附件,而不是付出代价之后才发现。结果只是一个估计值,因此需要按模型重新测量,并且永远不要复用另一个供应商分词器的计数结果。Claude Opus 4.7 及之后的 Opus 模型、Claude Fable 5 和 Claude Sonnet 5 使用一套更新的分词器,“对同一段文本会产生大约多 30% 的 token”。Claude Sonnet 4.6 及更早版本,以及 Claude Haiku 4.5 等,使用的是旧的那一套。

若需权威视图,Admin API 在 https://api.anthropic.com/v1/organizations/usage_report/messages 报告用量,在 https://api.anthropic.com/v1/organizations/cost_report 报告成本。两者都使用 admin key(sk-ant-admin01-...),通过 x-api-key: $ANTHROPIC_ADMIN_KEY 配合 anthropic-version: 2023-06-01 传入,并接受 bucket_width=1dgroup_by[]=modelapi_key_ids[]=。一个限制是:“Admin API 对个人账户不可用。”

最后一个参数是一个低成本的归集技巧:为每个任务分配一个独立的 API key,使用 api_key_ids[] 进行过滤,然后用 group_by[]=api_key_id 按 key 拆分报表。过滤参数是复数形式,分组维度是单数形式。把 key 放在环境变量中,而不是写死在代码里,正如 在 VPS 上搭建第一个 Claude API 应用 所做的那样。

限定循环次数,因为没有其他机制会替你做这件事

限定迭代次数在这里是必需的。循环由你控制,所以计数器也由你控制:

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)

上面的两个上限都不能替你完成这件事:max_tokens 只限制单次响应,而模型只会收到一个任务预算的建议。

在进程外部再加一道制动。用 systemd 定时器来运行任务,而不是用一个常驻进程,并在其服务单元上设置 RuntimeMaxSec=。配合 RuntimeMaxSec=600,一次卡住的运行会在十分钟后被终止,而不是一直空转直到你注意到。以 systemd 服务和定时器方式运行程序 介绍了单元文件本身的写法。用 journalctl -u triage-agent.service --since "1 hour ago" 查看一次运行做了什么。

同时也要限制重试次数,因为一个无限重试的处理程序会对每次尝试都计费。429 或 500 错误值得重试几次并配合退避策略。400 错误则完全不值得重试,因为同样的请求会以同样的方式失败。

AI 代理成本控制从读取自己的数据开始

没有人能告诉你一个常驻代理的运行成本,因为成本等于每次运行的 token 数乘以每天的运行次数,而这两部分都属于你。运行一次,读取你记录的用量数据,再按你的调度计划相乘。两天后将费用报表与这个算式核对。当两者不一致时,差额几乎总是缓存失效,或者循环运行的时长超出了你的预期。

这里的前提是你拥有 API 密钥,因为代理是你自己调用 Messages API 的程序。对于你自己的交互工作,哪种 Claude 套餐适合你的工作方式涵盖了订阅方面的内容。这里列出的所有价格和限额已于 2026 年 7 月与 Anthropic 的文档核对,因此在你制定预算之前请重新阅读定价页面。

FAQ

一个常驻 AI 智能体在 VPS 上运行要花多少钱?

账单有两份,其中只有一份是可预测的。服务器是固定的月费。模型 API 按 token 计费,所以成本等于单次运行的消耗乘以运行频率。Anthropic 没有公布自托管常驻智能体的费用,因此任何给出的数字都只能当作估算。请记录一次真实运行的 usage,再乘以你的运行计划。

max_tokens 和任务预算有什么区别?

max_tokens 是强制执行的,对模型不可见。它限制单次请求的输出,包括思考部分,触达上限会得到 stop_reason: "max_tokens"。任务预算则相反:模型被告知这个数字,并据此控制智能体循环的节奏,但“任务预算是一个软提示,不是硬性上限”,强制限制仍然是 max_tokens

为什么我的智能体的 cache_read_input_tokens 一直是 0?

因为调用之间的前缀发生了变化,或者前缀太短无法缓存。最常见的原因是时间戳或运行 ID 被插入到了系统提示中:缓存以前缀为键,因此任何字节变化都会使其后所有内容失效。修改工具定义或 effort 的值也会产生同样效果。否则就是长度问题,较短的提示不会被缓存,而且不会返回任何错误。

如何阻止 AI 智能体无限循环?

在循环代码中统计迭代次数,并在达到固定上限时停止,因为 max_tokens 只限制单次响应,而智能体会发起多次调用。再在进程外部加一个挂钟时间限制:通过 systemd 定时器启动任务,并设置 RuntimeMaxSec=,这样卡住的运行会按计划被终止。同时也要限制重试次数,因为重试循环会对每次尝试计费。

我可以为单个 Claude API 密钥设置消费上限吗?

文档中的消费上限是按工作区设置的,而不是按密钥设置,因此请为智能体单独分配一个工作区,并在那里设置月度消费上限。“无法在默认工作区上设置限额”。添加消费通知,这样达到阈值时会先向你发出告警。如果需要区分归属,可以为每个任务签发独立的密钥,然后使用 group_by[]=api_key_id 对使用情况进行分组统计。

#claude#ai#agents#api#cost