Module 4: Chain-of-Thought and Reasoning

3. Manual CoT: Designing Reasoning Chains

Overview

Manual CoT (also called Few-Shot CoT) hands the model complete examples with explicit reasoning. Unlike Zero-Shot CoT, where you only add a phrase, here you carefully design the reasoning steps you want the model to imitate. The standard format: Input → Reasoning → Answer.

In this capsule you'll learn to design effective reasoning chains for math, logic, code debugging, text analysis, and business decisions.

Estimated time: 75-90 minutes


Why Is Manual CoT More Powerful than Zero-Shot?

Zero-Shot CoT switches on generic reasoning patterns. Manual CoT lets you:

  1. Specify the exact reasoning style you want
  2. Define the level of detail (step by step vs. logical leaps allowed)
  3. Establish the technical vocabulary appropriate to your domain
  4. Control the output format precisely
  5. Demonstrate how to handle special cases or edge cases

Tradeoff: Manual CoT takes more up-front design, but it produces more consistent, more controlled reasoning.


The Standard Format

The canonical Manual CoT format is:

Q: [question or problem]
A: [explicit step-by-step reasoning] ... Answer: [final answer]

Or in a more explicit labeled format:

Input: [problem]
Reasoning: [step 1] → [step 2] → ... → [step N]
Answer: [final answer]

The Anatomy of a Good Example

An effective Manual CoT example has these characteristics:

  • Complexity similar to the real problem: Not too simple, not too different
  • Complete reasoning: It shows every step, it doesn't skip important intermediate steps
  • Consistent terminology: It uses the same terms the model will use when answering
  • A clear answer: The final answer is unambiguous, in the expected format
  • Appropriate length: 3-8 steps depending on the complexity of the domain

A Complete Example: Word Arithmetic

from openai import OpenAI

client = OpenAI()

MATH_WORD_COT_PROMPT = """Solve each math problem showing every reasoning step.

Q: A store has 120 shirts. On Monday it sold 35. On Tuesday it received a shipment of 50 and sold 28. How many shirts does it have now?
A: Let's go step by step.
At the start: 120 shirts.
Monday: sold 35 → 120 - 35 = 85 shirts.
Tuesday received: 85 + 50 = 135 shirts.
Tuesday sold: 135 - 28 = 107 shirts.
Answer: 107 shirts.

Q: Anna earns $2,400 a month. She pays $720 in rent (30%), $360 in food (15%), and saves 20%. How much is left after all of that?
A: Let's go step by step.
Monthly salary: $2,400.
Rent: 30% of 2400 = 0.30 × 2400 = $720.
Food: 15% of 2400 = 0.15 × 2400 = $360.
Savings: 20% of 2400 = 0.20 × 2400 = $480.
Total spent/saved: 720 + 360 + 480 = $1,560.
Money left: 2400 - 1560 = $840.
Answer: $840.

Q: {problem}
A:"""


def solve_math_word(problem: str) -> str:
    """Solves a word arithmetic problem with Manual CoT."""
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {
                "role": "user",
                "content": MATH_WORD_COT_PROMPT.format(problem=problem)
            }
        ],
        temperature=0,
        max_tokens=600
    )
    return response.choices[0].message.content


if __name__ == "__main__":
    problem = """
    A project has 3 teams. Team A does 40% of the work. 
    Team B does twice as much as team C. 
    If team C does 12 hours of work, how many hours does the project have in total?
    """
    print(solve_math_word(problem))

Example: Formal Logical Reasoning

