In the previous lesson we pinned down DocuBot's system prompt v1 and learned to structure a prompt into well-delimited parts. That covers the straightforward tasks: asking, summarizing, answering with context. But DocuBot has finer tasks ahead — for example, classifying each Nubelia support question to route it to the right team — where a good instruction isn't enough: the model needs to see examples, or needs room to reason before answering, or the task is simply too big for a single prompt. This lesson presents the three fundamental prompting techniques — few-shot, chain of thought, and task decomposition — and ends by turning our prompts into reusable Python templates, the format in which they'll travel to module 3 when we start calling the API from code. We're still not touching any API: Python appears only to build the text of the prompts.

Contents

  1. Zero-shot: the default technique
  2. Few-shot: teaching with examples
  3. Chain of thought: giving room to think
  4. Task decomposition: conceptual prompt chaining
  5. Prompt templates in Python
  6. Comparison table: which technique to use when

Zero-shot: the default technique

Zero-shot means asking for the task without showing any solved example: instructions only. Everything we did in 02-01 was zero-shot. Thanks to the instruction fine-tuning we saw in 01-02, today's models solve an enormous range of tasks this way: summarizing, translating, answering with context, extracting obvious data.

Take DocuBot's new task: classifying Nubelia support questions into categories for routing. Zero-shot version:

Classify the following Nubelia support question into ONE of these
categories: billing, permissions, integrations, api, incidents, other.
Respond only with the category name.

<question>
I can't get my colleague to see the Atlas project board even though I
sent her the invitation.
</question>

