接入教程

Claude API 批处理:五折账单、24 小时上限,和那个会静默错位的坑(2026)

Kenji Watanabe

批处理回答的是一个很窄的问题:那些必须做、但不必现在做的 Claude API 调用,该怎么处理?

Batches API 接收一组 Messages API 请求,按 Anthropic 的排期而不是你的排期异步跑完,代价是你交出了「什么时候完成」的控制权,换回来的是全部 token 用量按标准价的 50% 计费。这个五折是实打实的,覆盖批次里所有 token。而大多数批处理集成出问题,都是因为设计时盯错了一个数字。

Batches API 到底是什么

它不是另一个模型,也不是另一套能力面。一个批次就是一个装普通 Messages API 请求的容器。每一条由你自己指定的 custom_id 和一个 params 对象组成,而那个 params 就是你本来要发给同步端点的请求体——同样的模型、同样的 max_tokens、同样的 system prompt、同样的工具定义。

Messages API 支持的东西在批次里全都能用:视觉、工具调用、提示词缓存、结构化输出。你不需要写一套受限的子集,只是把本来就在构造的请求换个端点投递到 /v1/messages/batches

生命周期是三步:创建批次拿到 id;轮询这个 id 直到 processing_status 变成 ended;然后流式取回结果。

该拿来做设计的数字是 24 小时,不是 1 小时

大多数批次在一小时内完成。而文档写明的上限是 24 小时

这两句话经常被一起引用,然后只有第一句被拿去做设计——这是批处理集成变成故障工单最常见的一条路径。「通常一小时」是对典型行为的观察,「最长 24 小时」才是那个承诺边界。如果你的流水线在批次跑了六小时的时候就崩了,那你是照着观察值搭的,不是照着边界值搭的。

这条有很具体的设计后果:批处理的活必须可恢复,而且不能占住任何东西。不能有请求级的连接在那儿等结果,不能有用户对着转圈等,也不能有定时任务默认「昨天那批肯定跑完了」。把批次 id 持久化存下来,按计划轮询,把「还在处理中」当成一个正常状态而不是错误。

结果自创建起保留 29 天,宽裕到你完全不必赶着去收——按你自己的节奏取就行。

塑形批次之前该知道的两个上限

单个批次最多装 100,000 条请求或 256 MB,先撞到哪个算哪个。这两个天花板在实际使用中的行为差别很大。

请求条数很少成为约束。字节上限经常才是,因为它算的是完整渲染后的请求——每一份 system prompt 的副本、每一张 base64 图片、每一个文档。一万条请求各自带一份 20 KB 的共享 system prompt,光 system prompt 就吃掉 200 MB,真正的活还一个字没算。

所以定批次大小之前该跑的是这个算式:单条请求的载荷 × 请求条数,拿去和 256 MB 比,而不是默认「我的上限是十万条」。如果贴近字节上限,拆成几个小批次不花你任何代价——批次之间互相独立,也没有按批次收的费用。

结果是乱序回来的

这是最容易活着通过代码评审的一个坑,因为它的失败是静默的,而测试数据又都很小。

结果流的顺序不保证与你的输入数组对齐。结果的第 0 条不一定就是 custom_idrequest-0 的那条。按位置索引结果,会得到一个在三条数据的测试批次上完美工作、然后在生产环境里悄悄给一万条记录贴错标签的集成。

custom_id 索引,永远。 一边流式读一边建字典,读完再按 id 查。并且 custom_id 要取在你自己系统里有意义的值——数据库行 id、文档哈希——因为这个 id 是把结果连回「它到底是关于什么的」的唯一一根线。

四种结果类型怎么读

每条结果带一个类型,四种彼此不可互换:

  • succeeded——消息在 result.message 上,形状和同步响应完全一致。
  • errored——要看错误类型。invalid_request 表示请求本身就是坏的,原样重试还会再失败一次,必须改了再提;其余是服务端错误,可以原样重试。
  • canceled——你在这条请求还在途中时取消了批次。
  • expired——没在时限内完成,需要重新提交。

errored 内部的这个分岔是最值得写进 handler 的一条。一个把校验错误当成瞬时故障的重试循环,会把重试次数烧光,然后报告一个根本不存在的服务端问题。

取消可以在途中执行,会把批次置为 canceling 状态。已经跑完的请求照常返回结果,还在队列里的则以 canceled 回来。

