Skip to main content

pydantic-promptmodel

Write prompts as typed Python. Render them as clean Markdown or XML.

pydantic-promptmodel turns a natural class hierarchy into a ready-to-send LLM prompt. Define the structure once, validate values with Pydantic, and switch output formats with one method call—useful for comparing models, providers, and prompting strategies without maintaining two templates.

Quick start

uv add pydantic-promptmodel

Or:

pip install pydantic-promptmodel

Define the meaning of your prompt, without specifying heading levels, list markers, or XML tags:

from pydantic_promptmodel import PromptModel


class CodeReviewPrompt(PromptModel):
    _title: str = "Code Review"
    _body: str = "Review the supplied pull request as a senior engineer."
    focus_areas: tuple[str, ...] = (
        "Correctness and edge cases",
        "Security and data handling",
        "Clear, maintainable design",
    )
    output: str = "Return prioritized findings with a concrete fix for each one."


review = CodeReviewPrompt()

markdown_prompt = review.to_markdown()
xml_prompt = review.to_xml()

Choose a format at runtime with your own model-routing policy:

def render_for(model_name: str) -> str:
    if model_name.startswith("claude"):
        return review.to_xml()
    return review.to_markdown()

That routing choice belongs to your application; either renderer can be used with any model provider.

The Markdown is immediately useful:

# Code Review

Review the supplied pull request as a senior engineer.

## Focus Areas

- Correctness and edge cases
- Security and data handling
- Clear, maintainable design

**Output:** Return prioritized findings with a concrete fix for each one.

The same instance also produces equivalent XML:

<code-review-prompt title="Code Review">
  <content>Review the supplied pull request as a senior engineer.</content>
  <focus-areas>
    <focus-area>Correctness and edge cases</focus-area>
    <focus-area>Security and data handling</focus-area>
    <focus-area>Clear, maintainable design</focus-area>
  </focus-areas>
  <output>Return prioritized findings with a concrete fix for each one.</output>
</code-review-prompt>

Richer Markdown structures

Typed records, semantic item titles, compact scalar labels, and ordered steps compose without Markdown-specific layout code:

from typing import Annotated

from pydantic_promptmodel import Ordered, PromptModel


class TravelProcedure(PromptModel):
    _title: str
    customer_goal: str
    required_checks: tuple[str, ...]
    tool_call: str
    completion: str


class TravelSupportPrompt(PromptModel):
    _title: str
    _body: str
    available_tools: tuple[str, ...]
    procedures: list[TravelProcedure]
    handling_order: Annotated[list[str], Ordered()]

The complete runnable example generates compact Markdown while preserving the model hierarchy:

# Waypoint Travel Desk

Resolve the request with the documented booking tools and procedures.

## Available Tools

- get_booking
- search_flights
- change_flight

## Procedures

### Change a departure

**Customer Goal:** Move an existing trip to another available flight.

#### Required Checks

- Retrieve the current booking.
- Confirm the requested travel date and destination.

**Tool Call:** search_flights, then change_flight
**Completion:** Return the confirmed itinerary and any fare difference.

### Review an existing trip

**Customer Goal:** Answer a question about a confirmed itinerary.

#### Required Checks

- Retrieve the booking before describing it.
- Use the itinerary returned by the tool.

**Tool Call:** get_booking
**Completion:** Summarize only the requested itinerary details.

## Handling Order

1. Retrieve the booking before making a change.
2. Use the procedure matching the customer request.
3. Summarize the completed action.

Render existing models

You can render an existing Pydantic model without adding PromptModel:

from pydantic import BaseModel
from pydantic_promptmodel import render_markdown, render_xml


class FlightContext(BaseModel):
    departure_city: str
    arrival_city: str


context = FlightContext(
    departure_city="Lisbon",
    arrival_city="Tel Aviv",
)

markdown_context = render_markdown(context)
xml_context = render_xml(context)

Standard dataclass instances use the same standalone renderers.

If you own an existing BaseModel and want method syntax, add PromptModel after the Pydantic base:

