learn-claude-with-phoebe / Deep dive 6.2
Learn Claude with Phoebe · Deep-dive track 6.2

Prompt engineering & evals

Session 1 taught you to describe well. This page is the engineering-grade version: three techniques that reliably lift output quality, and then the part most people skip - measuring whether your prompt actually works, on a dataset, with a number. A prompt without an eval is a vibe.

🔴 Deep dive DS & AI 45 min self-paced or live
0-5 · Setup 5-35 · Core 35-45 · Try it
Part 0

Why this page exists

"It worked on my prompt" is the LLM equivalent of "it works on my machine." The official courses pair prompt engineering with prompt evaluation for a reason: technique tells you what to try, evals tell you whether it worked. This page covers both shared modules, and you leave with a runnable eval harness under 30 lines.

Core - do these in order Advanced - read once, return when needed Shared modules of all 3 engineering courses
★ What you walk out with today Three prompt techniques (direct + specific, XML structure, few-shot examples), two grading strategies (code-based and model-based), and a minimal harness that turns "I think the prompt is better" into "pass rate went from 71% to 92%".
Part 1 · covers the Prompt Engineering module

Three techniques that actually move the needle 14 min

Hundreds of "prompt hacks" circulate. Three survive contact with evals: be clear and specific, structure with XML tags, and show examples.

CoreClear, direct, and specific5 min

This is the 4D Description from Session 1, held to an engineering standard: every vague word in your prompt is a decision you delegated to the model. Name the audience, the format, the length, the edge-case behavior. The test: could a new hire execute your prompt without asking a follow-up question?

The same request, vague vs specific ✗ Vague prompt Summarize this customer feedback No format, length, or edge rule Every run reads differently ✓ Specific prompt 3 sections, exact bullet limits Evidence rule: no inferred tone Length cap + British English Most prompt engineering is just finishing your own specification.
🔍 Click to zoom - every vague word is a decision you handed to the model
★ Before and afterBEFORE (vague - every run different): Summarize this customer feedback. AFTER (specific - every decision made): Summarize the customer feedback below for the product team. Format: exactly 3 sections - "Top complaints" (max 5 bullets, each with a count of how many customers mentioned it), "Feature requests" (max 3 bullets), "Churn risks" (customers who mention cancelling, quoted verbatim). Rules: only claims supported by the feedback text - no inference about sentiment trends. If a section has no content, write "None found". British English. Under 250 words total.

Notice what "after" pins down: audience, sections, limits, evidence rule, empty-case behavior, dialect, length. None of that is clever - it is just complete. Most prompt "engineering" is finishing your own specification.

CoreStructure with XML tags4 min

Once a prompt contains more than one KIND of thing - instructions plus a document plus examples - prose runs together and Claude can confuse your data with your rules. XML tags draw hard boundaries. Claude is specifically trained to respect them, and you can reference tags by name ("the document in <context> tags").

★ A structured prompt in Pythonprompt = f""" <context> {document_text} </context> <instructions> Summarize the document in the <context> tags above in exactly 3 bullets. Each bullet under 20 words. Audience: executives who have not read it. If the document contradicts itself, add a 4th bullet starting "Caution:". </instructions> """ response = client.messages.create( model="claude-sonnet-5", max_tokens=1024, messages=[{"role": "user", "content": prompt}], )

Common tags: <context>, <instructions>, <examples>, <data>, <output_format>. The names are yours to choose - consistency matters more than the vocabulary. Tags also defuse a classic failure: a document that itself contains instruction-like sentences no longer hijacks your prompt, because your instructions live outside the <context> block.

CoreFew-shot: show, don't describe5 min

The highest-leverage technique per minute spent: 1-3 gold examples of input → ideal output routinely beat paragraphs of format description. An example carries tone, structure, level of detail, and edge-case handling all at once - things prose describes badly.

★ Few-shot ticket classifierprompt = """Classify the support ticket into exactly one category: billing, bug, feature_request, or other. Reply with the category only. <examples> Ticket: "I was charged twice this month, please refund one" Category: billing Ticket: "the export button does nothing when I click it on Safari" Category: bug Ticket: "would be great if dashboards could refresh hourly" Category: feature_request </examples> Ticket: "my invoice shows the old company name, can you update it" Category:"""

