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.
Compact record tables
Use Table() when a homogeneous sequence of shallow records is clearer as rows and
columns than as repeated sections:
from typing import Annotated
from pydantic_promptmodel import PromptModel, Table
class Finding(PromptModel):
severity: str
line_number: int
message: str
class ReviewPrompt(PromptModel):
findings: Annotated[list[Finding], Table()]
# Findings
| Severity | Line Number | Message |
| --- | --- | --- |
| high | 42 | Reject malformed input. |
| low | 8 | Clarify the example. |
Table() is deliberately explicit. It supports one-depth records with single-line
scalar cells, preserves declaration and row order, and leaves XML as the usual
repeated-element sequence. Optional values become empty cells, Label() and
naming= control column labels, and Ordered() adds a numbered first column.
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, title="Flight Context")
xml_context = render_xml(context)
Supplying title= produces a complete Markdown document with one root H1. Without
it, each root field begins at H1, which is useful when rendering a fragment.
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(title="Flight Context")
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, andsnake_casenames 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 place their content
below the same bold label without changing the document's heading hierarchy.
scalar_style="block" selects 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.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| pydantic_promptmodel-0.4.0.tar.gz | 17.9 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pydantic_promptmodel-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 39.9 kB
Release files / pydantic_promptmodel-0.4.0.tar.gz
| Download URL | pydantic_promptmodel-0.4.0.tar.gz |
|---|---|
| Size | 17.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
382e7384cf484e3cd075aae93f697850a65f6cba5973e5aa3f8836ce6bd60c16
|
|
BLAKE2b-256 checksum How to use checksums |
7f24de5411eb498813782df596378d7f074cfb33a7a77b69a56be0f17d7eef5f
|
| 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 Aug 3, 2026.
Transparency logRelease files / pydantic_promptmodel-0.4.0-py3-none-any.whl
| Download URL | pydantic_promptmodel-0.4.0-py3-none-any.whl |
|---|---|
| Size | 22.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a724168505e56285d38c540f3b611658cffb92f54f2c3b2902f7dd294c20be3d
|
|
BLAKE2b-256 checksum How to use checksums |
8ef5629fc41cc0795c72c86184b0c0d5652483c2aa29f7d9ddfe460fefad6a3b
|
| 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 Aug 3, 2026.
Transparency logSecurity for pydantic-promptmodel 0.4.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.
Attestation for pydantic_promptmodel-0.4.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 Aug 3, 2026.
Transparency logA 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.