Metadata-Version: 2.4
Name: pydantic-promptmodel
Version: 0.2.0
Summary: Convention-first typed prompt models with canonical Markdown and XML
Keywords: llm,markdown,prompt,pydantic,xml
Author: Hillel Twersky
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Typing :: Typed
Requires-Dist: pydantic>=2.13.4,<3
Requires-Python: >=3.11
Project-URL: Homepage, https://github.com/Thillel/pydantic-promptmodel
Project-URL: Repository, https://github.com/Thillel/pydantic-promptmodel
Project-URL: Issues, https://github.com/Thillel/pydantic-promptmodel/issues
Description-Content-Type: text/markdown

# 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

```text
uv add pydantic-promptmodel
```

Or:

```text
pip install pydantic-promptmodel
```

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

```python
from pydantic_promptmodel import PromptModel, prompt


@prompt
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()
```

The Markdown is immediately useful:

```markdown
# 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:

```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>
```

## 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:

```python
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
wins 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.

Version 0.2.0 supports Python 3.11+ and Pydantic 2.

## Learn more

- [Transformation contract](https://github.com/Thillel/pydantic-promptmodel/blob/main/docs/transformation-contract.md)
- [Changelog](https://github.com/Thillel/pydantic-promptmodel/blob/main/CHANGELOG.md)

## Development

```text
make format
make lint
make test
make check
make build
```

Release maintainers should follow the
[trusted-publishing guide](https://github.com/Thillel/pydantic-promptmodel/blob/main/docs/releasing.md).
