pydantic-ai-escalation
Escalate a Pydantic AI agent to a stronger model when output validation keeps failing.
Most requests in an extraction or classification pipeline are handled well by a cheap model. Output validators catch the rest: the totals don't add up, the entity isn't in the database, the date is in the future. EscalateOnOutputRetry gives the cheap model a chance to fix its answer from the validation error, and only when that isn't enough moves the run to a stronger model, which sees what went wrong before it.
mini ──✗──▶ mini + error ──✗──▶ terra + errors ──✓
Install
pip install pydantic-ai-escalation
Requires pydantic-ai-slim 2.51 or later and Python 3.10+.
Usage
from pydantic import BaseModel
from pydantic_ai import Agent, ModelRetry, RunContext
from pydantic_ai_escalation import EscalateOnOutputRetry, Level
class Invoice(BaseModel):
lines: list[float]
total: float
escalation = EscalateOnOutputRetry(
levels=[
Level('openai:gpt-4o-mini', output_retries=1),
Level('openai:gpt-5.6-terra', output_retries=1),
Level('openai:gpt-5.6-sol'),
]
)
agent = Agent(
output_type=Invoice,
capabilities=[escalation],
retries={'output': escalation.required_output_retries},
)
@agent.output_validator
def totals_match(ctx: RunContext[None], invoice: Invoice) -> Invoice:
if abs(sum(invoice.lines) - invoice.total) > 0.01:
raise ModelRetry(f'The lines add up to {sum(invoice.lines)}, but the total is {invoice.total}.')
return invoice
With these levels a run makes at most five requests:
| Request | Model | Sees |
|---|---|---|
| 1 | gpt-4o-mini |
the prompt |
| 2 | gpt-4o-mini |
the prompt, its answer, the validation error |
| 3 | gpt-5.6-terra |
everything above |
| 4 | gpt-5.6-terra |
everything above, plus its own attempt and error |
| 5 | gpt-5.6-sol |
everything above |
The run stops at the first answer that passes validation.
In an agent spec
The capability can be declared in a YAML or JSON agent spec:
# agent.yaml
retries:
output: 4
capabilities:
- EscalateOnOutputRetry:
levels:
- {model: openai:gpt-4o-mini, output_retries: 1}
- {model: openai:gpt-5.6-terra, output_retries: 1}
- {model: openai:gpt-5.6-sol}
from pydantic_ai import Agent
from pydantic_ai_escalation import EscalateOnOutputRetry
agent = Agent.from_file('agent.yaml', custom_capability_types=[EscalateOnOutputRetry])
How it works
- A level is a model plus
output_retries, the number of feedback retries it gets before the run moves on.output_retries=0(the default) escalates after the first failed attempt. - What counts as a failure: every failed output validation in the current run: a
ModelRetryfrom an output validator, an output that doesn't match the schema, or a response with no output at all. Retries of function tools don't count, and neither do failures from earlier runs in the message history. - The next level sees everything. Escalation doesn't reset the conversation: the stronger model receives the earlier attempts and their errors, so it knows what didn't work.
- The last level keeps going until the agent's output retry budget runs out, then the run fails the way it would without the capability.
The output retry budget
Pydantic AI stops a run once it has used up its output retry budget (retries={'output': ...}, which defaults to 1). A capability can't raise that budget, so set it yourself: required_output_retries is the budget that reaches every attempt on every level. If the budget is too small, the upper levels are never reached. On the tool output path (a structured output_type, the default) the capability detects this and emits an EscalationBudgetWarning; with plain text output it can't see the budget, so check it yourself.
Telemetry
When instrumentation is on, each escalation emits one zero-duration escalate_on_output_retry span with escalation.from_level, escalation.to_level and escalation.model. Requests that stay on the same level emit nothing extra: core's own spans already cover them. The span carries no prompt or output content.
Limitations
run_stream()can't escalate. Pydantic AI doesn't retry output validation inrun_stream(): it fails on the first invalid output, so there is never a second request to send to another model. Userun_stream_events()orrun()with anevent_stream_handlerinstead; both stream every response and escalate as usual.- Durable execution uses the same model selection path but hasn't been tested yet.
- The upcoming change to how Pydantic AI records retries (pydantic/pydantic-ai#8094) is handled, but only tested against a simulation of the new format.
Why it's built this way
See DESIGN.md for the decisions behind the API: why this is a capability rather than a FallbackModel, what exactly counts as a failure, and why the budget is yours to set.
License
MIT
Metadata
Release files for pydantic-ai-escalation 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| pydantic_ai_escalation-0.1.0.tar.gz | 14.9 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pydantic_ai_escalation-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 22.9 kB
Release files / pydantic_ai_escalation-0.1.0.tar.gz
| Download URL | pydantic_ai_escalation-0.1.0.tar.gz |
|---|---|
| Size | 14.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
799975f43103fb1a243652106d48bec5500e860aa66853d2817ef65f982919c3
|
|
BLAKE2b-256 checksum How to use checksums |
ff576173215e0ed3fdac4ea54fee7ccefb22ea01b316c80f1d88136956e77533
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.19 {"installer":{"name":"uv","version":"0.12.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / pydantic_ai_escalation-0.1.0-py3-none-any.whl
| Download URL | pydantic_ai_escalation-0.1.0-py3-none-any.whl |
|---|---|
| Size | 8.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0a1fe5b6ea139886ace4c5c6715ad927318cb7f151e1116d5705c9715d958fc7
|
|
BLAKE2b-256 checksum How to use checksums |
5ba8edfa71947b019f673b8a14f29ead9cd938fb2a08686410b1cd30eed3e7ac
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.19 {"installer":{"name":"uv","version":"0.12.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for pydantic-ai-escalation 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.