最新资讯
reddit.com OpenAI 称 Astra 仅满足 10% 安全任务标准 dev.to FreeToken:在自有 GPU 上运行前沿 MoE 模型及其内存需求 dev.to 当 AI Agent 说“完成”时,究竟完成了什么? dev.to 部署 Underdog Bark 3.0 Pro:逐字转写、自定义后端与分块设置 dev.to 为艰难对话构建 AI:五个 Agent、一条安全正则与一位盲评监督者 techcrunch.com Palo Alto Networks 洽谈以 5 亿美元收购 AI Agent 安全初创公司 Clutch theinformation.com 黄仁勋被要求就涉华间谍指控向国会作简报 www.infoq.com Edera 推出 Object Capability Bounded Authority,试图将凭证与 AI Agent 工作负载隔离 wechat 这波杀疯了!旗舰级模型不限量、1个月视频额度免费领 bloomberg.com AI 抛售蔓延至信用市场,软银领跌亚洲美元债 dev.to 如何在专用 GPU 服务器上用 OpenAI 兼容 API 部署 LLM dev.to 审计 153 份 Claude Code 与 Codex 会话记录:User-Agent 与 URL 中的邮箱泄露
多元动态
登录 注册
行业动态 dev.to · @jidonglab

Anthropic Batch API:我的 1,842 个任务中有 112 个没有返回

原文 我的收藏

作者统计 30 天内通过 Anthropic Batch API 提交的 1,842 个评分任务(96 个批次),其中 1,730 个可用(93.9%),71 个报错、24 个在 24 小时上限处过期、17 个显示成功但返回被截断。作者强调 batch.processing_status == "ended" 并不代表成功,需要读取 request_counts 并按每条结果的 result.type 分支处理。数据来自作者自述日志,未提供可独立核验的原始记录。

AI推荐理由 提供了 Anthropic Batch API 在 30 天、1,842 个请求中的具体失败统计(71 个报错、24 个 24 小时过期、17 个截断),并指出 batch.processing_status == "ended" 不等于成功,对使用该 API 的开发者有直接可操作的排错价值;数据来自作者自述日志,未经第三方验证。

72

正文

