Skip to main content

Type-safe, validated input/output handling for LLM calls with Pydantic models

Project description

pydantic-llm-io

Type-safe, validated input/output handling for LLM calls.

pydantic-llm-io is a Python library that provides type safety and validation for large language model (LLM) interactions. It combines Pydantic models for strict schema validation with automatic retry logic and detailed error handling.

Features

  • Type-Safe I/O: Define input and output schemas using Pydantic models
  • Automatic Validation: JSON parsing and Pydantic schema validation with clear error messages
  • Smart Retries: Exponential backoff retries with LLM self-correction prompts
  • Multi-Provider Support: Clean abstraction for easy provider switching (OpenAI, Anthropic, custom)
  • Async Support: Full async/await support for concurrent LLM calls
  • Excellent Errors: Rich error context including raw responses and validation details
  • Flexible Logging: Configurable logging with sensitive data controls

Installation

pip install pydantic-llm-io

Dependencies

  • Python 3.10+
  • pydantic >= 2.0
  • openai >= 1.0

Quick Start

1. Define Your Models

from pydantic import BaseModel, Field

class SummaryInput(BaseModel):
    """Schema for summarization input."""
    text: str = Field(..., description="Text to summarize")
    max_words: int = Field(100, description="Maximum summary length")

class SummaryOutput(BaseModel):
    """Schema for summarization output."""
    summary: str
    key_points: list[str]
    language: str

2. Call LLM with Validation

from pydantic_llm_io import call_llm_validated, OpenAIChatClient

# Initialize client
client = OpenAIChatClient(api_key="sk-...")

# Make validated call
result = call_llm_validated(
    prompt_model=SummaryInput(text="Long article...", max_words=50),
    response_model=SummaryOutput,
    client=client,
)

# Result is fully typed and validated
print(result.summary)
print(result.key_points)

That's it! The library handles:

✅ Serializing your input model to JSON ✅ Constructing system and user prompts with schema injection ✅ Calling the LLM API ✅ Parsing JSON response ✅ Validating against your output model ✅ Retrying with corrections if validation fails

Configuration

Retry Behavior

from pydantic_llm_io import LLMCallConfig, RetryConfig

config = LLMCallConfig(
    retry=RetryConfig(
        max_retries=3,                    # Retry up to 3 times
        initial_delay_seconds=1.0,        # First retry after 1 second
        backoff_multiplier=2.0,           # Double delay each time (1s, 2s, 4s)
    )
)

result = call_llm_validated(
    prompt_model=input_model,
    response_model=OutputModel,
    client=client,
    config=config,
)

Logging Configuration

from pydantic_llm_io import LoggingConfig

config = LLMCallConfig(
    logging=LoggingConfig(
        level="DEBUG",                    # DEBUG, INFO, WARNING, ERROR
        include_raw_response=True,        # Include full LLM response in logs
        include_validation_errors=True,   # Include validation error details
    )
)

Custom System Prompt

config = LLMCallConfig(
    custom_system_prompt="You are an expert JSON generator for summaries."
)

Async Usage

import asyncio
from pydantic_llm_io import call_llm_validated_async

async def main():
    result = await call_llm_validated_async(
        prompt_model=input_model,
        response_model=OutputModel,
        client=client,
    )
    return result

asyncio.run(main())

Error Handling

The library provides detailed exceptions:

from pydantic_llm_io import (
    LLMCallError,      # API request failed
    LLMParseError,     # JSON parsing failed
    LLMValidationError, # Pydantic validation failed
    RetryExhaustedError, # All retries failed
)

try:
    result = call_llm_validated(...)
except RetryExhaustedError as e:
    print(f"Failed after {e.context['attempts']} attempts")
    print(f"Last error: {e.context['last_error']}")
except LLMValidationError as e:
    print(f"Validation errors: {e.context['validation_errors']}")

Testing

Use FakeChatClient for testing without API calls:

import json
from pydantic_llm_io import FakeChatClient, call_llm_validated

# Create fake client with predefined response
response = json.dumps({
    "summary": "Test summary",
    "key_points": ["point1", "point2"],
    "language": "English"
})

