Core API Reference
ParallelBatchProcessor
llm_minder.ParallelBatchProcessor
ParallelBatchProcessor(*, post_processor: PostProcessorFunc[TOutput, TContext] | 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 |
|---|---|---|---|
post_processor
|
PostProcessorFunc[TOutput, TContext] | None
|
Optional async function called after each successful item |
None
|
config
|
ProcessorConfig | None
|
Processor configuration; |
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/llm_minder/parallel.py
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 | |
add_work
async
Queue a work item and register its identity-scoped admission state.
Source code in src/llm_minder/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/llm_minder/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/llm_minder/parallel.py
start
wait_for_abort
async
Wait until a configured batch deadline or fail-fast abort trips.
LLMWorkItem
llm_minder.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
llm_minder.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, 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. |
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.
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/llm_minder/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:~llm_minder.ResultSerializationError.
Source code in src/llm_minder/base.py
AttemptTiming
llm_minder.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
llm_minder.WorkItemTiming
dataclass
ProcessorConfig
llm_minder.ProcessorConfig
dataclass
ProcessorConfig(*, max_workers: int | 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, 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 defaults and derived knobs, then validate.
Source code in src/llm_minder/core/config.py
validate
Validate complete configuration.
Source code in src/llm_minder/core/config.py
432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 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 | |
RetryConfig
llm_minder.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/llm_minder/core/config.py
RateLimitConfig
llm_minder.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/llm_minder/core/config.py
validate
Validate rate limit configuration.
Source code in src/llm_minder/core/config.py
GuardrailConfig
llm_minder.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, min_cache_hit_rate: float | None = None, cache_hit_window: int = 50)
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:~llm_minder.AttemptUsage. A cost_function
without max_total_cost only tracks cost. Budgets apply to processor
runs; call(), call_result() and LLMCallPool reject them.
min_cache_hit_rate (provisional) stops the run when a cache group's hit
rate falls below it. Each provider attempt that returns a result is counted
under the cache_group in its metadata; the rate is cached over input
tokens across the group's last cache_hit_window attempts that reported
positive input, and a group isn't checked until that window is full. A trip
ends the run as fail_fast with the termination category
cache_hit_rate_below_floor; unstarted items finish batch_aborted.
Processor runs only, like budgets. LLMCallPool uses cache_hit_window
for the rolling rate in cache_stats(), without a floor.
AbortMode
llm_minder.AbortMode
Bases: str, Enum
How a controlled batch abort treats provider calls already running.
AttemptUsage
llm_minder.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. |
CacheGroupTracker
llm_minder.CacheGroupTracker
Prompt-cache reads and writes per cache group, with a sliding window.
Provisional: new in 0.30. The same accounting backs a processor's
get_stats(), LLMCallPool.cache_stats() and the
min_cache_hit_rate floor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
window
|
int
|
How many of a group's most recent measured calls
|
DEFAULT_WINDOW
|
Groups are labels from a call's cache_group metadata. A non-string or
empty label counts as ungrouped (None), and a label longer than 128
characters is replaced by "sha256:" plus its hash. At most 64 groups,
the ungrouped one included, are tracked; calls for further groups are
counted together in cache_groups_overflow, with one warning.
Not thread-safe: use one tracker from one event loop or thread.
Source code in src/llm_minder/cache_groups.py
below_floor
Return every breach of floor, the check min_cache_hit_rate applies.
A group breaches when its window is full and its window hit rate is below
floor; groups are listed lowest rate first. Overflow is a breach too,
listed last, since the floor can't be checked for untracked groups. An
empty list means the floor holds.
Source code in src/llm_minder/cache_groups.py
from_dict
classmethod
Restore a tracker saved with :meth:to_dict. Raises ValueError on bad data.
Source code in src/llm_minder/cache_groups.py
record
Count one call that returned a result.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
group
|
object
|
The call's |
required |
usage
|
Mapping[str, Any] | None
|
Its token usage: |
required |
miss_reason
|
object
|
A provider's explanation of a cache miss, kept per group
as |
None
|
Source code in src/llm_minder/cache_groups.py
record_result
Count a finished result, for rebuilding statistics from saved rows.
Uses the result's cache_group and cache_miss_reason metadata and
its token_usage. Failed results are skipped. A result's usage adds up
every attempt of its item, billed failed attempts included, whereas live
tracking counts only the attempt that returned; a tracker rebuilt this way
can read lower than the live one did when retries were billed. Save and
restore with :meth:to_dict/:meth:from_dict for an exact resume.
Source code in src/llm_minder/cache_groups.py
reset
Forget every group and the overflow counters.
Source code in src/llm_minder/cache_groups.py
stats
Return {"cache_groups": [...], "cache_groups_overflow": ...}.
Groups are listed by input tokens, most first. Each has group,
measured_calls, unmeasured_calls, input_tokens,
cached_input_tokens, cache_write_input_tokens, hit_rate
(cumulative), window_hit_rate and last_miss_reason. The overflow
entry has the same counters without group and window_hit_rate,
and is None unless there was overflow.
Source code in src/llm_minder/cache_groups.py
to_dict
Return the tracker's full state as JSON-serializable data.
Includes every group's counters and its window of recent calls, so
:meth:from_dict restores a tracker that behaves exactly as this one.
Source code in src/llm_minder/cache_groups.py
CacheFloorBreach
llm_minder.CacheFloorBreach
dataclass
CacheFloorBreach(group: str | None, window_hit_rate: float | None, floor: float, window: int, last_miss_reason: str | None = None, overflow: bool = False)
One reason :meth:CacheGroupTracker.below_floor reports a floor breach.
Either a group whose full window falls below the floor (overflow is
false; group is None for ungrouped calls), or, with overflow
true, the tracker having seen more than 64 groups, beyond which it can't
check the floor.
BatchBudgetExceeded
llm_minder.BatchBudgetExceeded
Bases: BatchAbortedError
The run's token or cost budget stopped an accepted item before completion.
Source code in src/llm_minder/strategies/errors.py
StartupRampConfig
llm_minder.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
llm_minder.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) |
total_cache_write_tokens |
int
|
Sum of input tokens written to a prompt cache across all items |
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, ...).
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/llm_minder/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/llm_minder/base.py
effective_input_tokens
Estimate billable input tokens after the cache discount and write premium.
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
|
Fraction (0.0–1.0) of the normal input price paid for cached tokens. Required since 1.0: rates differ widely between providers (Gemini 0.10, OpenAI ~0.50). |
required |
cache_write_rate
|
float | None
|
Multiple of the normal input price paid for
tokens written to a prompt cache ( |
None
|
Returns:
| Type | Description |
|---|---|
int
|
Effective input tokens billed. The discount is computed by |
int
|
truncating |
int
|
|
int
|
|
int
|
|
int
|
up when either has a fractional part — a deliberately |
int
|
conservative choice for cost reporting. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/llm_minder/base.py
estimated_cost
estimated_cost(input_per_mtok: float, output_per_mtok: float, cached_token_rate: float, *, cache_write_rate: float | None = None) -> float
Estimate total spend from per-million-token prices.
Applies the cache discount (and, with cache_write_rate, the cache
write premium) 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
|
Fraction of the normal input price paid for
cached tokens (see :class: |
required |
cache_write_rate
|
float | None
|
Multiple of the normal input price paid for
tokens written to a prompt cache (see
:attr: |
None
|
Returns:
| Type | Description |
|---|---|
float
|
Estimated total cost: ``effective_input / 1e6 * input_per_mtok + |
float
|
output / 1e6 * output_per_mtok``. |
Source code in src/llm_minder/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/llm_minder/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/llm_minder/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/llm_minder/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/llm_minder/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/llm_minder/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/llm_minder/base.py
716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 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 | |
to_dict
Return a versioned, JSON-safe representation of the batch.
to_json
Serialize this batch to a JSON string.
Source code in src/llm_minder/base.py
to_jsonl
Write one versioned result record per UTF-8 JSONL line.
Source code in src/llm_minder/base.py
BatchTermination
llm_minder.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
llm_minder.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/llm_minder/provider_output.py
GroundingSource
llm_minder.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/llm_minder/provider_output.py
ToolCall
llm_minder.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/llm_minder/provider_output.py
ErrorCategory
llm_minder.ErrorCategory
Bases: _StrValueEnum
Values of WorkItemResult.error_category and BatchTermination.error_category
produced by the library.
Every member except CACHE_HIT_RATE_BELOW_FLOOR can be an item's category. That
one only names why a run stopped; the items it stops finish batch_aborted.
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.
CACHE_HIT_RATE_BELOW_FLOOR
class-attribute
instance-attribute
Termination only (provisional): a cache group's hit rate fell below
GuardrailConfig.min_cache_hit_rate. Items the stop ends are batch_aborted.
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.
RESPONSE_TRUNCATED
class-attribute
instance-attribute
The response hit its output-token bound before producing usable text
(TruncatedResponseError). Not retried by default.
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
llm_minder.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.
LLMMinderError
llm_minder.LLMMinderError
Bases: Exception
Base class for the exception types llm-minder 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 LLMMinderError catches the library's own errors
and existing except TimeoutError (or RuntimeError, ValueError) clauses
keep working.
Lifecycle exceptions
llm_minder.StreamFinalizationError
Bases: RuntimeError, LLMMinderError
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/llm_minder/base.py
llm_minder.CleanupInterruptedError
Bases: RuntimeError, LLMMinderError
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/llm_minder/_internal/cleanup.py
Admission closure
llm_minder.BatchAdmissionClosedError
Bases: RuntimeError, LLMMinderError
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 (theresult.gemini_safety_ratingsfield was 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)
Two more reserved keys feed the per-group cache statistics (provisional, new in
0.30): cache_group, a string naming the prompt-cache group an attempt belongs to,
and cache_miss_reason, a provider's explanation of a cache miss. Built-in models
set cache_group; see Cache hit-rate floor.
Typed auxiliary output (grounding, reasoning, tool calls, logprobs)
Stable from 1.0, except
logprobs. Thegrounding,reasoningandtool_callsviews and reserved keys are covered by the stability promise.logprobscarries each provider's own structure and stays provisional, so read it defensively.
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
llm_minder.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. Part of
input_tokens.
cache_write_input_tokens: Number of input tokens written to a prompt
cache (OpenAI cache_write_tokens, Anthropic
cache_creation_input_tokens). Part of input_tokens; some
providers bill them above the normal input price.
CachedTokenRates
llm_minder.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.
The *_WRITE* constants are the other direction: the multiple of the
normal input price paid for tokens written to a cache. Pass them as
cache_write_rate to :meth:BatchResult.effective_input_tokens or
:meth:BatchResult.estimated_cost:
.. code-block:: python
result.estimated_cost(
1.25, 10.0,
cached_token_rate=CachedTokenRates.OPENAI,
cache_write_rate=CachedTokenRates.OPENAI_WRITE,
)
Notes
- Cache writes are reported in
cache_write_input_tokens. They are part ofinput_tokens, so without acache_write_ratethey are priced as normal input. - 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.
ANTHROPIC_WRITE
class-attribute
instance-attribute
Anthropic prompt cache writes with the default 5-minute TTL: 1.25× normal.
ANTHROPIC_WRITE_1H
class-attribute
instance-attribute
Anthropic prompt cache writes with the 1-hour TTL: 2× normal.
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.
OPENAI
class-attribute
instance-attribute
OpenAI prompt caching: cached tokens cost 50% of normal for gpt-4o-class models. Other model families may differ; check OpenAI's pricing page.
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.
llm_minder.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: ...
llm_minder.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
llm_minder.SimpleResult
module-attribute
Type alias for WorkItemResult[T, None].
Use when working with results that have no context.
Example
result: SimpleResult[MyOutput] = results[0]
llm_minder.PostProcessorFunc
module-attribute
Type of post_processor: called with each WorkItemResult; sync or async.
llm_minder.ProgressCallbackFunc
module-attribute
Type of progress_callback: called as (completed, total, current_item_id);
sync or async.
Removed names
BatchProcessor, ProcessingStats, LLMGateway and grounding_metadata_extractor
were removed in 1.0; see Removed in 1.0 for
replacements.