Skip to main content

LLM structured output with automatic retry on validation failure

Project description

validation_loop

LLM structured output with automatic retry on validation failure.

validation_loop sends a prompt to any LLM, forces the response into a Pydantic model, runs your custom validation logic, and retries automatically if validation fails -- feeding the error back to the LLM so it can self-correct.

It uses Instructor for structured output extraction, LiteLLM for provider routing, and Tenacity for retry orchestration.

Installation

# Core (you still need a provider SDK installed for LiteLLM to route to)
pip install validation-loop

# With a specific provider
pip install validation-loop[openai]
pip install validation-loop[anthropic]
pip install validation-loop[mistral]
pip install validation-loop[google]
pip install validation-loop[cohere]

# All providers
pip install validation-loop[all]

Quick start

Function form

from pydantic import BaseModel, ValidationError
from validation_loop import validation_loop


class MovieReview(BaseModel):
    title: str
    rating: float
    summary: str


def validate_review(review: MovieReview) -> dict:
    """Business logic that runs after Pydantic validation.
    Raise an exception listed in retry_exceptions to trigger a retry."""
    if len(review.summary) < 20:
        raise ValueError("Summary is too short to be useful")
    return {
        "title": review.title.upper(),
        "rating": review.rating,
        "summary": review.summary,
    }


result = validation_loop(
    schema=MovieReview,
    prompt="Review the movie Inception in one short sentence.",
    validation_callable=validate_review,
    model="openai/gpt-4.1-mini",        # any LiteLLM model string
    max_attempts=3,
    retry_exceptions=(ValidationError, ValueError),
)

Decorator form

The val_loop decorator turns a validation function into a ready-to-call LLM pipeline. The Pydantic schema is extracted from the first parameter's type annotation:

from pydantic import BaseModel, ValidationError
from validation_loop import val_loop


class MovieReview(BaseModel):
    title: str
    rating: float
    summary: str


@val_loop(model="openai/gpt-4.1-mini", max_attempts=3, retry_exceptions=(ValidationError, ValueError))
def review_movie(review: MovieReview) -> dict:
    if len(review.summary) < 20:
        raise ValueError("Summary too short")
    return {"title": review.title.upper(), "rating": review.rating}


# Call it with a prompt:
result = review_movie(prompt="Review the movie Inception.")

# Override settings at call time:
result = review_movie(
    prompt="Review The Matrix.",
    model="anthropic/claude-sonnet-4-20250514",
    max_attempts=5,
)

The decorator also works without arguments, using defaults:

@val_loop
def review_movie(review: MovieReview) -> dict:
    return {"title": review.title}

Prompt formats

The prompt parameter accepts a plain string (shown above) or a list for more advanced use cases -- multi-turn conversations, images from local files, and image URLs. See PROMPT_FORMATS.md for details and examples.

How it works

  1. Your Pydantic schema is wrapped in a subclass that runs validation_callable inside model_post_init, so both Pydantic validation errors and your custom errors are visible to Instructor's retry loop.
  2. Instructor calls the LLM via LiteLLM and parses the response into the schema.
  3. If validation fails, the error text is appended to the conversation and the LLM is called again.
  4. After max_attempts failures, a RuntimeError is raised with the last validation error as the cause.

API reference

validation_loop(schema, prompt, validation_callable, ...)

Parameter Type Default Description
schema Type[BaseModel] required Pydantic model defining the expected output
prompt str | list required See Prompt formats
validation_callable Callable required Called with the model instance; return value is returned on success
model str "openai/gpt-4.1-mini" Any LiteLLM model string
max_attempts int 3 Max LLM calls before giving up
retry_exceptions tuple[Type[Exception], ...] (ValidationError,) Exception types that trigger a retry

@val_loop / @val_loop(...)

Decorator arguments: model, max_attempts, retry_exceptions (same defaults as above).

The decorated function accepts: prompt (required), plus optional keyword overrides for model, max_attempts, retry_exceptions.

ImageURL

from validation_loop import ImageURL

A simple wrapper marking a URL string as an image for use in prompt lists. See PROMPT_FORMATS.md.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

validation_loop-0.1.0.tar.gz (7.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

validation_loop-0.1.0-py3-none-any.whl (8.7 kB view details)

Uploaded Python 3

File details

Details for the file validation_loop-0.1.0.tar.gz.

File metadata

  • Download URL: validation_loop-0.1.0.tar.gz
  • Upload date:
  • Size: 7.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.3.4 CPython/3.12.3 Linux/6.17.0-22-generic

File hashes

Hashes for validation_loop-0.1.0.tar.gz
Algorithm Hash digest
SHA256 6e55ec444b17172be94075d30f64aadbd46797d30aa9cf7f1b56771ebcf9dedf
MD5 b42ddc48e841455530a7b6e313c8fdd6
BLAKE2b-256 a0956ab51f0c110f597e6af5f64c7467770e7dbc0e72141c78a2a71d1eef8e18

See more details on using hashes here.

File details

Details for the file validation_loop-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: validation_loop-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 8.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.3.4 CPython/3.12.3 Linux/6.17.0-22-generic

File hashes

Hashes for validation_loop-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b2f005bb7325bf59f55b65961d3c4a4a0411d3436cd17e1f65025dbf0d3aa3b9
MD5 d7f2e1ab48ad3637e5fb4b0ed97cc037
BLAKE2b-256 7c1b3ffb043aff3afa4efd9aa7777554045b6d7854694308d424d287c936f3e6

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page