Skip to module content
Module 06 ยท ~8 min

Prompt Engineering at Production Quality

Prompts as versioned, schema-validated software, not vibes.

Reading progress
0/6 ยท 0%

The big idea

๐Ÿ’กKey idea
Here's the rule that changes everything: never let unvalidated model output touch a mutating tool. Think of the model the same way you'd think of an untrusted external caller โ€” it needs the same boundary validation you'd apply to any API input. A well-formed JSON response is not a guarantee of safe semantics. That means prompts are software. They get named sections, schema-validated outputs, version numbers in git, and a regression suite that tells you when a change broke something. Not vibes โ€” software.
Quick check
1 question ยท instant feedback
0/1
  1. You've asked for structured JSON via the API's JSON mode. Downstream code passes it directly to update_campaign. Risk?

Deep dive

4/4 open

Two reasons structure beats clever wording every time.

First: reviewability. When your prompt has named sections, you (or a collaborator reviewing an AI's edit) can diff the constraints section specifically. You can see exactly what changed and why โ€” no guessing.

Second: injection hygiene. When retrieved documents and user input live inside clearly delimited context blocks, the model treats them as data. When you concatenate untrusted text directly into your instructions, you've put them on equal footing โ€” and for a system wired to an ads API, prompt injection via a client document is a real spend risk, not a theoretical one.

That said, your primary defence is still ยง1's tool-surface design, not wording alone.

If a machine consumes the model's output downstream, you need two layers of protection โ€” not one.

Layer one: use the API's structured-output mode or function-calling parameters to declare a schema. This makes malformed output rare.

Layer two: validate at your boundary anyway โ€” with Zod or equivalent. Schema-constrained decoding is not a guarantee. More importantly, this is where semantic checks live: the schema can say `budget_delta: number`, but only your validator can say `abs(budget_delta) <= 0.2 * current_budget`.

On failure: one structured retry (feed the error messages back to the model), then fail loud into the error branch. No silent swallowing.

The default is zero-shot. Today's frontier models handle most well-specified tasks without examples โ€” don't pay the token cost until you know you need to.

Few-shot examples earn their keep when the task has format or judgment conventions that are hard to describe in words. If you add them: 2โ€“5 examples that span the edges (not the easy middle), versioned with the prompt. Watch for rot โ€” examples silently encode old behaviour and will quietly outvote updated instructions if you don't review them on every prompt bump.

Chain-of-thought helps on some genuinely multi-step reasoning tasks, but with modern reasoning-class models it's often redundant and costs you 3ร— the latency.

The rule: let the eval suite decide. Don't add complexity you haven't measured.

Treat prompts like code, because they are.

Prompts live in git, alongside your code. Every change ships as a PR with a changelog line that names the failure mode it's fixing โ€” for example: 'added no-unsupported-claims constraint after T2 (unfaithful summarisation) appeared in error analysis.' This is how future-you (and your clients) can understand why a prompt evolved.

At runtime, your code pins the prompt version explicitly โ€” `report-commentary@v7`, not 'the latest.' Traces record which version ran. The eval suite runs per-version so you can compare pass rates before merging.

The key test: 'Which prompt produced this bad output?' must be answerable from the trace alone. If it isn't, you're debugging blind.

If a UI-based prompt platform is in play, sync from git one-way. Git is source of truth; the platform is a view.

Quick check
1 question ยท instant feedback
0/1
  1. Best default for a well-specified classification task on a reasoning-class model?

How to run it

  1. Role
    What the system is.
  2. Context
    The data for this run โ€” injected, clearly delimited from instructions.
  3. Task
    What to do, precisely.
  4. Constraints
    What it must not do โ€” budget hints, forbidden claims, tone bounds.
  5. Output format
    The schema (JSON mode / function-calling parameters).
  6. Examples
    Few-shot when used โ€” 2โ€“5, chosen to span the edges not the middle.
Quick check
1 question ยท instant feedback
0/1
  1. A bad output in production. First question:

In the field

๐Ÿ”ฌWorked example
The commentary prompt from ยง5's skeleton โ€” Role: analyst writing for a marketing director ยท Context: delimited block carrying the MCP's summarised performance JSON ยท Constraints: no claims not derivable from the data; flag anomalies > 20%; โ‰ค150 words; never speculate on cause without a supporting number ยท Schema: {summary, anomalies: [{campaign, metric, delta}], recommended_checks[]}, validated with Zod ยท Examples: two, fixing tone. Lives at prompts/weekly-commentary@v3. Changelog v2โ†’v3: 'added no-unsupported-claims constraint after T2 (unfaithful summarisation) appeared in error analysis.' The suite's faithfulness judge is what proves v3 actually fixed it โ€” prompt discipline and evals are one loop, not two topics.
๐ŸšซWhen not to reach for it
Don't reach for chain-of-thought reflexively โ€” modern reasoning-class models often make prompted CoT redundant; you're paying 3ร— latency for identical eval numbers. Don't add few-shot examples you don't need โ€” tokens on every call, plus a maintenance surface that rots silently. Don't adopt a platform's hosted prompt management that reintroduces the untested hotfix path โ€” keep git as source of truth.
Quick check
1 question ยท instant feedback
0/1
  1. Where does retrieved-document text belong in a prompt?

Pitfalls & takeaways

Failure modes

  • Prompt-as-config drift. Edited live in a UI or DB; no diff, no eval run; behaviour changes nobody can date.
  • Trusting schema mode. Structured output parsed and passed straight to a mutating tool; the one malformed or adversarial output in ten thousand is the incident.
  • Few-shot rot. Examples demonstrating last quarter's format, silently outvoting this quarter's instructions.
  • Reflexive CoT. 3ร— latency on a classification task that eval'd identically without it.
  • Untracked versions. A bad output in production, and no way to know which prompt produced it.

Durable takeaways

  • Model output touching a mutating tool without validation is an incident waiting.
  • Structure the prompt in named parts; diff by section.
  • Zero-shot first; few-shot when needed; CoT only when evals demand it.
  • Prompt version pinned at runtime and recorded in every trace.

Do the work

๐Ÿ‹๏ธProve you learned it

Take the messiest prompt in your MCP workflows. Restructure into named parts; add a Zod-validated output schema; commit as @v2 with a changelog line; pin the version in the caller and record it in traces; run the ยง3 suite on v1 vs v2 and attach the pass-rate diff.

0 chars
๐Ÿ“ฆArtifact to produce
Prompt-library PR pattern: structure, schema, version pin, eval evidence, in one reviewable unit.