Imperal Docs
SDK Reference

Pydantic feedback loop

Pydantic feedback loop — the runtime retry layer that fixes malformed LLM tool args, closing about 75% of arg-quality hallucinations with bounded retries.

The Pydantic feedback loop is the runtime quality guarantee added in SDK v4.1.0. When the agent emits a tool call with arguments that fail Pydantic validation, the platform gives the agent a structured second chance — instead of failing the call outright with a VALIDATION_MISSING_FIELD.

This closes roughly 75% of arg-quality hallucinations observed in production traffic — the largest single hallucination class in the 2026-04-30 audit.

What it closes

ClassSeverityAfter v4.1.0
LLM omits required Pydantic fields (title, project_id)🔴 high✅ closed — bounded retry with prose feedback
LLM passes wrong-shape args (plural list vs singular string)🟠 medium✅ closed — Pydantic type errors translate to "expected X, got Y"
LLM passes ISO-incompatible date strings ("tomorrow")🟠 medium✅ closed — prose includes ISO format example
LLM emits unknown extra fields🟡 low✅ closed — extra_forbidden translates to "unknown field — remove it"
Agent hallucinates ID slugs in retry round🔴 critical✅ closed — the fabricated-ID guard re-runs on every retry input

What it behaves like

When your typed args fail validation, the platform gives the agent one structured second chance, bounded to at most 2 retries per tool call. Each retry carries a structured, human-readable summary of exactly what was wrong (which field, what was expected, what was received) so the model can correct itself. If the retries are exhausted without valid args, the call fails with VALIDATION_MISSING_FIELD — the same outcome you'd get without the loop, just reached after the bounded second chance.

The retry only fires for genuine validation errors. A fabricated ID on the retry input is still rejected (the anti-fabrication guard runs on every attempt), and the loop never masks unrelated failures or cancellations.

How feedback is formatted

Each Pydantic error becomes one human-readable line the agent can act on:

Pydantic error typeOutput line
missing- '{loc}': required field is missing — provide a value
string_*- '{loc}': expected string, got {input_type}
int_*- '{loc}': expected integer, got {input!r}
datetime_*- '{loc}': expected ISO datetime (e.g. '2026-05-03T00:00:00'), got {input!r}
list_type- '{loc}': expected list/array, got {input_type}
extra_forbidden- '{loc}': unknown field — remove it
(other)- '{loc}': {msg} (Pydantic's own message verbatim)

The full feedback includes a header and a retry instruction so the agent understands what to fix. You don't write or wire any of this — declaring Pydantic-typed params is enough to opt in.

What you need to do as an extension author

For Pydantic-typed @chat.function handlers, nothing. The retry loop activates automatically.

For legacy **kwargs handlers, the retry layer is a no-op. Migrate to typed parameters to opt in:

from pydantic import BaseModel, Field

class CreateTaskParams(BaseModel):
    title: str = Field(description="Task title")
    project_id: str = Field(description="Project UUID")
    due_date: str | None = Field(None, description="ISO datetime, e.g. 2026-06-15T09:00:00")

@chat.function(
    "create_task",
    description="Create a task in a project.",
)
async def create_task(ctx, params: CreateTaskParams):
    # ...

Pydantic models for @chat.function must be defined at module scope. Function-local (defined-inside-a-function) models silently disable the retry loop because the SDK's auto-detection can only resolve module-scope types. The imperal validate gate flags this as V17.

Cost

Sonnet 4.6 retry call ≈ 700 input + 150 output tokens — about $0.0044 per retry. At 7-day baseline rate (12 rejected/24h × max 2 retries × $0.0044), worst-case additional spend is **$3/month** per fleet. Negligible compared to baseline chain LLM cost.

Cross-references

On this page