Skip to content

Upgrading from v0.26.0 to v0.27.0

v0.27.0 deprecates the names 1.0 removes (v0.28 adds two more; see the v0.28 migration guide). It changes several defaults, so review the sections below before upgrading. JSONL and SQLite artifact schemas are unchanged, and v0.26 artifacts remain readable.

OpenAIModel uses the Responses API

OpenAIModel now calls OpenAI's Responses API by default and sends store=False. Most Chat-style request fields are translated for you (see Responses API). To keep the previous request behavior, opt out:

model = OpenAIModel.from_api_key("gpt-4o-mini", api_surface="chat_completions")
strategy = OpenAIStrategy(model, temperature=0.0)  # also restores the old temperature
  • SDK floor. The [openai] extra now requires openai>=1.66.2, the first release that parses Responses reasoning summaries.
  • Rejected request fields. n, stop, seed, presence_penalty, frequency_penalty, logit_bias, legacy functions/function_call, stream=True and background=True raise ValueError on the Responses surface. So do Chat content parts without a Responses equivalent. Use the opt-out if you rely on them.
  • Function-call-only replies now succeed with text="", finish_reason="tool_calls", and the calls in result.tool_calls. A response parser that expects text should handle the empty string. The Chat surface still raises EmptyResponseError.
  • Result metadata and raw. On the Responses surface metadata["logprobs"] is a list of per-token entries rather than the Chat logprobs object, tool_calls[].id is the Responses call_id, and LLMResponse.raw is a Responses object, not a ChatCompletion. New keys are api_surface ("responses"), provider_request_id, response_status and, when the model refuses, refusal. Code that parses logprobs or raw can branch on metadata.get("api_surface") == "responses". Both surfaces now add metadata["reasoning_tokens"] when the provider reports it; v0.26 didn't.
  • Other servers. If you pointed OpenAIModel at a non-OpenAI server with base_url or OPENAI_BASE_URL (vLLM, a proxy, an Azure-style endpoint), switch to OpenAICompatibleModel, or to llm("openai-compatible:<model>", base_url=...), which stay on Chat Completions. The alternative is api_surface="chat_completions". There is no automatic fallback.
  • Checkpoints. Automatic artifact identity for OpenAIModel now includes the API surface when it's the Responses API, so its results are stored and replayed separately:
  • A v0.26 checkpoint resumed with the new default re-runs its items; there is no error.
  • With api_surface="chat_completions" the identity is unchanged and v0.26 results replay.
  • If you pass an explicit ArtifactIdentity, it's used as-is: version it yourself if the surface change should invalidate replay.
  • OpenRouterModel, DeepSeekModel and OpenAICompatibleModel are unaffected.

Temperature is omitted by default