Rules of thumb: pick examples that span your real variety (one easy, one messy, one edge case); keep them SHORT - the model imitates length too; and make them genuinely gold, because Claude will faithfully reproduce any sloppiness you include. If you cannot produce a gold example yourself, that is a sign the task is underspecified, not a prompting problem.

Real world

A data team spent a week tuning instructions for converting analyst notes into release-note bullets - output kept drifting corporate and bloated. Deleting half the instructions and pasting in two real before/after pairs from their best writer fixed it in one attempt. Their eval pass rate (next section) jumped 23 points. Examples are compressed specification.

Part 2 · covers the Prompt Evaluation module

Evals: from vibes to numbers 16 min

You changed the prompt and the one output you looked at got better. Did the other 200 cases? An eval is just a test suite for a prompt: dataset in, grades out, one number to compare versions.

CoreWhy evals, and the workflow3 min

Prompts fail quietly and unevenly: a change that fixes your favorite test case often breaks three you never look at. The eval workflow is the same loop as ML model development:

  • 1 · Define the task - inputs, expected outputs, and what "correct" means, in writing.
  • 2 · Build a test dataset - 20-100 realistic cases with expected answers, including the ugly ones.
  • 3 · Run - execute the prompt against every case.
  • 4 · Grade - code-based checks for structured tasks, model-based rubrics for subjective ones.
  • 5 · Iterate - change ONE thing in the prompt, rerun, compare pass rates. Keep what wins.
When to build the eval Before you tune the prompt, not after. Twenty cases and an exact-match grader take 30 minutes and pay for themselves on the first iteration - otherwise you are A/B testing with a sample size of one and your own optimism as the judge.
CoreGenerating test datasets with Claude3 min

Writing 50 test cases by hand is why people skip evals. So don't: have Claude generate them, then human-review before trusting. You are curating, not authoring.

★ Dataset generator promptGenerate 20 test cases for a support-ticket classifier with categories: billing, bug, feature_request, other. Requirements: - One JSON object per line (JSONL): {"ticket": "...", "expected": "..."} - Realistic voices: typos, anger, politeness, non-native phrasing, ALL CAPS - Include at least 6 hard cases: tickets mixing two categories (pick the dominant one), vague one-liners, and 2 that are clearly "other" - Vary length from 5 to 80 words - No two tickets about the same product feature

Then the non-negotiable step: read every generated case and fix or delete the bad ones. Claude's label IS your answer key - an unreviewed answer key silently corrupts every eval you run against it. Add real production examples (anonymized) as they accumulate; synthetic data starts the flywheel, real data keeps it honest.

CoreCode-based grading: cheap and objective3 min

If the output is structured - a category, a JSON object, a SQL string - grade it with plain Python. Exact match, regex, or schema checks: free, instant, deterministic, and immune to flattery.

Which grader for this output Is the output structured (JSON, SQL)? yes no Code-based grader exact match or schema Model-based grader rubric graded by Claude Code grading is free and objective; model grading needs a spot-checked rubric.
🔍 Click to zoom - grade with code when you can, a rubric only when you must
★ Three code gradersimport json import re def grade_exact(output: str, expected: str) -> bool: return output.strip().lower() == expected.strip().lower() def grade_regex(output: str) -> bool: # e.g. answer must contain a date in YYYY-MM-DD form return re.search(r"\b\d{4}-\d{2}-\d{2}\b", output) is not None def grade_json_shape(output: str, required_keys: set) -> bool: try: data = json.loads(output) except json.JSONDecodeError: return False return required_keys.issubset(data.keys())

Design your task to be code-gradable where you can - forcing a category from a fixed list, or JSON with known keys, is often a better prompt AND a free grader. Save the expensive grading for outputs that genuinely need judgment.

CoreModel-based grading: rubrics for the subjective4 min

Summaries, emails, explanations - no regex can score "faithful and clear". Instead, a second Claude call grades the first against a written rubric. The rubric is the whole game: grade specific, checkable qualities, never "is this good?".