批处理和提示词缓存放在同一批次里

缓存在批次里是可用的,用法也是最直觉的那个:给一大段共享的 system prompt 打上缓存断点,批次里每条请求都从同一份缓存前缀读取,而不是各自按完整输入价付一遍自己的副本。

有一点建议你自己实测而不是想当然:默认的 ephemeral 缓存 TTL 是五分钟,而一个批次可能排上几小时。你建模时算出来的那笔缓存账能不能真的兑现,取决于批次内部各条请求的实际调度关系——这件事不该凭任何一篇博客(包括这一篇)的说法就信下来。测法是读返回消息上的 cache_read_input_tokens,和你预期的对一下。如果读取为零,说明你对这个交互的模型是错的,五折是那唯一在起作用的东西,缓存一点忙没帮上。

断点机制和那些会静默失效的因素,在我们的提示词缓存指南里写得更细。

什么时候批处理是错的工具

只要有人或有请求在等,批处理就是错的。规则就这一条,但它覆盖的情形比乍看上去多:

  • 任何面向用户的场景。一次对话轮次、检索时的摘要、行内建议。只要有人能感知到延迟,批处理就不是选项——该用流式
  • 任何处在别的系统关键路径上的调用。如果服务 B 阻塞等结果,那个异步的 24 小时上限就是 24 小时的故障风险。
  • 量太小。一天十条请求,撑不起批次创建、轮询、结果对账、过期处理这一整套运维面。十条请求省下的一半钱,付不起管理它们的那些代码。
  • 硬截止时间短于一天的活。「早上九点前必须出」和「最长 24 小时」不兼容,除非你提前整整一天提交。

另有两个能力在批次里明确不可用:服务端拒答回退的 fallbacks 参数在 Batches API 上会被拒绝,fast mode 也不走批处理。如果你的同步链路依赖其中任何一个,批处理链路需要自己的一套处理。

批处理真正擅长的是相反的形状:大量彼此独立、没有读者在等的活。分类回填、夜间数据富化、改完 prompt 之后重跑一遍存档、在固定测试集上跑评测。这些场景里一小时还是六小时没有区别,而账单减半有很大区别。

如果你的 Claude 流量走中转

动手之前值得先确认一件事:一个代理了 /v1/messages 的中转或网关,不一定代理 /v1/messages/batches。它们是不同的端点,有不同的生命周期——创建、轮询、流式取结果、取消——而一个为同步链路搭的代理,很可能这三样一样都没实现。

这个问题应该具体去问、去测,而不是往任何一个方向假设。用你实际在用的那个端点建一个两条请求的批次,轮询它,把结果读回来。上面三步里任何一步返回 404 或者挂住,你会在写对账逻辑之前就知道,而不是之后。同样的原则适用于你和 API 之间的任何代理或网关层:去验证你真正依赖的那个端点,而不是最容易测的那个。

常见问题

Claude API 批处理到底能省多少?

标准 token 价的 50%,覆盖批次里全部 token 用量——输入、输出、缓存 token 一视同仁。它与提示词缓存是叠加关系而非替代关系。这个五折所依附的各模型基础费率,见我们的 Claude API 价格拆解

一个 Claude API 批次要跑多久?

大多数在一小时内完成,文档写明的上限是 24 小时。按 24 小时设计。没有办法申请加急——那正是标准同步端点的用途。

批次里能用工具调用和视觉吗?

能。Messages API 的全部能力在批次请求里都受支持,包括工具调用、视觉、提示词缓存和结构化输出。请求体与同步发送时完全一致。

批次里混进一条格式错误的请求会怎样?

批次照常跑。那一条会以 errored 结果回来,错误类型是 invalid_request。同批次的其他请求不受影响——一条坏数据不会让整批失败。

批处理结果能保留多久?

自批次创建起 29 天。你不需要立刻收,但终归要收,而且没有延期。

提交之后还能取消吗?

能。取消会把批次置为 canceling 状态。已经完成的请求照常返回结果,还在队列里的以 canceled 回来。

一句话版本

Batches API 用延迟换来了「没人在等的活」上的半价账单。照 24 小时上限设计而不是照一小时的典型值,按 256 MB 字节上限定批次大小而不是按十万条上限,每一条结果都按 custom_id 而不是按位置索引。批处理集成是成是败——或者是不是在悄悄污染数据——就取决于这三个决定。