LOGIC_COT_PROMPT = """Analyze the validity of each logical argument, identifying the premises and the conclusion.

Q: If it rains, then the ground is wet. The ground is wet. Is it raining?
A: Let's go step by step.
Premise 1: If it rains → ground wet. (P → Q)
Premise 2: Ground wet. (Q is true)
Can we conclude that it's raining? (Is P true?)
Analysis: Having Q true does NOT guarantee P. The ground can be wet from sprinklers, a spill, etc.
This is the fallacy of "affirming the consequent".
Answer: INVALID. You cannot conclude that it's raining just because the ground is wet.

Q: All mammals are warm-blooded. Dolphins are mammals. Are dolphins warm-blooded?
A: Let's go step by step.
Premise 1: ∀x: mammal(x) → warm_blooded(x). (Universal)
Premise 2: a dolphin is a mammal.
Desired conclusion: a dolphin is warm-blooded.
Analysis: We apply modus ponens. For x = dolphin: mammal(dolphin) is true (premise 2). Therefore warm_blooded(dolphin) is true by premise 1.
Answer: VALID. Dolphins are indeed warm-blooded. A correct argument by modus ponens.

Q: {argument}
A:"""


def analyze_argument(argument: str) -> str:
    """Analyzes the validity of a logical argument with Manual CoT."""
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {
                "role": "user",
                "content": LOGIC_COT_PROMPT.format(argument=argument)
            }
        ],
        temperature=0,
        max_tokens=500
    )
    return response.choices[0].message.content

Example: Code Debugging

CODE_DEBUG_COT_PROMPT = """Debug the Python code by identifying the error, explaining why it happens, and proposing the fix.

Q: 
```python
def calculate_average(numbers):
    total = 0
    for n in numbers:
        total += n
    return total / len(numbers)

print(calculate_average([]))

A: Let's go step by step. Execution trace:

  • calculate_average is called with the empty list [].
  • The loop doesn't run (empty list).
  • total = 0.
  • return 0 / len([]) → 0 / 0. Error: ZeroDivisionError: division by zero. Root cause: There's no check for an empty list. Fix:
def calculate_average(numbers):
    if not numbers:
        return 0  # Or raise ValueError("Empty list")
    total = sum(numbers)
    return total / len(numbers)

Answer: ZeroDivisionError from an empty list. Add an if not numbers check.

Q:

names = ["Anna", "Beth", "Carl"]
for i in range(len(names) + 1):
    print(f"Hello, {names[i]}!")

A: Let's go step by step. Execution trace:

  • names has 3 elements: indices 0, 1, 2.
  • range(len(names) + 1) = range(4) = [0, 1, 2, 3].
  • Iteration i=0: names[0] = "Anna" → OK.
  • Iteration i=1: names[1] = "Beth" → OK.
  • Iteration i=2: names[2] = "Carl" → OK.
  • Iteration i=3: names[3] → IndexError: list index out of range. Error: IndexError at i=3 because the highest valid index is 2. Root cause: range uses an unnecessary + 1. Fix:
for i in range(len(names)):  # Drop the + 1
    print(f"Hello, {names[i]}!")
# Or better still:
for name in names:
    print(f"Hello, {name}!")

Answer: IndexError from range(len+1). Change it to range(len(names)) or iterate directly.

Q: {code} A:"""

def debug_code(code: str) -> str: """Debugs Python code with Manual CoT.""" response = client.chat.completions.create( model="gpt-4o-mini", messages=[ { "role": "user", "content": CODE_DEBUG_COT_PROMPT.format(code=code) } ], temperature=0, max_tokens=700 ) return response.choices[0].message.content


---

## Example: Contextual Sentiment Analysis

CoT isn't only for math. It works for ambiguous classification:

