Prompt Engineering 101
Theme
Palette
Download
Caption (tap to copy)
📖 Deep dive (full written explanation)
This is the build post, so the detail entries focus on why each piece of code is shaped the way it is, not just what it does. The throughline is that everything here is meant to be reused: a prompt builder you call everywhere, examples and reasoning applied consistently, a structured-output contract, and an eval harness that turns prompt tuning from guesswork into measurement. It is a paste-and-adapt skeleton, not a throwaway demo.
This step establishes setup the production-correct way: the API key comes from an environment variable, never source, and an assert fails fast if it's missing so a misconfiguration breaks loudly at startup rather than mysteriously on the first request. The model name is pinned to a constant so it appears once and can be changed in one place.
Pinning the model matters for prompting specifically because prompt behavior is model-dependent. A prompt tuned against one model can behave differently on another, so letting the model float undermines any eval work you do. Fixing it in a constant keeps your experiments honest and your results reproducible.
The build_prompt function is the most important piece of the post: it turns the anatomy from the first post into reusable code. It always states the task, optionally injects few-shot examples in a consistent format, includes the context, and optionally appends an output-format spec. Joining the parts with blank lines gives the model clear visual structure.
Centralizing prompt construction in one function is what makes prompting maintainable at scale. Instead of hand-assembling strings scattered across the codebase, every call routes through one place where you can adjust formatting, add delimiters, or change the example layout once and have it apply everywhere. It is the prompt-layer equivalent of not duplicating logic.
This slide puts zero-shot and few-shot side by side using a small ask() helper that wraps the API call and returns clean text. The zero-shot version sends only the instruction and the review; the few-shot version uses build_prompt to attach two labeled examples first. The contrast makes the technique from the previous post concrete and runnable.
The ask() wrapper is doing quiet but important work: it isolates the API call behind one function, sets max_tokens, and strips whitespace so callers get a clean string. This separation means the rest of the script reasons about prompts and outputs, not about SDK mechanics — the same isolate-the-provider discipline that keeps any LLM codebase manageable.
Structured output is where prompts connect to real software. Here a schema string describes the exact JSON shape wanted, build_prompt appends it as the output format, and the system message commands JSON-only output with no prose. The reply is parsed with json.loads and its fields are read directly.
The important caveat, expanded in the mistakes post, is that asking for JSON makes valid JSON likely, not guaranteed, and says nothing about whether the content is correct. This snippet shows the happy path to establish the pattern; production code wraps the json.loads in error handling and validates the parsed object against the schema before trusting it. Getting machine-readable output is the goal, but it must be verified, not assumed.
The flow diagram shows how the pieces compose at runtime: build_prompt assembles the text, ask sends it to the model, json.loads parses the response, and a validation step checks it against the expected schema. Each stage has one job, which is what makes the workflow extensible.
Seeing the composition clarifies where to add things later. Logging slots in around ask; retry logic wraps ask; schema validation lives in the validate stage; prompt changes happen entirely inside build_prompt. Because the stages are separated, you can harden one without disturbing the others — the structure is what lets the simple skeleton grow into a robust system.
This shows chain-of-thought that still returns a clean answer. The prompt asks the model to think step by step and then output the final result on a line with a fixed ANSWER: prefix. After the call, the code splits on that prefix to extract just the number, discarding the reasoning the model used to get there.
This resolves the practical tension with chain-of-thought: the reasoning improves accuracy but is hard to consume programmatically. By specifying an extractable answer line, you keep the accuracy benefit while still getting a parseable result. It's the runnable version of the technique from the previous post, and the split-on-prefix trick generalizes to any task where you want reasoning plus a clean final value.
The eval harness is what separates engineering from guessing. A small list of test cases pairs inputs with known-correct labels. The score function runs a given system prompt against every case, counts how often the prediction contains the gold label, and returns an accuracy fraction. Now a prompt change produces a number, not an opinion.
This tiny harness is the single most valuable habit in the whole day. Without it, prompt tuning is vibes — you change something, eyeball one output, and convince yourself it's better. With it, you can prove a change helps or hurts before shipping, catch regressions when you edit a prompt later, and compare candidates objectively. Even ten test cases dramatically raise the rigor of your prompting.
The compare diagram shows the harness paying off: Prompt A is a plain instruction scoring 0.67, while Prompt B adds few-shot examples and a format spec and scores 1.00. Prompt B costs more tokens but is reliable, and now that trade-off is visible as concrete numbers rather than a hunch.
The broader point is that the harness lets you make the cost-versus-reliability decision deliberately. Sometimes the cheaper, vaguer prompt is good enough; sometimes the extra tokens for examples and structure are clearly worth it. Either way, you are choosing based on measured scores, which is exactly the discipline the why-post argued teams skip at their peril.
The checklist condenses the post into the practices that make prompting production-grade. Version prompts in git so changes are reviewable and revertible, not lost in someone's head. Keep a fixed eval set and re-run it on every change to catch regressions. Validate JSON before trusting it. Pin the model and temperature for repeatable results. Log the exact prompt sent on each call for debugging.
These turn the runnable snippets above into a workflow you'd be comfortable putting behind a real feature. None of them are difficult individually; the value is in doing all of them, because the failures they prevent — silent regressions, unparseable output, irreproducible results — are exactly the ones that surface in production rather than in a demo.
With a runnable, testable prompting workflow in hand, the natural next question is what still goes wrong even with good tooling — the everyday prompt mistakes that make output unpredictable. That is exactly where the final post goes.
The transition is intentional: you have now seen the right way to build, structure, and evaluate prompts, so the mistakes post reads as a checklist of the specific ways teams deviate from these patterns and pay for it in flaky output.