Expected response: permissions. And for many cases it will work. The problem appears at the boundaries between categories, which instructions can't fully define in words:

  • "Why was I charged twice this month?" → billing or incidents? (It's a possible failure, but a billing one.)
  • "The Slack webhook stopped arriving yesterday" → integrations, api, or incidents?

You could try to cover every boundary with more written rules ("if it's a failure related to charges, use billing; if…"), but the rules multiply, contradict each other, and never cover everything. When you notice you're writing the fifth paragraph of nuances, that's the signal to switch techniques.

Few-shot: teaching with examples

Few-shot consists of including several solved examples in the prompt (typically 2 to 8) before the real input. The model, which as we saw in 01-02 is a pattern-continuation machine, picks up from the examples nuances that would be hard to verbalize: where the boundaries are, what exact format you want, what tone. Here is the same classification task, now with examples chosen precisely at the hard boundaries:

Classify the Nubelia support question into ONE of these categories:
billing, permissions, integrations, api, incidents, other.
Respond only with the category name.

Examples:

Question: How do I download the March invoice?
Category: billing

Question: Why was I charged twice this month?
Category: billing

Question: The boards page has been taking over a minute to load since yesterday.
Category: incidents

Question: The webhook I set up to Slack stopped sending messages.
Category: integrations

Question: What is the per-minute request limit on the tasks endpoint?
Category: api

Question: Do you offer discounts for NGOs?
Category: other

Question: {user_question}
Category:

Pay attention to the design decisions, because choosing the examples is 90% of few-shot:

  • The examples define the boundaries, not the center. "How do I download the invoice?" alone contributes almost nothing (zero-shot already gets it right); the valuable example is "I was charged twice" → billing, which resolves the billing/incident ambiguity by demonstration. Same with the Slack webhook → integrations (and not api or incidents).
  • Cover all the categories if you can, or at least the conflicting ones. A few-shot prompt where 5 of 6 examples are billing biases the model toward that label (models are sensitive to the distribution and even to the order of examples; it's worth shuffling them).
  • The format of the examples IS the format of the output. Because each example ends in Category: <label> and nothing else, the model will continue the pattern with the bare label. Few-shot is also the most effective way to pin down format — an idea that will reappear with the JSON of lesson 02-03.
  • Examples must be correct and fictional but realistic. A mislabeled example teaches the error just as effectively as a good one teaches the right answer.
  • Every example costs tokens on every call (remember the costs from 01-04). Six short examples are ~150 tokens: negligible. Fifty long examples on every classification, not so much. Start with 3-5 and add only those that fix observed errors.

When to use few-shot instead of zero-shot? Practical rule:

Situation Technique
Common, well-defined task (summarize, translate, answer with context) Zero-shot
Categories or criteria with fuzzy boundaries Few-shot
Very specific or unusual output format Few-shot
Tone/style hard to describe with adjectives Few-shot (style samples)
The zero-shot prompt fails on specific identifiable cases Few-shot with those cases as examples

Chain of thought: giving room to think

Remember the mechanics from 01-02: the model generates token by token, and each token leans on the previous ones. If you force DocuBot to answer billing as its first and only token, all the "deliberation" must fit into a single prediction. The chain-of-thought (CoT) technique consists of asking it to reason in writing before giving the final answer: each generated reasoning step becomes part of the context for the next one, and the conclusion rests on them. It is, literally, giving it room to think.

Example with an ambiguous Nubelia case:

Classify the support question into ONE category: billing, permissions,
integrations, api, incidents, other.

First, in an "Analysis:" section, reason briefly: what the user is
asking for, which categories could apply, and why you choose one.
Then, on a final "Category:" line, write only the label.

<question>
Since we connected Nubelia to our corporate SSO, new users come in as
Viewers and I can't promote them to Editors.
</question>

Expected response:

Analysis: the user mentions the SSO integration, but their real problem
is that they can't change the role of new users: it's a role-management
issue. The integration works (users get in); what's failing or unknown
is the assignment of permissions. I hesitate between integrations and
permissions; the actionable problem is one of permissions.

Category: permissions

Key points for using CoT well:

  • Order matters: reasoning first, answer after. If you ask for "the category and then a justification", the model decides first (with no room to think) and then rationalizes its choice — the justification won't have influenced the answer. CoT only works if the conclusion comes last.
  • Structure the reasoning. "Reason step by step" works; it works better to tell it which steps: "what the user is asking for, which categories could apply, why you choose one". And delimit the final answer with a fixed marker (Category:) so you can extract it reliably.
  • CoT has a cost. More output tokens = more latency and more spend (01-04). Reserve it for tasks with real reasoning: ambiguous cases, decisions with several factors, arithmetic, error analysis. For "how do I download the invoice?" it's throwing money away.
  • Context note: the most recent models incorporate native reasoning modes that do this internally. The technique remains valid and necessary as a prompting tool — and understanding it explains why those modes exist.
  • CoT and few-shot combine well: examples that include the written reasoning teach both the criterion and the process at once.

Task decomposition: conceptual prompt chaining

Some requests no single prompt handles well. Think about the full flow Nubelia wants for DocuBot on the support site:

  1. Classify the user's question (what is it about?).
  2. Given the category, select the relevant documentation.
  3. Draft the answer based on that documentation.
  4. Check that the answer follows the rules (cites a source, doesn't invent).

Cramming it all into one mega-prompt ("classify, and depending on the category search these 12 documents, and draft, and check that…") produces mediocre results: too many competing instructions (the over-constraining problem from 02-01), a huge context with "lost in the middle" risk (01-04), and a failure at any step ruins the whole without you knowing which step failed.

The alternative is prompt chaining: splitting the task into steps, each with its own focused prompt, where the output of one step is the input of the next:

flowchart LR
    A[User question] --> B[Prompt 1:\nclassify]
    B -->|category| C[Select docs\nfor that category]
    C -->|context| D[Prompt 2:\ndraft answer]
    D -->|draft| E[Prompt 3:\ncheck rules]
    E --> F[Final answer]

Advantages for the developer — and note they're the same as those of decomposing functions in code:

  • Each prompt is simple and does one thing: the classifying one doesn't need to know how to write; the checking one only verifies rules.
  • Debuggable: if the final answer is bad, you inspect the intermediate outputs and locate the guilty step.
  • Testable in parts: you can evaluate the classifier separately from the writer — this will be gold in lesson 02-04.
  • Optimizable in parts: the classification step can use a cheaper, faster model than the drafting one (criteria from 01-03).

The cost: more calls = more total latency and more orchestration complexity. For now we stay at the conceptual level and at the design of each step's prompts; the "glue" (code that runs the chain, handles errors, and passes outputs to inputs) is module 3 material, and its evolution — letting the model itself decide which step to run — is the agents of module 5.

Prompt templates in Python

So far we've written prompts as finished text. But look at the classifier's few-shot prompt: it ended in {user_question} — a slot. A production prompt is always a template: a fixed part (instructions, examples) + slots for what varies (the data of each request). Formalizing this in Python is the bridge to module 3. No HTTP calls or SDKs: functions that return text.

# prompts.py — DocuBot prompt templates (v1)

SYSTEM_DOCUBOT = """You are DocuBot, the internal documentation and \
support assistant for Nubelia, a project-management SaaS platform.
...
(the complete system prompt v1 from lesson 02-01)
"""

def answer_prompt(documentation: str, question: str) -> str:
    """Builds the user message for answering a question
    from selected documentation."""
    return f"""<documentation>
{documentation}
</documentation>

<question>
{question}
</question>

Remember: if the answer is not in <documentation>, say exactly
"I can't find that information in the available documentation."."""


CATEGORIES = ["billing", "permissions", "integrations",
              "api", "incidents", "other"]

CLASSIFICATION_EXAMPLES = [
    ("How do I download the March invoice?", "billing"),
    ("Why was I charged twice this month?", "billing"),
    ("The boards page takes over a minute to load.", "incidents"),
    ("The webhook to Slack stopped sending messages.", "integrations"),
    ("What is the request limit on the tasks endpoint?", "api"),
    ("Do you offer discounts for NGOs?", "other"),
]

def classification_prompt(question: str) -> str:
    """Builds the few-shot question-classification prompt."""
    examples = "\n\n".join(
        f"Question: {q}\nCategory: {c}" for q, c in CLASSIFICATION_EXAMPLES
    )
    return f"""Classify the Nubelia support question into ONE of these
categories: {", ".join(CATEGORIES)}.
Respond only with the category name.

Examples:

{examples}

Question: {question}
Category:"""

A breakdown for those just starting with Python:

  • SYSTEM_DOCUBOT is a constant: the system prompt is stable (lesson 02-01: what's stable goes in system), so it lives as a module constant and isn't rebuilt every time.
  • f-strings (f"""...""") interpolate variables into the text: {question} is replaced with the real value. It's the simplest form of templating in Python.
  • The examples live in a list of tuples, not embedded in the text. Huge advantage: adding or fixing an example means touching a data item, not editing a fragile block of text; and in 02-04 we'll be able to try variants with different sets of examples without duplicating the template.
  • Instruction-data separation, now in code: the template (instructions, from the developer) and the arguments (question, documentation, from the user) come in through different paths, and the data always ends up inside delimiters. Never build prompts by concatenating user text outside its tags.
  • Pure functions: they take data, return a str. Easy to test (assert "How do I archive" in answer_prompt(doc, "How do I archive...?")), easy to version in git like any code, and ready to plug into the API in 03-01.

An illustrative use:

print(classification_prompt("I can't invite an external user"))
# → prints the full prompt with the 6 examples and the question at the
#   end, ready to be sent to the model (module 3).

Comparison table: which technique to use when

Technique What it is When to use it Extra cost DocuBot example
Zero-shot Instructions only Common, well-defined tasks None Answering questions with documentation in the context
Few-shot Instructions + 2-8 solved examples Fuzzy boundaries, exact format, style hard to describe Input tokens per call Classifying support questions into 6 categories
Chain of thought Reasoning in writing before answering Real ambiguity, multiple factors, analysis, arithmetic Output tokens and latency Deciding the category of an SSO/permissions boundary case
Decomposition (chaining) Several chained prompts, output → input The task has distinct phases or a single prompt fails/is undebuggable Multiple calls, orchestration Classify → select docs → draft → check

And they combine: DocuBot's chain will use few-shot in the classification step and zero-shot in the drafting step; the checking step can use CoT. Choosing a technique is a per-step decision, not a per-system one.

Common Mistakes and Tips

  • "Textbook" few-shot examples that teach nothing. If all your examples are obvious cases that zero-shot already gets right, you pay tokens without gaining accuracy. The valuable examples are the ones you yourself would struggle to classify on the first try.
  • Examples with inconsistent formats. If one example ends in Category: permissions and another in Answer: the category is permissions., the model doesn't know which pattern to continue. Identical format in all of them.
  • Asking for the justification after the answer and believing it's CoT. Conclusion first = rationalization, not reasoning. The final answer always goes last.
  • Applying CoT to everything by default. On trivial tasks it only adds cost and latency, and sometimes even hurts: the model invents complexity where there is none.
  • The mega-prompt that does everything. If your prompt has three distinct tasks ("classify and also draft and also check"), you'll almost always come out ahead by splitting it into a chain.
  • Building prompts by concatenating strings without delimiters ("Summarize: " + user_text). It reintroduces the instruction/data problem we solved in 02-01. Data always goes inside its tags and through the template's parameters.
  • Tip: keep your templates in their own module (prompts.py) from day one. Prompts are code: they get reviewed, versioned, and — as we'll see in 02-04 — tested.

Exercises

Exercise 1. DocuBot's few-shot classifier systematically confuses these two questions: "The API has been returning 500 since this morning on /v1/tasks" (should be incidents: something is broken) and "What error codes can /v1/tasks return?" (should be api: a usage question). Write the two few-shot examples you would add to the prompt to fix it and justify why those.

Exercise 2. Write a chain-of-thought prompt for this Nubelia support case: "I want external clients to see the project's progress but without being able to see the costs." The prompt must structure the reasoning (what the user needs, what options the documentation offers, which one fits) and end with a recommendation delimited by the marker Recommendation:. Include a made-up <documentation> block with two plausible product options (for example, "guests with the Viewer role" and "public project status page").

Exercise 3. Write in Python a function summary_prompt(title: str, text: str, max_points: int = 5) -> str that generates a prompt to summarize a document from Nubelia's wiki in at most max_points bullet points, aimed at support agents, with the text delimited in <document> and the critical instruction repeated at the end (remember "lost in the middle").

Solutions

Solution 1. Examples to add:

Question: The API has been returning error 500 since this morning on /v1/tasks.
Category: incidents

Question: What error codes can the /v1/tasks endpoint return?
Category: api

Justification: they are a minimal contrastive pair — nearly identical on the surface (both mention the API and errors) but with different labels. Placed together, they teach exactly the boundary the model confuses: something worked and has brokenincidents; a question about documented behaviorapi. This is the general strategy: turn every observed error into the example that fixes it.

Solution 2. One possible solution:

You are DocuBot. A Nubelia employee describes a client's need.
Analyze the situation using ONLY the provided documentation.

Structure your response like this:
Analysis: (1) what exactly the user needs, (2) which options from the
documentation could work, (3) what each one offers and what it doesn't
cover.
Recommendation: the chosen option and its main limitation, in 2
sentences.

<documentation>
## External guests
You can invite external users with the Viewer role: they see boards,
tasks and progress, including budget fields if the project has them
visible.

## Project status page
Each project can publish a read-only status page with milestones and
percentage of progress. It does not show tasks, comments or budget
fields.
</documentation>

<question>
I want external clients to see the project's progress but without
being able to see the costs.
</question>

Expected response (abridged): the analysis should detect that the Viewer role exposes the budget fields (it fails the requirement of hiding costs) and that the status page shows progress without budget, and conclude Recommendation: use the project status page; limitation: clients won't see the task-level detail. The value of CoT here is that it forces contrasting each option against the "no costs" requirement before choosing.

Solution 3.

def summary_prompt(title: str, text: str, max_points: int = 5) -> str:
    """Prompt for summarizing a document from Nubelia's wiki."""
    return f"""Summarize the Nubelia wiki document titled
"{title}" in at most {max_points} bullet points, aimed at a support
agent who needs to answer tickets. Prioritize actionable steps and
product limitations; omit history and acknowledgements.

<document>
{text}
</document>

Remember: at most {max_points} bullet points and only information
present in <document>."""

Key points: max_points with a default value is interpolated twice (initial instruction and final reminder), the text is delimited, and the function is pure — it takes data and returns the prompt as a str.

Conclusion

Your prompting toolbox now has depth: zero-shot as the default option, few-shot for teaching boundaries and formats through well-chosen examples (the Nubelia support classifier's are now pinned down in CLASSIFICATION_EXAMPLES), chain of thought to give the model deliberation space before the answer, and task decomposition when the task overflows a single prompt — DocuBot's chain design (classify → select → draft → check) that we'll materialize over the coming modules. And all of it already lives as Python templates in prompts.py: pure functions that return text, ready to be connected to a real API. But there's a loose end: the classifier returns a label as free text, and Nubelia's support site will need quite a bit more — category, answer, sources, and confidence level — in a format that code can parse without surprises. That's the topic of the next lesson: structured outputs, or how to get a probabilistic model to return JSON you can trust.

© Copyright 2026. All rights reserved