Module 3 — Zero-shot, one-shot, few-shot
Module 2 stabilised the framing of the running case. On most emails, the model now returns the four fields as requested. On a few emails, it still fails in interesting ways: ambiguous urgency, sarcasm mistaken for a request, several products mentioned. This module fixes those cases by showing the model what "good" looks like, and explains why the number and order of examples both matter.
Three modes, one continuum
The three names describe the same lever pulled by different amounts:
| Mode | Number of examples | Typical use |
|---|---|---|
| Zero-shot | 0 | task the model likely saw during training, well-known format |
| One-shot | 1 | you need to lock down the exact output format |
| Few-shot | 2 to 10 | you need to teach a distinction the model does not naturally make |
There is no magic number. Beyond ten examples, gains plateau on most tasks and the token cost climbs linearly. Below that, each added example teaches the model something specific, and the choice of which example to add is what this module is about.
What one example teaches
A single example is often enough to fix the format without touching the task. Adding one shot to the running case, still without changing the system message:
EXAMPLE_EMAIL = """Subject: Item DEF-12 arrived scratched
Hello, the DEF-12 bookshelf I ordered on the 3rd arrived with a long
scratch on the top panel. It's not urgent, I just want a partial refund
or a replacement panel if possible. Thanks, Marc."""
EXAMPLE_OUTPUT = """reason: cosmetic damage on arrival
product: DEF-12 bookshelf
urgency: low
requested_action: partial refund or replacement panel"""
def extract(email: str) -> str:
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": SYSTEM},
{"role": "user", "content": EXAMPLE_EMAIL},
{"role": "assistant", "content": EXAMPLE_OUTPUT},
{"role": "user", "content": email},
],
temperature=0,
)
return response.choices[0].message.content
Notice the pattern: the example is passed as a real user turn followed by a real assistant turn. This is the strongest way to demonstrate format, because it matches the exact shape the model will produce.
What several contrasting examples teach
Format is only worth one example. Distinctions require several, each showing a different regime. For the running case, three examples that contrast on urgency are more useful than three examples that all look alike:
FEW_SHOT = [
("Item DEF-12 arrived scratched...", "urgency: low"),
("I fly Wednesday and the charger stopped...", "urgency: high"),
("Whenever you get a chance, the app crashes on...", "urgency: low"),
]
The pair "I fly Wednesday" versus "whenever you get a chance" teaches the model the axis you care about — presence or absence of a deadline — rather than the surface wording. Two examples that both say "urgent" teach nothing beyond the word.
The general principle: cover the boundaries of your labels, not the easy middle. Three examples of clearly high urgency and three of clearly low urgency leave the model as helpless as before on the ambiguous cases in between.
Order matters: the recency bias
Language models weigh later tokens more heavily than earlier ones. On a few-shot prompt with five examples of the same class followed by one of another, the model tends to predict the last-seen class. This is the recency bias, and it destroys accuracy on class-imbalanced tasks.
Two defences:
- Shuffle the order of examples across calls, so no single class sits systematically at the end. In an evaluation loop this is done once per test input.
- Match the proportion of labels in the examples to the proportion in your real inputs. If eighty per cent of complaints are low urgency, five examples all high urgency is deliberately misleading.
import random
def build_messages(email, examples, system):
random.shuffle(examples)
messages = [{"role": "system", "content": system}]
for e_email, e_output in examples:
messages.append({"role": "user", "content": e_email})
messages.append({"role": "assistant", "content": e_output})
messages.append({"role": "user", "content": email})
return messages
Reusing an example from the same distribution as the input inflates apparent accuracy: the model has seen the answer nearby. Draw examples from a held-out set kept only for prompting, never from the same data you evaluate on.
Token cost and its consequences
Every example is billed on every call. On the running case, one example adds about a hundred tokens; five examples add five hundred. For a million monthly calls at per million input tokens, the difference is real:
\bar{t}_{\text{example}} \times p_{\text{token}}$$ With $N_{\text{calls}} = 10^6$, $N_{\text{examples}} = 5$, $\bar{t}_{\text{example}} = 100$ and $p_{\text{token}} = 1.5 \times 10^{-7}$ dollars per token, the extra bill is 75 dollars per month per prompt. Modest, but a five-prompt library at fifty examples each starts to matter. The good news is that models with **prompt caching** amortise repeated system messages and examples across calls at a fraction of the price, often a tenth or less. Long, stable prefixes are far cheaper than long, variable ones — a good reason to keep the system message and the few-shot examples in a fixed order per template. :::tip[Compare shot counts on a real test set] Do not choose zero, one or five shots by intuition. Run each variant on the same twenty inputs, score per field, and read the numbers. Module 9 turns this into a repeatable protocol. ::: ## In summary - Add **one example** to lock down the output format; add **several contrasting examples** to teach a distinction the model does not naturally make. - Cover the **boundaries** of your labels rather than the easy middle, and match the proportion of labels in examples to the proportion in real inputs. - Beware the **recency bias**: the last-seen class weighs more, so shuffle examples and never stack the same label at the end. - Every example is billed on every call; **prompt caching** amortises stable prefixes cheaply, provided the order does not change. Next module: chain-of-thought reasoning, when it lifts scores and when it silently drops them.