Normandy.Batch.Processor
(normandy v2.2.0)
View Source
Batch processing utilities for handling multiple agent requests concurrently.
Provides efficient batch processing with configurable concurrency, a delay between chunks, and result aggregation for AI agent operations.
Features
- Concurrent request processing with Task.async_stream
- Configurable concurrency limits
- Ordered and unordered result collection
- Error handling and partial failure support
- Progress tracking callbacks
Example
# Basic batch processing
inputs = [
%{chat_message: "Hello"},
%{chat_message: "How are you?"},
%{chat_message: "Goodbye"}
]
{:ok, results} = Normandy.Batch.Processor.process_batch(
agent,
inputs,
max_concurrency: 5
)
# With progress callback
{:ok, results} = Normandy.Batch.Processor.process_batch(
agent,
inputs,
on_progress: fn completed, total ->
IO.puts("Progress: #{completed}/#{total}")
end
)Telemetry
An item that runs past :timeout is killed mid-run, so its
[:normandy, :agent, :run] span never closes on its own. The processor
emits a synthetic [:normandy, :agent, :run, :exception] with
reason: :killed for it, possibly without a matching :start; the
llm_call/tool spans open inside it stay open. See
"Killed runs" in Normandy.Telemetry.
Summary
Functions
Process a batch of inputs through an agent concurrently.
Process a batch in chunks with a delay between chunks.
Process a batch and return detailed statistics.
Types
@type batch_option() :: {:max_concurrency, pos_integer()} | {:ordered, boolean()} | {:timeout, timeout()} | {:on_progress, progress_callback()} | {:on_error, error_callback()} | {:chunk_size, pos_integer()} | {:chunk_delay, non_neg_integer()}
@type batch_options() :: [batch_option()]
@type batch_result() :: %{ success: [term()], errors: [{term(), term()}], total: non_neg_integer(), success_count: non_neg_integer(), error_count: non_neg_integer() }
@type progress_callback() :: (pos_integer(), pos_integer() -> any())
Functions
@spec process_batch(agent :: struct(), inputs :: [term()], batch_options()) :: {:ok, [item_result()] | batch_result()}
Process a batch of inputs through an agent concurrently.
Options
:max_concurrency(pos_integer/0) - Items processed at once. The default value is10.:timeout(timeout/0) - Timeout per item, in ms. An item that runs past it is stopped and fails with a:task_downNormandy.TurnError; the other items carry on. The default value is300000.:on_progress(function of arity 2) - Called in the caller after each item completes:(completed, total -> any).:on_error(function of arity 2) - Called in the caller for each failed item:(input, error -> any).:ordered(boolean/0) -true: one result per input, in input order.false: abatch_resultmap. The default value istrue.
Returns
With
ordered: true:{:ok, [{:ok, response} | {:error, error}]}, one per input, in input order.- With
ordered: false:{:ok, batch_result}.errorspairs each error with its input,{input, error}; both lists are in completion order.
An error is what the other coordinators return, never a stacktrace: Normandy's own
exception (Normandy.LLMError, ...), the exception a bug raised (logged with its
stacktrace, arities only), or a :task_down Normandy.TurnError for an exit, a throw
or an item stopped at :timeout.
Examples
# Simple batch
{:ok, [{:ok, first} | _]} = Processor.process_batch(agent, inputs)
# With configuration
{:ok, results} = Processor.process_batch(
agent,
inputs,
max_concurrency: 5,
on_progress: fn completed, total ->
IO.puts("Progress: #{completed}/#{total}")
end
)
@spec process_batch_chunked(agent :: struct(), inputs :: [term()], batch_options()) :: {:ok, [item_result()] | batch_result()}
Process a batch in chunks with a delay between chunks.
Useful for very large batches: :chunk_delay is a fixed pause between chunks, not a rate limiter.
Options
:max_concurrency(pos_integer/0) - Items processed at once. The default value is10.:timeout(timeout/0) - Timeout per item, in ms. An item that runs past it is stopped and fails with a:task_downNormandy.TurnError; the other items carry on. The default value is300000.:on_progress(function of arity 2) - Called in the caller after each item completes:(completed, total -> any).:on_error(function of arity 2) - Called in the caller for each failed item:(input, error -> any).:ordered(boolean/0) -true: one result per input, in input order.false: abatch_resultmap. The default value istrue.:chunk_size(pos_integer/0) - Items per chunk. The default value is100.:chunk_delay(non_neg_integer/0) - Milliseconds to wait between chunks. The default value is0.
Returns the same shape as process_batch/3 for the same :ordered option,
covering all chunks.
Example
{:ok, results} = Processor.process_batch_chunked(
agent,
large_input_list,
chunk_size: 50,
chunk_delay: 1000, # 1 second between chunks
max_concurrency: 5
)
@spec process_batch_with_stats(agent :: struct(), inputs :: [term()], batch_options()) :: {:ok, batch_result()}
Process a batch and return detailed statistics.
Returns a map with separate success/error lists and counts.
Options
:max_concurrency(pos_integer/0) - Items processed at once. The default value is10.:timeout(timeout/0) - Timeout per item, in ms. An item that runs past it is stopped and fails with a:task_downNormandy.TurnError; the other items carry on. The default value is300000.:on_progress(function of arity 2) - Called in the caller after each item completes:(completed, total -> any).:on_error(function of arity 2) - Called in the caller for each failed item:(input, error -> any).
Example
result = Processor.process_batch_with_stats(agent, inputs)
#=> %{
success: [result1, result2],
errors: [{input3, error}],
total: 3,
success_count: 2,
error_count: 1
}