★ A model graderimport json import anthropic client = anthropic.Anthropic() GRADER_SYSTEM = """You are grading a summary against a rubric. Score 1-5: 5 = all key facts present, nothing invented, under the length limit, plain language 4 = one minor omission, otherwise correct 3 = key facts present but includes unsupported claims OR over length 2 = a major fact missing or distorted 1 = misleading, off-topic, or ignores the format Respond with ONLY JSON: {"score": int, "reason": "one sentence"}""" def grade_summary(source: str, summary: str) -> dict: response = client.messages.create( model="claude-sonnet-5", max_tokens=256, temperature=0, system=GRADER_SYSTEM, messages=[ {"role": "user", "content": f"<source>\n{source}\n</source>\n\n<summary>\n{summary}\n</summary>"}, {"role": "assistant", "content": "{"}, ], ) return json.loads("{" + response.content[0].text)

Note the 6.1 moves reused: temperature 0, JSON prefill, validation. Spot-check the grader against your own judgment on 10 cases before trusting it at scale - a grader is a prompt, and prompts need evals too. Which grader when:

Code-basedModel-based
Best forCategories, JSON, extraction, SQL, anything with one right answerSummaries, tone, explanations, "did it follow the style guide"
Cost & speedFree, instantOne extra API call per case
ObjectivityPerfectGood with a tight rubric; drifts without one
Watch out forFalse fails on harmless format variationGrading its own vibe - always spot-check vs a human
AdvancedA minimal eval harness3 min

Everything above, assembled. A JSONL dataset, a loop, a grader, a pass rate. This is genuinely all an eval needs to be useful - frameworks add convenience later, not correctness.

★ eval.py - the whole harnessimport json import anthropic client = anthropic.Anthropic() PROMPT_VERSION = "v3-fewshot" SYSTEM = """Classify the support ticket into exactly one category: billing, bug, feature_request, or other. Reply with the category only.""" def run_case(ticket: str) -> str: response = client.messages.create( model="claude-sonnet-5", max_tokens=16, temperature=0, system=SYSTEM, messages=[{"role": "user", "content": ticket}], ) return response.content[0].text.strip().lower() passed, failed = 0, [] with open("tickets_eval.jsonl") as f: cases = [json.loads(line) for line in f] for case in cases: got = run_case(case["ticket"]) if got == case["expected"]: passed += 1 else: failed.append((case["ticket"][:60], case["expected"], got)) print(f"{PROMPT_VERSION}: {passed}/{len(cases)} passed ({passed / len(cases):.0%})") for ticket, expected, got in failed: print(f" FAIL: {ticket!r} expected={expected} got={got}")

Workflow: change the prompt, bump PROMPT_VERSION, rerun, read the failures - they tell you exactly what to fix next. Keep a log of version → pass rate and prompt tuning becomes boring, measurable engineering. Which was the point.

Try it yourself

Three builds ◐ 10 min in session, finish after

Keep the dataset Your eval set outlives any single prompt - it also regression-tests model upgrades ("does claude-sonnet-6 still pass?") and fast-tier swaps from 6.1. Treat it like test code: version it, grow it with real failures.
Source material

Official courses covered

This page teaches the shared Prompt Engineering and Prompt Evaluation modules from the three official 8-hour engineering courses at claude.com/resources/courses.

Prompt Engineering module - all 3 coursesbeing clear and direct, being specific, structure with XML tags, providing examples
Prompt Evaluation module - all 3 courseseval workflow, generating test datasets, code-based and model-based grading

Deep dive 6.2 cheat sheet · pin this

Specific beats cleverName audience, format, limits, and empty-case behavior. Every vague word is a delegated decision.
XML tagsWrap different kinds of content: <context>, <instructions>, <examples>. Boundaries beat prose.
Few-shot1-3 gold input→output examples beat paragraphs of description. Short, varied, genuinely gold.
Eval loopDefine task → build dataset → run → grade → change ONE thing → rerun. Compare pass rates, not vibes.
GradingStructured output → code (exact/regex/schema, free). Subjective → model grader with a tight 1-5 rubric, spot-checked.
Dataset ruleClaude generates cases, a human reviews every label. Unreviewed answer keys corrupt every eval after them.