What goes into a prompt, how to build one from a structure, and how to write each part well.
The parts of a prompt
Prompt
All the text sent to the model for one call: the instructions you write once, and the input that changes per request. Level 2, module 1 showed how system and user messages carry it. This module is about what goes inside.
The parts of the example prompt. Colour shows how often each part changes.
Part
Question it answers
Typical mistake
Role
Who is the model acting as?
A long biography instead of a job
Task
What exactly should it do?
Two tasks in one sentence
Context
What facts does it need?
Assuming it knows your labels or your policy
Constraints
What limits apply?
"Keep it short" (how short?)
Output format
What shape must the reply have?
Not saying, then parsing prose
Examples
What does a good answer look like?
Examples that contradict the rules (module 2)
Input
The thing to work on
Mixing it into the instructions (module 6)
Most applications put the instructions you write into the system prompt and the user's own text into the user prompt. That split is a convention, not a law. You can move instructions around, and it is worth testing which arrangement works best for your task.
Two kinds of template
A prompt template is yours: text with placeholders such as {{ticket}} that your code fills in. A chat template belongs to the model: the special tokens that wrap the system and user messages into the single text the model really reads. Chat templates differ between models, and a provider can change one between versions of the same model. When you use a hosted API or an official library, it applies the chat template for you. When you run a local model yourself, getting it wrong can quietly make results worse.
Build prompts from a structure
SPEC=PromptSpec(role="You are a support triage assistant for a software company.",task="Classify the support ticket below into exactly one label.",context="Labels: billing, bug, account, other",constraints=("Pick exactly one label.","The reason must be at most 8 words."),output_format='Return JSON only: {"label": "<label>", "reason": "<why>"}',)print(render(SPEC))
Why a structure and not a string:
Reviewable. A change to one part shows up as a change to one field.
Testable. You can run the same inputs with and without a part.
Reusable. The same output_format can be shared by several prompts.
Fillable.{{name}} placeholders are replaced in one place, and a linter can see any that were forgotten.
Order
Keep the stable parts first and the changing input last. It reads naturally, and it is also the order that lets a provider reuse the start of your prompt from cache (Level 2, module 6). Context that changes per request, such as retrieved documents, belongs after the fixed parts.
For the curious A bug the first version had
render filled placeholders one after another with str.replace. If a value itself contained text like {{audience}}, a later step filled that in too. A document that happened to contain {{audience}} was silently rewritten, and in a real system that is a way for input text to alter your instructions. render now fills the template in one pass, so inserted values are never scanned again, and a placeholder with no value stays visible for the linter. Two tests pin both behaviours.
Check yourself
Which two parts of the example prompt change on every call, and which stay the same? (Only the input, the ticket, changes on every call. Role, task, labels, rules and format stay the same. Retrieved context is the part that may change when you use documents.)
Write each part well
Task. One job per prompt. "Classify the ticket and write a reply" is two jobs and gives mixed results, so split it (module 3 shows how).
Context. Put in the facts a smart stranger would need: the label list, the definitions, the policy. If the model must follow your policy, the policy text must be in the prompt.
Constraints. Give numbers. "Short" means different things to different readers, while "at most 8 words" can be checked by code. The linter flags rules that use words like short, brief or good with no number.
Output format. Say exactly what to return and show the shape. If code will read the reply, ask for JSON and validate it (Level 2, module 3). Without this part the example model answers in chatty prose, and parseability drops to 0%.
Role. A one-line role helps set tone and focus. Do not expect it to add knowledge the model does not have.
Examples. Showing the model a few input and answer pairs ("shots") often steers it better than describing the answer in words. Module 2 covers how to choose them.
Check yourself
Rewrite "Give a brief summary" so a program could check it. (For example: "Summarise in at most 3 sentences", then count sentences.)