Pydantic AIPydantic AI 2.51 · Python 3.10+
Dashboard
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
29 small wins to finish your pathNext lesson →

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

python
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})])
Example
agent = Agent(FunctionModel(clumsy), output_type=Ticket)
result = agent.run_sync("I was charged twice")
print(result.output)
print(result.usage.requests)

The run ended with a valid Ticket and took two requests. The message list shows why.

Reading the retry prompt

Example
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())

What the second request carried

  • The second request holds a retry-prompt part.
  • 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

Example
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")

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

Example
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)

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 limitRetry loop
Where it livesOn the Pydantic modelOn the agent, as retries
CatchesA value out of range or wrong shapeThe model's failed attempts
CostNone, checked locallyOne 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.
Watch out. A high 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.
Try it yourself
  • Make clumsy send "category": "refunds" first and read the retry prompt.
  • Set retries=0 on the first agent.
  • Send a string, "high", as the priority and read the error type.

Slow is fine. Stopping is the only problem.