Metadata-Version: 2.4
Name: structout-llm
Version: 0.1.0
Summary: Guaranteed structured output from any LLM — with automatic retries and validation
Author: JALLAD
License-Expression: MIT
Project-URL: Homepage, https://github.com/ES7/structout-llm
Project-URL: Repository, https://github.com/ES7/structout-llm
Project-URL: Issues, https://github.com/ES7/structout-llm/issues
Keywords: llm,structured-output,pydantic,openai,anthropic,gemini,ai,extraction
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: pydantic>=2.0
Provides-Extra: openai
Requires-Dist: openai>=1.0; extra == "openai"
Provides-Extra: anthropic
Requires-Dist: anthropic>=0.20; extra == "anthropic"
Provides-Extra: gemini
Requires-Dist: google-genai>=0.5; extra == "gemini"
Provides-Extra: all
Requires-Dist: openai>=1.0; extra == "all"
Requires-Dist: anthropic>=0.20; extra == "all"
Requires-Dist: google-genai>=0.5; extra == "all"
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Requires-Dist: pydantic>=2.0; extra == "dev"

# structout-llm

> Guaranteed structured output from any LLM — with automatic retries and Pydantic validation.

```bash
pip install structout-llm
```

## The Problem

LLMs don't always return valid JSON. They add markdown fences, extra text, wrong types, missing fields. Your code breaks. You add manual parsing. It breaks again.

`structout-llm` fixes this — automatically.

## Quick Start

```python
from pydantic import BaseModel
from structout import extract

class Person(BaseModel):
    name: str
    age: int
    city: str

result = extract(
    instruction="Extract person details",
    schema=Person,
    provider="openai",
    model="gpt-4o",
    api_key="sk-...",
    text="John Doe is 28 years old and lives in Mumbai."
)

print(result.data.name)     # "John Doe"
print(result.data.age)      # 28
print(result.attempts)      # 1
print(result.latency_ms)    # 1243.5
```

## Supported Providers

| Provider   | Install                      | Models                          |
|------------|------------------------------|---------------------------------|
| OpenAI     | `pip install openai`         | gpt-4o, gpt-4o-mini, ...        |
| Anthropic  | `pip install anthropic`      | claude-sonnet-4, claude-haiku-4 |
| Gemini     | `pip install google-genai`   | gemini-2.5-flash, gemini-1.5-pro|

## Features

- ✅ **Auto retry** — retries with error context when validation fails
- ✅ **Pydantic validation** — full type safety and field validation
- ✅ **JSON extraction** — handles markdown fences, extra text, trailing commas
- ✅ **Safe mode** — `extract_safe()` never raises, returns error in result
- ✅ **Metrics** — tokens, latency, attempts tracked per call
- ✅ **Provider agnostic** — same API for OpenAI, Anthropic, Gemini

## extract() vs extract_safe()

```python
# extract() raises ExtractionError if all retries fail
try:
    result = extract(instruction, schema, provider, model, api_key)
except ExtractionError as e:
    print(f"Failed after {e.attempts} attempts: {e.message}")

# extract_safe() never raises — check result.success instead
result = extract_safe(instruction, schema, provider, model, api_key)
if not result.success:
    print(f"Failed: {result.error}")
```

## ExtractionResult

```python
result.data          # Validated Pydantic model instance
result.success       # True if extraction succeeded
result.attempts      # How many retries were needed
result.tokens_in     # Input tokens used
result.tokens_out    # Output tokens used
result.latency_ms    # Total time in milliseconds
result.raw_output    # Raw LLM response string
result.error         # Error message if failed, else None
```

## License

MIT
