Core API Reference
ParallelBatchProcessor
async_batch_llm.ParallelBatchProcessor
ParallelBatchProcessor(max_workers: int | None = None, post_processor: PostProcessorFunc[TOutput, TContext] | None = None, timeout_per_item: float | None = None, rate_limit_cooldown: float | None = None, config: ProcessorConfig | None = None, error_classifier: ErrorClassifier | None = None, rate_limit_strategy: RateLimitStrategy | None = None, middlewares: list[Middleware[TInput, TOutput, TContext]] | None = None, observers: list[ProcessorObserver] | None = None, progress_callback: ProgressCallbackFunc | None = None, artifact_store: ArtifactStore | None = None, resume: ResumePolicy = ResumePolicy.NONE)
Batch processor that executes items in parallel as individual agent calls.
This refactored version uses: - Pluggable error classification (provider-agnostic) - Pluggable rate limit strategies - Middleware pipeline for extensibility - Observer pattern for monitoring - Configuration objects for easier setup
Initialize the parallel batch processor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_workers
|
int | None
|
Maximum concurrent workers (deprecated, use config) |
None
|
post_processor
|
PostProcessorFunc[TOutput, TContext] | None
|
Optional async function called after each successful item |
None
|
timeout_per_item
|
float | None
|
Timeout per item in seconds (deprecated, use config) |
None
|
rate_limit_cooldown
|
float | None
|
Cooldown duration (deprecated, use config) |
None
|
config
|
ProcessorConfig | None
|
Processor configuration object (recommended) |
None
|
error_classifier
|
ErrorClassifier | None
|
Optional global classifier override. When omitted, each strategy's recommendation is resolved independently. |
None
|
rate_limit_strategy
|
RateLimitStrategy | None
|
Strategy for handling rate limits |
None
|
middlewares
|
list[Middleware[TInput, TOutput, TContext]] | None
|
List of middleware to apply |
None
|
observers
|
list[ProcessorObserver] | None
|
List of observers for events |
None
|
progress_callback
|
ProgressCallbackFunc | None
|
Optional callback(completed, total, current_item_id) for progress updates |
None
|
Source code in src/async_batch_llm/parallel.py
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 | |
add_work
async
Queue a work item and register its identity-scoped admission state.
Source code in src/async_batch_llm/parallel.py
get_stats
async
Get processor statistics (thread-safe).
Returns:
| Type | Description |
|---|---|
dict
|
Dictionary containing processing statistics including: |
dict
|
|
dict
|
|
dict
|
|
dict
|
|
dict
|
|
dict
|
|
dict
|
|
Source code in src/async_batch_llm/parallel.py
shutdown
async
Release every owned resource; safe to call repeatedly.
Same ordered close as async with exit: runtime tasks and
callbacks, admission resources, prepared strategies, then the
artifact store (including after an early stream exit or a failed
run). Concurrent calls share one attempt; a step that succeeded is
never repeated and a failed one is retried by the next call. Raises
the first cleanup failure after every step has been attempted.
Source code in src/async_batch_llm/parallel.py
start
wait_for_abort
async
Wait until a configured batch deadline or fail-fast abort trips.
LLMWorkItem
async_batch_llm.LLMWorkItem
dataclass
LLMWorkItem(item_id: str, strategy: LLMCallStrategy[TOutput], prompt: str = '', context: TContext | None = None, submission_index: int | None = None, _artifact_key: Any = None, _capacity_warning_source: tuple[str, int, str] | None = None)
Bases: Generic[TInput, TOutput, TContext]
Represents a single work item to be processed by an LLM strategy.
Attributes:
| Name | Type | Description |
|---|---|---|
item_id |
str
|
Unique identifier for this work item |
strategy |
LLMCallStrategy[TOutput]
|
LLM call strategy that encapsulates how to make the LLM call |
prompt |
str
|
The prompt/input to pass to the LLM |
context |
TContext | None
|
Optional context data passed through to results/post-processor |
WorkItemResult
async_batch_llm.WorkItemResult
dataclass
WorkItemResult(item_id: str, success: bool, output: TOutput | None = None, error: str | None = None, context: TContext | None = None, token_usage: TokenUsage = (lambda: {'input_tokens': 0, 'output_tokens': 0, 'total_tokens': 0})(), metadata: dict[str, Any] | None = None, gemini_safety_ratings: dict[str, str] | None = None, exception: Exception | None = None, admission_wait_seconds: float = 0.0, timing: WorkItemTiming = WorkItemTiming(), submission_index: int | None = None, error_category: str | None = None, replayed_from_artifact: bool = False)
Bases: ProviderOutputViews, Generic[TOutput, TContext]
Result of processing a single work item.
Attributes:
| Name | Type | Description |
|---|---|---|
item_id |
str
|
ID of the work item |
success |
bool
|
Whether processing succeeded |
output |
TOutput | None
|
Agent output if successful, None if failed |
error |
str | None
|
Error message if failed, None if successful |
context |
TContext | None
|
Context data from the work item |
token_usage |
TokenUsage
|
Token usage stats (input_tokens, output_tokens, total_tokens) |
metadata |
dict[str, Any] | None
|
Provider-specific metadata returned alongside the response —
e.g. |
gemini_safety_ratings |
dict[str, str] | None
|
Deprecated. Use |
exception |
Exception | None
|
The originating exception for a failed result, when one was
raised (all retries exhausted, or a permanent non-retryable error).
|
admission_wait_seconds |
float
|
Total time this item spent waiting for provider capacity across all attempts. This wait occurs before the per-attempt execution timeout starts. |
timing |
WorkItemTiming
|
Structured end-to-end and per-attempt timing details. |
quota_wait_seconds
property
Combined RPM/TPM wait, separate from provider-capacity admission.
__post_init__
Backfill gemini_safety_ratings from metadata['safety_ratings']
for backward compatibility. Once gemini_safety_ratings is removed,
this method goes away. (Reads/writes go through __dict__ directly
so the framework itself never triggers the deprecation warning.)
Source code in src/async_batch_llm/base.py
from_dict
classmethod
from_dict(data: Mapping[str, Any], *, output_decoder: ValueDecoder | None = None, context_decoder: ValueDecoder | None = None) -> WorkItemResult[Any, Any]
Restore a result from :meth:to_dict output.
Without decoders, custom values (including Pydantic models and dataclasses) are restored as JSON-native mappings/lists.
Source code in src/async_batch_llm/base.py
to_dict
Return a versioned, JSON-safe representation of this result.
encoder may convert application-specific output/context values to
supported JSON-safe values. Unsupported values raise
:class:~async_batch_llm.ResultSerializationError.
Source code in src/async_batch_llm/base.py
AttemptTiming
async_batch_llm.AttemptTiming
dataclass
AttemptTiming(attempt: int, try_number: int, total_seconds: float = 0.0, admission_wait_seconds: float = 0.0, startup_ramp_wait_seconds: float = 0.0, execution_seconds: float = 0.0, provider_seconds: float | None = None, cooldown_wait_seconds: float = 0.0, retry_backoff_seconds: float = 0.0, success: bool = False, error_type: str | None = None, error_category: str | None = None, timeout_category: str | None = None, quota_wait_seconds: float = 0.0, estimated_input_tokens: int | None = None, estimated_output_tokens: int | None = None, reserved_tokens: int = 0, reported_tokens: int | None = None, reconciliation_delta_tokens: int | None = None, quota_scope_id: int | None = None)
Timing and outcome details for one physical strategy execution try.
WorkItemTiming
async_batch_llm.WorkItemTiming
dataclass
ProcessorConfig
async_batch_llm.ProcessorConfig
dataclass
ProcessorConfig(max_workers: int | None = None, timeout_per_item: float | None = None, post_processor_timeout: float = 90.0, concurrent_post_processing: bool = False, retry: RetryConfig = RetryConfig(), rate_limit: RateLimitConfig = RateLimitConfig(), max_requests_per_minute: float | None = None, progress_interval: int = 10, progress_callback_timeout: float | None = 5.0, enable_detailed_logging: bool = False, max_queue_size: int = 0, dry_run: bool = False, max_provider_concurrency: int | None = None, startup_ramp: StartupRampConfig | None = None, guardrails: GuardrailConfig = GuardrailConfig(), attempt_timeout: float | None = None, concurrency: int | None = None, max_result_queue_size: int = 0, progress_refresh_interval_seconds: float = 0.1, max_tokens_per_minute: int | None = None, token_estimator: TokenEstimator | None = None, quota_burst_seconds: float | None = None)
Complete configuration for batch processor.
__post_init__
Resolve the deprecated timeout alias, then validate.
Source code in src/async_batch_llm/core/config.py
validate
Validate complete configuration.
Source code in src/async_batch_llm/core/config.py
454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 | |
RetryConfig
async_batch_llm.core.RetryConfig
dataclass
RetryConfig(max_attempts: int = 3, initial_wait: float = 1.0, max_wait: float = 60.0, exponential_base: float = 2.0, jitter: bool = True, max_rate_limit_retries: int = 20)
Configuration for retry behavior.
max_attempts bounds retries for content/transport failures (validation
errors, timeouts, connection errors, generic 5xx). Rate-limit errors are
exempt from this budget: a 429 / quota / coordinated-cooldown signal is a
"wait and try again", not a failed attempt, so it does not consume an
attempt — the same logical attempt number is retried after the
cooldown. Rate-limit retries are instead bounded separately by
max_rate_limit_retries (a backstop against an endpoint that throttles
forever). This keeps model-escalation strategies (which key behavior off the
attempt number) from escalating just because the endpoint was busy.
__post_init__
validate
Validate retry configuration.
Source code in src/async_batch_llm/core/config.py
RateLimitConfig
async_batch_llm.core.RateLimitConfig
dataclass
RateLimitConfig(cooldown_seconds: float = 300.0, slow_start_items: int = 50, slow_start_initial_delay: float = 2.0, slow_start_final_delay: float = 0.1, backoff_multiplier: float = 1.5, max_cooldown_seconds: float = _DEFAULT_MAX_COOLDOWN_SECONDS)
Configuration for rate limit handling.
__post_init__
Validate configuration on construction.
Source code in src/async_batch_llm/core/config.py
validate
Validate rate limit configuration.
Source code in src/async_batch_llm/core/config.py
GuardrailConfig
async_batch_llm.GuardrailConfig
dataclass
GuardrailConfig(total_timeout_per_item: float | None = None, batch_timeout: float | None = None, abort_on_error_categories: frozenset[str] = frozenset(), abort_mode: AbortMode = AbortMode.DRAIN_ACTIVE, max_total_tokens: int | None = None, max_total_cost: float | None = None, cost_function: Callable[[AttemptUsage], float] | None = None)
Optional deadlines, fail-fast policy, and a total token/cost budget.
max_total_tokens and max_total_cost are soft caps on provider usage
for one processor run. Usage is counted per physical provider attempt,
including failed attempts and retries, as each attempt finishes; replayed
results are not counted. Reaching a cap stops the run like a batch deadline:
no new provider call starts, abort_mode decides whether calls already
running drain or are cancelled, and unstarted items finish with
batch_budget_exceeded. Calls already running when the cap is reached can
overshoot it; see the guardrails documentation for the bound.
max_total_cost requires cost_function, a synchronous callable that
prices one :class:~async_batch_llm.AttemptUsage. A cost_function
without max_total_cost only tracks cost. Budgets apply to processor
runs; call(), call_result() and LLMCallPool reject them.
AbortMode
async_batch_llm.AbortMode
Bases: str, Enum
How a controlled batch abort treats provider calls already running.
AttemptUsage
async_batch_llm.AttemptUsage
dataclass
AttemptUsage(item_id: str, attempt: int, try_number: int, strategy: Any, usage: Mapping[str, int], success: bool)
Provider-observed usage of one physical attempt, passed to a cost function.
GuardrailConfig.cost_function receives one of these for every provider
attempt whose usage the provider reported, success or failure. Fields may be
added in later releases, so read them by name.
Attributes:
| Name | Type | Description |
|---|---|---|
item_id |
str
|
The work item's ID. |
attempt |
int
|
The logical attempt number (as in |
try_number |
int
|
The physical try number, which also counts rate-limit
retries (as in |
strategy |
Any
|
The strategy that made the call. It is the live object; don't mutate it from a cost function. |
usage |
Mapping[str, int]
|
A read-only copy of the attempt's token usage ( |
success |
bool
|
Whether the provider call succeeded. |
BatchBudgetExceeded
async_batch_llm.BatchBudgetExceeded
Bases: BatchAbortedError
The run's token or cost budget stopped an accepted item before completion.
Source code in src/async_batch_llm/strategies/errors.py
StartupRampConfig
async_batch_llm.StartupRampConfig
dataclass
StartupRampConfig(initial_concurrency: int = 1, concurrency_step: int = 1, ramp_interval_seconds: float = 1.0, max_concurrency: int | None = None, jitter_seconds: float = 0.0)
Optional concurrency ramp applied when an execution host starts.
BatchResult
async_batch_llm.BatchResult
dataclass
BatchResult(results: list[WorkItemResult[TOutput, TContext]], termination: BatchTermination = BatchTermination(), wall_time_seconds: float | None = None)
Bases: Generic[TOutput, TContext]
Result of processing a batch of work items.
Attributes:
| Name | Type | Description |
|---|---|---|
results |
list[WorkItemResult[TOutput, TContext]]
|
Individual work item results, in completion order — the
order items finished, which (with parallel workers, retries, and
rate-limit cooldowns) is generally NOT the order they were added.
Use :meth: |
total_items |
int
|
Total number of items in the batch |
succeeded |
int
|
Number of successful items |
failed |
int
|
Number of failed items |
total_input_tokens |
int
|
Sum of input tokens across all items |
total_output_tokens |
int
|
Sum of output tokens across all items |
total_cached_tokens |
int
|
Sum of cached input tokens across all items (v0.2.0) |
cache_hit_rate
property
Percentage (0.0–100.0) of input tokens served from cache.
A property since v0.20.0 (issue #93), consistent with the sibling
zero-arg scalars (total_cached_tokens, succeeded, ...).
The v0.18 method spelling batch.cache_hit_rate() still works
via a transitional callable-float return value and emits a
DeprecationWarning; drop the parentheses.
failures
property
The failed results only, in completion order.
successes
property
The successful results only, in completion order.
__post_init__
Calculate summary statistics from results.
Source code in src/async_batch_llm/base.py
by_id
Map item_id -> result for direct lookup.
Results are ordered by completion, so use this when you need to align
outputs back to specific inputs. If two results somehow share an
item_id, the later-completed one wins.
Source code in src/async_batch_llm/base.py
effective_input_tokens
Estimate billable input tokens after the cache discount.
cached_token_rate is the fraction of the normal input-token price
you pay for tokens served from cache. For example, Gemini charges
10% of the normal price (rate = 0.10), so 1000 cached tokens cost
the same as 100 uncached tokens.
Use the named constants on :class:CachedTokenRates to avoid
hardcoding magic numbers:
.. code-block:: python
result.effective_input_tokens(CachedTokenRates.OPENAI)
result.effective_input_tokens(CachedTokenRates.GEMINI)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cached_token_rate
|
float | None
|
Fraction (0.0–1.0) of the normal input price
paid for cached tokens. Omitting it is deprecated and it will
be required in 1.0. When omitted it defaults to
|
None
|
Returns:
| Type | Description |
|---|---|
int
|
Effective input tokens billed. The discount is computed by |
int
|
truncating |
int
|
|
int
|
rounded up when the discount would have a fractional |
int
|
part — a deliberately conservative choice for cost reporting |
int
|
(your real bill is at most this number, never more). |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/async_batch_llm/base.py
estimated_cost
estimated_cost(input_per_mtok: float, output_per_mtok: float, cached_token_rate: float | None = None) -> float
Estimate total spend from per-million-token prices.
Applies the cache discount to input tokens via
:meth:effective_input_tokens, so cached tokens are billed at their
reduced rate.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input_per_mtok
|
float
|
Price per 1,000,000 input tokens (in your currency). |
required |
output_per_mtok
|
float
|
Price per 1,000,000 output tokens. |
required |
cached_token_rate
|
float | None
|
Fraction of the normal input price paid for
cached tokens (see :class: |
None
|
Returns:
| Type | Description |
|---|---|
float
|
Estimated total cost: ``effective_input / 1e6 * input_per_mtok + |
float
|
output / 1e6 * output_per_mtok``. |
Source code in src/async_batch_llm/base.py
from_dict
classmethod
from_dict(data: Mapping[str, Any], *, output_decoder: ValueDecoder | None = None, context_decoder: ValueDecoder | None = None) -> BatchResult[Any, Any]
Restore a batch from :meth:to_dict output.
Source code in src/async_batch_llm/base.py
from_json
classmethod
from_json(value: str | bytes, *, output_decoder: ValueDecoder | None = None, context_decoder: ValueDecoder | None = None) -> BatchResult[Any, Any]
Restore a batch from a JSON string or UTF-8 bytes.
Source code in src/async_batch_llm/base.py
from_jsonl
classmethod
from_jsonl(path: str | Path, *, output_decoder: ValueDecoder | None = None, context_decoder: ValueDecoder | None = None) -> BatchResult[Any, Any]
Restore a batch from :meth:to_jsonl output.
Source code in src/async_batch_llm/base.py
in_input_order
Return a new batch whose results are sorted by submission order.
The current batch and its result list are not mutated. Ordering is
never inferred from item_id because IDs may be duplicated.
Source code in src/async_batch_llm/base.py
outputs
Iterate over the outputs of successful results, in result order.
The happy-path accessor — no if item.success loop needed:
.. code-block:: python
for output in batch.outputs():
print(output)
for item_id, output in batch.outputs(with_ids=True):
print(item_id, output)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
with_ids
|
bool
|
When True, yield |
False
|
Added in v0.20.0. Failed results are skipped; iterate .failures
(or check batch.failed) to handle them.
Source code in src/async_batch_llm/base.py
summary
Return a printable plain-text report of the whole run.
print(batch.summary()) is a complete post-run report: item
counts, termination, retry totals, token totals (with replayed work
accounted separately, consistent with v0.18 replay accounting),
admission/execution percentiles, wall time, and failures grouped by
error category. Works identically on collected batches and on
batches restored via from_dict/from_json/from_jsonl.
Added in v0.20.0.
Source code in src/async_batch_llm/base.py
750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 | |
to_dict
Return a versioned, JSON-safe representation of the batch.
Source code in src/async_batch_llm/base.py
to_json
Serialize this batch to a JSON string.
Source code in src/async_batch_llm/base.py
to_jsonl
Write one versioned result record per UTF-8 JSONL line.
Source code in src/async_batch_llm/base.py
BatchTermination
async_batch_llm.BatchTermination
dataclass
BatchTermination(kind: Literal['completed', 'batch_timeout', 'fail_fast', 'budget_exceeded', 'artifact_error'] = 'completed', reason: str | None = None, error_category: str | None = None, triggering_item_id: str | None = None)
Serializable reason a batch stopped accepting or executing work.
Grounding
async_batch_llm.Grounding
dataclass
Grounding(sources: list[GroundingSource] = list(), queries: list[str] = list(), supports: list[dict[str, Any]] = list())
Web-grounding data from a grounded call (e.g. Gemini google_search).
Attributes:
| Name | Type | Description |
|---|---|---|
sources |
list[GroundingSource]
|
The web sources the answer was grounded in. |
queries |
list[str]
|
Search queries the model issued ( |
supports |
list[dict[str, Any]]
|
Answer-span → source-index links, as plain dicts
( |
from_metadata
classmethod
Parse a metadata['grounding'] dict; lenient, never raises.
Source code in src/async_batch_llm/provider_output.py
GroundingSource
async_batch_llm.GroundingSource
dataclass
One web source backing a grounded response.
Attributes:
| Name | Type | Description |
|---|---|---|
uri |
str
|
Source URL (always present; entries without one are dropped). |
title |
str | None
|
Human-readable page title, when the provider supplied one. |
snippet |
str | None
|
Excerpt from the source, when the provider supplied one. |
from_metadata
classmethod
Parse one metadata['grounding']['sources'] entry; lenient, never raises.
Source code in src/async_batch_llm/provider_output.py
ToolCall
async_batch_llm.ToolCall
dataclass
One tool/function call the model requested. Visibility only — the framework never executes tools; feed these to your own dispatch loop.
Attributes:
| Name | Type | Description |
|---|---|---|
id |
str | None
|
Provider call id, when supplied. |
name |
str
|
Tool/function name (always present; entries without one are dropped). |
arguments |
str
|
The raw JSON-string arguments, deliberately unparsed —
parse with |
from_metadata
classmethod
Parse one metadata['tool_calls'] entry; lenient, never raises.
Source code in src/async_batch_llm/provider_output.py
ErrorCategory
async_batch_llm.ErrorCategory
Bases: _StrValueEnum
Values of WorkItemResult.error_category produced by the library.
API_ERROR
class-attribute
instance-attribute
A provider error with no recognized status. Retried conservatively.
API_TIMEOUT
class-attribute
instance-attribute
The SDK or transport timed out waiting for the provider. Retried.
ARTIFACT_PREPARATION_ERROR
class-attribute
instance-attribute
The item's input could not be prepared for the artifact store.
ARTIFACT_SERIALIZATION_ERROR
class-attribute
instance-attribute
The item's result could not be serialized for the artifact store.
AUTHENTICATION
class-attribute
instance-attribute
The provider rejected the credentials (HTTP 401). Not retried.
BATCH_ABORTED
class-attribute
instance-attribute
The run stopped (fail-fast or an explicit abort) before this item finished.
BATCH_BUDGET_EXCEEDED
class-attribute
instance-attribute
The run reached its token or cost budget before this item finished.
BATCH_DEADLINE_EXCEEDED
class-attribute
instance-attribute
The run's batch_timeout expired before this item finished.
CLASSIFIER_ERROR
class-attribute
instance-attribute
The error classifier itself raised. Not retried.
CLIENT_ERROR
class-attribute
instance-attribute
Another non-retryable 4xx rejection, such as a bad request or unknown model.
CONNECTION_ERROR
class-attribute
instance-attribute
A connection failure seen by the default classifier. Retried.
EMPTY_RESPONSE
class-attribute
instance-attribute
The provider returned a billed response with no usable text. Not retried.
FRAMEWORK_TIMEOUT
class-attribute
instance-attribute
One attempt exceeded attempt_timeout. Retried.
FRAMEWORK_TOTAL_ITEM_TIMEOUT
class-attribute
instance-attribute
The item's end-to-end deadline (total_timeout_per_item) expired.
INSUFFICIENT_BALANCE
class-attribute
instance-attribute
Billing or credit exhaustion (for example OpenAI insufficient_quota). Not retried.
LOGIC_ERROR
class-attribute
instance-attribute
A programming error in application or strategy code (ValueError,
TypeError, KeyError and similar). Not retried.
MIDDLEWARE_CONTRACT_ERROR
class-attribute
instance-attribute
Middleware returned a value that breaks its contract.
MIDDLEWARE_FILTERED
class-attribute
instance-attribute
Middleware filtered the item out, so it was not executed. Never replayed.
NETWORK_ERROR
class-attribute
instance-attribute
A connection failure reported by the OpenAI-compatible SDK. Retried.
PERMISSION_DENIED
class-attribute
instance-attribute
The credentials lack access to the model or resource (HTTP 403). Not retried.
QUOTA_EXHAUSTED
class-attribute
instance-attribute
A Gemini 429 whose quota violations are all daily limits, so waiting minutes won't help. Not retried.
QUOTA_SCOPE_ERROR
class-attribute
instance-attribute
A strategy's quota_scope could not be resolved.
RATE_LIMIT
class-attribute
instance-attribute
The provider rate-limited the request (HTTP 429 or equivalent). Retried after a coordinated cooldown.
RATE_LIMIT_RETRIES_EXCEEDED
class-attribute
instance-attribute
The item hit more rate limits than RetryConfig.max_rate_limit_retries
allows. Not retried.
SERVER_ERROR
class-attribute
instance-attribute
A retryable 5xx response from the provider.
SERVER_OVERLOAD
class-attribute
instance-attribute
The provider reported it is overloaded (Gemini 503). Retried.
SERVER_TIMEOUT
class-attribute
instance-attribute
The provider timed out upstream (Gemini 504). Retried.
STRUCTURED_OUTPUT_SCHEMA_REJECTED
class-attribute
instance-attribute
The provider rejected the requested schema itself. Not retried.
STRUCTURED_OUTPUT_VALIDATION_ERROR
class-attribute
instance-attribute
Structured output did not match the requested schema. Retried.
TIMEOUT
class-attribute
instance-attribute
A timeout reported by the Gemini SDK. Retried.
TOKEN_ESTIMATE_EXCEEDS_LIMIT
class-attribute
instance-attribute
One attempt's estimate can never fit in the TPM limit.
TOKEN_ESTIMATION_ERROR
class-attribute
instance-attribute
The token estimator raised or returned an invalid estimate.
TOKEN_ESTIMATOR_REQUIRED
class-attribute
instance-attribute
TPM admission is enabled but no token estimator applies to the item.
UNKNOWN
class-attribute
instance-attribute
An exception no classifier recognized. Retried, in case it is transient.
UPSTREAM_ERROR
class-attribute
instance-attribute
OpenRouter could not reach or use an upstream provider. Retried.
USAGE_LIMIT_EXCEEDED
class-attribute
instance-attribute
PydanticAI stopped the run at a configured usage limit. Not retried.
TimeoutCategory
async_batch_llm.TimeoutCategory
Bases: _StrValueEnum
Values of AttemptTiming.timeout_category and WorkItemTiming.timeout_category.
ADMISSION_TIMEOUT
class-attribute
instance-attribute
An item or batch deadline expired while the item waited for provider capacity.
FRAMEWORK_EXECUTION_TIMEOUT
class-attribute
instance-attribute
One provider attempt exceeded attempt_timeout.
FRAMEWORK_TOTAL_ITEM_TIMEOUT
class-attribute
instance-attribute
The item deadline expired during a provider call.
AsyncBatchLLMError
async_batch_llm.AsyncBatchLLMError
Bases: Exception
Base class for the exception types async-batch-llm defines.
Catch it to handle the library's own errors in one place. They keep their
other bases too (for example :class:ItemDeadlineExceeded is still a
:class:TimeoutError and :class:ArtifactError a :class:RuntimeError), so
existing except clauses still match. Invalid arguments are still reported
with the built-in ValueError/TypeError, and provider SDK exceptions and
errors raised by your own strategies are not wrapped.
Every exception type the library exports subclasses it, alongside its original
built-in base, so except AsyncBatchLLMError catches the library's own errors
and existing except TimeoutError (or RuntimeError, ValueError) clauses
keep working.
Lifecycle exceptions
async_batch_llm.StreamFinalizationError
Bases: RuntimeError, AsyncBatchLLMError
The result stream could not finish normally.
Raised by :meth:ParallelBatchProcessor.results when finalization was cancelled
(for example by shutdown() before finish() completed) or when a
process-control exception interrupted it. Deliberately distinct from
:class:asyncio.CancelledError: the consumer itself was not cancelled.
The originating exception, when there is one, is __cause__.
Source code in src/async_batch_llm/base.py
async_batch_llm.CleanupInterruptedError
Bases: RuntimeError, AsyncBatchLLMError
A cleanup step was interrupted without the closing caller being cancelled.
Raised when a step's private task was cancelled by a third party, or when
a user cleanup() raised :class:asyncio.CancelledError itself. The
step is not checkpointed; a later explicit close retries it. This is an
ordinary :class:Exception so it never marks the caller's task cancelled.
Source code in src/async_batch_llm/_internal/cleanup.py
Admission closure
async_batch_llm.BatchAdmissionClosedError
Bases: RuntimeError, AsyncBatchLLMError
The processor no longer accepts work; create a new processor to submit more.
add_work() raises BatchAdmissionClosedError after finish, shutdown, or abort.
Rejected submissions receive no submission index. Use a new processor for further work.
Cleanup and shutdown
async def cleanup() -> None / async def shutdown() -> None
Release every owned resource in dependency order. cleanup(), shutdown(),
and async with exit all run the same ordered close:
- Runtime tasks and callbacks: the stream finalizer, workers, queued work, progress callbacks, background post-processors, and callback threads.
- Admission resources (quota scopes and the rate-limit coordinator).
- Prepared strategies (
strategy.cleanup()). - The artifact store, including after an early stream exit or a failed run.
Behavior (see docs/cleanup-lifecycle-contract.md for the full contract):
- Every step is attempted. A failing strategy
cleanup()does not stop sibling strategies or the artifact store from closing. Every failure is logged with a traceback; with no body exception, the first failure is raised after all steps have run. Insideasync with, a body exception stays primary and cleanup failures are only logged. - Repeat calls are safe. A step that succeeded is never repeated. A step
that failed or was interrupted is retried by the next explicit close.
Concurrent calls share one in-flight attempt. Because a retry re-invokes a
strategy's whole
cleanup(), usercleanup()implementations must be idempotent and safe after a partially completed earlier attempt. - No deadline. Cleanup waits for the artifact store and the call-pool drain to finish. The two-second worker and progress thresholds only log a warning and keep waiting; any step still running after 30 seconds logs one warning. A dependent resource is never closed while the resource it depends on is still running, including synchronous callback threads.
- Cancellation. Cancelling the task that is closing once defers that cancellation until cleanup finishes, then re-raises it (cleanup errors are logged, not raised). Cancelling it a second time force-aborts: the running step is abandoned, later steps are skipped and logged, and the cancellation propagates immediately. This is the operator's escape from a cleanup that never returns and deliberately sacrifices durability.
- Interruptions. A strategy
cleanup()that raisesCancelledErroritself, or whose private task is cancelled by something else, surfaces asCleanupInterruptedError(an ordinaryException); the caller's task is not cancelled and the next close retries the step. KeyboardInterruptandSystemExitraised by a cleanup step keep their type and take precedence over a deferred cancellation.- Once a close has started, preparing a new strategy raises
RuntimeError.
Provider metadata
Response metadata (WorkItemResult.metadata)
Provider metadata (Gemini safety ratings and finish reason, OpenRouter
provider/routed model, etc.) flows into WorkItemResult.metadata — a plain
dict[str, Any] | None. The parsed output stays in WorkItemResult.output;
you no longer wrap it in a separate response object.
Conservative structured-output recovery uses three reserved metadata keys:
structured_output_recovered, structured_output_recovery_reason, and
structured_output_retries_avoided. Read them through the corresponding typed
properties on LLMResponse or WorkItemResult.
Removed: the old
GeminiResponsewrapper and theinclude_metadataopt-in were removed in v0.6.0. Read metadata offresult.metadatainstead. For Gemini safety ratings specifically,result.metadata["safety_ratings"]carries them (the deprecatedresult.gemini_safety_ratingsfield is still backfilled for compatibility, and is removed in 1.0).
Usage:
result = await processor.process_all()
first = result.results[0]
ratings = (first.metadata or {}).get("safety_ratings")
if ratings and ratings.get("HARM_CATEGORY_HATE_SPEECH") == "HIGH":
log_flagged_content(first.output)
Typed auxiliary output (grounding, reasoning, tool calls, logprobs)
Provisional. The shapes were adjusted in v0.27 for OpenAI's Responses API. The views and the reserved keys' shapes may be promoted to stable at 1.0.0rc1 if real-provider runs fill them in correctly; until then they can change in a minor release, and
logprobsis likely to stay provisional after 1.0. Themetadatadict channel itself is stable. If you persist these shapes, read them back defensively. See API stability.
Provider-specific structured output travels through metadata under four
reserved keys with documented plain-dict shapes (JSON-serializable, so
persisting metadata as-is works):
| Key | Shape | Emitted by |
|---|---|---|
grounding |
{"sources": [{"uri", "title"}], "queries": [str], "supports": [dict]} |
Gemini models, when the response carries google_search grounding |
reasoning |
str: the model's reasoning/thinking trace |
Chat Completions: reasoning_content (e.g. DeepSeek), falling back to reasoning (e.g. OpenRouter). Responses API: reasoning-item text, else the joined reasoning summaries (OpenAI, when requested) |
tool_calls |
[{"id": str\|None, "name": str, "arguments": str}], with arguments as the raw JSON string |
OpenAI-compatible models: Chat tool_calls, or Responses function_call items (id is the call_id) |
logprobs |
provider-shaped dict/list (plain JSON) |
OpenAI-compatible models, when requested: the Chat logprobs object, or a list of Responses per-token entries |
Both LLMResponse and WorkItemResult expose lazy read-only typed
views over these keys — parsed from metadata on each access, never
cached, never stored twice:
result = await processor.process_all()
item = result.results[0]
if item.grounding: # Grounding | None
for source in item.grounding.sources: # list[GroundingSource]
print(source.uri, source.title)
print(item.grounding.queries) # list[str]
print(item.reasoning) # str | None
for call in item.tool_calls or []: # list[ToolCall] | None
print(call.name, call.arguments) # arguments: raw JSON string
print(item.logprobs) # Any | None (provider-shaped)
The view classes (Grounding, GroundingSource, ToolCall) are exported
at the top level. Parsing is lenient: malformed metadata yields None (or
drops the bad entry) rather than raising — which also means a future shape
change degrades to None views rather than errors.
Boundaries:
tool_callsis visibility only — the framework never executes tools. Feed them to your own dispatch loop (or use an agent framework viaPydanticAIStrategy). Extracted for OpenAI-compatible models only; Gemini function-call parts are not extracted.- On Chat Completions, a response whose
contentisnull(for example a pure tool-call turn) raisesEmptyResponseErrorbefore any result exists, sotool_callssurfaces only when the model returned text alongside the calls. OnOpenAIModel's Responses surface, a function-call-only reply succeeds with empty text andtool_callspopulated. - Auxiliary output does not survive empty/safety-blocked responses — the call fails first.
TokenUsage
async_batch_llm.TokenUsage
Bases: TypedDict
Token usage statistics from LLM API calls.
All fields are optional to accommodate different provider APIs. Different providers may return different subsets of these fields.
Fields
input_tokens: Number of tokens in the input/prompt output_tokens: Number of tokens in the output/completion total_tokens: Provider-reported total, or input + output when omitted. A valid explicit total is authoritative even when the components differ. cached_input_tokens: Number of input tokens served from cache (Gemini)
CachedTokenRates
async_batch_llm.CachedTokenRates
Named cached-token price rates for the providers we support.
Each constant is the fraction of the normal input-token price you
pay for tokens served from cache (so 0.10 = "10% of normal" =
"90% discount"). Pass these to
:meth:BatchResult.effective_input_tokens to compute provider-aware
billable token estimates:
.. code-block:: python
result.effective_input_tokens(CachedTokenRates.OPENAI)
Rates are accurate as of v0.9.0 (early 2026); confirm with each provider's pricing page before using these for invoicing.
Notes
- Anthropic prompt caching is asymmetric: cache reads are at
ANTHROPIC_READ(10% of normal), but cache writes are billed at a 25% premium over normal input price. This helper covers read-side savings only; the write premium is a separate cost best computed at billing time from your usage logs. - OpenRouter routes to many upstream providers, each with its
own rate. Pick the constant for the upstream that actually
served your request (visible in
LLMResponse.metadata['provider']).
ANTHROPIC_READ
class-attribute
instance-attribute
Anthropic prompt cache reads: 10% of normal (cache writes are billed at 1.25× normal — not modeled here).
DEEPSEEK
class-attribute
instance-attribute
DeepSeek context cache: cached tokens cost ~2% of normal (V4 Flash bills cache hits at $0.0028/M vs $0.14/M cache-miss input, a price drop that took effect April 2026; earlier the discount was ~10%).
GEMINI
class-attribute
instance-attribute
Gemini context cache: cached tokens cost 10% of normal.
Type aliases
SimpleBatchProcessor[T], SimpleWorkItem[T] and SimpleResult[T] shorten the
generic types for string prompts with no context. PostProcessorFunc and
ProgressCallbackFunc are the callable types accepted by post_processor and
progress_callback.
async_batch_llm.SimpleBatchProcessor
module-attribute
Type alias for ParallelBatchProcessor[str, T, None].
Use when you have string prompts, a typed output, and no context.
Example
async with SimpleBatchProcessorMyOutput as processor: ...
async_batch_llm.SimpleWorkItem
module-attribute
Type alias for LLMWorkItem[str, T, None].
Use when creating work items with string prompts, typed output, and no context.
Example
item = SimpleWorkItemMyOutput
async_batch_llm.SimpleResult
module-attribute
Type alias for WorkItemResult[T, None].
Use when working with results that have no context.
Example
result: SimpleResult[MyOutput] = results[0]
async_batch_llm.PostProcessorFunc
module-attribute
Type of post_processor: called with each WorkItemResult; sync or async.
async_batch_llm.ProgressCallbackFunc
module-attribute
Type of progress_callback: called as (completed, total, current_item_id);
sync or async.
Deprecated names
BatchProcessor, ProcessingStats, LLMGateway and grounding_metadata_extractor
still import from async_batch_llm but warn and leave the public API in 1.0. They
aren't documented here; see the v0.27 migration guide
for replacements.