我们做 Claude API 开发时都会遇到同一个问题:单条请求处理少量文本很轻松,可一旦要跑几千条甚至几万条文本,比如批量翻译商品描述、给用户评论打标签、对日志做安全巡检,一条一条同步调用 API 不仅慢,而且成本容易被忽视。网上关于 Claude 同步调用的教程很多,但专门讲 Batch Processing(批处理)的中文系统教程却不多。这篇文章会从一个完整可运行的批量文本摘要项目入手,带你理解 Claude 批处理的核心工作流,掌握输入文件构造、任务提交、状态轮询、结果拉取全链路,并附上高频报错与工程建议,适合刚接触 Claude API 的初学者,也适合正在做成本优化和数据批处理的后端开发。
1. 背景与核心概念:为什么 AI 调用需要批处理
1.1 从同步调用说起
先看一个最常见的调用方式:你有一个 Python 脚本,循环读取文本列表,每次调用 Claude 接口生成摘要,拿到结果后存起来。代码如下:
# 文件路径:sync_demo.py from anthropic import Anthropic client = Anthropic() texts = ["第一段文本", "第二段文本", "第三段文本"] results = [] for text in texts: message = client.messages.create( model="MODEL_NAME_HERE", max_tokens=1024, messages=[{"role": "user", "content": f"请为下面内容生成摘要:{text}"}], ) results.append(message.content[0].text) print(results)这个写法单看没有任何问题,但放到生产环境就会暴露出三个短板:
- 延迟叠加:每条请求都要经过完整网络往返,如果单次耗时 3 秒,1000 条任务就是 3000 秒,再算上重试,整体时间不可控。
- 并发难控制:加线程池可以提速,但并发太高容易触发限流,需要自己实现退避重试逻辑,代码复杂度明显上升。
- 成本不划算:每条请求都是相同计费,没有批量优惠,任务量上来之后 API 开销会占很大比例。
同步调用适合交互式场景,比如聊天机器人、实时翻译插件,用户必须在几秒内得到结果。但是对离线数据处理来说,同步方案不是最优解。
1.2 批处理到底解决什么问题
Claude 的 Batch Processing 是一个异步批量接口。你可以把大量独立请求打包成一个批处理任务提交给服务端,服务端在后台统一调度处理,处理完成后你再一次性拉取结果。
这个模式带来的好处非常直接:
- 吞吐量高:不需要自己控制并发,把请求交给服务端调度,再多的请求也只需要提交一次。
- 成本优势:批处理任务通常有更优惠的计费策略,具体折扣以官方实时政策为准,但对文本量大的团队来说,选择批处理往往能显著摊薄单条成本。
- 断点友好:批处理是异步任务,有明确状态,中间失败了可以查询、重试,不需要从头再来。
- 逻辑清晰:提交、轮询、下载三个阶段相对独立,代码结构比线程池方案简单很多。
1.3 典型适用场景
哪些场景适合用批处理?从工程经验来看,满足“离线、大量、非实时”三个特征的任务都可以纳入考虑:
- 舆情评论分类:一次性给几万条评论打情感标签。
- 商品文案生成:批量生成商品标题和卖点描述。
- 文档摘要抽取:对历史工单、合同、论文做重点提炼。
- 命名实体识别:从大量文本中抽取出人名、地名、机构名。
- 数据集清洗与增强:为机器学习构建训练集,做改写、翻译、纠错。
- 模型评估:用一批评测问题跑多个模型,然后对比输出。
这些任务都不要求秒级响应,用户只需要知道“任务提交成功,稍后回来取结果”,这就和批处理的设计目标完全一致。
1.4 不适合批处理的场景
批处理并不万能。如果业务要求实时应答,比如客服机器人、IDE 代码补全、聊天对话,那必须使用同步接口。
另外,批处理也不适合任务之间有依赖关系的场景。比如后一个请求需要前一个请求的输出作为输入,这类串联任务需要靠编排引擎来驱动,批处理只适合互相独立的并行请求。
2. 环境准备与版本说明
2.1 前置条件
在开始之前,你需要准备好以下环境:
- 一个可用的 Claude API Key,并且账号已开通对应模型访问权限。
- Python 3.9 及以上版本,推荐使用 3.10 或 3.11。
- 能正常访问 Anthropic API 的网络环境。
- 安装了
anthropicPython SDK。
这里要说明一点:不同时期 SDK 版本差异较大,批处理相关接口在不同版本中的命名可能不同,甚至有部分接口处于 beta 阶段。下面所有代码都以“常见 SDK 写法”演示,你实际运行时如果遇到AttributeError或接口不存在,第一优先是检查 SDK 版本,然后查阅官方 Python SDK 文档。
2.2 安装 SDK
创建虚拟环境并安装依赖:
python -m venv .venv source .venv/bin/activate # Windows 下使用 .venv\Scripts\activate pip install --upgrade anthropic验证安装是否成功:
python -c "import anthropic; print(anthropic.__version__)"如果能够正常输出版本号,说明安装成功。
2.3 项目结构规划
为了让教程更贴近真实工程,我们这里先规划好项目目录:
claude-batch-demo/ ├── input.jsonl # 批处理输入文件 ├── submit_batch.py # 提交批处理任务 ├── check_status.py # 查询任务状态 ├── download_results.py # 拉取并解析结果 ├── results/ # 结果存放目录 └── README.md # 说明文档实际项目里可能还需要config.py统一管理 API Key 和模型名,这里我们先用最直接的方式,方便你把注意力聚焦到批处理本身。
2.4 环境变量配置
不要把 API Key 硬编码在代码里。强烈建议使用环境变量:
export ANTHROPIC_API_KEY="your-api-key-here"Python 代码中通过os.environ.get("ANTHROPIC_API_KEY")读取即可,SDK 会自动读取这个环境变量。
3. 批处理核心原理解析
3.1 工作流程总览
批处理不像同步接口那样“发起请求,立即拿响应”,它更像一个任务队列。完整流程分为三步:
- 提交输入:把多条请求组合成一个 JSONL 文件,调用批处理接口提交。
- 轮询状态:服务端后台开始调度处理,我们定时查询任务状态。
- 拉取结果:任务完成后,根据结果文件地址批量下载,再按业务逻辑解析。
一句话概括:提交时打包,运行时异步,完成时批量拉取。
3.2 JSONL 输入格式
批处理任务的核心输入是 JSONL 文件,后缀通常为.jsonl。JSONL 严格每行一个 JSON 对象,行与行之间不能有空行,也不能出现分隔符。
每一行里包含两个关键字段:
custom_id:你自己定义的请求标识。它在整个批处理任务里必须唯一,后续下载结果时就是靠这个 ID 来对应输入输出。建议使用字母、数字、连字符、下划线,避免中文或空格。params:正常调用 messages.create 时的参数对象,包含model、max_tokens、messages等,也可以带上temperature、top_p等采样参数。
一个合法的输入文件示例:
{"custom_id": "task-001", "params": {"model": "MODEL_NAME_HERE", "max_tokens": 512, "messages": [{"role": "user", "content": "请为下面的内容生成摘要:人工智能技术正在改变制造业的生产组织方式。"}]}} {"custom_id": "task-002", "params": {"model": "MODEL_NAME_HERE", "max_tokens": 512, "messages": [{"role": "user", "content": "请为下面的内容生成摘要:企业数字化转型需要数据治理作为基础支撑。"}]}}注意model字段需要替换为你账号实际可用的模型名称,不同账号、不同时期可用的模型代号不一样,这里不要照抄。
3.3 任务状态生命周期
批处理任务并不是提交后立即完成。它通常会经历以下几个阶段:
- 创建中:刚提交,系统还在校验输入文件。
- 处理中:服务端已接收任务,正在排队和分批执行。
- 已完成:所有请求处理完毕,可以拉取结果。
- 失败:输入文件格式严重错误,或任务级异常。
- 已取消:主动撤销任务,或超过处理时效未完成。
整个处理窗口通常是数小时到 24 小时不等,不要拿同步接口的时延预期来套批处理。这也是为什么批处理不适合实时场景。
3.4 为什么批处理能降低成本
批量任务在服务端可以复用批级调度资源,所以单条请求的单位计费通常比同步调用更低。这是平台设计上的常见取舍:用时间和灵活性换取成本。如果你的任务完全不急,批处理是更经济的选择。
3.5 批处理与同步调用的取舍
这里做一个简单对比:
| 维度 | 同步调用 | 批处理 |
|---|---|---|
| 响应延迟 | 秒级 | 分钟到小时级 |
| 适用于实时交互 | 是 | 否 |
| 请求量级 | 适合少量 | 适合大批量 |
| 成本 | 通常较高 | 通常有优惠 |
| 并发控制 | 自己实现 | 服务端调度 |
| 代码复杂度 | 简单 | 中等(含轮询) |
实际项目中,两者不是二选一,往往共存。实时入口用同步,离线跑批用批处理,分工很明确。
4. 完整实战案例:批量文本摘要
这一节我们完成一个可运行的端到端项目。假设你有 100 条文本需要做摘要,用批处理批量完成。
4.1 第一步:构造输入 JSONL 文件
先写一个生成脚本。为了演示方便,这里假设数据源是 CSV 文件,生产环境通常也是从数据库或消息队列中抽取数据。
# 文件路径:prepare_input.py import csv import json def build_jsonl(csv_path, jsonl_path, model, max_tokens=512): with open(csv_path, "r", encoding="utf-8") as f: reader = csv.DictReader(f) with open(jsonl_path, "w", encoding="utf-8") as out: for idx, row in enumerate(reader, start=1): content = row["content"] request_obj = { "custom_id": f"summary-{idx:04d}", "params": { "model": model, "max_tokens": max_tokens, "messages": [ {"role": "user", "content": f"请为下面的内容生成摘要,要求简洁准确:{content}"} ], }, } out.write(json.dumps(request_obj, ensure_ascii=False) + "\n") print(f"已生成 {jsonl_path}") if __name__ == "__main__": build_jsonl("input.csv", "input.jsonl", model="MODEL_NAME_HERE")这段脚本做的事是:遍历 CSV 每一行,将文本字段拆成一条批处理请求,写入 JSONL 文件。
ensure_ascii=False很重要,它会保证中文内容以可读方式写入文件,避免被转成\uXXXX形式。
如果你没有 CSV 文件,也可以在命令行直接准备一个很简单的input.jsonl,手动编写几行即可,不必拘泥于脚本。
4.2 第二步:提交批处理任务
拿到输入文件之后,调用批处理接口提交。核心代码:
# 文件路径:submit_batch.py import os from anthropic import Anthropic client = Anthropic() def create_batch(jsonl_path): with open(jsonl_path, "rb") as f: batch = client.beta.messages.batches.create( requests=[ {"custom_id": "batch-1", "params": {}} ], file=f, ) return batch if __name__ == "__main__": batch = create_batch("input.jsonl") print("批处理任务 ID:", batch.id) print("任务状态:", batch.processing_status)这里需要说明:新版 SDK 对文件上传型批处理的支持方式可能变化,有些版本直接支持传入文件对象,有些版本需要先构造 requests 数组。上面示例是“通过文件对象上传”的思路,如果报错,请检查当前 SDK 对batches.create的签名。
提交成功后,脚本会打印批处理任务 ID,例如batch_01XXXXXXXXXX,记得保存它。后续查询状态、下载结果都要用到这个 ID。项目里建议把 task ID 写入本地 txt 文件,方便下一个脚本复用:
with open("batch_id.txt", "w") as f: f.write(batch.id)4.3 第三步:轮询任务状态
任务提交后不会立刻完成,我们需要定时查询。实现一个简单的轮询脚本:
# 文件路径:check_status.py import time import os from anthropic import Anthropic client = Anthropic() def check_batch(batch_id): batch = client.beta.messages.batches.retrieve(batch_id) print("状态:", batch.processing_status) print("请求总数:", batch.request_counts.total) print("已完成:", batch.request_counts.succeeded) print("失败数:", batch.request_counts.failed) return batch.processing_status if __name__ == "__main__": batch_id = open("batch_id.txt").read().strip() while True: status = check_batch(batch_id) if status in ["ended", "canceled"]: break time.sleep(30)轮询间隔不建议太短。批处理任务通常运行几分钟到几小时,设置 30 到 60 秒一次已经足够。间隔太短只会浪费请求配额,并不会加快任务执行。
4.4 第四步:下载并解析结果
任务状态变为完成后,从结果文件拉取内容并解析:
# 文件路径:download_results.py import json from anthropic import Anthropic client = Anthropic() def download_results(batch_id, output_dir="results"): import os os.makedirs(output_dir, exist_ok=True) result_file = os.path.join(output_dir, "result.jsonl") with open(result_file, "w", encoding="utf-8") as out: for result in client.beta.messages.batches.results(batch_id): out.write(json.dumps(result, ensure_ascii=False) + "\n") print("结果已保存到", result_file) if __name__ == "__main__": batch_id = open("batch_id.txt").read().strip() download_results(batch_id)下载完成后,结果文件同样是 JSONL 格式,每一行对应一个请求。每个结果对象里包含custom_id、result等字段,result中又包含type、message、content等子字段。
实际业务中,我们需要从结果里提取最终文本:
# 文件路径:parse_results.py import json def parse_result(line): obj = json.loads(line) custom_id = obj.get("custom_id") result = obj.get("result", {}) if result.get("type") == "succeeded": content = result.get("message", {}).get("content", []) text = "".join(block.get("text", "") for block in content if block.get("type") == "text") return custom_id, text else: return custom_id, f"ERROR: {result}" if __name__ == "__main__": with open("results/result.jsonl", "r", encoding="utf-8") as f: for line in f: line = line.strip() if not line: continue cid, text = parse_result(line) print(cid, text[:100])4.5 预期输出说明
整条流程跑完,result.jsonl中每一行会输出一个结果对象。解析后你会发现每个custom_id都能和输入文件中的task-001或summary-0001一一对应。这就是设计custom_id的意义:无论结果返回顺序如何,你都能安全地把输出映射到业务数据。
这里额外强调一个小细节:结果顺序可能与输入顺序不一致。批处理是并行执行,所以写代码时不要依赖行号对应,必须通过custom_id关联。
5. 常见问题与排查思路
批处理在落地过程中会碰到不少报错,这里整理高频问题清单。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
提交时报invalid jsonl格式错误 | JSONL 有空行、非 JSON 内容、字段缺失 | 用 Pythonjson.loads逐行校验,修正后再提交 |
提示model 不存在或无权访问 | 模型名填错或账号不可用 | 确认当前模型代号,换成账号可用的模型 |
任务一直处于processing | 队列繁忙或输入量非常大 | 耐心等待;如果超 24 小时未完成,联系官方支持渠道 |
结果里出现大量custom_id解码错误 | custom_id包含非法字符 | 只使用字母、数字、连字符、下划线,并保证唯一 |
| 下载结果时超时 | 结果文件过大 | 增加超时时间,分段下载或用流式读取 |
调用batches.create报AttributeError | SDK 版本过旧或接口路径变更 | 升级到最新 SDK,查看当前版本官方文档 |
| 拉取结果时出现 529 状态码 | 服务端过载 | 退避重试,等几秒后再次请求 |
5.1 输入文件校验小工具
为了减少格式问题,建议提交前写一个快速校验脚本:
# 文件路径:validate_jsonl.py import json import sys def validate(path): with open(path, "r", encoding="utf-8") as f: for line_num, line in enumerate(f, start=1): line = line.strip() if not line: print(f"第 {line_num} 行为空行,请移除") continue try: obj = json.loads(line) except json.JSONDecodeError as e: print(f"第 {line_num} 行解析失败: {e}") continue if "custom_id" not in obj or "params" not in obj: print(f"第 {line_num} 行缺少 custom_id 或 params") print("校验完成") if __name__ == "__main__": validate(sys.argv[1] if len(sys.argv) > 1 else "input.jsonl")这个脚本虽然简单,但在大批量提交前跑一遍,能省去很多麻烦。
5.2 排查优先级
遇到问题时,推荐按这个顺序排查:
- 检查输入 JSONL 是否合法。这是最容易被忽略的坑。
- 检查模型名是否拼写正确,并发一条同步请求确认模型可用。
- 检查 API Key 是否有批处理权限或配额。
- 检查 SDK 版本是否足够新。
- 检查网络是否能稳定访问 Anthropic API。
6. 最佳实践与工程建议
6.1 输入数据与 custom_id 设计
custom_id不只是字符串,它是业务数据的关联键。实际项目中建议使用业务主键或复合键,例如user_id:order_id的拼接形式,但要先确认替换为符合字符要求的格式。更好的做法是维护一张映射表:
{ "summary-0001": {"user_id": 1001, "order_id": "A10001"}, "summary-0002": {"user_id": 1002, "order_id": "A10002"} }下载结果后,通过custom_id反查映射表,就能把模型输出写回数据库。
6.2 任务拆分与文件大小控制
不要试图一次提交几十万条请求。建议按业务维度把任务切分成多个批处理文件,每个文件控制在合理规模:
- 每个批处理文件内请求数量适中,避免文件过大导致上传和下载超时。
- 每组任务单独记录
batch_id,方便单独重跑。 - 按时间分区,例如按天、按小时生成独立的批处理任务,便于排查问题。
6.3 状态轮询策略
轮询不要太频繁,建议使用指数退避:
- 前 5 分钟每 30 秒查一次。
- 5 分钟后每 5 分钟查一次。
- 超过 1 小时后每 15 分钟查一次。
这样可以避免无效请求占用 API 配额。生产环境更推荐用任务表记录状态,由调度系统统一驱动。
6.4 结果落库与失败重跑
下载结果后不要直接覆盖原文件,建议按批次号和任务 ID 命名,例如:
results/batch_20250211_1200_01.jsonl解析结果时,将succeeded和failed分开记录。失败请求可以汇总到retry.jsonl,稍后重新提交。
6.5 安全与合规
处理涉密或敏感数据时要特别谨慎:
- 不要把真实手机号、身份证号直接放进 JSONL,建议先做脱敏。
- 设置严格的 API Key 权限,最小化访问范围。
- 生产环境对工具链做访问控制,禁止非授权人员提交、取消、下载批处理任务。
- 对结果数据设置合理的保存周期,过期清理。
6.6 成本控制
批处理虽然单位成本更低,但任务量巨大时总开销仍然不小。建议:
- 按天统计请求数量和 token 消耗。
- 对超出预期的任务量做风控告警。
- 明确模型选择策略:简单任务用小模型,复杂任务才上大模型。
- 在
params中合理控制max_tokens,避免输出过长造成浪费。
6.7 日志与监控
线上环境要保留轨迹,建议至少记录:
- 任务提交时间、提交人、输入文件路径。
- 批量任务 ID、请求总数。
- 完成时间、成功数、失败数。
- 失败原因归类。
这些信息是后续排查问题的基础。
7. 总结与学习路线
通过这篇文章,你已经掌握了 Claude 批处理的完整链路:从 JSONL 输入文件构造、批处理任务提交、状态轮询、结果下载到按custom_id解析输出。你也知道了批处理适合什么场景、不适合什么场景,以及如何优化成本和排查报错。
下一步建议你做三件事:
第一,把示例代码跑通,用自己的数据集替换文本摘要需求,改成分类、翻译、实体抽取,加深对流程的熟悉。第二,学习如何把批处理和消息队列或任务调度框架整合,实现离线数据平台级的自动触发、重试、告警。第三,研究一下结果文件中的 token 使用统计,结合你的账单理解批处理任务的实际成本和优惠幅度。
如果你在跑批处理时遇到奇奇怪怪的报错,欢迎把错误信息和任务状态发在评论区。读代码是学习,亲手跑通一条批处理流水线才是真正的掌握。