Upgrading from v0.32 to 1.0
1.0 is the first release covered by the API stability promise. It renames the package, requires Python 3.11, and removes everything 0.27 and 0.28 deprecated. Two behaviors change for empty and truncated responses. Checkpoints and saved results from earlier releases still load and resume.
Before you upgrade
Upgrade to v0.32 first and run your tests with deprecation warnings as errors:
Code that runs on v0.32 without a DeprecationWarning only needs the rename below and
the behavior changes. Each warning names its replacement; the
v0.28 and v0.27 guides explain them in more detail.
The package is now llm-minder
Releases through v0.32 were published as async-batch-llm. From 1.0:
| Before | From 1.0 |
|---|---|
pip install async-batch-llm[openai] |
pip install llm-minder[openai] |
import async_batch_llm |
import llm_minder |
logging.getLogger("async_batch_llm") |
logging.getLogger("llm_minder") |
Prometheus metrics async_batch_llm_* |
llm_minder_* |
AsyncBatchLLMError |
LLMMinderError (the old name still works as an alias) |
Exported names, signatures and behavior don't change with the rename, so most code needs only its imports updated:
Two things are easy to miss:
- Logging configuration that names
async_batch_llmsilently stops applying. Search your code and logging config files for the old name. - Dashboards and alerts built on the Prometheus output need the new metric names.
Artifact and result files keep their format, including the async-batch-llm-result
schema name, so a run checkpointed by an earlier release resumes under 1.0.
Python 3.11 or newer
1.0 requires Python 3.11. v0.32 is the last release for Python 3.10.
Removed names and parameters
Everything 0.27 and 0.28 deprecated is gone. A removed top-level name raises
AttributeError naming its replacement. A removed config or processor keyword raises
TypeError naming its replacement; WorkItemResult(gemini_safety_ratings=...) raises
Python's plain TypeError.
| Removed | Use instead |
|---|---|
LLMGateway |
LLMCallPool |
BatchProcessor |
ParallelBatchProcessor |
ProcessingStats |
The dict from get_stats() |
grounding_metadata_extractor |
Nothing: built-in Gemini models already emit grounding |
ParallelBatchProcessor(max_workers=..., timeout_per_item=..., rate_limit_cooldown=...) |
ParallelBatchProcessor(config=ProcessorConfig(...)) |
ProcessorConfig(timeout_per_item=...) |
ProcessorConfig(attempt_timeout=...) |
ProcessorConfig(enable_detailed_logging=True) |
The llm_minder logger's level (Logging) |
| Positional arguments to the five config classes | Keyword arguments |
WorkItemResult.gemini_safety_ratings |
result.metadata["safety_ratings"] |
batch.cache_hit_rate() |
batch.cache_hit_rate (a property) |
effective_input_tokens() / estimated_cost() without a rate |
Pass cached_token_rate=CachedTokenRates.<PROVIDER> |
Integer prompts to process_prompts / process_stream |
String prompts |
A 2-tuple from execute() |
Return (output, tokens, metadata) |
A strategy that still returns (output, tokens) doesn't fail when you build it: each
item it runs fails with a ValueError (category logic_error, not retried), and its tokens
are still counted.
Build WorkItemResult by keyword
Removing gemini_safety_ratings moves every WorkItemResult field after it up one
position. Code that builds results positionally, usually in tests or custom
middleware, can silently put values in the wrong fields. Build them by keyword:
from llm_minder import WorkItemResult
result = WorkItemResult(item_id="a", success=True, output="ok", error_category=None)
Behavior changes
Truncated responses have their own category
A response that hits the output-token limit before producing any usable text now fails
with ErrorCategory.RESPONSE_TRUNCATED ("response_truncated") and raises
TruncatedResponseError, a subclass of EmptyResponseError. It used to be
empty_response. It still isn't retried.
A response that hits the limit after producing partial text still succeeds with that
text, as before; result.metadata["finish_reason"] ("length", "MAX_TOKENS" or
"incomplete", depending on the provider) shows it was cut off. The exception is
DeepSeekModel on the Responses API, which fails any incomplete response.
If you abort on, filter on or count empty_response, add the new category:
from llm_minder import ErrorCategory, GuardrailConfig
guardrails = GuardrailConfig(
abort_on_error_categories=frozenset(
{ErrorCategory.EMPTY_RESPONSE, ErrorCategory.RESPONSE_TRUNCATED}
)
)
except EmptyResponseError still catches truncations.
A cut-off empty string now fails
An empty-string reply ("", as opposed to missing text) from Gemini or a Chat
Completions model now raises EmptyResponseError when the provider says it was cut
off, blocked or refused. Before 1.0 it succeeded with empty text. An empty string after
a normal stop, or alongside tool calls, still succeeds.
If you relied on getting "" back, for example to detect blocked prompts, check the
failure instead. EmptyResponseError.reason says why, the same way for every
provider:
from llm_minder import EmptyResponseError, WorkItemResult
def why_empty(result: WorkItemResult) -> str | None:
if isinstance(result.exception, EmptyResponseError):
return result.exception.reason # "max_tokens", "content_filter", "refusal", ...
return None
Now stable
The typed provider-output views .grounding, .reasoning and .tool_calls, and the
Grounding, GroundingSource and ToolCall classes, are stable from 1.0. Real
Gemini, OpenAI, OpenRouter and DeepSeek runs confirmed their shapes. .logprobs,
EmptyResponseError.reason and the other APIs listed under
Provisional can still change in a minor
release.
Checklist
- [ ] Your tests pass on v0.32 with
-W error::DeprecationWarning. - [ ] Python 3.11 or newer.
- [ ] Dependency changed from
async-batch-llmtollm-minder; imports updated. - [ ] Logging configuration and dashboards use the
llm_mindernames. - [ ] Code matching
empty_responsealso handlesresponse_truncated. - [ ] Nothing depends on a cut-off or blocked reply succeeding with
"". - [ ]
WorkItemResultis built by keyword.