Generating Prompts
For a model to emit blocks your registry can parse, it needs to know the block format. Rather than hand-writing that prompt, Registry.to_prompt() builds it from your block definitions — the docstring, metadata fields, content format, and examples. The same registry then parses the model's output, so the instructions and the parser never drift apart. See Blocks & Registry for the round-trip idea.
Make your blocks self-describing
Prompt quality comes straight from your block definitions. Three things feed the prompt:
- the block docstring: the first paragraph is the description; a paragraph starting with
Usage:becomes the usage guidance (this is the only convention — implicit phrasing is not detected). - the content format:
@parse_as_json/@parse_as_yamlmark the body format, rendered as a JSON/YAML structure hint built from the content model's fields and theirField(description=...). - examples:
__examples__entries are serialized in the registry's syntax and shown verbatim.
from typing import ClassVar, Literal
from hother.streamblocks import (
BaseContent,
BaseMetadata,
Block,
DelimiterFrontmatterSyntax,
Registry,
generate_block_prompt,
parse_as_yaml,
)
class SearchMetadata(BaseMetadata):
"""Metadata for a catalog search block."""
block_type: Literal["search"] = "search"
@parse_as_yaml()
class SearchContent(BaseContent):
"""Search parameters."""
query: str = ""
limit: int = 10
class Search(Block[SearchMetadata, SearchContent]):
"""Search the product catalog.
Usage: emit a search block to look up products before answering.
"""
# Declared examples are serialized into the prompt so the model sees a
# concrete, correctly-formatted block.
__examples__: ClassVar[list[dict[str, object]]] = [
{
"metadata": {"id": "s1", "block_type": "search"},
"content": {"query": "wireless headphones", "limit": 5},
},
]
Generate a registry prompt
Registry.to_prompt() documents every registered block in the registry's syntax:
registry = Registry(syntax=DelimiterFrontmatterSyntax())
registry.register("search", Search)
# A full system prompt documenting every registered block: the syntax
# format, the block description + "Usage:" line, its metadata fields, the
# YAML content format (from @parse_as_yaml), and serialized examples.
prompt = registry.to_prompt()
print(prompt)
Pass include_examples=False to omit the examples section. Because the prompt is built from the registry's own syntax, switching syntaxes (for example MarkdownFrontmatterSyntax) changes the format shown to the model automatically.
Document a single block
For one block type, call generate_block_prompt(block_class, syntax, ...):
# A prompt for a single block type -- here without the examples section.
single = generate_block_prompt(Search, registry.syntax, include_examples=False)
print(single)
Customize the template
Prompts render through Jinja2 templates. Register a custom template and select it with template_version; the template context exposes syntax_name, syntax_format, and blocks:
# Register a custom template and select it with template_version. Templates
# receive `syntax_name`, `syntax_format`, and `blocks` in their context.
registry.register_template("compact", "Available blocks: {{ blocks | length }}", mode="registry")
print(registry.to_prompt(template_version="compact"))
This is handy for A/B testing prompt phrasings without touching block definitions.
Serialize blocks back to text
Registry.serialize_block() (and each syntax's serialize_block()) renders a Block instance into its wire format — the same mechanism used to render __examples__. It is the inverse of parsing, useful for few-shot examples or round-trip tests. BaseSyntax.describe_format() returns the human-readable format description embedded in the prompt.
Next steps
- Pydantic AI: feed the generated prompt to an agent, then extract blocks from its stream.
- Defining Custom Blocks: the block models the prompt is built from.
- Prompts reference: the full API.