client = FakeChatClient(response)

# Use in tests exactly like real client
result = call_llm_validated(
    prompt_model=input_model,
    response_model=OutputModel,
    client=client,
)

# Inspect calls made
assert client.call_count == 1
assert "schema" in client.last_system

Architecture

Provider Abstraction

The library uses an abstract ChatClient interface for provider independence:

from pydantic_llm_io import ChatClient

class CustomClient(ChatClient):
    """Custom provider implementation."""

    def send_message(self, system: str, user: str, temperature: float = 0.7) -> str:
        # Your provider logic here
        pass

    async def send_message_async(self, system: str, user: str, temperature: float = 0.7) -> str:
        # Your async provider logic here
        pass

    def get_provider_name(self) -> str:
        return "custom"

# Use your custom client
client = CustomClient(api_key="...")
result = call_llm_validated(..., client=client)

Adding new providers requires only implementing the ChatClient interface. The rest of the library is provider-agnostic.

How Retries Work

When validation fails, the library:

  1. Catches the validation error (JSON parse or Pydantic schema)
  2. Waits using exponential backoff
  3. Sends a correction prompt to the LLM with error details
  4. The LLM can see what went wrong and fix it
  5. Retries validation
  6. Repeats until success or max retries exhausted

This leverages the LLM's ability to self-correct, significantly improving success rates for structured outputs.

Examples

See the examples/ directory for:

  • basic_usage.py - Simple summarization with FakeChatClient
  • with_openai.py - Code review using OpenAI API
  • async_example.py - Concurrent translations with async

API Reference

Main Functions

call_llm_validated()

def call_llm_validated(
    prompt_model: PromptModelT,           # Input schema instance
    response_model: type[ResponseModelT],  # Output schema class
    client: ChatClient,                    # Provider client
    config: LLMCallConfig | None = None,   # Optional configuration
) -> ResponseModelT:
    """Call LLM with type-safe validation."""

call_llm_validated_async()

Async version of call_llm_validated().

Classes

  • LLMCallConfig: Complete configuration (retry, logging, temperature, etc.)
  • RetryConfig: Retry strategy configuration
  • LoggingConfig: Logging detail level
  • ChatClient: Abstract provider interface
  • OpenAIChatClient: OpenAI implementation
  • FakeChatClient: Testing double

Exceptions

  • LLMIOError: Base exception
  • LLMCallError: API request failed
  • LLMParseError: JSON parsing failed
  • LLMValidationError: Pydantic validation failed
  • RetryExhaustedError: All retries exhausted
  • ConfigError: Configuration error

Contributing

Contributions welcome! Areas for enhancement:

  • Additional provider implementations (Anthropic, Cohere, local models)
  • Streaming response support
  • Token counting and cost estimation
  • Structured caching of responses
  • Additional validation strategies

License

MIT

Support

For issues, questions, or suggestions:

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

pydantic_llm_io-1.0.0.tar.gz (13.0 kB view details)

Uploaded Source

Built Distribution

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

pydantic_llm_io-1.0.0-py3-none-any.whl (11.4 kB view details)

Uploaded Python 3

File details

Details for the file pydantic_llm_io-1.0.0.tar.gz.

File metadata

  • Download URL: pydantic_llm_io-1.0.0.tar.gz
  • Upload date:
  • Size: 13.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for pydantic_llm_io-1.0.0.tar.gz
Algorithm Hash digest
SHA256 4478f6e24f086c0b7a6a9995bcca28e20ca80703db5de8c7635580e85c59bf53
MD5 3d76695f4bc18972f6cec17c07d2b011
BLAKE2b-256 ef5a80a4f446e02b9d2312e5e8dcef6731ae129beb75b670bb170cb13eb11376

See more details on using hashes here.

File details

Details for the file pydantic_llm_io-1.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for pydantic_llm_io-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 00447adb0c5eb712031c584ed4bdcdaf2bbedd2f2c8defeae7d0ee95526a117f
MD5 97941a4d7b6bc171fc521b0e90df2f3e
BLAKE2b-256 45286b21303dacb3aa0c4cb195bf50a2184ae623fae15502655418d851d472d2

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