```python
SENTIMENT_COT_PROMPT = """Classify the sentiment of the text (POSITIVE, NEGATIVE, NEUTRAL or MIXED) by reasoning about each element.

Q: "The hotel had an incredible view, but the rooms smelled of damp and the breakfast service was painfully slow."
A: Let's go step by step.
Element 1: "incredible view" → very positive (a standout sensory experience).
Element 2: "rooms smelled of damp" → very negative (an unpleasant sensory experience, directly hits comfort).
Element 3: "painfully slow breakfast service" → negative (hurts convenience).
Count: 1 positive vs. 2 negatives.
Weight: Damp in the rooms is a bigger problem than the view is a benefit.
Conclusion: The text is predominantly negative with one positive element.
Answer: MIXED (leaning NEGATIVE). Confidence: 0.75.

Q: "Delivered on time as always. Thanks."
A: Let's go step by step.
Element 1: "Delivered on time" → positive (meets the expectation).
Element 2: "as always" → reinforces the positive (consistency).
Element 3: "Thanks" → mildly positive (an expression of gratitude).
There are no negative elements.
Conclusion: Clearly positive text, though moderate (no exceptional enthusiasm).
Answer: POSITIVE. Confidence: 0.90.

Q: {text}
A:"""


def analyze_sentiment(text: str) -> dict:
    """Analyzes sentiment with explicit reasoning."""
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {
                "role": "user",
                "content": SENTIMENT_COT_PROMPT.format(text=text)
            }
        ],
        temperature=0,
        max_tokens=400
    )
    output = response.choices[0].message.content
    
    # Parse the structured result
    import re
    sentiment_match = re.search(
        r'Answer:\s*(POSITIVE|NEGATIVE|NEUTRAL|MIXED)', 
        output, re.IGNORECASE
    )
    confidence_match = re.search(r'Confidence:\s*(0\.\d+|1\.0)', output)
    
    return {
        "reasoning": output,
        "sentiment": sentiment_match.group(1) if sentiment_match else "UNKNOWN",
        "confidence": float(confidence_match.group(1)) if confidence_match else None
    }

Example: Business Decisions

DECISION_COT_PROMPT = """Analyze the business decision by evaluating the key factors step by step.

Q: A 10-person startup has $200K in the bank. They burn $40K/month. They're offered a $500K investment round with 20% dilution. The CEO estimates that with that money they could reach break-even in 12 months. Without the investment, they have 5 months of runway. Should they take the investment?
A: Let's go step by step.
Current situation: $200K cash / $40K burn = 5 months of runway.
Option A (Take the investment):
- Cash after the round: 200K + 500K = $700K.
- Runway: 700K / 40K = 17.5 months.
- Dilution: the CEO and the team lose 20% of their stake.
- Upside: 12 months to reach break-even; if they make it, the remaining 80% could be worth far more.
Option B (Don't take the investment):
- Only 5 months to become profitable or find another source of capital.
- High time pressure; sub-optimal decisions under pressure.
- No dilution, but a high risk of shutting down.
Risk analysis: With 5 months, if break-even isn't reached, the startup dies. With 17.5 months, there's room to pivot if needed.
Qualitative factors: Quality of the investors, terms of the deal, the CEO's confidence in the 12-month plan.
Answer: TAKE THE INVESTMENT. The risk of running out of money in 5 months outweighs the cost of 20% dilution, especially if the investors add strategic value.

Q: {situation}
A:"""

Designing Examples: Advanced Principles

Principle 1: Diversity of Reasoning Types

Your examples should cover the different "types" of problem the model may run into:

# For a QA system over legal contracts
EXAMPLES_LEGAL = [
    {
        "input": "Can the client cancel the contract if the supplier delivers 3 days late?",
        "type": "direct_interpretation",
        "reasoning": """
Look for the delay clause in the contract.
Clause 8.2: "Delays under 5 business days do not constitute a material breach."
3 days < 5 days → It is not a material breach under the clause.
With no material breach, the client cannot cancel without a penalty.
Answer: NO, they cannot cancel without a penalty. A 3-day delay is not a material breach under clause 8.2."""
    },
    {
        "input": "The contract says 'prices subject to CPI adjustment'. CPI rose 8.5% this year. How much does the $10,000/month price go up?",
        "type": "calculation_with_interpretation",
        "reasoning": """
Adjustment clause: price subject to CPI (Consumer Price Index).
Current price: $10,000/month.
CPI change: 8.5%.
Calculation: 10,000 × 1.085 = $10,850/month.
Increase: $850/month.
Answer: The new price is $10,850/month. An increase of $850/month."""
    }
]

Principle 2: Consistency in the Answer Format

# ❌ Inconsistent format across examples
bad_example = """
Q: What is 5 × 7?
A: 5 times 7 = 35

Q: What is 8 × 9?
A: Let's go step by step. 8 × 9 = 72.
Answer: seventy-two
"""

# ✅ Consistent format
good_example = """
Q: What is 5 × 7?
A: Step by step.
5 × 7 = 35.
Answer: 35.

Q: What is 8 × 9?
A: Step by step.
8 × 9 = 72.
Answer: 72.
"""

Principle 3: Representative Difficulty

def select_examples(example_pool: list[dict], new_problem: str) -> list[dict]:
    """
    Selects the most representative examples for a new problem.
    
    In production, you could use embeddings for similarity search.
    For prototypes, manual selection is enough.
    """
    # Simplified: select by problem type
    # In production: use sentence embeddings + cosine similarity
    
    detected_type = detect_type(new_problem)
    examples_of_type = [e for e in example_pool if e["type"] == detected_type]
    
    # Select 2-3 examples from different sub-types
    return examples_of_type[:3]


def detect_type(problem: str) -> str:
    """Detects the problem type in order to select examples."""
    keywords = {
        "arithmetic": ["how much", "total", "average", "percentage", "$", "price"],
        "logic": ["implies", "if...then", "therefore", "valid", "all", "some"],
        "code": ["def ", "class ", "error", "bug", "function", "python", "javascript"],
        "decision": ["should i", "should we", "best option", "advantages", "disadvantages"],
    }
    
    problem_lower = problem.lower()
    for problem_type, kwords in keywords.items():
        if any(kw in problem_lower for kw in kwords):
            return problem_type
    return "general"

Comparison: Zero-Shot CoT vs. Manual CoT

AspectZero-Shot CoTManual CoT
Design effortMinimalHigh
Tokens in the promptFewMany (examples)
Format consistencyMediumHigh
Control over the reasoningLowHigh
GeneralizationHighMedium (depends on the examples)
Best forPrototypes, general domainsProduction, specific domains
Cost per callLowerHigher (more input tokens)

How Many Examples You Need

The research suggests a curve of diminishing returns:

Number of examplesTypical improvementToken cost
0 (Zero-Shot)Baseline0 extra
1 example+10-15%~200-400 tokens
2-3 examples+20-30%~400-800 tokens
4-5 examples+30-35%~800-1500 tokens
6-8 examples+35-37%~1500-2500 tokens
> 8 examplesMinimal returnsVery expensive

Practical recommendation: 2-3 well-designed examples are almost always enough.


Advanced Implementation with Pydantic

from pydantic import BaseModel, Field
from openai import OpenAI
from typing import Literal

client = OpenAI()


class CoTExample(BaseModel):
    """Structure for a Manual CoT example."""
    input_problem: str = Field(description="The problem or question")
    reasoning: str = Field(description="The reasoning steps")
    answer: str = Field(description="The final answer")
    type: str = Field(default="general", description="Category of the problem")


class ManualCoTEngine:
    """A configurable reasoning engine built on Manual CoT."""
    
    def __init__(
        self, 
        examples: list[CoTExample],
        system_instruction: str = "You are an assistant that reasons step by step.",
        max_tokens: int = 700
    ):
        self.examples = examples
        self.system_instruction = system_instruction
        self.max_tokens = max_tokens
    
    def build_prompt(self, problem: str, n_examples: int = 3) -> str:
        """Builds the prompt with the selected examples."""
        # Take the first n_examples (or all of them if there are fewer)
        selected_examples = self.examples[:n_examples]
        
        parts = []
        for ex in selected_examples:
            parts.append(
                f"Q: {ex.input_problem}\n"
                f"A: {ex.reasoning}\n"
                f"Answer: {ex.answer}"
            )
        
        examples_prompt = "\n\n".join(parts)
        
        return f"""{examples_prompt}

Q: {problem}
A:"""
    
    def solve(self, problem: str, n_examples: int = 3) -> dict:
        """Solves a problem using Manual CoT."""
        prompt = self.build_prompt(problem, n_examples)
        
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[
                {"role": "system", "content": self.system_instruction},
                {"role": "user", "content": prompt}
            ],
            temperature=0,
            max_tokens=self.max_tokens
        )
        
        output = response.choices[0].message.content
        
        return {
            "problem": problem,
            "full_reasoning": output,
            "tokens_used": response.usage.total_tokens,
            "n_examples_used": n_examples
        }


# Using the engine
if __name__ == "__main__":
    math_examples = [
        CoTExample(
            input_problem="A coffee shop sells 150 cups/day at $3.50 each. Variable costs are $1.20/cup and fixed costs $200/day. What is the daily profit?",
            reasoning="Revenue: 150 × 3.50 = $525.\nVariable costs: 150 × 1.20 = $180.\nFixed costs: $200.\nTotal costs: 180 + 200 = $380.\nProfit: 525 - 380 = $145.",
            answer="$145",
            type="financial"
        ),
        CoTExample(
            input_problem="How many days does a team of 4 people take to finish a task that one person would do in 20 days?",
            reasoning="Total work: 1 task = 20 person-days.\nTeam capacity: 4 people/day.\nDays needed: 20 / 4 = 5 days.",
            answer="5 days",
            type="work"
        )
    ]
    
    engine = ManualCoTEngine(
        examples=math_examples,
        system_instruction="You are an expert in business math. Solve step by step."
    )
    
    result = engine.solve(
        "A company has 3 salespeople. In one month they sold $45,000, $38,000 and $52,000 respectively. The commission is 6% of total sales. How much commission does each salesperson earn?"
    )
    
    print(result["full_reasoning"])
    print(f"\nTokens used: {result['tokens_used']}")

Troubleshooting

Problem 1: The model doesn't follow the example format

Symptoms: The model gives the answer with no reasoning, or uses a different format.

# ❌ Cause: The examples are in a different language than the problem
bad_prompt = """
Q: ¿Cuánto es 5 + 3?
A: Paso a paso. 5 + 3 = 8. Respuesta: 8.

Q: What is 7 × 6?
A:"""  # The model may answer in Spanish or without the format

# ✅ Fix: Same language and format in the examples and the problem
good_prompt = """
Q: What is 5 + 3?
A: Step by step. 5 + 3 = 8. Answer: 8.

Q: What is 7 × 6?
A:"""

Problem 2: The examples are too simple

Symptoms: The model follows the format but applies shallow reasoning to the real problem.

# ❌ An example that's far too simple for complex problems
unsuitable_example = """
Q: What is 2 + 2?
A: 2 + 2 = 4. Answer: 4.

Q: [Complex conditional probability problem]
A:"""
# The model "imitates" the simplicity of the example

# ✅ Examples of a difficulty similar to the real problem
suitable_example = """
Q: [Moderate probability problem]
A: [5-6 steps of reasoning with probabilities]

Q: [Complex conditional probability problem]
A:"""

Problem 3: Fabricated reasoning in the examples

Symptoms: There are errors in the examples you wrote and the model imitates them.

# CRITICAL: ALWAYS verify that your examples are correct
def verify_example(example: CoTExample) -> bool:
    """
    Verifies that a CoT example is correct, using another LLM as the verifier.
    """
    verification_prompt = f"""
    Verify whether the following mathematical reasoning is correct.
    
    Problem: {example.input_problem}
    Reasoning: {example.reasoning}
    Answer: {example.answer}
    
    Is it correct? Reply CORRECT or INCORRECT. 
    If there's an error, explain what it is.
    """
    
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": verification_prompt}],
        temperature=0,
        max_tokens=200
    )
    
    result = response.choices[0].message.content.upper()
    return "CORRECT" in result

Problem 4: Prompts that get too long from too many examples

Symptoms: High costs, high latency, or context errors.

# ✅ Compress the examples without losing the structure
def compress_example(example: CoTExample) -> str:
    """A compressed version of an example, for long prompts."""
    # Condense the reasoning down to the key points
    steps = example.reasoning.split('\n')
    important_steps = [p for p in steps if p.strip() and '→' in p or '=' in p]
    compressed_reasoning = ' → '.join(important_steps[:4])
    
    return f"Q: {example.input_problem[:100]}\nA: {compressed_reasoning} Answer: {example.answer}"

Exercises

Exercise 1: Build an example for an age problem

Design a complete Manual CoT example for this type of problem: "Anna is twice Louis's age. In 5 years their ages will add up to 40. How old is each of them now?"

See solution
ages_example = CoTExample(
    input_problem="Peter is three times Sophie's age. Three years ago, their ages added up to 22. How old are they now?",
    reasoning="""Define the variables: Let S = Sophie's current age. Peter = 3S (three times).
Three years ago: Sophie was (S-3), Peter was (3S-3).
Equation: (S-3) + (3S-3) = 22
Simplify: 4S - 6 = 22
4S = 28
S = 7
Peter = 3 × 7 = 21.
Check: Three years ago: 4 + 18 = 22 ✓""",
    answer="Sophie is 7, Peter is 21."
)

# Now the prompt for the Anna and Louis problem
AGES_PROMPT = f"""Solve the age problem step by step using algebra.

Q: {ages_example.input_problem}
A: {ages_example.reasoning}
Answer: {ages_example.answer}

Q: Anna is twice Louis's age. In 5 years their ages will add up to 40. How old is each of them now?
A:"""

response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": AGES_PROMPT}],
    temperature=0,
    max_tokens=400
)
print(response.choices[0].message.content)
# Expected answer: Louis = 10, Anna = 20

Exercise 2: Few-shot CoT for urgency classification

Design 3 Manual CoT examples to classify support tickets as URGENT, NORMAL or LOW. Then test with 5 new tickets.

See solution
SUPPORT_COT_PROMPT = """Classify the urgency of each support ticket (URGENT, NORMAL, LOW) by reasoning about the impact.

Q: "The payment system is down. We haven't been able to process a single transaction for 2 hours."
A: Let's go step by step.
Operational impact: Payment system = core business function.
Duration: 2 hours of downtime.
Users affected: Everyone trying to pay → sales blocked.
Financial loss: High and growing by the minute.
Classification: Maximum revenue impact + many users affected.
Answer: URGENT.

Q: "The analytics dashboard isn't showing data from the last 48 hours."
A: Let's go step by step.
Operational impact: Analytics = reporting function, not core.
Duration: 48 hours without fresh data.
Users affected: Marketing/analytics team (internal, few users).
Financial loss: Delayed decisions, but operations aren't blocked.
Classification: Medium impact (it affects decisions but not critical operations).
Answer: NORMAL.

Q: "Could you change the color of the export button? The current blue doesn't match our brand."
A: Let's go step by step.
Operational impact: Cosmetic change, the export function still works.
User's urgency: "could you?" signals a non-urgent request.
Users affected: Personal/aesthetic preference.
Financial loss: None.
Classification: No functional or financial impact.
Answer: LOW.

Q: {ticket}
A:"""

test_tickets = [
    "My password doesn't work and I have a presentation in 30 minutes",
    "When will you add support for exporting to Excel?",
    "The API is returning a 500 error for 30% of requests in production",
    "The onboarding tutorial has a typo in step 3",
    "The main server went down. Every user is seeing a 503 page",
]

for ticket in test_tickets:
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": SUPPORT_COT_PROMPT.format(ticket=ticket)}],
        temperature=0,
        max_tokens=300
    )
    print(f"Ticket: {ticket[:50]}...")
    print(f"Answer: {response.choices[0].message.content[-100:]}\n")

Exercise 3: Design CoT for data analysis

Create a Manual CoT prompt to analyze whether a sales figure is a statistical outlier given a dataset.

See solution
OUTLIER_COT_PROMPT = """Determine whether a value is a statistical outlier using the IQR method.

Q: Daily sales for the last 7 days: [120, 135, 128, 142, 118, 131, 890]. Is 890 an outlier?
A: Let's go step by step.
Sort the data: 118, 120, 128, 131, 135, 142, 890.
Compute the quartiles:
- Q1 (25th percentile): position 2 = 120.
- Q2 (median): position 4 = 131.
- Q3 (75th percentile): position 6 = 142.
Compute the IQR: IQR = Q3 - Q1 = 142 - 120 = 22.
Compute the bounds:
- Lower bound: Q1 - 1.5×IQR = 120 - 33 = 87.
- Upper bound: Q3 + 1.5×IQR = 142 + 33 = 175.
Check: Is 890 > 175? Yes. 890 >> 175.
Answer: YES, 890 is a statistical outlier (it exceeds the upper bound of 175 by a factor of ~5x).

Q: {data_and_value}
A:"""

Exercise 4: Compare the accuracy of Manual CoT vs. Zero-Shot CoT

Use the same 10 problems with both approaches and measure which is more accurate in your domain.

See solution
from openai import OpenAI

client = OpenAI()

# Design your own examples for whatever domain interests you
MANUAL_EXAMPLES = """
Q: [Example 1 relevant to your domain]
A: [Reasoning 1]
Answer: [A1]

Q: [Example 2, also relevant]
A: [Reasoning 2]
Answer: [A2]
"""

benchmark_problems = [
    ("Problem 1", "Answer 1"),
    # ... 10 problems with known answers
]

def solve_manual_cot(p):
    prompt = MANUAL_EXAMPLES + f"\nQ: {p}\nA:"
    r = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}],
        temperature=0, max_tokens=500
    )
    return r.choices[0].message.content

def solve_zero_shot_cot(p):
    r = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": f"{p}\n\nLet's think step by step."}],
        temperature=0, max_tokens=500
    )
    return r.choices[0].message.content

manual_ok = sum(
    1 for p, correct in benchmark_problems
    if correct in solve_manual_cot(p)
)
zero_ok = sum(
    1 for p, correct in benchmark_problems  
    if correct in solve_zero_shot_cot(p)
)

n = len(benchmark_problems)
print(f"Manual CoT: {manual_ok}/{n} ({manual_ok/n:.0%})")
print(f"Zero-Shot CoT: {zero_ok}/{n} ({zero_ok/n:.0%})")

Exercise 5: Spot and fix a broken example

The following Manual CoT example has an error. Find it and fix it:

Q: A product costs $80 with a 25% discount. What is the original price?
A: Discounted price: $80.
Discount: 25%.
Original price = 80 + 25% = 80 + 20 = $100.
Answer: $100.
See solution

The error: The operation "80 + 25% of 80" is not the mathematically correct way to recover the original price.

Correct reasoning:

Discounted price: $80.
A 25% discount means that $80 represents 75% of the original price (100% - 25%).
Original price = 80 / 0.75 = $106.67.

Check: 106.67 × 0.25 = $26.67 of discount. 106.67 - 26.67 = $80 ✓.
Answer: $106.67.

The error is confusing "add the percentage of the discounted price" with the correct inverse operation. To go from a discounted price back to the original price you have to divide by (1 - discount).

# Corrected example
corrected_discount_example = CoTExample(
    input_problem="A product costs $80 with a 25% discount. What is the original price?",
    reasoning="""Discounted price: $80.
The percentage it represents: 100% - 25% = 75% of the original price.
Original price = 80 / 0.75 = 106.67.
Check: 106.67 × 0.25 = 26.67. 106.67 - 26.67 = 80 ✓""",
    answer="$106.67"
)

Summary

  • Manual CoT uses examples with explicit Reasoning + Answer to steer the model
  • Standard format: Input → Step-by-step reasoning → Answer
  • 2-3 examples are usually enough; more doesn't always help
  • By domain: Math, logic, code, sentiment and business each need a different style
  • Quality > quantity: Incorrect examples hurt performance; always verify them
  • vs. Zero-Shot: More design effort, more consistency and control

Additional resources

  1. Chain-of-Thought Prompting Elicits Reasoning (Wei et al., 2022)
  2. Complexity-Based Prompting for Multi-Step Reasoning (Fu et al., 2022)
  3. Automatic Chain of Thought Prompting (Zhang et al., 2022)
  4. Few-Shot Prompting - Learn Prompting
  5. OpenAI Cookbook: Techniques to improve reliability