Validation retries: when the model gets it wrong
A validation retry is Pydantic AI sending the model its own validation error and asking again, so the model can read what was wrong and fix it.
Last updated: 28 Sep, 2026 · Pydantic AI 2.51
In Structured output: a typed ticket a bad answer ended the run. Usually it does not: when validation fails, the error goes back to the model first. This model function gets the priority wrong, then corrects it once told:
Making the model fail then correct itself
def clumsy(messages, info):
last = messages[-1].parts[-1]
priority = 4 if last.part_kind == "retry-prompt" else 9
return ModelResponse(parts=[ToolCallPart("final_result", {"category": "billing", "priority": priority})])agent = Agent(FunctionModel(clumsy), output_type=Ticket)
result = agent.run_sync("I was charged twice")
print(result.output)
print(result.usage.requests)category='billing' priority=4 2
The run ended with a valid Ticket and took two requests. The message list shows why.
Reading the retry prompt
for message in result.all_messages():
print(message.kind, [part.part_kind for part in message.parts])
print(result.all_messages()[2].parts[0].model_response())request ['user-prompt']
response ['tool-call']
request ['retry-prompt']
response ['tool-call']
request ['tool-return']
1 validation error:
```json
[
{
"type": "less_than_equal",
"loc": [
"priority"
],
"msg": "Input should be less than or equal to 5",
"input": 9
}
]
```
Fix the errors and try again.What the second request carried
- The second request holds a
retry-promptpart. - model_response() is the text the model reads: the validation error as JSON, with the field, the rule and the bad value, then "Fix the errors and try again."
- The last request,
tool-return, is the agent confirming the output tool call so the message list stays valid if the chat continues.
When the model never gets it right
def stubborn(messages, info):
return ModelResponse(parts=[ToolCallPart("final_result", {"category": "billing", "priority": 9})])
agent = Agent(FunctionModel(stubborn), output_type=Ticket)
agent.run_sync("I was charged twice")pydantic_core._pydantic_core.ValidationError: 1 validation error for Ticket
priority
Input should be less than or equal to 5 [type=less_than_equal, input_value=9, input_type=int]
For further information visit https://errors.pydantic.dev/2.13/v/less_than_equal
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "main.py", line 6, in <module>
agent.run_sync("I was charged twice")
pydantic_ai.exceptions.UnexpectedModelBehavior: Exceeded maximum output retries (1)In the installed version the model gets one retry, which the run above names as Exceeded maximum output retries (1). After that the run raises UnexpectedModelBehavior, with the last validation error attached as its cause, which is why Python prints that first. Catch it where your app can fall back to a person.
Allowing more retries
attempts = iter([9, 7, 4])
def slow_learner(messages, info):
return ModelResponse(parts=[ToolCallPart("final_result", {"category": "billing", "priority": next(attempts)})])
agent = Agent(FunctionModel(slow_learner), output_type=Ticket, retries=2)
result = agent.run_sync("I was charged twice")
print(result.output, result.usage.requests)category='billing' priority=4 3
Setting retries=2 allows two corrections, and this model needs both, so the run made three requests. Each retry is another request, so set the number yourself rather than relying on the default.
A field limit vs a retry loop
| Field limit | Retry loop | |
|---|---|---|
| Where it lives | On the Pydantic model | On the agent, as retries |
| Catches | A value out of range or wrong shape | The model's failed attempts |
| Cost | None, checked locally | One request per retry |
When to raise the retry count
- A schema the model gets right most of the time but occasionally slips on.
- Never as a way to force a bad prompt or an impossible schema to pass.
- Keep it low so a stuck model fails fast instead of billing you per attempt.
retries value hides a bad prompt or schema behind repeated paid requests. If the model keeps failing, fix the schema or the instructions rather than raising the count.Related
- Previous: Structured output: a typed ticket
- Next: Output validators: rules Pydantic cannot check
- Reference: Output: retries
- Make
clumsysend"category": "refunds"first and read the retry prompt. - Set
retries=0on the first agent. - Send a string,
"high", as the priority and read the error type.
Slow is fine. Stopping is the only problem.