Prompt Engineering in Production
Beyond clever phrasing: how production AI teams design, measure, and systematically improve prompts at scale.
Introduction
Lesson 3 covered how to write effective prompts: roles, few-shot examples, chain-of-thought. This lesson is about the next level, using prompts in production systems where reliability, repeatability, and measurability are non-negotiable. The craft has evolved from a standalone job into a software engineering discipline: you need evaluation datasets, metrics, regression prevention, and automated pipelines to confidently change a prompt and ship it to users.
1. When It's Required
When casual prompting breaks down
2. The Evolution
From clever tricks to context engineering
3. Modern Techniques
System messages, XML structure, few-shot
4. Prompt Evaluation
Datasets, model-as-judge, metrics
5. CI/CD for Prompts
Regression prevention and platforms
6. Reference Project
Full evaluation workflow in Python
1. When Prompt Engineering is Required
For everyday use: summarizing a document, brainstorming ideas or answering questions, you rarely need a rigorous engineering process. You iterate in the chat window, it works well enough, and you move on. Prompt engineering as a discipline kicks in when the stakes change.
Casual Use - No Engineering Needed
- One-off questions and research
- Personal productivity tasks
- Exploratory conversations
- Drafts you will manually review and edit
Production AI - Engineering Required
- Same task repeated with different inputs (inference pipeline)
- Outputs consumed programmatically without human review
- Multi-step AI agents with tool calls
- Multiple engineers modifying prompts over time
The Litmus Test
Ask yourself: "If I change one sentence in this prompt, can I tell whether it made things better or worse?" If the answer is "I'd have to eyeball a few outputs and guess," you need a formal evaluation process. AI systems are probabilistic, changing "please" to "kindly" might improve some edge cases while silently degrading others. Without measurement, you are flying blind.
2. From Clever Tricks to System Design
Early language models responded dramatically differently to small phrasings. Practitioners discovered "magic" patterns: "Act as a senior engineer...", "Think step by step...", "You will be penalized for..." These were real workarounds for limited model capabilities, not engineering principles.
Modern frontier models understand natural language well. The incantations matter far less. What matters now is what you put into context, not just how you phrase it. This shift from word-level tricks to context-level architecture is the defining change in the discipline.
Context Engineering
Context engineering means deliberately designing everything that enters the model's context window. For a production system, this includes:
System Message
Role definition, behavioral constraints, output format requirements, tone guidelines, and what the model should refuse to do.
Few-Shot Examples
Concrete input/output pairs that demonstrate the expected quality, format, and style. One good example beats a paragraph of description.
Tool Schemas
For AI agents: the tool definitions shape the model's reasoning. Well-named tools with clear descriptions lead to better tool selection and chaining.
Domain Context
Relevant documents, past decisions, code snippets, and repo constraints injected per request. This is what retrieval-augmented generation (RAG) automates at scale.
A Note on Advanced Reasoning Models
Extended thinking models (like Claude with extended thinking enabled) can sometimes over-reason on simple tasks. For straightforward inference pipelines, a standard model with a well-structured prompt often outperforms a reasoning model with a vague one. Match the tool to the task and measure to confirm.
3. Modern Prompt Engineering Techniques
System Message Design
A strong system message does three things: sets the role, defines constraints, and specifies the output format. Vague roles produce vague outputs. Explicit constraints prevent common failure modes before they happen.
import anthropic
client = anthropic.Anthropic()
system_message = """You are a meal planning assistant for competitive athletes.
CONSTRAINTS:
- Always include exact macro breakdowns (protein, fat, carbs in grams)
- Always specify meal timing (e.g., 7:00 AM, 12:00 PM)
- Never suggest foods that violate the athlete's dietary restrictions
- Always list portion sizes in grams
OUTPUT FORMAT:
- Calorie total for the day
- Macro breakdown (protein/fat/carbs)
- Numbered meal list with timing, foods, portions, and per-meal macros"""
response = client.messages.create(
model="claude-haiku-4-5",
max_tokens=1024,
system=system_message,
messages=[
{"role": "user", "content": "Create a meal plan for a 75kg runner training for a marathon."}
]
)Structured Input with XML Tags
Claude is trained to recognize XML-style tags as semantic separators. Using tags like <athlete_information> or <sample_input> helps the model distinguish between data, instructions, and examples - reducing ambiguity and improving consistency.
Before and After: The Meal Plan Example
The reference project includes two versions of the same meal plan prompt. The v1 version is intentionally naive. The v2 version adds three key improvements: structured XML input, numbered guidelines, and a few-shot example. The average evaluation score jumps from 4.3/10 to 8.7/10.
Prompt v1 - Naive (avg score: 4.3/10)
from evaluator.prompt_evaluator import add_user_message, chat
def run_prompt(prompt_inputs: dict) -> str:
prompt = f"""
Write a one-day meal plan for an athlete.
Height: {prompt_inputs["height"]}cm
Weight: {prompt_inputs["weight"]}kg
Goal: {prompt_inputs["goal"]}
Dietary restrictions: {prompt_inputs["restrictions"]}
"""
messages = []
add_user_message(messages, prompt)
return chat(messages)Prompt v2 - Improved (avg score: 8.7/10)
from evaluator.prompt_evaluator import add_user_message, chat
def run_prompt(prompt_inputs: dict) -> str:
prompt = f"""
Generate a one-day meal plan for an athlete that meets their dietary restrictions.
<athlete_information>
- Height: {prompt_inputs["height"]}
- Weight: {prompt_inputs["weight"]}
- Goal: {prompt_inputs["goal"]}
- Dietary restrictions: {prompt_inputs["restrictions"]}
</athlete_information>
Guidelines:
1. Include accurate daily calorie amount
2. Show protein, fat, and carb amounts
3. Specify when to eat each meal
4. Use only foods that fit restrictions
5. List all portion sizes in grams
6. Keep budget-friendly if mentioned
Here is an example with a sample input and an ideal output:
<sample_input>
height: 170, weight: 70
goal: Maintain fitness and improve cholesterol levels
restrictions: High cholesterol
</sample_input>
<ideal_output>
Calorie Target: approximately 2500 calories
Macros: Protein (140g), Fat (70g), Carbs (340g)
Breakfast (7:00 AM): Oatmeal (80g) with berries (100g) and walnuts (15g). Skim milk (240g).
Protein: 15g, Fat: 15g, Carbs: 60g
Mid-Morning Snack (10:00 AM): Apple (150g) with almond butter (30g).
Protein: 7g, Fat: 18g, Carbs: 25g
Lunch (1:00 PM): Grilled chicken breast (120g) salad with mixed greens (150g) and vinaigrette (30g).
Protein: 40g, Fat: 15g, Carbs: 70g
Dinner (7:00 PM): Baked salmon (140g) with steamed broccoli (200g) and quinoa (75g dry).
Protein: 40g, Fat: 20g, Carbs: 80g
</ideal_output>
"""
messages = []
add_user_message(messages, prompt)
return chat(messages)XML Structure
Separates data from instructions, reducing parser ambiguity
Numbered Guidelines
Explicit requirements prevent common omissions
Few-Shot Example
One ideal output anchors format and quality expectations
4. Prompt Evaluation: The Core Discipline
Evaluation is what separates prompt engineering from prompt guessing. Without it, every change is a bet. With it, you can prove a change improved quality, quantify the gain, and catch regressions before they reach users. This is the most important skill in the discipline today.
The Evaluation Pipeline
Prompt Improvement Cycle
Figure 1: Every prompt change moves through this loop, measure first, then improve
Building an Evaluation Dataset
A good dataset covers the distribution of real inputs your system will see, including edge cases that are rare but important. For the meal plan example, this means athletes with unusual goals, strict dietary restrictions, and conflicting constraints. You can generate this dataset automatically using Claude itself: ask it to brainstorm diverse scenarios, then convert each scenario into a structured test case with inputs and success criteria.
Happy Path
Standard, representative inputs
Edge Cases
Unusual, boundary, or tricky inputs
Diversity
Broad coverage of real-world scenarios
Criteria
Measurable success conditions per case
Model-as-Judge
Manual evaluation does not scale. Instead, use a second Claude call to score each output, this is called model-as-judge. The judge receives the task description, the inputs, the output to evaluate, and the success criteria. It returns a structured score (1-10) with reasoning.
The critical detail: run the judge at temperature=0.0 to ensure deterministic, reproducible grading. The same output should always receive the same score. Using Claude Haiku for judging keeps costs low even across large datasets.
def grade_output(self, test_case: dict, output: str) -> dict:
"""Score a prompt output 1-10 using Claude as the judge."""
eval_prompt = f"""
Evaluate the following AI output with extreme rigor.
Task: {test_case['task_description']}
Inputs: {test_case['prompt_inputs']}
Output to evaluate: {output}
Success criteria: {test_case['solution_criteria']}
Scoring guide:
- Score 1-3: fails one or more mandatory requirements
- Score 4-6: meets requirements but has significant gaps
- Score 7-8: meets all requirements with minor issues
- Score 9-10: fully satisfies all criteria
Return JSON with keys: strengths, weaknesses, reasoning, score (1-10)
"""
messages = []
add_user_message(messages, eval_prompt)
# temperature=0.0 ensures deterministic, reproducible grading
result = chat(messages, temperature=0.0)
return json.loads(result)Metrics to Track
Quality Metrics
- Average score - mean judge score across all test cases (1-10)
- Pass rate - percentage of cases scoring 7 or above
- Worst-case score - lowest score in the dataset (catch catastrophic failures)
Operational Metrics
- Token count - prompt tokens consumed per call (cost driver)
- Latency - time-to-first-token and total response time
- Brand adherence - custom criteria for tone, format, or compliance rules
5. CI/CD for Prompts
A prompt is code. It should be versioned, reviewed, and tested before it ships. The same rigor that prevents a bad function from reaching production should prevent a broken prompt from reaching users. In practice this means running your evaluation suite automatically on every prompt change, and blocking the merge if scores regress beyond a defined threshold.
The Automated Evaluation Flow
Prompt CI/CD Pipeline
Figure 2: Every prompt change triggers the evaluation suite before merging
Regression Prevention
A common pattern: define a minimum acceptable average score (e.g., 7.5/10) and a minimum pass rate (e.g., 80%). A prompt change that drops either metric below the threshold fails the CI check. This enforces the rule that you can only ship a prompt change if you can prove it did not make things worse.
The compare-prompts command in the reference project demonstrates this: it runs both prompt versions against the same dataset and prints the score delta side-by-side.
Evaluation Platforms: Braintrust
At scale, engineering teams use dedicated evaluation platforms rather than rolling their own. Braintrust is the most widely adopted in the industry. It provides:
- Dataset versioning - track your test cases as they evolve
- Prompt versioning - compare any two prompt versions on the same dataset
- Score tracking over time - see quality trends across deployments
- Multi-provider support - works with Anthropic, OpenAI, Google, and others
- Human-in-the-loop - mix automated scoring with manual review sessions
Building your own evaluation tooling (like the reference project below) is a great way to understand the mechanics. For production systems handling thousands of prompt iterations, a platform like Braintrust pays for itself quickly.
6. Reference Project: Meal Plan Evaluator
The reference project at gitlab.com/bytecode-solutions/examples/prompt-engineering implements the full evaluation workflow using the Anthropic Python SDK. It is structured as a reusable evaluator/ package with a concrete meal plan example in examples/meal_plan/.
Project Structure
evaluator/prompt_evaluator.py- reusable evaluation engineevaluator/report.py- HTML report generatorexamples/meal_plan/prompt_v1.py- naive promptexamples/meal_plan/prompt.py- improved promptexamples/meal_plan/cli.py- Click CLI commandsmanager.py- entry point
What It Demonstrates
- Dataset generation using Claude as the scenario designer
- Parallel evaluation with
ThreadPoolExecutor - Model-as-judge at temperature=0.0
- HTML report with per-case reasoning and score coloring
- Side-by-side v1 vs v2 comparison with score delta
Three-Step Workflow
# Step 1: Generate a diverse dataset of test cases using Claude python manager.py generate-dataset --num-cases 10 # Step 2: Run the improved prompt against the dataset and generate an HTML report python manager.py run-evaluation # Step 3: Compare the naive prompt (v1) against the improved prompt (v2) side-by-side python manager.py compare-prompts
Expected Output
# (example output, actual scores vary by dataset and model run) Evaluating v1: naive (no structure, no few-shot)... Average score: 4.3/10 --> examples/meal_plan/output_v1.html Evaluating v2: improved (structured guidelines + few-shot example)... Average score: 8.7/10 --> examples/meal_plan/output_v2.html --- Summary --- v1 (naive): 4.3/10 v2 (improved): 8.7/10 Improvement: +4.4 points (+102%) Open the HTML reports to understand why scores differ: v1: examples/meal_plan/output_v1.html v2: examples/meal_plan/output_v2.html
The Key Insight
The 102% score improvement is not magic, it is the direct result of adding three structural changes (XML tags, guidelines, few-shot example) and then measuring the impact. Without the evaluation pipeline, you would not know which change drove the improvement or whether any improvement happened at all. Measure first, then improve.
Bonus: core-genai - Evaluation as a Library
The reference project ships its own evaluator/ package built from scratch. If you want the same pipeline without building it yourself, core-genai packages it as a reusable Python library available on PyPI. It wraps the model-as-judge pattern behind a provider-agnostic IAgent interface, so you can swap between Claude, Gemini, ChatGPT, or Grok without changing your evaluation code.
pip install core-genaioruv pip install core-genaifrom core_genai.agents.claude import ClaudeAgent
from core_genai.prompt.evaluator import PromptEvaluator
agent = ClaudeAgent(api_key="your-api-key")
evaluator = PromptEvaluator(
agent=agent,
model="claude-haiku-4-5-20251001",
max_concurrent_tasks=5,
)
# Generate a diverse test dataset using Claude as the scenario designer
dataset = evaluator.generate_dataset(
task_description="Write a compact 1-day meal plan for an athlete",
prompt_inputs_spec={
"height": "Athlete's height in cm",
"weight": "Athlete's weight in kg",
"goal": "Goal of the athlete",
"restrictions": "Dietary restrictions",
},
num_cases=10,
output_file="dataset.json",
)
# Run your prompt against every test case and produce a scored HTML report
results = evaluator.run_evaluation(
run_prompt_function=run_prompt,
dataset_file="dataset.json",
extra_criteria="Must include daily caloric total and macronutrient breakdown",
html_output_file="report.html",
)- Provider-agnostic - swap
ClaudeAgentforGeminiAgentorChatGPTAgentwith no other changes to your evaluation code - Same pipeline -
generate_dataset(),run_evaluation(), and the HTML report are all included out of the box render()helper - lightweight template engine for{placeholder}substitution in prompt strings, with passthrough for unknown keys- Concurrent execution -
ThreadPoolExecutorruns test cases in parallel; tune throughput viamax_concurrent_tasks - Normalized metadata -
get_cost()andget_metadata()return consistent token and USD cost fields across all providers
Key Takeaways
- Measure before changing - without an evaluation dataset, every prompt change is a guess.
- Context engineering over word tricks - what goes into context (examples, constraints, domain data) matters more than exact phrasing.
- XML structure reduces ambiguity - Claude recognizes semantic tags; use them to separate inputs, instructions, and examples.
- Model-as-judge at temperature=0 - a separate Claude call can grade outputs reproducibly and at scale, no humans required.
- Treat prompts like code - version them, review them in PRs, and run automated evaluation on every change.
- Platforms like Braintrust automate the eval pipeline for teams handling many prompt versions across multiple models.