Skip to main content

Type-safe LLM output parsing with Pydantic models and Jinja2 prompt templates

Project description

llmcast

A lightweight Python library for structured LLM output parsing. Define prompts as typed Pydantic templates, get back validated Python objects — with retries, token tracking, and support for both sync and async workflows.

Requirements: Python 3.13+

Installation

pip install llmcast

Core concepts

BaseTemplate — typed prompt

A prompt is a Pydantic model whose docstring is a Jinja2 template. Fields become template variables and are automatically rendered on str(). The full Jinja2 syntax is available — conditionals, loops, filters, etc.

from llmcast import BaseTemplate

class ExtractCompanyInfo(BaseTemplate):
    """
    Extract company information from the text below.
    Respond in {{ output_format }} format.

    Text: {{ text }}
    """
    text: str

The built-in output_format field ("json" by default) can be referenced in the template to instruct the model which format to use.

SyncLLMParser / AsyncLLMParser — parsers

Wrap an OpenAI-compatible client and call .parse() to get a validated Pydantic object back.

from openai import OpenAI
from pydantic import BaseModel
from llmcast import SyncLLMParser

class CompanyInfo(BaseModel):
    name: str
    founded: int
    employees: int

client = OpenAI()
parser = SyncLLMParser(client, "gpt-4o")

result = parser.parse(
    prompt=ExtractCompanyInfo(text="Anthropic was founded in 2021 and has ~500 employees."),
    result_schema=CompanyInfo,
)

if result:
    company, usage = result
    print(company.name)          # Anthropic
    print(usage.total_tokens)    # 142

Async usage with concurrency control:

import asyncio
from openai import AsyncOpenAI
from llmcast import AsyncLLMParser

parser = AsyncLLMParser(AsyncOpenAI(), "gpt-4o", concurrency_limit=5)

results = await asyncio.gather(*[
    parser.parse(ExtractCompanyInfo(text=text), CompanyInfo)
    for text in texts
])

Structured output vs. text parsing

By default (structured_output=True) the parser uses OpenAI's native structured output API (response_format), which guarantees schema-valid JSON. For providers or models that don't support this, set structured_output=False — the parser will instead extract and validate output from the raw text response.

# Provider doesn't support structured output — parse from text
parser = SyncLLMParser(client, "mistral-large-latest", structured_output=False)

When structured_output=False, the library only strips code fences and validates the result against the schema — it does not instruct the model how to respond. You are fully responsible for crafting a prompt that reliably produces output in the expected format. The output_format field is provided as a convenience variable you can reference in your template, but it has no effect unless you explicitly use it.

class SummaryPrompt(BaseTemplate):
    """
    Summarize the following. Reply in {{ output_format }}.

    {{ text }}
    """
    text: str
    output_format: str = "yaml"

Retry policy

Configure retries with exponential backoff and jitter via RetryPolicy:

from llmcast import RetryPolicy

policy = RetryPolicy(
    n_tries=5,
    backoff=1.0,       # initial delay in seconds
    multiplier=2.0,    # exponential factor
    max_backoff=30.0,  # cap
    jitter=True,       # randomize to avoid thundering herd
)

parser = SyncLLMParser(client, "gpt-4o", retry_policy=policy)

Rate limit errors, timeouts, and server errors (429, 408, 5xx) are retried with backoff. Parse validation failures are also retried. Non-retryable errors (auth, bad request) are raised immediately.

A per-call policy can override the instance default:

result = parser.parse(prompt, MySchema, retry_policy=RetryPolicy(n_tries=1))

Token usage

Every .parse() call returns a (result, TokenUsage) tuple. If multiple attempts were needed, usage is summed across all of them.

result, usage = result
print(usage.prompt_tokens)      # 98
print(usage.completion_tokens)  # 44
print(usage.total_tokens)       # 142

License

MIT — see LICENSE.

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

llmcast-0.2.0.tar.gz (11.0 kB view details)

Uploaded Source

Built Distribution

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

llmcast-0.2.0-py3-none-any.whl (9.5 kB view details)

Uploaded Python 3

File details

Details for the file llmcast-0.2.0.tar.gz.

File metadata

  • Download URL: llmcast-0.2.0.tar.gz
  • Upload date:
  • Size: 11.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"25.10","id":"questing","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for llmcast-0.2.0.tar.gz
Algorithm Hash digest
SHA256 2c34ab417986d414a7cc8acb2231dd7cf255530489f706df03039b59e857e4a4
MD5 71261e2e615f90afd7e29c2b2e18026e
BLAKE2b-256 8290dfb65bbbe71e070d852938820917456f51d1fe6a849588f295c631514f16

See more details on using hashes here.

File details

Details for the file llmcast-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: llmcast-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 9.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"25.10","id":"questing","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for llmcast-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ebbd73cc17f452daa6635d798c1ab47ea2b11b953608406f8523623a340da831
MD5 7a4a56e9884d0dc4d7c1b1dfb08af950
BLAKE2b-256 905454deaae70c24117b76d2ef0421e64e536be8a1836109b11eebb915a0902e

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