Built-in models, strategies, llm() and the LLMModel protocol now default to temperature=None, which sends no temperature so each provider's default applies. The old default was 0.0, which some models or reasoning modes reject. Pass temperature=0.0 explicitly to keep the previous behavior.

  • Custom LLMModel implementations. ModelStrategy (and so llm() and every built-in strategy) always passes temperature= to model.generate(). A custom model wrapped in one of them now receives temperature=None where it used to get 0.0. Treat None as "omit the parameter" rather than forwarding it to your SDK, or construct the strategy with temperature=0.0.
  • Resumed checkpoints. Automatic artifact identity covers the provider and model (plus a few provider fields, such as OpenAIModel's API surface), not request settings such as temperature. A v0.26 checkpoint resumed with resume=ResumePolicy.REUSE_SUCCESSES or REUSE_ALL still replays results that were produced at temperature=0.0, while items that re-run use the new default. (OpenAIModel on the new Responses default re-runs everything anyway; see above.) To keep a resumed run consistent, pass temperature=0.0. To re-run everything at the new default, use a new store or pass an explicit ArtifactIdentity that records the setting (for example extra={"temperature": None}) or a changed prompt_version. See Results and artifacts.

Error classification trusts types and status first

Application errors whose message mentions 429, quota, a timeout or a connection no longer start coordinated cooldowns or retries:

  • Built-in programming errors (ValueError, TypeError, KeyError, …) are logic_error.
  • An exception carrying HTTP 429 is a rate limit, with Retry-After honored.
  • An exception carrying any other status is never a rate limit by message.
  • insufficient_quota is a non-retryable insufficient_balance everywhere.
  • Bare "quota" no longer signals a rate limit; "quota exceeded" does.
  • Message matching still applies to untyped exceptions without a status.

If your code raised plain exceptions with rate-limit text to trigger a cooldown, raise an exception with a status_code = 429 attribute instead. MockAgent's simulated rate limit now carries code = 429.

Configuration failures re-execute on resume

token_estimator_required, token_estimation_error, token_estimate_exceeds_limit and quota_scope_error describe the run's configuration, not the item. They no longer replay from artifacts, even under ResumePolicy.REUSE_ALL: after you fix the estimator or quota scope, a resumed run re-executes those items. This applies to records written by v0.26 too, on both JSONL and SQLite. No action is needed unless you relied on REUSE_ALL to skip these items; filter them before submission instead.

Raised SDK minimums

Extra Before Now Why
[openrouter] openai>=1.50.0 openai>=1.66.2 openai 1.50–1.55.2 fails with current httpx
[deepseek] openai>=1.66.0 openai>=1.66.2 One floor for every OpenAI-family extra
[pydantic-ai] pydantic-ai>=1.0.0 pydantic-ai>=1.32.0 Earlier releases fail to import with current opentelemetry

If you pin an older SDK, upgrade it; the new floors are the lowest releases CI tests.

Deprecations

These still work in v0.27, emit a warning, and will be removed in 1.0:

Deprecated Replacement
execute() returning (output, tokens) Return (output, tokens, None), or real metadata instead of None
effective_input_tokens() / estimated_cost() without cached_token_rate Pass a CachedTokenRates constant
LLMGateway LLMCallPool (the same class)
BatchProcessor ParallelBatchProcessor, its only implementation
ProcessingStats The dict returned by ParallelBatchProcessor.get_stats()
grounding_metadata_extractor Nothing: built-in Gemini models already set metadata["grounding"]

Earlier deprecations are removed in 1.0 too, and their warnings now say so:

Warns since Deprecated Replacement
v0.5 ParallelBatchProcessor(max_workers=..., timeout_per_item=..., rate_limit_cooldown=...) ParallelBatchProcessor(config=ProcessorConfig(...))
v0.20 ProcessorConfig(timeout_per_item=...) and ProcessorConfig.timeout_per_item attempt_timeout
v0.16 WorkItemResult.gemini_safety_ratings result.metadata["safety_ratings"]
v0.20 BatchResult.cache_hit_rate() (called) BatchResult.cache_hit_rate (property)
v0.26 Integer prompts in process_prompts / process_stream String prompts

Each emits a DeprecationWarning, which Python hides outside __main__ by default. Run your tests with -W error::DeprecationWarning, or check pytest's warnings summary, to find remaining uses. The one exception is an omitted rate while cached tokens are present, which stays a visible UserWarning, as before.

The four deprecated names stay in async_batch_llm.__all__ until 1.0, so from async_batch_llm import * warns once for each of them, even if your code uses none, and raises under -W error::DeprecationWarning. Replace star imports with explicit ones (from async_batch_llm import ParallelBatchProcessor, llm) before turning DeprecationWarning into an error. A plain import async_batch_llm raises none of these warnings, but on Python 3.14 an installed google-genai emits its own DeprecationWarning at import time; ignore it with -W "ignore::DeprecationWarning:google.genai.types" after the error filter.

New in v0.27

  • Token and cost budgets: GuardrailConfig(max_total_tokens=..., max_total_cost=..., cost_function=...). See the guardrails guide. A BatchResult serialized with termination.kind == "budget_exceeded" can't be read by v0.26.
  • Category constants: ErrorCategory and TimeoutCategory name every built-in category, for example abort_on_error_categories={ErrorCategory.AUTHENTICATION}.
  • API stability draft: API stability shows what 1.0 is planned to keep stable, what stays provisional, and what is deprecated.
  • Custom endpoints in the factory: llm("openai-compatible:<model>", base_url=...).
  • Capacity-wait timing on deadlines and aborts: an item whose capacity wait ends at a deadline or abort now reports that wait in admission_wait_seconds, and a deadline sets timing.timeout_category == "admission_timeout". Dashboards keyed on timeout_category see the new value.
  • Stricter guardrail timeouts: a boolean or non-numeric GuardrailConfig(total_timeout_per_item=..., batch_timeout=...) now raises ValueError; pass a positive number or None.
  • Plain Gemini metadata values: metadata["finish_reason"] is "STOP", not "FinishReason.STOP", and metadata["safety_ratings"] maps "HARM_CATEGORY_HATE_SPEECH" to "LOW" instead of using the enum class prefixes. If you matched the prefixed strings, match the plain names; results replayed from older artifacts still carry the prefixed form.
  • macOS is now tested in CI and supported.