Prompt templating
Prompt code gets hard to maintain when message strings, runtime data, and expected output shape are all mixed together. AgentLane keeps those concerns separate so a prompt can stay readable while each run still provides its own values.
Most prompt flows use
PromptTemplate for text messages or
MultiPartPromptTemplate when a
message needs multiple content parts. PromptSpec
is the small object that binds one of those templates to the values for the
current run.
A Template Is Structure Plus Values
The usual flow is:
- define a template once
- pass values later
- let the harness or model layer render the final messages
That is what PromptSpec is for. It
keeps the prompt definition and the current values together so the rest of the
framework can build a request at the right time.
Text Prompts
Use PromptTemplate when your prompt
fits naturally into optional system and user messages.
This is the most common case. Template strings are rendered with Jinja, so you can keep the prompt readable and still pass typed values into it.
from typing import TypedDict
from agentlane.models import PromptSpec, PromptTemplate
class IntakeVars(TypedDict):
clinic_name: str
question: str
# `output_schema` defaults to plain string output, so instruction-only and
# plain-text prompts can omit it entirely.
template = PromptTemplate[IntakeVars, str](
system_template="You are helping with {{ clinic_name }} patient intake.",
user_template="{{ question }}",
)
prompt = PromptSpec(
template=template,
values={
"clinic_name": "Harborview Endocrinology",
"question": "I feel dizzy after starting a new medication.",
},
)
Multipart Prompts
Use MultiPartPromptTemplate when
the model input needs more than plain text.
That usually means one of two things:
- the message content should be built from multiple parts
- the prompt should include file or image input alongside text
Multipart templates use
TextPart,
FilePart, and
ImagePart. TextPart renders a
template string with values. FilePart and ImagePart carry already-prepared
payloads.
from agentlane.models import ImagePart, MultiPartPromptTemplate
from agentlane.models import PromptSpec, TextPart
receipt_image_b64 = "..."
template = MultiPartPromptTemplate[dict[str, str], str](
system_parts=[TextPart("Summarize the issue shown in the image.")],
user_parts=[
TextPart("Customer note: {{ note }}"),
ImagePart(base64_data=receipt_image_b64, media_type="image/png"),
],
)
prompt = PromptSpec(
template=template,
values={"note": "The package arrived damaged."},
)
How OutputSchema Fits
Every prompt template carries an
OutputSchema. That schema is
how the model layer knows whether you expect plain text or structured output,
and whether a provider should enforce a JSON schema at request time.
output_schema is optional. When omitted, the template defaults to plain string
output (OutputSchema(str)), which renders no structured response_format.
Pass an explicit OutputSchema only when the template drives structured output:
from pydantic import BaseModel
from agentlane.models import OutputSchema, PromptTemplate
class Triage(BaseModel):
urgency: str
summary: str
structured = PromptTemplate[IntakeVars, Triage](
system_template="Classify the intake request.",
output_schema=OutputSchema(Triage),
)
It helps to think of the pieces this way:
- the template describes what goes in
- the output schema describes what should come back
When To Render Directly
Templates ultimately render through render_messages(...), but most
application code does not need to call that method itself.
If you are building on the harness, keep prompts at the
PromptSpec level and let the runner
build the final request. Reach for direct rendering when you are implementing a
lower-level model flow and need the canonical message list immediately.
Rendering System Instruction Text
When you need the system-instruction text a PromptSpec produces — for
logging, assertions, or feeding another component — use
render_instruction_text instead of
re-implementing role filtering over render_messages(...). It renders the
spec, keeps only the system-role messages, and joins their plain text:
from agentlane.models import render_instruction_text
system_text = render_instruction_text(prompt)
It raises ValueError if the spec renders no system message or if the system
content is not plain text, so a misconfigured prompt fails loudly instead of
silently producing an empty string.