A user emailed me a screenshot of an empty report. Score field blank. I opened the database and found the row sitting there with score = NULL, created four hours earlier, no error logged anywhere. My worker had run, my batch had finished, my code had written nothing and moved on cheerfully. That was the third one that week. So I pulled 30 days of logs and counted: I had pushed 1,842 scoring jobs through the Anthropic Batch API, and 112 of them never produced a usable result. Not 112 crashes. 112 silent holes, because "the batch ended" and "my requests succeeded" are two completely different facts and I had written code that assumed they were the same one. TL;DR Over 30 days I sent 1,842 requests in 96 batches through the Anthropic Batch API. 1,730 were usable (93.9%). 71 errored, 24 expired at the 24-hour wall, and 17 "succeeded" but came back truncated. batch.processing_status == "ended" does not mean success. Read request_counts and switch on each result line's result.type (succeeded / errored / expired / canceled). Results come back in arbitrary order. Zipping them against your input list silently attaches the wrong output to the wrong record. This was my worst bug, and it never threw. A succeeded result with stop_reason: "max_tokens" is a truncated JSON body. My parser swallowed it and wrote a null. Latency is wildly skewed: median 9 minutes, p99 over 6 hours. Batch is for work nobody is staring at. My bill halved ($209 → $104), which is the only reason any of this was worth it. What is the Anthropic Batch API actually good for? It is asynchronous bulk inference at roughly half price. You hand Anthropic a list of up to 100,000 requests, each wrapped in a custom_id, and poll until the batch ends. The tradeoff is the entire point: you give up latency guarantees and you get a discount. There is a 24-hour ceiling on processing. My workload fit on paper. I run a platform that does mock voice interviews and hands back a written report afterward, plus a separate scoring pass over the candidate's portfolio. The portfolio pass is nightly, nobody is watching, and it is a fat prompt. Perfect batch job. The interview report is not a batch job, and I put it in one anyway. That was the first mistake and I'll get to it. batch = client.messages.batches.create( requests=[ { "custom_id": session_uuid, # not the user's email "params": { "model": "claude-sonnet-5", "max_tokens": 4096, "messages": [{"role": "user", "content": prompt}], }, } for session_uuid, prompt in pending ] ) Enter fullscreen mode Exit fullscreen mode One small thing worth doing on day one: custom_id comes back to you in the results file, so make it an opaque internal ID. I used the session UUID. Do not put an email address there because it was convenient. Why did 112 of 1,842 Anthropic Batch API jobs never come back? Because I only checked one flag. Here is the real breakdown from the 30-day run: result.type count succeeded 1,747 errored 71 expired 24 canceled 0 Of the 71 errored: 52 overloaded_error, 14 invalid_request_error (transcripts that blew past my own token budget), 5 generic api_error. Then, of the 1,747 that succeeded, 17 came back with stop_reason: "max_tokens" — valid API responses containing half a JSON object. My json.loads threw, my except block logged at DEBUG, and the row stayed null. 71 + 24 + 17 = 112. A 6.1% silent failure rate on a pipeline I thought was green. Does a batch with processing_status: "ended" mean my requests succeeded? No. ended means Anthropic is done working on the batch, including the parts it gave up on. The per-request outcome lives in two places: batch.request_counts, and the JSONL results stream. My original loop, in full embarrassing honesty: while batch.processing_status != "ended": time.sleep(30) batch = client.messages.batches.retrieve(batch.id) results = list(client.messages.batches.results(batch.id)) for session, entry in zip(pending_sessions, results): # <-- both bugs live here save_report(session.id, entry.result.message.content[0].text) Enter fullscreen mode Exit fullscreen mode Two failures in one line. entry.result.message does not exist on an errored or expired entry, so those raise an AttributeError I was catching too broadly. And zip against pending_sessions assumes the results arrive in submission order. They do not. Why did my results get attached to the wrong session? Because batch results come back in arbitrary order and I matched by position instead of by custom_id. When every request succeeds, the order is often close enough to input order that nothing looks wrong. The moment one request errors out, the list shortens by one and every entry after it shifts up a slot. Candidate A gets Candidate B's report. This is the bug that actually scared me. No exception, no alert, no 500. Just quietly wrong output rendered in a nice template. I found it by reading a report that praised a Kubernetes project the person had never mentioned. Full disclosure: the product is Preterview, which I built and run. It does voice interviews with three interviewer styles and returns a written report, and a report is the entire deliverable, so shipping one person's feedback to another person's account is about as bad as my failure modes get. Two sessions were affected before I caught it. I emailed both people, which is a conversation I recommend avoiding by writing the loop correctly the first time. The fix is four lines and I should have written them on day one: by_id = {} for entry in client.messages.batches.results(batch.id): by_id[entry.custom_id] = entry # never trust order for session in pending_sessions: entry = by_id.get(session.id) if entry is None: mark_retry(session.id, "missing_from_results") continue match entry.result.type: case "succeeded": msg = entry.result.message if msg.stop_reason == "max_tokens": mark_retry(session.id, "truncated") else: save_report(session.id, msg.content[0].text) case "errored": mark_retry(session.id, entry.result.error.type) case "expired" | "canceled": mark_retry(session.id, entry.result.type) Enter fullscreen mode Exit fullscreen mode Every branch writes a row. There is no path through that block where a request disappears. How long does an Anthropic Batch API job actually take? Median 9 minutes across my 96 batches. p90 was 51 minutes. p99 was 6 hours 20 minutes. And 24 requests, spread across two unlucky batches, sat until the 24-hour ceiling and came back expired. That distribution is the whole design constraint. The median tempts you into thinking batch is a slightly slower sync API. It is not. You have to build for the tail, because the tail is where your users live. Which is why the post-interview report is back on the synchronous API, where it always belonged. Someone just finished talking for 25 minutes; they are not going to refresh for six hours. The nightly portfolio rescoring stayed on batch, and that is the workload the discount was designed for. The reconciler is the actual deliverable The piece of this I'd write first next time is not the submitter. It is the reconciler: a table of (custom_id, batch_id, status, attempts) and a cron job that sweeps it. Rules I landed on after the postmortem: Write the row before you submit. Every custom_id exists in the database as pending before the batch is created. If it never comes back, it is already visible as a gap. Switch on result.type exhaustively. No default branch that silently passes. Check stop_reason on success. max_tokens is a failure wearing a success costume. Two batch attempts, then fall back to the sync API. Paying full price for 6% of jobs is cheaper than an empty report. Alert on the gap, not the error. My monitor now compares rows submitted to rows resolved per batch. The one number that would have caught all 112. After the reconciler shipped, the next 30 days ran 2,100-ish requests with zero unresolved rows. Still had errors. Still had a handful of expiries. But every one of them now ends up retried or dead-lettered instead of vanishing into a null column. So what happens to the jobs that never come back from the Anthropic Batch API? Nothing happens to them. That is the problem. A batch reaching processing_status: "ended" only means Anthropic stopped working on it, and the results JSONL can contain errored, expired, or canceled entries alongside successes, in arbitrary order, plus succeeded entries truncated at max_tokens. If your consumer zips results against the input list and reads .result.message without checking .result.type, those requests are not reported as failures — they are dropped, or worse, shifted onto the wrong record. Match on custom_id, handle all four result types, verify stop_reason, and reconcile submitted-versus-resolved counts per batch. In my 30-day run that was the difference between 6.1% silent data loss and a clean retry queue, at half the API cost. Written by the developer behind Preterview, an interview prep platform.

来源报道 · 1 篇 · 以下链接均前往原始媒体

← 返回精选动态 原文

更多内容