Skip to main content

pydantic-promptmodel

Ask DeepWiki

Compile typed Python prompt structures into deterministic Markdown or XML.

pydantic-promptmodel gives prompt code the same properties you expect from other application code: typed inputs, validation, reusable structure, deterministic output, and reviewable diffs. Define the meaning once, then choose Markdown or XML without maintaining parallel templates.

In 20 seconds

uv add pydantic-promptmodel

Or use pip install pydantic-promptmodel.

from pydantic_promptmodel import PromptModel


class ReviewPrompt(PromptModel):
    _title: str = "Code Review"
    _body: str = "Review the supplied change as a senior engineer."
    task: str
    checks: tuple[str, ...] = ("Correctness", "Security", "Maintainability")


prompt = ReviewPrompt(task="Return prioritized findings with concrete fixes.")
# Code Review

Review the supplied change as a senior engineer.

**Task:** Return prioritized findings with concrete fixes.

## Checks

- Correctness
- Security
- Maintainability
markdown_prompt = prompt.to_markdown()
xml_prompt = prompt.to_xml()

Both outputs preserve the same field order, hierarchy, values, and repetition. The renderer owns heading levels, labels, list markers, XML names, indentation, and escaping.

The maintenance problem it removes

An interpolated prompt begins simply, but its structure, escaping, validation, and alternate formats quickly become application responsibilities:

prompt_text = f"""# Support Triage

Classify the ticket and propose the next action.

## Priorities

- Protect account access
- Avoid unsupported promises

## Ticket

```text
{ticket_text}
```
"""

Represent the same meaning as typed data instead:

from typing import Annotated

from pydantic_promptmodel import PromptModel, Slot


class TriagePrompt(PromptModel):
    _title: str = "Support Triage"
    _body: str = "Classify the ticket and propose the next action."
    priorities: tuple[str, ...] = (
        "Protect account access",
        "Avoid unsupported promises",
    )
    ticket: Annotated[str, Slot()]


prompt_text = TriagePrompt(ticket=ticket_text).to_markdown()

Pydantic validates the input, the renderer safely expands a Markdown fence when the ticket contains backticks, and .to_xml() remains available without a second prompt definition.

Who is this for?

Use pydantic-promptmodel when prompts are maintained as application code and you care about:

  • reviewing prompt changes as stable pull-request diffs;
  • validating runtime inputs before rendering;
  • sharing one prompt structure across model providers or output formats;
  • snapshot testing complete prompts;
  • composing prompts from existing Pydantic models or dataclasses; or
  • making data boundaries and unsupported structures explicit.

It deliberately produces one canonical representation per format. It is not intended for pixel-level control over arbitrary Markdown/XML layouts or extensive template control flow.

What you get

  • One typed source of truth. Nested models, collections, schemas, literal examples, and runtime values compose through normal Python types.
  • Canonical Markdown and XML. The two renderers lower independently from one shared plan; neither format is converted into the other.
  • Deterministic output. Declaration order and stored sequence order are preserved, making snapshots and prompt review useful.
  • Pydantic validation. Prompt values are checked before they reach a model client.
  • Useful defaults with narrow overrides. Ordinary models need little or no metadata; explicit annotations handle genuine ambiguity such as tables or XML item names.
  • No required inheritance. Standalone functions render existing Pydantic models and standard dataclasses.

Render an existing model

You do not need to inherit from 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)

Standard dataclass instances use the same standalone renderers. If you own an existing BaseModel and prefer method syntax, inherit as class FlightContext(BaseModel, PromptModel).

Use it with Pydantic AI

Rendered prompts are ordinary strings, so they can be passed directly to a model client or agent framework. With Pydantic AI:

from pydantic_ai import Agent


agent = Agent(
    "openai:gpt-5.2",
    system_prompt=prompt.to_markdown(),
)
result = agent.run_sync("Review src/checkout.py.")

Pydantic AI remains an independent optional dependency; install it separately when using this integration. The rendered XML can be supplied in the same way.

Snapshot-test a prompt

Deterministic rendering makes a checked-in prompt snapshot an exact contract:

from pathlib import Path