from pydantic import BaseModel
from pydantic_promptmodel import PromptModel


class FlightContext(BaseModel, PromptModel):
    departure_city: str
    arrival_city: str


markdown_context = FlightContext(
    departure_city="Lisbon",
    arrival_city="Tel Aviv",
).to_markdown()

Why use it?

  • One prompt model, multiple formats. Change .to_markdown() to .to_xml() without rewriting or synchronizing templates.
  • Natural structure. Nested models become nested sections, list[str] becomes a readable list, short scalar fields stay compact, and snake_case names become human-friendly labels.
  • Pydantic validation. Prompt inputs are typed and validated before they reach the model provider.
  • Deterministic output. The same prompt instance always renders the same text, making snapshots, reviews, and A/B tests straightforward.
  • Useful defaults first. Most prompts need no formatting metadata. Local overrides are available when a label, XML name, ordering rule, literal block, or runtime slot needs special treatment.

_title names the current prompt or nested section, while _body is prose owned directly by it. Ordinary fields named title and body remain available for your domain.

Document-level controls

Renderer options apply across the complete model graph, including unmodified Pydantic BaseModel instances:

from pydantic_promptmodel import render_markdown, render_xml


markdown_fragment = render_markdown(
    existing_model,
    naming="verbatim",
    title="system_instructions",
    start_level=3,
    heading_overflow="bold",
    scalar_style="auto",
    empty_sequences="marker",
)

xml_prompt = render_xml(
    existing_model,
    naming="verbatim",
    root_name="system_instructions",
    fallback_item_name="criterion",
)

naming="verbatim" preserves field identifiers in Markdown labels and XML field wrappers. fallback_item_name is used only when conservative singularization would otherwise emit <item>. Local Label, XmlName, and ItemName metadata still take precedence over document-level inference.

Markdown automatically renders single-line scalar values of up to 120 characters as compact **Label:** value lines. Longer and multiline values retain headings. scalar_style="block" restores heading-based scalars for a document; Inline() and Block() provide local overrides. Empty sequences render as - (none) by default or can be dropped with empty_sequences="omit".

PromptModel.to_markdown() and .to_xml() accept the same format-specific options. Defaults retain the canonical output shown in the quick start.

Design philosophy

The model is the source of truth. The library aims to produce one beautiful, correct, and useful representation for each format—not reproduce every possible hand-written Markdown or XML layout.

Supports Python 3.11+ and Pydantic 2.

Learn more

Development

make format
make lint
make test
make check
make build

Release maintainers should follow the trusted-publishing guide.

Metadata

Release files for pydantic-promptmodel 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pydantic-promptmodel 0.3.0
File Size Uploaded Provenance
pydantic_promptmodel-0.3.0.tar.gz 15.5 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for pydantic-promptmodel 0.3.0
File Interpreter ABI Platform
pydantic_promptmodel-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 35.0 kB

Release files / pydantic_promptmodel-0.3.0.tar.gz

Download URL pydantic_promptmodel-0.3.0.tar.gz
Size 15.5 kB
Tags Source
SHA-256 checksum
How to use checksums
267451a144005158c5f68430df98f4791bf5a72aff0baa8ab769d20dc3594f8f
BLAKE2b-256 checksum
How to use checksums
70147b8098fe298c05e224cd4992ae7f1252e20b1db87b655aa8a6c41b120b89
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 30, 2026.

Transparency log

Release files / pydantic_promptmodel-0.3.0-py3-none-any.whl

Download URL pydantic_promptmodel-0.3.0-py3-none-any.whl
Size 19.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8e47ba204174b35efce92a4ac7e59d5ef1efc90ecd00fe50aa83ccf8cf8c6485
BLAKE2b-256 checksum
How to use checksums
c2c08411f4901deb541e0f6afc12659152893bf36de1f1ff4f56bb31bd084518
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 30, 2026.

Transparency log

Security for pydantic-promptmodel 0.3.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for pydantic_promptmodel-0.3.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 30, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page