Skip to content

Layer 1: PromptBlocks

Build prompts from composable, priority-ranked blocks with intelligent assembly.

Quick Example

from promptise.prompts import prompt
from promptise.prompts.blocks import blocks, Identity, Rules, OutputFormat

@prompt(model="openai:gpt-5-mini")
@blocks(
    Identity("Expert data analyst", traits=["statistical thinking", "clear communication"]),
    Rules(["Cite specific data points", "Include confidence intervals"]),
    OutputFormat(format="markdown", instructions="Use ## headers for each section"),
)
async def analyze(dataset: str, question: str) -> str:
    """Given this dataset:
{dataset}

Answer this question: {question}"""

result = await analyze(
    dataset="Monthly users: Jan=10k, Feb=12k, Mar=15k",
    question="What is the growth trajectory?",
)

Concepts

PromptBlocks are typed, reusable components that compose into system prompts. Each block has a priority (1--10) that determines inclusion order. The PromptAssembler renders all blocks and includes them in priority order.

Priority scale:

  • 10 = Always included (Identity)
  • 9 = Critical rules (Rules)
  • 8 = Output specification (OutputFormat)
  • 6 = Runtime context (ContextSlot)
  • 5 = Custom sections (Section)
  • 4 = Nice-to-have (Examples)
  • 1--3 = Background, extras

Block Types

Identity

Defines who the agent is. Always included (priority 10).

from promptise.prompts.blocks import Identity

identity = Identity(
    "Senior financial analyst",
    traits=["quantitative", "risk-aware", "concise"],
)

Renders as:

You are Senior financial analyst.

Key traits:
- quantitative
- risk-aware
- concise

Rules

Behavioral constraints for the agent. Priority 9.

from promptise.prompts.blocks import Rules

rules = Rules([
    "Always cite specific numbers from the data",
    "Include a confidence level for each insight",
    "Keep analysis under 100 words",
])

OutputFormat

Specifies the response structure. Priority 8. Accepts format ("text", "json", "markdown"), optional schema (Pydantic model or dataclass), and instructions.

from promptise.prompts.blocks import OutputFormat
from pydantic import BaseModel

class AnalysisResult(BaseModel):
    summary: str
    confidence: float
    recommendations: list[str]

fmt = OutputFormat(
    format="json",
    schema=AnalysisResult,
    instructions="Include exactly 3 recommendations",
)

ContextSlot

Dynamic injection point filled at runtime. Priority configurable (default 6). Call .fill() to provide content -- it returns a new copy with the content set.

from promptise.prompts.blocks import ContextSlot, PromptAssembler, Identity

slot = ContextSlot("user_data", priority=6, default="No data provided")

assembler = PromptAssembler(
    Identity("Analyst"),
    slot,
)

# Fill at runtime -- returns a new assembler (immutable)
filled = assembler.fill_slot("user_data", "Revenue: $2.3M, Growth: 15%")
assembled = filled.assemble()

Section

Custom named section with static text or a dynamic callable. Priority configurable (default 5).

from promptise.prompts.blocks import Section

# Static content
static = Section("guidelines", "Follow company style guide v3.2", priority=5)

# Dynamic content -- callable receives BlockContext
dynamic = Section(
    "status",
    lambda ctx: f"Current phase: {ctx.phase}, turn: {ctx.turn}",
    priority=5,
)

Examples

Few-shot examples with automatic truncation. Priority 4.

from promptise.prompts.blocks import Examples

examples = Examples(
    [
        {"input": "Revenue up 10%", "output": "Positive growth trend"},
        {"input": "Revenue down 5%", "output": "Concerning decline"},
        {"input": "Revenue flat", "output": "Stable but stagnant"},
    ],
    max_count=2,  # Optionally cap the number of examples
)

The Examples block automatically manages the number of examples included.

Conditional

Renders only when a condition is true. Inherits priority from the inner block.

from promptise.prompts.blocks import Conditional, Rules, BlockContext

technical_rules = Conditional(
    "technical_rules",
    Rules(["Include error codes", "Reference API docs"]),
    condition=lambda ctx: ctx is not None and ctx.state.get("audience") == "technical",
)

