Strategies API Reference
CallableStrategy
Adapter for an existing asynchronous SDK, gateway client, agent, or application service. See Use Your Existing Async Client for callback and retry-state semantics.
async_batch_llm.CallableStrategy
CallableStrategy(invoke: InvokeCallback[TOutput], *, identity: ArtifactIdentity | None = None, error_classifier: ErrorClassifier | None = None, prepare: LifecycleCallback | None = None, cleanup: LifecycleCallback | None = None, on_error: ErrorCallback | None = None, dry_run: DryRunCallback[TOutput] | None = None, max_concurrency: int | None = None, concurrency_scope: object | None = None, quota_scope: object | None = None, token_estimator: TokenEstimator | None = None, request_concurrency: RequestConcurrencyCallback | None = None)
Bases: LLMCallStrategy[TOutput]
Adapt an existing async client or application operation to ABL.
The invocation callback has this canonical shape::
async def invoke(
prompt: str,
*,
attempt: int,
timeout: float,
state: RetryState | None,
) -> CallOutcome[TOutput]: ...
attempt is ABL's logical attempt number, timeout is the effective
executor-owned attempt budget, and state belongs exclusively to the
current work item. This class adapts callbacks to LLMCallStrategy; the
existing ItemExecutor remains responsible for retries, timeouts,
cooldowns, accounting, and cancellation.
Source code in src/async_batch_llm/callable_strategy.py
artifact_identity
property
Stable artifact identity, or None when the caller omitted one.
CallOutcome
async_batch_llm.CallOutcome
dataclass
CallOutcome(output: TOutput, token_usage: Mapping[str, int] = dict(), metadata: Mapping[str, Any] | None = None)
Bases: Generic[TOutput]
The output, reported token usage, and metadata from an async operation.
Token usage and metadata are copied and validated when the outcome crosses the strategy boundary. An empty usage mapping means the upstream operation did not report usage; ABL does not estimate missing values.
LLMCallStrategy
async_batch_llm.LLMCallStrategy
Bases: ABC, Generic[TOutput]
Abstract base class for LLM call strategies.
A strategy encapsulates how LLM calls are made, including: - Resource initialization (caches, clients) - Call execution with retries - Resource cleanup
The framework calls: 1. prepare() once per unique strategy instance before its first execution 2. execute() for each attempt (including retries) 3. cleanup() once per prepared strategy when the processor exits or shuts down
concurrency_scope
property
Identity whose capacity is shared by concurrent calls.
max_concurrency
property
Maximum safe concurrent calls advertised by this strategy.
None means the capacity is unknown. Model-backed strategies forward
capacity metadata from their model; custom strategies can override this
property when they own a bounded client or transport.
quota_scope
property
Identity whose RPM, TPM, and coordinated cooldown are shared.
The default follows :attr:concurrency_scope for backward-compatible
ownership. Override it when one provider/account quota spans multiple
clients or when one shared client serves independent quota budgets.
Object identity, not equality, defines sharing.
cleanup
async
Clean up resources when the processor exits or shuts down.
Called once per prepared strategy instance, not once per work item.
Use this for: - Closing connections/sessions - Releasing locks - Logging final metrics - Deleting temporary files
Do NOT use this for: - Deleting caches intended for reuse across runs - Destructive cleanup that prevents resource reuse
Note on Caches (v0.2.0):
For reusable resources like Gemini caches with TTLs, consider letting
them expire naturally to enable cost savings across multiple pipeline
runs. See GeminiCachedModel for an example.
Default: no-op
Source code in src/async_batch_llm/llm_strategies.py
dry_run
async
Return mock output for dry-run mode (testing without API calls).
Override this method to provide realistic mock data for testing. Default implementation returns placeholder values that may not match your output type.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prompt
|
str
|
The prompt that would have been sent to the LLM |
required |
Returns:
| Type | Description |
|---|---|
tuple[TOutput, TokenUsage]
|
Tuple of (mock_output, mock_token_usage) |
Default behavior: - Returns string "[DRY-RUN] Mock output" as output - Returns mock token usage: 100 input, 50 output, 150 total
Source code in src/async_batch_llm/llm_strategies.py
estimate_tokens
estimate_tokens(prompt: str, attempt: int, state: RetryState | None) -> TokenEstimate | Awaitable[TokenEstimate] | None
Return a local token estimate for TPM admission, if supported.
The default supplies no estimate. Configure ProcessorConfig with a
run-level estimator or override this hook. No provider call should be
made solely to estimate tokens.
Source code in src/async_batch_llm/llm_strategies.py
execute
abstractmethod
async
execute(prompt: str, attempt: int, timeout: float, state: RetryState | None = None) -> tuple[TOutput, TokenUsage] | tuple[TOutput, TokenUsage, dict[str, Any] | None]
Execute an LLM call for the given attempt.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prompt
|
str
|
The prompt to send to the LLM |
required |
attempt
|
int
|
Which logical retry attempt this is (1, 2, 3, ...).
Guarantee: rate-limit errors do NOT advance this number. If a
call is throttled (429 / coordinated cooldown), the framework
retries the same |
required |
timeout
|
float
|
Maximum time to wait for response (seconds) |
required |
state
|
RetryState | None
|
Optional retry state that persists across attempts (v0.3.0) |
None
|
Returns:
| Type | Description |
|---|---|
tuple[TOutput, TokenUsage] | tuple[TOutput, TokenUsage, dict[str, Any] | None]
|
Either a 2-tuple |
tuple[TOutput, TokenUsage] | tuple[TOutput, TokenUsage, dict[str, Any] | None]
|
|
tuple[TOutput, TokenUsage] | tuple[TOutput, TokenUsage, dict[str, Any] | None]
|
TokenUsage dict with optional keys |
tuple[TOutput, TokenUsage] | tuple[TOutput, TokenUsage, dict[str, Any] | None]
|
|
tuple[TOutput, TokenUsage] | tuple[TOutput, TokenUsage, dict[str, Any] | None]
|
|
tuple[TOutput, TokenUsage] | tuple[TOutput, TokenUsage, dict[str, Any] | None]
|
|
tuple[TOutput, TokenUsage] | tuple[TOutput, TokenUsage, dict[str, Any] | None]
|
|
tuple[TOutput, TokenUsage] | tuple[TOutput, TokenUsage, dict[str, Any] | None]
|
pass |
tuple[TOutput, TokenUsage] | tuple[TOutput, TokenUsage, dict[str, Any] | None]
|
supported for backward compatibility but will be removed in a |
tuple[TOutput, TokenUsage] | tuple[TOutput, TokenUsage, dict[str, Any] | None]
|
future release; built-in strategies all return the 3-tuple shape. |
Raises:
| Type | Description |
|---|---|
Exception
|
Any exception propagated to trigger a retry (if retryable) or a permanent failure. |
Note (v0.3.0): The state parameter allows strategies to maintain state across retry attempts for multi-stage retry patterns. See RetryState documentation for examples.
Source code in src/async_batch_llm/llm_strategies.py
on_error
async
Handle errors that occur during execute().
Called by the framework when execute() raises an exception, before deciding whether to retry. This allows strategies to: - Inspect the error type to adjust retry behavior - Store error information for use in next attempt - Modify prompts based on validation errors - Track error patterns across attempts
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
exception
|
Exception
|
The exception that was raised during execute() |
required |
attempt
|
int
|
Which attempt number failed (1, 2, 3, ...) |
required |
state
|
RetryState | None
|
Optional retry state that persists across attempts (v0.3.0) |
None
|
Default: no-op
Example
async def on_error( self, exception: Exception, attempt: int, state: RetryState | None = None ) -> None: if state is not None: # Track validation errors separately from other errors if isinstance(exception, ValidationError): count = state.get('validation_failures', 0) + 1 state.set('validation_failures', count) # Save partial results for recovery if hasattr(exception, 'partial_data'): state.set('partial_data', exception.partial_data)
Source code in src/async_batch_llm/llm_strategies.py
prepare
async
Initialize resources before making any LLM calls.
Called once per unique strategy instance before the first work item using that instance executes. Use this to set up shared caches, clients, etc. Per-item retry state belongs in execute()/on_error() via RetryState.
Default: no-op
Source code in src/async_batch_llm/llm_strategies.py
recommended_error_classifier
Return the error classifier best suited to this strategy's provider.
The execution host calls this once per strategy identity when the caller
didn't pass error_classifier explicitly.
Returns None by default ("no preference"), which lets the framework
fall back to :class:DefaultErrorClassifier. Provider strategies
(GeminiStrategy, OpenAIStrategy, …) override this to return their
matching classifier. An explicit error_classifier= on the processor
always wins over this recommendation.
Source code in src/async_batch_llm/llm_strategies.py
ModelStrategy
Shared base for the provider-named strategies below; delegates to an
LLMModel. Use directly for a custom model you don't want a dedicated
subclass for.
async_batch_llm.ModelStrategy
ModelStrategy(model: LLMModel, response_parser: Callable[[LLMResponse], TOutput] | None = None, *, temperature: float | None = 0.0, generation_config: dict[str, Any] | None = None, quota_scope: object | None = None, token_estimator: TokenEstimator | None = None)
Bases: LLMCallStrategy[TOutput]
Base strategy for any provider exposed as an :class:LLMModel.
Holds the machinery shared by all model-backed strategies: the model
reference, an optional response parser, lifecycle delegation to
:class:ManagedLLMModel, and an execute() that calls
model.generate(), parses the response, and forwards
LLMResponse.metadata as the third tuple element.
The provider-named subclasses (:class:GeminiStrategy,
:class:OpenAIStrategy, :class:OpenRouterStrategy) are thin shells over
this base — they exist so users can pick the strategy named after the
provider they're using. Use this base directly for a custom
:class:LLMModel you don't want to name a dedicated subclass for.
Added in v0.10.0 (extracted from the formerly-duplicated provider strategy classes).
Initialize strategy.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
LLMModel
|
An LLMModel instance (e.g., GeminiModel, OpenAIModel). |
required |
response_parser
|
Callable[[LLMResponse], TOutput] | None
|
Function to parse LLMResponse into TOutput. Defaults to
returning |
None
|
temperature
|
float | None
|
Default sampling temperature. Pass |
0.0
|
generation_config
|
dict[str, Any] | None
|
Provider-specific config forwarded to
|
None
|
quota_scope
|
object | None
|
Optional identity shared by strategies consuming the same provider/account quota. Defaults to the model, matching the existing concurrency scope. |
None
|
token_estimator
|
TokenEstimator | None
|
Optional strategy-owned local estimator used when TPM admission is enabled and the processor has no override. |
None
|
Source code in src/async_batch_llm/llm_strategies.py
concurrency_scope
property
Strategies wrapping the same model share one admission limit.
max_concurrency
property
Forward optional client/transport capacity advertised by the model.
cleanup
async
Delegate to model.cleanup() if the model has a managed lifecycle.
execute
async
execute(prompt: str, attempt: int, timeout: float, state: RetryState | None = None) -> tuple[TOutput, TokenUsage, dict[str, Any] | None]
Execute the LLM call via the model and parse the response.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prompt
|
str
|
The prompt to send to the LLM. |
required |
attempt
|
int
|
Which retry attempt this is (1, 2, 3, ...). |
required |
timeout
|
float
|
Maximum time for response (enforced by the framework). |
required |
state
|
RetryState | None
|
Optional retry state for cross-attempt persistence. |
None
|
Returns:
| Type | Description |
|---|---|
TOutput
|
3-tuple |
TokenUsage
|
is forwarded from |
dict[str, Any] | None
|
model, safety_ratings, etc.). Added the metadata slot in v0.10.0; the |
tuple[TOutput, TokenUsage, dict[str, Any] | None]
|
framework still accepts the legacy 2-tuple shape from custom |
tuple[TOutput, TokenUsage, dict[str, Any] | None]
|
strategies via a compat shim. |
Source code in src/async_batch_llm/llm_strategies.py
prepare
async
Delegate to model.prepare() if the model has a managed lifecycle.
request_concurrency
async
Forward a concurrency request to the model, when supported.
Execution surfaces call this when ProcessorConfig.concurrency is
set (v0.20.0) so built-in models can right-size their connection
pools. Models without the hook (custom LLMModel implementations,
Gemini) simply return False and are left untouched.
Source code in src/async_batch_llm/llm_strategies.py
PydanticAIStrategy
async_batch_llm.PydanticAIStrategy
Bases: LLMCallStrategy[TOutput]
Strategy for using PydanticAI agents.
This strategy wraps a PydanticAI agent, providing a clean interface for batch processing. The agent handles all model interaction, validation, and parsing.
Best for: Structured output with Pydantic models, using PydanticAI's features.
Initialize PydanticAI strategy.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
agent
|
Agent[None, TOutput]
|
Configured PydanticAI agent |
required |
Source code in src/async_batch_llm/llm_strategies.py
dry_run
async
Return mock output based on agent's result_type for dry-run mode.
Source code in src/async_batch_llm/llm_strategies.py
execute
async
execute(prompt: str, attempt: int, timeout: float, state: RetryState | None = None) -> tuple[TOutput, TokenUsage, dict[str, Any] | None]
Execute PydanticAI agent call.
Note: timeout parameter is provided for information but timeout enforcement is handled by the framework wrapping this call in asyncio.wait_for().
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prompt
|
str
|
The prompt to send to the LLM |
required |
attempt
|
int
|
Which retry attempt this is (1, 2, 3, ...) |
required |
timeout
|
float
|
Maximum time to wait for response (seconds) |
required |
state
|
RetryState | None
|
Optional retry state (v0.3.0, unused by this strategy) |
None
|
Returns:
| Type | Description |
|---|---|
TOutput
|
3-tuple |
TokenUsage
|
object doesn't expose provider-side metadata uniformly, so |
dict[str, Any] | None
|
|
Source code in src/async_batch_llm/llm_strategies.py
Structured JSON Parsing
async_batch_llm.pydantic_json_parser
pydantic_json_parser(model_cls: type[TModel], *, recover_trailing_markdown: bool = False) -> Callable[[LLMResponse], TModel]
Build a response_parser that fence-strips then validates with Pydantic.
Returns a function suitable for the response_parser argument of any
:class:~async_batch_llm.ModelStrategy subclass (OpenAIStrategy,
DeepSeekStrategy, GeminiStrategy, etc.). It runs
:func:strip_code_fences over LLMResponse.text before calling
model_cls.model_validate_json, so markdown-fenced JSON validates
cleanly instead of raising. Set recover_trailing_markdown=True to opt
into a conservative fallback for one complete top-level JSON object/array
followed only by a recognized closing-fence artifact. It never repairs
malformed JSON or discards arbitrary prose/multiple values.
Example
from pydantic import BaseModel from async_batch_llm import DeepSeekModel, DeepSeekStrategy from async_batch_llm.parsing import pydantic_json_parser
class Classification(BaseModel): ... label: str ... confidence: float
model = DeepSeekModel.from_api_key("deepseek-chat", json_mode=True) parser = pydantic_json_parser(Classification, recover_trailing_markdown=True) strategy = DeepSeekStrategy(model, parser)
Validation failures raise pydantic.ValidationError, which the built-in
error classifiers treat as retryable (the model may produce valid output on
a retry).
Source code in src/async_batch_llm/parsing.py
async_batch_llm.strip_code_fences
Strip a leading/trailing markdown code fence from text, if present.
Handles the common shapes models emit around JSON:
```json\n{...}\n``````\n{...}\n```- bare text with no fences (returned stripped, unchanged otherwise)
Only an outer fence is removed; fences in the interior of the payload are left alone. Returns the stripped inner content.
Source code in src/async_batch_llm/parsing.py
GeminiStrategy
async_batch_llm.GeminiStrategy
GeminiStrategy(model: LLMModel, response_parser: Callable[[LLMResponse], TOutput] | None = None, *, temperature: float | None = 0.0, generation_config: dict[str, Any] | None = None, quota_scope: object | None = None, token_estimator: TokenEstimator | None = None)
Bases: ModelStrategy[TOutput]
Strategy for calling a Gemini model and parsing the response.
Accepts an LLMModel (e.g., GeminiModel or GeminiCachedModel) and a response parser. The model handles the API call and token extraction; the strategy handles response parsing and lifecycle delegation.
For caching, use GeminiStrategy(model=GeminiCachedModel(...)).
v0.6.0: Accepts LLMModel instead of raw client + model string.
Example
model = GeminiModel("gemini-2.5-flash", client) strategy = GeminiStrategy(model, response_parser=lambda r: r.text)
With caching:
cached_model = GeminiCachedModel("gemini-2.5-flash", client, cached_content=[...]) strategy = GeminiStrategy(cached_model, response_parser=lambda r: r.text)
Source code in src/async_batch_llm/llm_strategies.py
OpenAIStrategy
async_batch_llm.OpenAIStrategy
OpenAIStrategy(model: LLMModel, response_parser: Callable[[LLMResponse], TOutput] | None = None, *, temperature: float | None = 0.0, generation_config: dict[str, Any] | None = None, quota_scope: object | None = None, token_estimator: TokenEstimator | None = None)
Bases: ModelStrategy[TOutput]
Strategy for calling an OpenAI-compatible model and parsing the response.
Accepts an LLMModel (typically OpenAIModel) and an optional response parser. The model handles the API call and token extraction; the strategy handles response parsing and lifecycle delegation.
Added in v0.9.0.
Example
model = OpenAIModel.from_api_key("gpt-4o-mini", api_key="sk-...") strategy = OpenAIStrategy(model)
Structured output via response_parser:
strategy = OpenAIStrategy( ... model, ... response_parser=lambda r: MyModel.model_validate_json(r.text), ... )
Source code in src/async_batch_llm/llm_strategies.py
OpenRouterStrategy
async_batch_llm.OpenRouterStrategy
OpenRouterStrategy(model: LLMModel, response_parser: Callable[[LLMResponse], TOutput] | None = None, *, temperature: float | None = 0.0, generation_config: dict[str, Any] | None = None, quota_scope: object | None = None, token_estimator: TokenEstimator | None = None)
Bases: ModelStrategy[TOutput]
Strategy for calling an OpenRouter-backed model and parsing the response.
Functionally identical to :class:OpenAIStrategy (both delegate to an
LLMModel via :class:ModelStrategy); the separate class exists for
provider-named symmetry so users can pick the strategy named after the
provider they're using. For OpenRouter, LLMResponse.metadata typically
includes provider (the upstream that served the request), model
(the actually-routed model), and finish_reason.
Added in v0.9.0.
Example
model = OpenRouterModel.from_api_key( ... "anthropic/claude-haiku-4-5", api_key="sk-or-...", ... ) strategy = OpenRouterStrategy(model)
Source code in src/async_batch_llm/llm_strategies.py
DeepSeekStrategy
async_batch_llm.DeepSeekStrategy
DeepSeekStrategy(model: LLMModel, response_parser: Callable[[LLMResponse], TOutput] | None = None, *, temperature: float | None = 0.0, generation_config: dict[str, Any] | None = None, quota_scope: object | None = None, token_estimator: TokenEstimator | None = None)
Bases: ModelStrategy[TOutput]
Strategy for calling a DeepSeek model and parsing the response.
Functionally identical to :class:OpenAIStrategy (both delegate to an
LLMModel via :class:ModelStrategy); the separate class exists for
provider-named symmetry. Pair it with :class:DeepSeekModel, which
surfaces DeepSeek's native cache-hit token counts.
Added in v0.10.0.
Example
model = DeepSeekModel.from_api_key("deepseek-chat", api_key="sk-...") strategy = DeepSeekStrategy(model)
Use the model's strict-schema parser when one is configured.
Source code in src/async_batch_llm/llm_strategies.py
Models
GeminiModel
async_batch_llm.GeminiModel
GeminiModel(model: str, client: Client, *, safety_settings: list[dict[str, Any]] | None = None, system_instruction: str | None = None, metadata_extractors: list[MetadataExtractor] | None = None)
LLM model backed by the Google Gemini API.
Wraps a genai.Client and model name, handling API calls, token extraction, and response normalization. Implements the LLMModel protocol.
Example
client = genai.Client(api_key="...") model = GeminiModel("gemini-2.5-flash", client) response = await model.generate("Hello!") print(response.text, response.input_tokens)
Added in v0.6.0.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
str
|
Model name (e.g., "gemini-3.1-flash-lite-preview"). |
required |
client
|
Client
|
Initialized genai.Client. |
required |
safety_settings
|
list[dict[str, Any]] | None
|
Default safety settings for all calls. |
None
|
system_instruction
|
str | None
|
Default system instruction (overridable per-call). |
None
|
metadata_extractors
|
list[MetadataExtractor] | None
|
Optional hooks that contribute extra keys to
|
None
|
Source code in src/async_batch_llm/models.py
generate
async
generate(prompt: str | list[Any], *, temperature: float | None = 0.0, system_instruction: str | None = None, config: dict[str, Any] | None = None) -> LLMResponse
Generate a response from Gemini.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prompt
|
str | list[Any]
|
Text prompt or list of content parts (multimodal). |
required |
temperature
|
float | None
|
Sampling temperature. Pass |
0.0
|
system_instruction
|
str | None
|
Override default system instruction. |
None
|
config
|
dict[str, Any] | None
|
Additional provider-specific config entries. |
None
|
Returns:
| Type | Description |
|---|---|
LLMResponse
|
Normalized LLMResponse. |
Source code in src/async_batch_llm/models.py
GeminiCachedModel
async_batch_llm.GeminiCachedModel
GeminiCachedModel(model: str, client: Client, cached_content: list[Content], *, cache_ttl_seconds: int = 3600, cache_renewal_buffer_seconds: int = 300, auto_renew: bool = True, cache_tags: dict[str, str] | None = None, safety_settings: list[dict[str, Any]] | None = None, metadata_extractors: list[MetadataExtractor] | None = None)
LLM model backed by Google Gemini with context caching.
Wraps a genai.Client with cache lifecycle management. Implements the ManagedLLMModel protocol: call prepare() before first use, cleanup() when done.
IMPORTANT — share one instance across work items. Create ONE GeminiCachedModel and reuse it across every LLMWorkItem that should share the cached context. Constructing a new instance per item defeats caching entirely and can cost 10× more. The framework calls prepare() exactly once per unique instance, so sharing is the intended lifecycle. See examples/example_llm_strategies.py for the pattern.
This provides 70-90% cost savings when shared correctly.
Example
model = GeminiCachedModel( ... "gemini-2.5-flash", client, ... cached_content=[system_instruction, context_docs], ... ) await model.prepare() # finds or creates cache response = await model.generate("Process this") await model.cleanup() # preserves cache for reuse
Added in v0.6.0.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
str
|
Model name (e.g., "gemini-2.5-flash"). |
required |
client
|
Client
|
Initialized genai.Client. |
required |
cached_content
|
list[Content]
|
Content to cache (system instructions, documents). |
required |
cache_ttl_seconds
|
int
|
Cache TTL in seconds (default: 3600 = 1 hour). |
3600
|
cache_renewal_buffer_seconds
|
int
|
Renew this many seconds before expiry (default: 300 = 5 minutes). |
300
|
auto_renew
|
bool
|
Auto-renew expired caches in generate() (default: True). |
True
|
cache_tags
|
dict[str, str] | None
|
Tags for precise cache matching. Encoded into the cache's
|
None
|
safety_settings
|
list[dict[str, Any]] | None
|
Default safety settings for all calls. |
None
|
metadata_extractors
|
list[MetadataExtractor] | None
|
Optional hooks that contribute extra keys to
|
None
|
Source code in src/async_batch_llm/models.py
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 | |
cleanup
async
Preserve cache for reuse (does not delete). Idempotent.
Source code in src/async_batch_llm/models.py
delete_cache
async
Explicitly delete the cache.
Safe to call concurrently: the cache lock serializes delete attempts so the provider API fires at most once, and late callers that arrive after the cache is cleared return silently.
Source code in src/async_batch_llm/models.py
generate
async
generate(prompt: str | list[Any], *, temperature: float | None = 0.0, system_instruction: str | None = None, config: dict[str, Any] | None = None) -> LLMResponse
Generate a response using the cached context.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prompt
|
str | list[Any]
|
Text prompt or multimodal content parts. |
required |
temperature
|
float | None
|
Sampling temperature. Pass |
0.0
|
system_instruction
|
str | None
|
Not supported with caching — raises ValueError. |
None
|
config
|
dict[str, Any] | None
|
Additional provider-specific config entries. |
None
|
Returns:
| Type | Description |
|---|---|
LLMResponse
|
Normalized LLMResponse. |
Source code in src/async_batch_llm/models.py
600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 | |
prepare
async
Find or create the Gemini cache. Idempotent.
Source code in src/async_batch_llm/models.py
OpenAICompatibleModel
async_batch_llm.OpenAICompatibleModel
OpenAICompatibleModel(model: str, client: AsyncOpenAI, *, system_instruction: str | None = None, extra_headers: dict[str, str] | None = None, extra_body: dict[str, Any] | None = None, metadata_extractors: list[MetadataExtractor] | None = None)
Base class for OpenAI chat-completions-compatible providers.
Wraps an AsyncOpenAI client pointed at any chat-completions endpoint
(OpenAI itself, OpenRouter, DeepSeek, HuggingFace Inference Providers,
Together, Fireworks, local vLLM, etc.). Subclasses customize the default
base URL, the install-extras hint, the env var read by
:meth:from_api_key, and optionally the token/metadata extractors.
Implements the ManagedLLMModel protocol — :meth:cleanup closes the
underlying AsyncOpenAI client when this model owns it (i.e. it was
constructed via :meth:from_api_key). User-provided clients are left
alone.
Models built with :meth:from_api_key and max_connections=N expose
max_concurrency=N so execution surfaces can diagnose worker/pool
mismatches. It remains None for caller-supplied clients because their
effective transport capacity cannot be inspected reliably.
Added in v0.9.0.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
str
|
Provider model id (e.g. "gpt-4o-mini" or "anthropic/claude-haiku-4-5"). |
required |
client
|
AsyncOpenAI
|
Initialized AsyncOpenAI (point |
required |
system_instruction
|
str | None
|
Default system message prepended to each call.
Per-call |
None
|
extra_headers
|
dict[str, str] | None
|
Default headers forwarded on every call (e.g.
OpenRouter's |
None
|
extra_body
|
dict[str, Any] | None
|
Default extra body fields forwarded on every call
(e.g. OpenRouter |
None
|
metadata_extractors
|
list[MetadataExtractor] | None
|
Optional hooks that contribute extra keys to
|
None
|
Source code in src/async_batch_llm/models.py
cleanup
async
Close the underlying AsyncOpenAI client if this model owns it.
Models constructed directly with OpenAIModel(model, client=...)
do NOT own the client — the caller is expected to close it. Models
constructed via :meth:from_api_key do own the client and close
it here so repeated processor runs don't leak httpx connections.
Source code in src/async_batch_llm/models.py
from_api_key
classmethod
from_api_key(model: str, api_key: str | None = None, *, base_url: str | None = None, system_instruction: str | None = None, extra_headers: dict[str, str] | None = None, extra_body: dict[str, Any] | None = None, json_mode: bool = False, max_connections: int | None = None, metadata_extractors: list[MetadataExtractor] | None = None, _instance_kwargs: dict[str, Any] | None = None, **client_kwargs: Any) -> TM
Build the model with a freshly-constructed AsyncOpenAI client.
The returned model owns the client — its connections are released
when the framework calls :meth:cleanup (typically when the
ParallelBatchProcessor exits).
Uses base_url (if provided) or the class's _default_base_url.
Pass client_kwargs to forward additional kwargs (timeout,
max_retries, http_client, etc.) to the SDK constructor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
str
|
Provider model id. |
required |
api_key
|
str | None
|
API key. If
|
None
|
json_mode
|
bool
|
When |
False
|
max_connections
|
int | None
|
Size of the underlying httpx connection pool
(both |
None
|
Source code in src/async_batch_llm/models.py
1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 | |
generate
async
generate(prompt: str | list[Any], *, temperature: float | None = 0.0, system_instruction: str | None = None, config: dict[str, Any] | None = None) -> LLMResponse
Call client.chat.completions.create and normalize the response.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prompt
|
str | list[Any]
|
A string (becomes a single user message) or a list of
OpenAI-shaped message dicts (passed through unchanged — used
for multimodal content and Anthropic-via-OpenRouter
|
required |
temperature
|
float | None
|
Sampling temperature. Pass |
0.0
|
system_instruction
|
str | None
|
Per-call override for the system message. |
None
|
config
|
dict[str, Any] | None
|
Per-call extra kwargs forwarded to the SDK call (merged
over the instance's |
None
|
Returns:
| Type | Description |
|---|---|
LLMResponse
|
Normalized LLMResponse. |
Source code in src/async_batch_llm/models.py
934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 | |
prepare
async
request_concurrency
async
Ask the model to support concurrency parallel requests.
Called by execution surfaces when ProcessorConfig.concurrency is
set (v0.20.0, issue #97), before the first request. When this model
owns its client (built via :meth:from_api_key) and no explicit
max_connections was given, the AsyncOpenAI client is rebuilt
with an httpx pool sized to concurrency (the SDK's default pool of
~100 connections would otherwise silently cap throughput) and
max_concurrency starts advertising the new size.
Returns True when the pool was resized. Returns False — leaving the
model untouched — for caller-supplied clients and for models built
with an explicit max_connections (an explicit value always wins;
a genuine contradiction is surfaced by the existing capacity warning
instead).
Source code in src/async_batch_llm/models.py
OpenAIModel
async_batch_llm.OpenAIModel
OpenAIModel(model: str, client: AsyncOpenAI, *, system_instruction: str | None = None, extra_headers: dict[str, str] | None = None, extra_body: dict[str, Any] | None = None, metadata_extractors: list[MetadataExtractor] | None = None)
Bases: OpenAICompatibleModel
LLM model backed by OpenAI's chat completions API.
Uses the OpenAI SDK's default base URL (https://api.openai.com/v1).
OpenAI's automatic prompt cache surfaces in cached_input_tokens for
prompts longer than ~1024 tokens.
Example
model = OpenAIModel.from_api_key("gpt-4o-mini", api_key="sk-...") response = await model.generate("Hello!") print(response.text, response.cached_input_tokens)
Added in v0.9.0.
Source code in src/async_batch_llm/models.py
OpenRouterModel
async_batch_llm.OpenRouterModel
OpenRouterModel(model: str, client: AsyncOpenAI, *, system_instruction: str | None = None, extra_headers: dict[str, str] | None = None, extra_body: dict[str, Any] | None = None, metadata_extractors: list[MetadataExtractor] | None = None)
Bases: OpenAICompatibleModel
LLM model backed by OpenRouter (https://openrouter.ai).
OpenRouter exposes a unified OpenAI-compatible API for many upstream
providers (Anthropic, OpenAI, Google, Mistral, DeepSeek, etc.). Model
ids are prefixed with the provider, e.g. "anthropic/claude-haiku-4-5".
Caching is provider-dependent:
- OpenAI / Gemini (implicit) / DeepSeek — automatic;
cached_input_tokensis populated when the upstream cache hits. - Anthropic — opt-in. Pass
promptas a list of message dicts withcache_control: {"type": "ephemeral"}markers on the blocks you want cached.
Example
model = OpenRouterModel.from_api_key( ... "anthropic/claude-haiku-4-5", ... api_key="sk-or-...", ... referer="https://my-app.example.com", ... title="My App", ... ) response = await model.generate("Hello!")
Added in v0.9.0.
Source code in src/async_batch_llm/models.py
from_api_key
classmethod
from_api_key(model: str, api_key: str | None = None, *, base_url: str | None = None, system_instruction: str | None = None, extra_headers: dict[str, str] | None = None, extra_body: dict[str, Any] | None = None, json_mode: bool = False, max_connections: int | None = None, referer: str | None = None, title: str | None = None, metadata_extractors: list[MetadataExtractor] | None = None, **client_kwargs: Any) -> OpenRouterModel
Build an OpenRouterModel.
If api_key is None, reads OPENROUTER_API_KEY from the
environment and raises ValueError if neither is set. (The
OpenAI SDK doesn't know about OPENROUTER_API_KEY, so we have
to read it ourselves rather than relying on the SDK's default.)
referer and title map to OpenRouter's optional
HTTP-Referer and X-Title headers (used for app attribution
on openrouter.ai's leaderboard).
Source code in src/async_batch_llm/models.py
DeepSeekModel
async_batch_llm.DeepSeekModel
DeepSeekModel(model: str, client: AsyncOpenAI, *, system_instruction: str | None = None, extra_headers: dict[str, str] | None = None, extra_body: dict[str, Any] | None = None, thinking: bool | None = None, api_surface: str = 'chat_completions', response_schema: type[BaseModel] | Mapping[str, Any] | None = None, schema_name: str | None = None, json_mode: bool = False, metadata_extractors: list[MetadataExtractor] | None = None)
Bases: OpenAICompatibleModel
LLM model backed by DeepSeek's OpenAI-compatible API.
Points at https://api.deepseek.com and reads
DEEPSEEK_API_KEY in :meth:from_api_key. Model ids are bare DeepSeek
names, e.g. "deepseek-chat" or "deepseek-reasoner".
DeepSeek's automatic context cache reports hits at the top level of the
usage object (prompt_cache_hit_tokens / prompt_cache_miss_tokens)
rather than under OpenAI's nested prompt_tokens_details.cached_tokens —
so this subclass overrides :meth:_extract_tokens to surface them in
cached_input_tokens. Use :attr:CachedTokenRates.DEEPSEEK (~2%) when
computing billable tokens.
(Calling DeepSeek through OpenRouter uses :class:OpenRouterModel
instead; the native cache fields aren't reliably forwarded there, which is
why direct access via this class gives better cache telemetry.)
Thinking mode. DeepSeek's V4 models (deepseek-v4-flash /
deepseek-v4-pro) default to thinking, which for a batch
classification job is a surprising, expensive default — thinking can emit
several times the output tokens (and cost, and latency) of non-thinking.
Pass thinking=False to force non-thinking mode explicitly rather than
relying on the deepseek-chat (non-thinking) / deepseek-reasoner
(thinking) aliases, which DeepSeek is deprecating. Under the hood this sends
extra_body={"thinking": {"type": "disabled"}} for Chat Completions,
or reasoning={"effort": "none"} on the Responses API.
Strict structured output. Set api_surface="responses" and pass a
Pydantic model class or JSON Schema mapping as response_schema. The
schema is sent through text.format.type="json_schema" and
:class:DeepSeekStrategy parses successful output automatically. DeepSeek
currently exposes Responses only for deepseek-v4-flash; unsupported
models fail locally instead of silently falling back to weaker JSON mode.
Example
model = DeepSeekModel.from_api_key( ... "deepseek-v4-flash", api_key="sk-...", thinking=False ... ) response = await model.generate("Hello!") print(response.text, response.cached_input_tokens)
Added in v0.10.0.
See :class:OpenAICompatibleModel; adds the DeepSeek thinking
toggle and opt-in Responses API structured output.
Source code in src/async_batch_llm/models.py
1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 | |
artifact_identity_extra
property
Replay identity fields that distinguish transport/output contracts.
from_api_key
classmethod
from_api_key(model: str, api_key: str | None = None, *, base_url: str | None = None, system_instruction: str | None = None, extra_headers: dict[str, str] | None = None, extra_body: dict[str, Any] | None = None, json_mode: bool = False, max_connections: int | None = None, thinking: bool | None = None, api_surface: str = 'chat_completions', response_schema: type[BaseModel] | Mapping[str, Any] | None = None, schema_name: str | None = None, metadata_extractors: list[MetadataExtractor] | None = None, **client_kwargs: Any) -> DeepSeekModel
Build a DeepSeekModel; reads DEEPSEEK_API_KEY when api_key is
None. Adds the thinking toggle and Responses API structured-output
options (see the class docstring) on top of the shared constructor.
Source code in src/async_batch_llm/models.py
generate
async
generate(prompt: str | list[Any], *, temperature: float | None = 0.0, system_instruction: str | None = None, config: dict[str, Any] | None = None) -> LLMResponse
Call the selected DeepSeek API surface and normalize its response.
Source code in src/async_batch_llm/models.py
parse_structured_response
Parse provider-enforced output into its Pydantic type or JSON value.
Source code in src/async_batch_llm/models.py
Protocols
LLMModel
async_batch_llm.LLMModel
Bases: Protocol
Protocol for LLM model instances that can generate responses.
Implementations wrap a specific provider's client and model configuration, handling API calls and response normalization. Strategies call generate() without needing to know about provider-specific details.
Added in v0.6.0.
generate
async
generate(prompt: str | list[Any], *, temperature: float | None = 0.0, system_instruction: str | None = None, config: dict[str, Any] | None = None) -> LLMResponse
Generate a response from the LLM.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prompt
|
str | list[Any]
|
Text prompt, or list of content parts for multimodal input. |
required |
temperature
|
float | None
|
Sampling temperature (0.0 = deterministic). Pass |
0.0
|
system_instruction
|
str | None
|
System instruction override (None = use default). |
None
|
config
|
dict[str, Any] | None
|
Provider-specific configuration (e.g., response_mime_type). |
None
|
Returns:
| Type | Description |
|---|---|
LLMResponse
|
Normalized LLMResponse with text, token counts, and metadata. |
Source code in src/async_batch_llm/core/protocols.py
ManagedLLMModel
async_batch_llm.ManagedLLMModel
Bases: LLMModel, Protocol
LLMModel with lifecycle management (e.g., caching).
Models that need one-time setup (creating a cache) or cleanup implement this protocol. The strategy delegates prepare/cleanup calls to the model.
Added in v0.6.0.
cleanup
async
LLMResponse
async_batch_llm.LLMResponse
dataclass
LLMResponse(text: str, input_tokens: int, output_tokens: int, total_tokens: int, cached_input_tokens: int = 0, metadata: dict[str, Any] | None = None, raw: Any = None)
Bases: ProviderOutputViews
Normalized response from any LLM provider.
Returned by LLMModel.generate(). Provides a provider-agnostic interface so strategies don't need to know about Gemini, OpenAI, etc. response formats.
Attributes:
| Name | Type | Description |
|---|---|---|
text |
str
|
The response text content. |
input_tokens |
int
|
Number of input/prompt tokens. |
output_tokens |
int
|
Number of output/completion tokens. |
total_tokens |
int
|
Total tokens used. |
cached_input_tokens |
int
|
Input tokens served from cache (0 if no caching). |
metadata |
dict[str, Any] | None
|
Provider-specific metadata (safety ratings, finish reason, etc.).
The keys |
raw |
Any
|
The raw provider response object, for edge cases. |
Added in v0.6.0.