Few-Shot Examples
What Few-Shot Prompting Is
Few-shot prompting means showing the model a small number of worked examples of the task — input paired with the exact output you want — before giving it the real input to handle. The name contrasts with zero-shot prompting, where you only describe the task in words and give no examples at all, which is what every example in Lessons 1 and 2 of this unit has done so far.
# Zero-shot: describe the task, no examples
instructions = "Classify the tone of the message as formal, casual, or urgent."
# Few-shot: show the task being done, then ask for it again
instructions = """Classify the tone of the message as formal, casual, or urgent.
Message: "Per our conversation, please find the attached report."
Tone: formal
Message: "hey lol did you see that email"
Tone: casual
Message: "Server is down, need eyes on this NOW."
Tone: urgent"""
Both versions ask the model to do the same job. The difference is that the second version demonstrates the job three times before asking the model to do it a fourth time on new input. This works because language models are fundamentally pattern-continuation engines: shown a consistent pattern of input-output pairs, the strong statistical pull is to continue that exact pattern rather than to improvise a new one. Few-shot prompting harnesses that pull directly, rather than relying entirely on the model correctly interpreting a verbal description of what you want.
Why Examples Succeed Where Descriptions Sometimes Fail
Lesson 2 of this unit was about removing ambiguity from instructions by describing every decision point explicitly. Few-shot examples are a second, complementary way to remove ambiguity — instead of describing the boundary between categories in words, you demonstrate it with a concrete instance on each side of the boundary. This matters for exactly the cases where a verbal description is hard to write precisely but an example is easy to produce.
Take the tone classifier above. Try to write a purely verbal definition of "casual" precise enough to reliably separate it from "formal" across every message you might encounter, and you will find yourself writing paragraphs of caveats about punctuation, abbreviation, capitalisation, and word choice — and still leaving edge cases unaddressed. Show three examples instead, and the model infers the pattern of what separates the categories directly from the demonstrated instances, often more effectively than from an equivalent-length written definition, because the examples encode nuance (word choice, punctuation style, capitalisation) that would be tedious and incomplete to spell out explicitly.
This is also why few-shot examples are particularly valuable for output formatting that is easier to show than to describe. Explaining in words exactly how you want a citation formatted, or how you want a code diff annotated, or how you want a multi-field extraction laid out, is often more error-prone than simply showing one or two instances of the exact formatting and letting the model match it.
Choosing and Ordering Examples
Few-shot prompting is not simply "add examples until it works" — the choice, number, and order of examples materially affects both accuracy and consistency, and a poorly chosen set of examples can actively mislead the model.
Cover the actual category boundaries, not just easy cases. If a classifier needs to distinguish "mixed" feedback from "negative" feedback, at least one example needs to sit right at that boundary — genuinely mixed feedback that a less careful example set might have simplified into "negative." Examples that are all clear-cut, unambiguous cases teach the model your labels exist, but not where the actual dividing lines are, which is exactly where classification tends to go wrong on real traffic.
Keep the format of every example identical. If your first example writes "Tone: formal" and your third writes "tone -> formal", you have taught the model that the output format is itself somewhat flexible, which undermines the entire reason for showing examples in the first place. Formatting consistency across your examples is at least as important as the content of the examples.
Order matters, and recency has a real effect. Models tend to weight the most recent examples in a sequence somewhat more heavily than earlier ones — a form of recency bias. If your examples are not perfectly balanced across categories, placing an example of a less common but important category last, rather than first, can help counteract the ordering effect and make outputs more balanced across categories rather than mildly biased toward whatever category dominates the least-recent early examples.
Use realistic examples, not sanitised ones. An example written by you, in clean and careful prose, may not resemble the real, messy, occasionally ungrammatical input your application will actually receive. Pull real examples (with any private information removed or replaced) from your actual traffic or logs whenever possible — a few-shot set built from realistic input teaches the model to handle the actual distribution of inputs it will see, not an idealised version of the task.
How Many Examples Are Enough?
There is no fixed number that works universally — it depends on task complexity and how many distinct category boundaries or format nuances need to be demonstrated — but a few practical guidelines hold up well across most classification and extraction tasks.
One example per output category, at minimum, so the model has seen every possible answer at least once, not just described it in words. For a binary classification, two examples is a bare minimum; for a five-category classification, five is a reasonable floor.
Two to three examples per category is a common sweet spot for moderately nuanced tasks, giving enough coverage of the boundary cases described above without the prompt growing so long that it becomes expensive and unwieldy — remember from Unit 1 that every token in your examples is sent, and billed, on every single request.
More examples help less than you'd expect past a certain point, and can sometimes hurt. Adding a tenth example to a task that was already well-specified by five rarely improves accuracy meaningfully, and a very long few-shot block increases cost per request substantially while doing comparatively little additional work — the model has generally extracted the pattern well before example ten. If you find yourself adding many examples and still seeing inconsistent output, the more likely fix is Lesson 2's technique of tightening the verbal instruction alongside the examples, not adding still more examples.
Diminishing and sometimes negative returns from redundant examples. If several of your examples are near-duplicates of each other (three formal examples that are all polite corporate emails, say, with none showing formal-but-terse or formal-but-emotional variation), they teach the model less than three genuinely varied formal examples would, because the model is inferring the boundary of "formal" from the variation across your examples as much as from their count.
Static vs. Dynamic Few-Shot Selection
Lesson 1 of this unit drew a distinction between content that belongs in instructions (stable, applies to every request) and content that belongs in input (specific to the current request). Few-shot examples can genuinely belong in either, depending on how they're selected.
Static few-shot — the same fixed set of examples used for every request — belongs in instructions, exactly like the tone-classifier example earlier in this lesson. This is the right approach when a small, fixed set of examples adequately covers the task's category boundaries, and it has a real efficiency advantage: because instructions is a stable prefix, a static few-shot block benefits fully from prompt caching (Unit 1, Lesson 5), making the marginal cost of the examples on every subsequent request very small.
Dynamic few-shot — selecting the most relevant examples for this specific input from a larger pool, typically via embedding similarity (Unit 10 covers the mechanism in full) — belongs in input, because the selected examples change on every request by definition. This approach is worth the added complexity when your task spans a wide variety of input shapes that no single small static set can adequately represent — a general-purpose support ticket classifier handling everything from billing questions to bug reports, say, where showing five examples of a similar past ticket is more useful than five fixed generic examples that may not resemble the current one at all.
# Dynamic few-shot: examples selected per request, so they belong in input
def build_input(current_message: str, similar_examples: list[tuple[str, str]]) -> str:
example_block = "\n\n".join(
f'Message: "{msg}"\nTone: {tone}' for msg, tone in similar_examples
)
return f"{example_block}\n\nMessage: \"{current_message}\"\nTone:"
response = client.responses.create(
model="gpt-5.6-luna",
instructions="Classify the tone of the message as formal, casual, or urgent. Follow the pattern shown in the examples.",
input=build_input(current_message, similar_examples),
)
Note that even in the dynamic case, the task description ("classify the tone...") still belongs in instructions as the stable part, while only the selected examples plus the new message move into input as the part that changes per request — the static/dynamic distinction applies to the examples specifically, not to the whole prompt.
Few-Shot for Format, Zero-Shot for Judgment
A useful heuristic when deciding whether a given prompt needs few-shot examples at all: examples are most valuable when the hard part of the task is matching a specific format or style, and least valuable — sometimes actively unhelpful — when the hard part is exercising judgment on a case that genuinely differs from anything you could show in advance.
For formatting-heavy tasks — extract these fields in this exact layout, write in this exact tone, follow this exact citation style — few-shot examples are close to essential, because format is precisely the kind of thing that's easy to show and hard to describe completely in words.
For judgment-heavy tasks — is this a good business decision, does this code have a subtle bug, is this argument logically sound — a fixed set of examples can sometimes narrow the model's reasoning toward the surface pattern of your examples rather than genuinely reasoning about the new case on its merits, especially if your examples happen to share superficial features (all bugs in the examples were off-by-one errors, say) that don't generalise to the actual variety of cases you'll see. For this category of task, a precise zero-shot instruction (Lesson 2's techniques) combined, where the model supports it, with a higher reasoning_effort (Lesson 4 of this unit) is often more effective than trying to demonstrate judgment through a handful of worked examples.
Negative and Near-Miss Examples
Every example shown so far has been a positive example — an input paired with the correct output. There is a second, less commonly used technique worth understanding: showing the model an input that looks like it belongs to one category but is deliberately labeled with the correct, less obvious answer, specifically to correct a mistake the model would otherwise make by surface-level pattern matching.
Consider a support-ticket urgency classifier. A message reading "This is absolutely critical, please respond ASAP!!!" looks urgent by its choice of words and punctuation alone. But if that particular phrasing, in your actual data, tends to come from a small number of users who mark everything as critical regardless of actual severity, a purely surface-level classifier will over-predict "urgent" for that user's traffic. A near-miss example makes the boundary explicit:
instructions = """Classify the message as low, medium, or high urgency, based on the
actual technical impact described, not the emotional intensity of the wording.
Message: "This is absolutely critical, please respond ASAP!!! My dashboard widget is the wrong color."
Urgency: low
Message: "Hey, whenever you get a chance — production database is returning errors for all users."
Urgency: high
Message: "URGENT URGENT please help my export button is missing a tooltip"
Urgency: low"""
Both "low" examples use urgent-sounding language attached to a low-impact issue, and the "high" example uses calm language attached to a severe issue. This is a stronger training signal than three examples that simply pair urgent language with "high" and calm language with "low," because it directly demonstrates the distinction the classifier actually needs to make — impact, not tone — rather than leaving the model free to fall back on the surface correlation between emotional language and urgency that its pretraining data likely reinforces.
Near-miss examples are most useful precisely when you have observed the model making a specific, describable mistake in practice. Rather than guessing at what might confuse the model in advance, this technique works best as a repair — after production output review or evaluation (Unit 13 covers this systematically) surfaces a real error pattern, add one example that demonstrates the boundary the model is currently missing. Adding near-miss examples speculatively, before you've observed an actual failure mode, risks manufacturing examples for problems that don't occur in your real traffic, which wastes both prompt tokens and design effort without clear benefit.
Few-Shot Examples Alongside Structured Output Schemas
Unit 6 introduces text.format for enforcing a strict output schema — a JSON structure the API guarantees the model's output will conform to, independent of prompting. It's worth previewing the relationship between few-shot examples and schema enforcement now, because it resolves a question that comes up naturally once you know both techniques exist: if a schema already guarantees the shape of the output, do few-shot examples still add value?
They do, but for a different reason than in the unstructured case. A schema enforces which fields exist and their types — it guarantees you'll get a string where you asked for a string, and an array where you asked for an array. It says nothing about content quality within those constraints: whether a "summary" field is actually concise and accurate, whether a "category" field picks the label that best fits a genuinely ambiguous case, whether extracted values are normalized consistently (e.g., dates as 2026-09-13 rather than sometimes September 13, 2026). Few-shot examples continue to do useful work at this level even when a schema is enforcing the surrounding structure:
response = client.responses.create(
model="gpt-5.6-luna",
instructions="""Extract the invoice fields into the given schema.
Example:
Input: "Invoice #4471, dated March 3rd 2026, due net-30, total $1,240.00 from Acme Supplies."
Output: {"invoice_number": "4471", "date": "2026-03-03", "due_date": "2026-04-02", "total": 1240.00, "vendor": "Acme Supplies"}""",
input=invoice_text,
text={"format": {"type": "json_schema", "name": "invoice", "schema": INVOICE_SCHEMA, "strict": True}},
)
The schema guarantees the response is valid JSON with exactly these fields and types; the example teaches the specific normalization choice — ISO date format, net-30 converted into an actual due date, total as a bare number without a currency symbol — that the schema alone cannot express. This is a case where zero-shot-plus-schema handles the shape problem and few-shot handles the content convention problem, and combining both is more reliable than either alone.
A Full Worked Example: From Zero-Shot Failure to Few-Shot Fix
To see the technique's practical value end to end, consider a task that on the surface looks like simple extraction: pulling structured line items out of free-text purchase requests submitted by employees to a procurement system.
Iteration 1 — zero-shot.
response = client.responses.create(
model="gpt-5.6-luna",
instructions="Extract the item name, quantity, and unit price from the purchase request. Return one line per item.",
input="Need 3 standing desks at around $450 each, and a dozen HDMI cables, maybe $12 a piece.",
)
A plausible zero-shot output:
Standing desks - 3 - $450
HDMI cables - 12 - $12
This looks reasonable at first glance, but it's already inconsistent in a way that will break any downstream parser: item names aren't capitalized consistently with the source ("HDMI cables" vs. "Standing desks" — arbitrary casing decisions the model made on its own), quantities and prices use different separator characters depending on the model's momentary choice ("3" then "-" then "$450" — no fixed field delimiter), and "a dozen" was correctly converted to "12" but nothing in the instructions actually told the model to normalize quantity phrases at all — it happened to get this one right, which is not the same as it reliably getting every phrasing right.
Iteration 2 — few-shot with explicit normalization rules.
instructions = """Extract purchase request line items. For each item, output exactly:
name|quantity|unit_price
Normalize quantities to plain integers (convert phrases like "a dozen" to 12, "a pair" to 2).
Normalize prices to a plain number with two decimal places, no currency symbol.
Use title case for item names.
Example:
Input: "Grab 2 boxes of pens, about $4 each, and a dozen legal pads at $3.50."
Output:
Pens|2|4.00
Legal Pads|12|3.50"""
response = client.responses.create(
model="gpt-5.6-luna",
instructions=instructions,
input="Need 3 standing desks at around $450 each, and a dozen HDMI cables, maybe $12 a piece.",
)
Expected output now:
Standing Desks|3|450.00
Hdmi Cables|12|12.00
This version is dramatically more reliable for a downstream parser: the pipe delimiter is unambiguous, the price format is fixed at two decimals, and the quantity-phrase normalization ("a dozen" → 12) is demonstrated rather than left to chance. Notice this is a case where the example is doing work that a purely verbal instruction struggles to fully specify — you could write out a rule for every possible quantity phrase ("a dozen" means 12, "a pair" means 2, "a few" means...), but one example combined with the general rule "normalize quantities to plain integers" communicates the pattern of normalization far more compactly and completely than an exhaustive verbal enumeration ever could. (One rough edge remains: "Hdmi Cables" is technically wrong title-casing for an acronym — a mistake worth catching in testing and fixing either with a second example that includes an acronym, or by adding an explicit instruction about preserving existing capitalization for acronyms.)
Comparison: Zero-Shot, Static Few-Shot, and Dynamic Few-Shot
| Aspect | Zero-shot | Static few-shot | Dynamic few-shot |
|---|---|---|---|
| Where it lives | instructions only | instructions (stable block) | input (selected per request) |
| Best suited for | Judgment-heavy tasks; simple, well-defined formats | Formatting-heavy tasks with a small, stable set of category boundaries | Tasks spanning a wide, varied input distribution |
| Token cost per request | Lowest | Fixed, moderate | Variable, and does not benefit from caching |
| Prompt caching benefit | Full (entire instructions is stable) | Full (example block is part of the stable prefix) | None (selected examples change every request) |
| Setup complexity | Lowest — just write the instruction | Low — write and test the fixed examples | Higher — requires an example pool, embedding retrieval (Unit 10), and selection logic |
| Failure mode if misused | Ambiguous edge cases handled inconsistently | Overfits to the fixed examples' incidental surface features | Retrieval picks unhelpful or misleading "similar" examples |
This table is a starting point for a design decision, not a strict ranking — many production prompts in practice combine a zero-shot instruction for the general rule with a small static few-shot block for the trickiest boundary cases, without ever needing the added complexity of dynamic selection at all.
Few-Shot Examples Across a Chained Conversation
Lesson 1 of this unit established that instructions are not automatically carried forward when you chain calls with previous_response_id — each call's instructions field applies only to that call. This has a direct, easily overlooked consequence for few-shot examples specifically: if your few-shot block lives in instructions, and you chain a follow-up call without re-supplying that same instructions value, the model no longer has your examples in context for the follow-up turn, even though it "remembers" the earlier conversation's input and output content through the chain.
first = client.responses.create(
model="gpt-5.6-luna",
instructions=FEW_SHOT_TONE_INSTRUCTIONS, # contains the three tone examples
input="Message: \"per our conversation, following up on the report\"\nTone:",
)
# Wrong: omitting instructions means the few-shot examples are gone for this turn
second = client.responses.create(
model="gpt-5.6-luna",
previous_response_id=first.id,
input="Message: \"omg thanks so much!!\"\nTone:",
)
# Right: re-supply the same instructions (cheap, due to prompt caching on the stable prefix)
second = client.responses.create(
model="gpt-5.6-luna",
instructions=FEW_SHOT_TONE_INSTRUCTIONS,
previous_response_id=first.id,
input="Message: \"omg thanks so much!!\"\nTone:",
)
The fix is simple once you know the rule — re-pass the same instructions value on every chained call in the conversation — and because that value is an identical stable string, prompt caching (Unit 1, Lesson 5) means the repeated cost of re-sending it is small. The mistake is easy to make precisely because the first call's behavior looks correct, and the failure only shows up several turns later when classification quality silently degrades, which is why it's worth testing a multi-turn conversation's later turns specifically, not just its first response, whenever few-shot examples live in instructions.
Measuring the Cost of a Few-Shot Block
Because few-shot examples add real, billable tokens, it's worth actually measuring the cost rather than assuming it's negligible, especially before deciding between a static block (cached) and a dynamic one (not cached).
def estimate_fewshot_cost(examples_tokens: int, requests_per_day: int,
input_price_per_million: float, cached_price_per_million: float,
cache_hit_rate: float = 0.0) -> float:
"""Rough daily cost, in dollars, of sending a fixed few-shot block on every request."""
effective_price = (
cached_price_per_million * cache_hit_rate
+ input_price_per_million * (1 - cache_hit_rate)
)
return (examples_tokens * requests_per_day * effective_price) / 1_000_000
# Static few-shot: cached after the first request, using gpt-5.6-luna pricing
static_cost = estimate_fewshot_cost(
examples_tokens=650, requests_per_day=50_000,
input_price_per_million=0.20, cached_price_per_million=0.02, cache_hit_rate=0.9,
)
# Dynamic few-shot: different examples selected per request, so no cache benefit
dynamic_cost = estimate_fewshot_cost(
examples_tokens=650, requests_per_day=50_000,
input_price_per_million=0.20, cached_price_per_million=0.02, cache_hit_rate=0.0,
)
print(f"Static (mostly cached): ${static_cost:.2f}/day")
print(f"Dynamic (never cached): ${dynamic_cost:.2f}/day")
At 50,000 requests a day and a 650-token example block, a static block with a 90% cache hit rate costs roughly $0.65 a day, while the same block sent uncached every time costs roughly $6.50 a day — a tenfold difference driven entirely by whether the examples are stable enough to sit in a cached prefix. This is the concrete version of the caching argument made earlier in this lesson: at meaningful request volume, the decision between static and dynamic few-shot is not just an accuracy trade-off, it's a cost decision worth actually running numbers on rather than guessing at, particularly once a prompt is heading toward production traffic rather than staying at prototype scale.
Common Mistakes
Inconsistent formatting across examples. Covered above — even small inconsistencies (a colon versus an arrow, a trailing period on some outputs but not others) teach the model that the format itself is negotiable, which is precisely the opposite of what few-shot prompting is meant to establish.
Examples that don't cover the actual boundary cases. All-easy examples fail to teach the model where your categories actually diverge, which is exactly where real input tends to be hardest to classify.
Too many examples, driving up cost with little accuracy benefit. Past roughly two to three well-chosen examples per category, additional examples usually add cost without proportional benefit; audit a bloated few-shot block for redundancy before assuming more will help.
Using few-shot to compensate for a vague instruction, instead of alongside a precise one. Examples and precise verbal instructions reinforce each other; examples alone, with no instruction stating the categories and rules explicitly, leave the model to infer the entire task purely from pattern-matching, which is less reliable than combining both.
Forgetting that few-shot examples are billed tokens on every request. A five-example, 800-token few-shot block sent on every single classification call adds real cost at scale; weigh that against the accuracy gain, and consider whether Unit 6's structured outputs or a smaller, more targeted example set can achieve the same reliability more cheaply.
Best Practices
Match your examples' format exactly to the format you want back, down to punctuation and capitalisation — the model will match what it sees, not what you meant.
Include at least one example near each category boundary, not only clear-cut cases, so the model learns where your categories actually diverge.
Prefer real, messy examples from actual data over clean, hand-written ones, since real input is what the model will actually face in production.
Keep static few-shot blocks in instructions to benefit from prompt caching; move to dynamically selected examples in input only when task diversity genuinely requires it.
Re-evaluate your few-shot set whenever you change models. A set of examples tuned against one model's particular tendencies may transfer imperfectly to another, especially across a model family change rather than a minor version bump — treat few-shot examples as part of the same testing discipline Lesson 2 recommended for verbal instructions.