general_rules = Conditional(
    "general_rules",
    Rules(["Use simple language", "Include analogies"]),
    condition=lambda ctx: ctx is not None and ctx.state.get("audience") == "general",
)

Assemble with a BlockContext to activate the right blocks:

from promptise.prompts.blocks import PromptAssembler, Identity, BlockContext

assembler = PromptAssembler(Identity("Support agent"), technical_rules, general_rules)
ctx = BlockContext(state={"audience": "technical"})
assembled = assembler.assemble(ctx)

Composite

Groups multiple blocks as a single unit. Priority is the maximum of all inner blocks.

from promptise.prompts.blocks import Composite, Rules, Section

safety_package = Composite(
    "safety",
    [
        Rules(["Never reveal internal data"]),
        Section("disclaimer", "Responses are for informational purposes only."),
    ],
    separator="\n\n",
)

PromptAssembler

The PromptAssembler renders and composes blocks into a final prompt.

from promptise.prompts.blocks import (
    PromptAssembler, Identity, Rules, OutputFormat, Examples, Section
)

assembler = PromptAssembler(
    Identity("Senior financial analyst"),              # priority 10
    Rules(["Show reasoning", "Include risk assessment"]),  # priority 9
    OutputFormat(format="markdown"),                    # priority 8
    Section("background", long_background_text, priority=3),  # priority 3
    Examples(few_shot_examples),                        # priority 4
)

assembled = assembler.assemble()

AssembledPrompt

The assemble() method returns an AssembledPrompt with full introspection:

Attribute Type Description
text str The final assembled prompt string
included list[str] Names of blocks that made it in
excluded list[str] Names of blocks that were not included
estimated_tokens int Token estimate for the final text
block_details list[BlockTrace] Per-block trace with priority, tokens, render time

Chaining Methods

PromptAssembler supports method chaining:

assembled = (
    PromptAssembler(Identity("Analyst"))
    .add(Rules(["Be concise"]))
    .fill_slot("data", csv_content)
    .assemble()
)
Method Description
add(block) Add a block, returns self
remove(name) Remove a block by name, returns self
fill_slot(name, content) Fill a ContextSlot by name, returns self
assemble(ctx=None) Build the final prompt, returns AssembledPrompt

The @blocks Decorator

The @blocks decorator attaches blocks to a @prompt-decorated function. It stacks below @prompt:

from promptise.prompts import prompt
from promptise.prompts.blocks import blocks, Identity, Rules

@prompt(model="openai:gpt-5-mini")
@blocks(Identity("Expert analyst"), Rules(["Cite sources"]))
async def analyze(text: str) -> str:
    """Analyze: {text}"""

Runtime Composition with with_blocks()

Attach blocks at runtime without the decorator:

@prompt(model="openai:gpt-5-mini")
async def analyze(text: str) -> str:
    """Analyze: {text}"""

configured = analyze.with_blocks(
    Identity("Senior data analyst", traits=["precise"]),
    Rules(["Always cite numbers", "Keep under 100 words"]),
    OutputFormat(format="markdown"),
)
result = await configured("Q1 revenue: $2.3M, Q2: $2.8M")

Block Protocol

Create custom block types by implementing the Block protocol:

from promptise.prompts.blocks import Block, BlockContext

class Disclaimer:
    """Custom block that always adds a legal disclaimer."""

    @property
    def name(self) -> str:
        return "disclaimer"

    @property
    def priority(self) -> int:
        return 7

    def render(self, ctx: BlockContext | None = None) -> str:
        return "DISCLAIMER: This analysis is for informational purposes only."

Empty renders are excluded

If a block's render() returns an empty string, it is automatically excluded from the assembled prompt regardless of priority.

Tips

  • Use assembled.block_details to inspect per-block token usage and render times.
  • ContextSlot.fill() returns a copy -- the original slot is unchanged, so you can reuse assemblers across requests.
  • Combine Conditional blocks with BlockContext.state to build audience-aware or phase-aware prompts without manually managing if/else logic.

What's Next?