@agentproto/batch

中文文档
0.2.3发布于 2026/10/19Apache-2.0TypeScriptES Module构建来源已验证近 30 天下载 3748

One batch-inference contract over provider Batch APIs (Anthropic Message Batches, OpenRouter Batch) plus a local-queue emulation for providers without one — submit N Messages-shaped requests, poll, and collect results keyed by custom_id, all driver-agnost

原始使用文档
文档目录11 节
  1. Why
  2. The contract
  3. Pre-submit validation
  4. Durable store
  5. Drivers
  6. anthropicBatchDriver({ apiKey, baseUrl?, fetch? })
  7. openrouterBatchDriver({ apiKey, baseUrl?, fetch? })
  8. localQueueDriver({ complete, concurrency?, store, retry? })
  9. Helpers
  10. What this package deliberately doesn't do
  11. License

@agentproto/batch

One contract over provider batch-inference APIs — submit N Anthropic Messages-shaped requests, poll, and collect results keyed by custom_id, without caring which provider runs them. No SDK deps: raw fetch + zod.

Why

Anthropic and OpenRouter both expose an async Batch API at 50% of token price: submit up to 100k requests, get a batch id back, poll it, collect results within a 24h window. Batch is not a model — it's a delivery mode wrapped around the same Messages body (model, system, messages, max_tokens, tools…). This package gives callers (distillation, eval judging, dataset generation, proxy fan-out) one BatchDriver contract so they don't have to special-case each provider's envelope.

The contract

interface BatchDriver {
  readonly id: string
  submit(requests: readonly BatchRequest[], opts?: BatchSubmitOptions): Promise<BatchHandle>
  status(handle: BatchHandle): Promise<BatchStatus>
  results(handle: BatchHandle): AsyncIterable<BatchResult>
  cancel(handle: BatchHandle): Promise<void>
}
  • BatchRequest = { customId, body: MessagesBody } — body is the same Anthropic Messages shape you'd send synchronously.
  • BatchHandle.id is ours (b_<ulid>), stable across process restarts; provider.batchIds is whatever the underlying API assigned (a driver may fan out to several — see OpenRouter below).
  • Results are keyed by customId, never by position — batch APIs return them in arbitrary order.
  • expired is not a failure. It means the item never ran inside the 24h window and should be resubmitted; use expiredCustomIds(results) to find which ones.
import {
  anthropicBatchDriver,
  pollUntilEnded,
  collectResults,
  expiredCustomIds,
} from "@agentproto/batch"

const driver = anthropicBatchDriver({ apiKey: process.env.ANTHROPIC_API_KEY! })
const handle = await driver.submit([
  { customId: "doc-1", body: { model: "claude-sonnet-5", max_tokens: 1024, messages: [...] } },
])
await pollUntilEnded(driver, handle, { onTick: s => console.error(s.counts) })
const results = await collectResults(driver, handle)
const toResubmit = expiredCustomIds(results.values())

Pre-submit validation

validateForBatch(request) rejects fields a batch envelope can't carry — stream, speed, fallbacks, max_tokens < 1, and a forced tool_choice of any/tool — naming the offending customId. validateBatchRequests also rejects duplicate customIds across a submit (results would be ambiguous otherwise). Every driver's submit() runs this before talking to a provider.

Durable store

BatchStore persists a batch to <stateDir>/batches/<id>/{manifest.json, requests.jsonl, results.jsonl} so it outlives the process — anyone with the id can re-attach via store.load(id). stateDir is an explicit constructor option; this package never defaults to a real home directory.

import { BatchStore } from "@agentproto/batch"
const store = new BatchStore({ stateDir: "/path/to/workspace/.batch" })

Drivers

anthropicBatchDriver({ apiKey, baseUrl?, fetch? })

One provider batch per submit. results() streams results_url's JSONL line by line. cancel() posts the documented cancel endpoint.

openrouterBatchDriver({ apiKey, baseUrl?, fetch? })

OpenRouter's batch API (beta) scopes one provider batch to one model, so a submit with mixed models fans out to N provider batches — grouped by body.model, one POST per group with endpoint: "/v1/messages" (Anthropic body shape, passed through as-is). The request body's key order (endpoint, model, requests) is intentional — OpenRouter stream-parses it. cancel() throws BatchUnsupportedError rather than pretend it works — OpenRouter's beta docs don't document one.

Status/count aggregation across a submit's provider batches is exact when the driver instance that submitted is still resident (its per-group customId lists are cached in memory); across a process restart with no cache, status() still sums to the handle's total request count but can't attribute partial progress to a specific still-running group.

localQueueDriver({ complete, concurrency?, store, retry? })

Emulation for providers with no batch API of their own (Requesty, Moonshot, Groq, xAI, a local llm-endpoint…). Runs every request through the injected complete(body) with bounded concurrency, retrying a thrown RetryableCompletionError (the signal a complete implementation uses for a 429/5xx from its underlying provider) with backoff up to retry.max attempts before recording a permanent errored result. Every result is appended to store as it lands. Full price — there is no provider-side batch to get the 50% discount from.

submit() runs every pending item to completion before resolving (there's no external service to poll). If the process crashes mid-run, a fresh localQueueDriver pointed at the same store can call the extra resume(handle) method — not part of the shared BatchDriver contract — to reload the original requests and re-run only the ones still missing a result.

Helpers

  • pollUntilEnded(driver, handle, { intervalMs?, timeoutMs?, onTick? }) — polls status() until ended/failed (default 24h timeout, matching every provider's batch window).
  • collectResults(driver, handle) — drains results() into a Map<customId, BatchResult>.

What this package deliberately doesn't do

No OpenAI/Gemini/Mistral native batch drivers, no daemon MCP/CLI tools, no cron polling, no model-catalog batch-pricing fields. MessagesBody covers the Anthropic Messages request shape only — the batch envelope, not a provider-agnostic request format.

License

Apache-2.0

版本与发布记录

@agentproto/batch 共发布 4 个版本,首次发布于 2026/09/02;过去 12 个月发布了 4 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.2.3latest2026/10/1919 天前
0.2.22026/09/171 个月前
0.2.12026/09/091 个月前
0.2.02026/09/022 个月前

关于 @agentproto/batch 的常见问题

@agentproto/batch 是什么?

One batch-inference contract over provider Batch APIs (Anthropic Message Batches, OpenRouter Batch) plus a local-queue emulation for providers without one — submit N Messages-shaped requests, poll, and collect results keyed by custom_id, all driver-agnost

如何安装 @agentproto/batch?

在项目目录运行 npm install @agentproto/batch,或使用 pnpm add / yarn add / bun add。

@agentproto/batch 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@agentproto/batch 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@agentproto/batch 可以商用吗?

@agentproto/batch 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@agentproto/batch 的下载量有多少?

根据 npm 官方下载统计,@agentproto/batch 在 2026/10/05 – 2026/11/03 这 30 天内被下载 3,748 次,日均约 125 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@agentproto/batch 还在维护吗?

最新版本 0.2.3 发布于 2026/10/19(19 天前);至今共发布 4 个版本,其中过去 12 个月发布了 4 个;首次发布于 2026/09/02。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。