10 · Prompt engineering
A prompt is structured input you assemble in code: a system role that sets behaviour, a user role with the task, and — when you need it — worked examples. Templates and few-shot assembly are the day-to-day craft.
A prompt is not a sentence you type — it's a messages list you build programmatically: a system role that sets the persona and rules, a user role with the concrete task, and optionally a few worked examples. Templates with named fields and a few-shot assembler turn 'prompt engineering' into ordinary, testable code.
Without this:
Without templates and roles you hand-glue strings, forget to separate instructions from data, and get inconsistent answers because each call's prompt is subtly different. With them, every prompt is reproducible and easy to A/B test.
By now you know the model takes a messages list and predicts the next message. Prompt engineering is the craft of building that list well. Three ideas carry most of the weight:
1. System vs user roles. The system message sets durable behaviour — who the model is, the rules it must follow, the output format. The user message carries the specific task for this turn. Keeping them separate means you can change the persona without touching the task, and you can A/B test instructions independently of inputs. A typical list is one system message followed by alternating user/assistant turns.
2. Zero-shot vs few-shot. Zero-shot means you just describe the task. Few-shot means you also include a handful of worked (input, output) examples before the real input, so the model imitates the pattern. Few-shot shines for tasks where the format matters (classification labels, a specific JSON shape, a tone) — the examples teach by demonstration what's hard to say in words.
3. Be specific, and template it. Vague prompts get vague answers. Name the format ("answer in one sentence", "return only the label"), the constraints, and the audience. Then wrap the wording in a prompt template — a string with named fields you fill in with str.format — so every call uses the exact same vetted instructions and only the data changes.
The rest is engineering: a template is just a function from inputs to a messages list.
Here is a prompt template built from a plain string with named {fields}, plus a few-shot assembler that turns a list of (input, output) example pairs into the messages list a chat API expects. Notice that the examples become real user/assistant turns — to the model, your demonstrations look exactly like a prior conversation it should continue.
Python (in browser)
A prompt template + few-shot assembler: examples become user/assistant turns, so the new input is just the conversation's next user message.
Python runs entirely in your browser via Pyodide (~6 MB on first Run, cached after).
Few-shot is not free — every example spends tokens (lesson 2) and adds latency. So treat the number of shots as a tunable knob: more examples buy more format-reliability up to a point, then the gains flatten while cost keeps rising. The tiny experiment below uses a deterministic mock classifier (a stand-in for the real model) whose accuracy improves as it "sees" more examples, to show the shape of that trade-off.
Python (in browser)
Shots vs cost (mock model): the first example helps most; extra shots keep costing tokens for diminishing accuracy gains.
Python runs entirely in your browser via Pyodide (~6 MB on first Run, cached after).
In production the assembled messages list goes straight to the chat API — your template and few-shot logic are the part you actually own and test.
You're using few-shot prompting and include three (input, output) example pairs before the real input. In the messages list, how should those examples appear?
- A prompt is a messages list you build in code: system role = durable behaviour/rules/format, user role = the concrete task.
- Zero-shot just describes the task; few-shot adds worked (input, output) examples as prior user/assistant turns to teach format by demonstration.
- Wrap wording in a template (str.format named fields) so instructions stay fixed and vetted while only data changes — making prompts reproducible and testable.
Every prompt-template library (LangChain PromptTemplate, OpenAI message lists, Jinja-based prompt files) is this same idea; few-shot is the cheapest way to steer a model without fine-tuning.
If you remove it: Without templates and roles, prompts drift call-to-call, instructions leak into data (inviting prompt injection), and you can't reproduce or A/B test results.