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:
- Catches the validation error (JSON parse or Pydantic schema)
- Waits using exponential backoff
- Sends a correction prompt to the LLM with error details
- The LLM can see what went wrong and fix it
- Retries validation
- 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 FakeChatClientwith_openai.py- Code review using OpenAI APIasync_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 configurationLoggingConfig: Logging detail levelChatClient: Abstract provider interfaceOpenAIChatClient: OpenAI implementationFakeChatClient: Testing double
Exceptions
LLMIOError: Base exceptionLLMCallError: API request failedLLMParseError: JSON parsing failedLLMValidationError: Pydantic validation failedRetryExhaustedError: All retries exhaustedConfigError: 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:
- Open an issue on GitHub
- Check documentation
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4478f6e24f086c0b7a6a9995bcca28e20ca80703db5de8c7635580e85c59bf53
|
|
| MD5 |
3d76695f4bc18972f6cec17c07d2b011
|
|
| BLAKE2b-256 |
ef5a80a4f446e02b9d2312e5e8dcef6731ae129beb75b670bb170cb13eb11376
|
File details
Details for the file pydantic_llm_io-1.0.0-py3-none-any.whl.
File metadata
- Download URL: pydantic_llm_io-1.0.0-py3-none-any.whl
- Upload date:
- Size: 11.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
00447adb0c5eb712031c584ed4bdcdaf2bbedd2f2c8defeae7d0ee95526a117f
|
|
| MD5 |
97941a4d7b6bc171fc521b0e90df2f3e
|
|
| BLAKE2b-256 |
45286b21303dacb3aa0c4cb195bf50a2184ae623fae15502655418d851d472d2
|