def test_code_review_prompt() -> None:
    expected = Path("tests/snapshots/code-review.md").read_text()
    assert prompt.to_markdown() == expected

A semantic model change produces an ordinary text diff. Accidental changes to headings, order, labels, escaping, or runtime boundaries fail the test.

How it works

PromptModel / Pydantic BaseModel / dataclass
                    |
          reflection and validation
                    |
          shared rendering plan
               /          \
 canonical Markdown    canonical XML

The public plan is inspectable, but normal application code renders model instances directly.

Complete examples

  • Travel support models tools, procedures, ordered handling rules, and repeated records.
  • Code review isolates a runtime diff and renders a prompt-visible output schema.
  • Research brief combines runtime source material with typed research and citation requirements.

Each example is runnable with uv run python examples/<name>.py.

Rich rendering structures

Natural model hierarchy becomes sections and repeated elements. Use explicit local metadata only where the model alone cannot choose the intended presentation.

For example, Table() renders a homogeneous sequence of shallow records as a Markdown table while XML retains its normal repeated-element sequence:

from typing import Annotated

from pydantic_promptmodel import PromptModel, Table


class Finding(PromptModel):
    severity: str
    line_number: int
    message: str


class ReviewResult(PromptModel):
    findings: Annotated[list[Finding], Table()]
# Findings

| Severity | Line Number | Message |
| --- | --- | --- |
| high | 42 | Reject malformed input. |
| low | 8 | Clarify the example. |

Table() supports one-depth records with single-line scalar cells. Optional values become empty cells, declaration and row order are preserved, and Ordered() adds a numbered first column. Unsupported table shapes fail with a structural location.

Other narrow overrides include Label, XmlName, ItemName, Inline, Block, Ordered, LiteralBlock, and Slot.

Runtime data boundaries

Slot() marks validated runtime data. Markdown places its value inside an escape-aware code fence; XML emits escaped character data. This prevents runtime content from altering the generated Markdown or XML structure.

The boundary is syntactic, not semantic. A model can still interpret instructions inside fenced or escaped data, so Slot() is not a prompt-injection defense.

LiteralBlock provides the same opaque rendering behavior for prompt-authored examples such as JSON, source code, or logs.

Document-level controls

Renderer options apply across the complete model graph:

markdown_fragment = render_markdown(
    context,
    naming="verbatim",
    title="flight_context",
    start_level=2,
    heading_overflow="bold",
    scalar_style="auto",
    empty_sequences="marker",
)

xml_prompt = render_xml(
    context,
    naming="verbatim",
    root_name="flight_context",
    fallback_item_name="criterion",
)

naming="verbatim" preserves identifiers. Markdown scalar and heading policies control canonical presentation without changing the model. XML root and fallback item names resolve document-level ambiguity. Field-local metadata takes precedence.

Design philosophy

The model is the source of truth. The library aims to produce one clear, 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.1

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.4.1
File Size Uploaded Provenance
pydantic_promptmodel-0.4.1.tar.gz 18.2 kB With provenance Details

Built distribution (wheel)

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

Total release size: 40.4 kB

Release files / pydantic_promptmodel-0.4.1.tar.gz

Download URL pydantic_promptmodel-0.4.1.tar.gz
Size 18.2 kB
Tags Source
SHA-256 checksum
How to use checksums
cf8331fd31dbedf757909610ffebe6fd246f46ba4cec93e1e8ba84720ad8e02a
BLAKE2b-256 checksum
How to use checksums
fc344e2a419a4962e11f353bcdfa8b7a836ce7be3e667d5fc5deac777c1bbd17
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 4, 2026.

Transparency log

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

Download URL pydantic_promptmodel-0.4.1-py3-none-any.whl
Size 22.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c063fb67cf68bce88d9ed9fbe541c588203fe314011b3d3b6ea266d1856f1968
BLAKE2b-256 checksum
How to use checksums
d03100419bd3983b27afc2c93c000b8c16f079f026155d0e2e8c2b0fb5e5407a
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 4, 2026.

Transparency log

Security for pydantic-promptmodel 0.4.1

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.4.1.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 4, 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