Claudex.Messages.Batches (Claudex v0.6.1)

Copy Markdown View Source

The Message Batches API — send up to 100,000 Messages requests at once for asynchronous processing, at half the token cost.

The flow is submit, check back, collect:

requests = [
  %{
    custom_id: "ticket-1",
    params: %{
      model: "claude-opus-5",
      max_tokens: 1024,
      messages: [%{role: "user", content: "..."}]
    }
  }
]

{:ok, batch} = Batches.create(client, requests)

# later, from a job runner
{:ok, batch} = Batches.retrieve(client, batch.id)

if Claudex.Messages.Batch.ended?(batch) do
  {:ok, results} = Batches.results(client, batch.id)
  Enum.each(results, &handle/1)
end

Claudex doesn't poll for you. A batch has 24 hours to finish, so check on it from your app's job runner with the batch id persisted.

Each request's params are validated asynchronously, and a validation error arrives with that request's result once the batch has ended — so a batch containing a request the Messages API would reject outright is still accepted. Check a request's shape against Claudex.Messages.create/2 before batching a lot of them.

Results come back in whatever order the requests finished, so match them to your requests by custom_id. results/2 streams them, so a batch far too big to hold in memory is still fine to walk.

Summary

Functions

Asks for a batch to be canceled.

Submits a batch.

Deletes a batch. It must have finished processing first.

Lists your batches, newest first.

Streams a finished batch's results as Claudex.Messages.BatchResult structs.

Looks up a batch. This is the endpoint to poll for completion.

Functions

cancel(client, batch_id)

@spec cancel(Claudex.Client.t(), String.t()) ::
  {:ok, Claudex.Messages.Batch.t()} | {:error, Claudex.Error.t()}

Asks for a batch to be canceled.

Cancellation isn't immediate: the batch moves to "canceling", and requests already in flight may still finish, so expect a mix of canceled and succeeded results.

create(client, requests)

@spec create(Claudex.Client.t(), [map()]) ::
  {:ok, Claudex.Messages.Batch.t()} | {:error, Claudex.Error.t()}

Submits a batch.

Each request is a map with a :custom_id — 1 to 64 characters of letters, digits, hyphens, and underscores, unique within the batch — and :params, which takes exactly what Claudex.Messages.create/2 takes. :tools in those params accepts a module the same way, and max_tokens must be at least 1.

Examples

requests = [
  %{
    custom_id: "ticket-1",
    params: %{
      model: "claude-opus-5",
      max_tokens: 1024,
      messages: [Claudex.Message.user("Summarise: the printer is offline")]
    }
  },
  %{
    custom_id: "ticket-2",
    params: %{
      model: "claude-opus-5",
      max_tokens: 1024,
      messages: [Claudex.Message.user("Summarise: billed twice in March")]
    }
  }
]

{:ok, batch} = Claudex.Messages.Batches.create(client, requests)
batch.id
#=> "msgbatch_01HkcTjaV5uDC8jWR4ZsDV8d"

delete(client, batch_id)

@spec delete(Claudex.Client.t(), String.t()) :: :ok | {:error, Claudex.Error.t()}

Deletes a batch. It must have finished processing first.

list(client, opts \\ [])

Lists your batches, newest first.

Takes :limit, :after_id, and :before_id — the same id cursors Claudex.Models.list/2 uses.

results(client, batch_id)

@spec results(Claudex.Client.t(), String.t()) ::
  {:ok, Enumerable.t()} | {:error, Claudex.Error.t()}

Streams a finished batch's results as Claudex.Messages.BatchResult structs.

Returns {:error, %Claudex.Error{}} if the batch hasn't ended yet — check Claudex.Messages.Batch.ended?/1 first, or just match on the error and try again later.

The stream reads the results file as it goes rather than pulling it all into memory, so enumerating it lazily (Stream.filter/2, Enum.reduce/3) keeps a 100,000-request batch manageable. Enumerating raises Claudex.Error if the download fails part-way.

retrieve(client, batch_id)

@spec retrieve(Claudex.Client.t(), String.t()) ::
  {:ok, Claudex.Messages.Batch.t()} | {:error, Claudex.Error.t()}

Looks up a batch. This is the endpoint to poll for completion.