Module 5: ReAct, Self-Consistency, and Advanced Patterns

7. Decision Framework: Which Technique to Use

Overview

Knowing the techniques is only half the job. The other half is knowing when to apply each one. This decision framework gives you a systematic process for picking the right technique based on your task's characteristics, your budget constraints and your quality requirements.


The Main Decision Tree

Does your task need real-time data or external tools?
├── YES → ReAct (+ Structured Output if the output must be parseable)
│         └── Accuracy critical? → ReAct + extra verification
│
└── NO → Is it a reasoning task (math, logic, multi-step)?
    ├── YES → Is accuracy critical (errors are expensive)?
    │   ├── YES → CoT + Self-Consistency (N=3-5)
    │   └── NO → CoT alone (enough in most cases)
    │
    └── NO → Are there multiple valid strategies?
        ├── YES → Tree-of-Thought (only if CoT fails systematically)
        └── NO → Is it classification, extraction, or a direct answer?
            ├── YES → Zero-shot or Few-shot
            └── NO → Do you need to optimize the prompt or the answer quality?
                ├── Suboptimal prompt → Meta-prompting
                └── Improvable quality → Self-Refine

Quick Reference Table

TaskRecommended techniqueAPI callsLatencyRelative cost
Text classificationFew-shot1Low1x
Entity extractionZero-shot + JSON mode1Low1x
TranslationZero-shot or Few-shot1Low1x
Text summarizationZero-shot1Low1x
Math word problemCoT1Medium1.5x
Critical math (no errors)CoT + Self-Consistency N=55High5x
QA with web searchReAct3-8High5-10x
QA with a databaseReAct2-5Medium-High3-7x
Planning with alternativesTree-of-Thought10-30Very high10-30x
High-quality codeSelf-Refine (2-3 iter)4-8High4-8x
Optimize a prompt in productionMeta-prompting + evaluation10-20High10-20x

Detailed Framework by Dimension

Dimension 1: Task type

from openai import OpenAI
from enum import Enum
from dataclasses import dataclass
from typing import Optional

client = OpenAI()

class TaskType(Enum):
    CLASSIFICATION = "classification"     # Assign to a category
    EXTRACTION = "extraction"             # Extract structured information
    REASONING_MATH = "math"              # Numeric operations/logic
    REASONING_LOGIC = "logic"            # Deductions, inferences
    TEXT_GENERATION = "generation"       # Creative or technical writing
    QA_FACTUAL = "qa_factual"            # Questions about known facts
    QA_REALTIME = "qa_realtime"          # Questions that need current data
    PLANNING = "planning"                # Designing strategies, roadmaps
    CODE = "code"                        # Writing or reviewing code
    MULTIMODAL = "multimodal"            # Combines several types

@dataclass
class TaskAnalysis:
    task_type: TaskType
    accuracy_critical: bool         # Are errors expensive?
    needs_external_data: bool       # Does it need APIs, a database?
    structured_output: bool         # Must the output be JSON/parseable?
    call_budget: int                # Maximum API calls allowed
    max_latency_sec: float          # Maximum acceptable response time

def analyze_task_auto(description: str) -> TaskAnalysis:
    """
    Automatically analyzes a task description to classify it.
    """
    prompt = f"""Analyze this task and classify it. Reply in JSON:
    
Task: {description}

{{
    "type": "classification|extraction|math|logic|generation|qa_factual|qa_realtime|planning|code",
    "accuracy_critical": true/false,
    "needs_external_data": true/false,
    "structured_output": true/false,
    "call_budget": 1-20,
    "max_latency_sec": 5-60
}}

Rules for accuracy_critical: true if errors imply financial loss, legal risk, or user impact.
Rules for needs_external_data: true if it needs prices, real-time data, database queries."""
    
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}],
        temperature=0,
        response_format={"type": "json_object"}
    )
    
    import json
    data = json.loads(response.choices[0].message.content)
    
    type_map = {
        "classification": TaskType.CLASSIFICATION,
        "extraction": TaskType.EXTRACTION,
        "math": TaskType.REASONING_MATH,
        "logic": TaskType.REASONING_LOGIC,
        "generation": TaskType.TEXT_GENERATION,
        "qa_factual": TaskType.QA_FACTUAL,
        "qa_realtime": TaskType.QA_REALTIME,
        "planning": TaskType.PLANNING,
        "code": TaskType.CODE
    }
    
    return TaskAnalysis(
        task_type=type_map.get(data.get("type", "logic"), TaskType.REASONING_LOGIC),
        accuracy_critical=data.get("accuracy_critical", False),
        needs_external_data=data.get("needs_external_data", False),
        structured_output=data.get("structured_output", False),
        call_budget=data.get("call_budget", 5),
        max_latency_sec=data.get("max_latency_sec", 30.0)
    )

The Technique Selection Function

@dataclass
class TechniqueRecommendation:
    main_technique: str
    secondary_technique: Optional[str]
    suggested_params: dict
    rationale: str
    estimated_calls: int
    relative_cost: float
    alternatives: list[str]

def select_technique(analysis: TaskAnalysis) -> TechniqueRecommendation:
    """
    Selects the optimal technique based on the task analysis.
    
    Args:
        analysis: The result of analyze_task_auto, or built by hand
    
    Returns:
        A recommendation with the technique, parameters and rationale
    """
    task_type = analysis.task_type
    accuracy = analysis.accuracy_critical
    external = analysis.needs_external_data
    structured = analysis.structured_output
    budget = analysis.call_budget
    
    # Rule 1: External data → ReAct
    if external:
        if accuracy and budget >= 8:
            return TechniqueRecommendation(
                main_technique="ReAct",
                secondary_technique="extra_verification",
                suggested_params={"max_steps": 6, "output_format": "json" if structured else "text"},
                rationale="It needs external data. With critical accuracy, add a verification of the result.",
                estimated_calls=8,
                relative_cost=8.0,
                alternatives=["react_with_fallback"]
            )
        return TechniqueRecommendation(
            main_technique="ReAct",
            secondary_technique="structured_output" if structured else None,
            suggested_params={"max_steps": 5},
            rationale="External data requires ReAct for the tools.",
            estimated_calls=5,
            relative_cost=5.0,
            alternatives=["react_simplified"]
        )
    
    # Rule 2: Math/Logic with critical accuracy → CoT + SC
    if task_type in [TaskType.REASONING_MATH, TaskType.REASONING_LOGIC]:
        if accuracy and budget >= 5:
            n = min(5, budget)
            return TechniqueRecommendation(
                main_technique="CoT + Self-Consistency",
                secondary_technique=None,
                suggested_params={"n": n, "temperature": 0.7},
                rationale=f"Reasoning with critical accuracy. N={n} samples for consensus.",
                estimated_calls=n,
                relative_cost=float(n),
                alternatives=["cot_only", "react" if accuracy else None]
            )
        return TechniqueRecommendation(
            main_technique="CoT",
            secondary_technique=None,
            suggested_params={"temperature": 0},
            rationale="Mathematical/logical reasoning. CoT with step-by-step.",
            estimated_calls=1,
            relative_cost=1.5,
            alternatives=["cot_sc_n3"]
        )
    
    # Rule 3: Planning → ToT
    if task_type == TaskType.PLANNING:
        if budget >= 15:
            return TechniqueRecommendation(
                main_technique="Tree-of-Thought",
                secondary_technique="Self-Consistency" if accuracy else None,
                suggested_params={"breadth": 3, "depth": 2},
                rationale="Planning has multiple strategies. ToT explores the alternatives.",
                estimated_calls=15,
                relative_cost=15.0,
                alternatives=["cot", "tot_simplified"]
            )
        return TechniqueRecommendation(
            main_technique="ToT simplified",
            secondary_technique=None,
            suggested_params={"breadth": 2, "depth": 1},
            rationale="Limited budget. Simplified ToT: 2 approaches, 1 level.",
            estimated_calls=6,
            relative_cost=6.0,
            alternatives=["cot"]
        )
    
    # Rule 4: Code → Self-Refine
    if task_type == TaskType.CODE:
        max_iter = 2 if budget >= 4 else 1
        return TechniqueRecommendation(
            main_technique="Self-Refine",
            secondary_technique=None,
            suggested_params={"max_iterations": max_iter, "criteria": ["correctness", "efficiency", "readability"]},
            rationale=f"Code requires iterative review. {max_iter} refine iterations.",
            estimated_calls=max_iter * 2,
            relative_cost=float(max_iter * 2),
            alternatives=["cot_with_tests"]
        )
    
    # Rule 5: Classification/Extraction → Few-shot/Zero-shot
    if task_type in [TaskType.CLASSIFICATION, TaskType.EXTRACTION]:
        return TechniqueRecommendation(
            main_technique="Few-shot",
            secondary_technique="structured_output" if structured else None,
            suggested_params={"n_examples": 2 if task_type == TaskType.CLASSIFICATION else 1, "temperature": 0},
            rationale="Simple classification/extraction doesn't need complex techniques.",
            estimated_calls=1,
            relative_cost=1.2,
            alternatives=["zero_shot", "cot_for_ambiguous_cases"]
        )
    
    # Rule 6: Factual QA → Zero-shot
    if task_type == TaskType.QA_FACTUAL:
        return TechniqueRecommendation(
            main_technique="Zero-shot",
            secondary_technique="few_shot" if accuracy else None,
            suggested_params={"temperature": 0},
            rationale="Factual QA using the model's knowledge. Zero-shot is enough.",
            estimated_calls=1,
            relative_cost=1.0,
            alternatives=["rag" if external else "cot"]
        )
    
    # Default: CoT
    return TechniqueRecommendation(
        main_technique="CoT",
        secondary_technique=None,
        suggested_params={"temperature": 0},
        rationale="A general reasoning technique applicable to most tasks.",
        estimated_calls=1,
        relative_cost=1.5,
        alternatives=["few_shot", "zero_shot"]
    )


# Main function for easy use:
def recommend_technique(task_description: str, verbose: bool = True) -> TechniqueRecommendation:
    """
    Given a task description, recommends the optimal technique.
    """
    analysis = analyze_task_auto(task_description)
    recommendation = select_technique(analysis)
    
    if verbose:
        print(f"=== TASK ANALYSIS ===")
        print(f"Type: {analysis.task_type.value}")
        print(f"Accuracy critical: {analysis.accuracy_critical}")
        print(f"External data: {analysis.needs_external_data}")
        print(f"\n=== RECOMMENDATION ===")
        print(f"Technique: {recommendation.main_technique}")
        if recommendation.secondary_technique:
            print(f"Combined with: {recommendation.secondary_technique}")
        print(f"Parameters: {recommendation.suggested_params}")
        print(f"Rationale: {recommendation.rationale}")
        print(f"Estimated calls: {recommendation.estimated_calls}")
        print(f"Relative cost: {recommendation.relative_cost}x")
        print(f"Alternatives: {[a for a in recommendation.alternatives if a]}")
    
    return recommendation

Cost vs Accuracy vs Latency Analysis

import time

def benchmark_techniques_on_task(
    problem: str,
    correct_answer: Optional[str] = None
) -> dict:
    """
    Benchmarks several techniques on a specific problem.
    Useful for calibrating which technique to use in a particular use case.
    """
    results = {}
    
    # Zero-shot
    t0 = time.time()
    resp_zero = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": problem}],
        temperature=0, max_tokens=200
    ).choices[0].message.content
    results["zero_shot"] = {
        "answer": resp_zero[:100],
        "latency": time.time() - t0,
        "calls": 1,
        "relative_cost": 1.0
    }
    
    # CoT
    t0 = time.time()
    resp_cot = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": f"{problem}\nThink step by step."}],
        temperature=0, max_tokens=400
    ).choices[0].message.content
    results["cot"] = {
        "answer": resp_cot[:100],
        "latency": time.time() - t0,
        "calls": 1,
        "relative_cost": 1.5
    }
    
    # Self-Consistency N=3
    t0 = time.time()
    from collections import Counter
    import re
    def extract_num(t):
        nums = re.findall(r'-?\d+\.?\d*', t)
        return nums[-1] if nums else t.strip()[-20:]
    
    sc_answers = []
    for _ in range(3):
        r = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": f"{problem}\nThink step by step. Answer: [value]"}],
            temperature=0.7, max_tokens=300
        ).choices[0].message.content
        sc_answers.append(extract_num(r))
    sc_winner = Counter(sc_answers).most_common(1)[0][0]
    results["sc_n3"] = {
        "answer": sc_winner,
        "latency": time.time() - t0,
        "calls": 3,
        "relative_cost": 3.0
    }
    
    # Add the evaluation if a correct answer is provided
    if correct_answer:
        def is_correct(pred, correct):
            try:
                return abs(float(re.sub(r'[^\d.-]', '', pred)) - float(correct)) < 0.01
            except Exception:
                return pred.strip().lower() == correct.strip().lower()
        
        for technique in results:
            results[technique]["correct"] = is_correct(
                results[technique]["answer"], correct_answer
            )
    
    return results


# Use it on a real case:
test_problem = "If I invest $1000 at 7% a year for 10 years with compound interest, how much will I have?"
results = benchmark_techniques_on_task(test_problem, correct_answer="1967.15")

print("Technique       | Answer           | Latency  | Calls    | Correct?")
print("-" * 75)
for t, data in results.items():
    correct = data.get("correct", "N/A")
    print(f"{t:<15} | {data['answer'][:15]:<15} | {data['latency']:.2f}s  | {data['calls']}        | {correct}")

Practical Guides by Industry

Fintech / Financial Analysis

FINTECH_GUIDE = {
    "risk_calculations": {
        "technique": "CoT + Self-Consistency N=5",
        "reason": "Errors in risk calculations have a high financial impact",
        "example": "Value at Risk, financial options, credit scoring"
    },
    "market_data": {
        "technique": "ReAct + Structured Output",
        "reason": "Prices, exchange rates and indices require real-time data",
        "example": "Stock prices, volatility, correlations"
    },
    "transaction_classification": {
        "technique": "Few-shot",
        "reason": "Fraud/normal classification doesn't require advanced techniques",
        "example": "Detecting suspicious transactions by category"
    },
    "regulatory_reports": {
        "technique": "Self-Refine (3 iterations)",
        "reason": "Language quality and precision are critical in regulatory reports",
        "example": "Basel III, MIFID II compliance reports"
    }
}

Legal / Compliance

LEGAL_GUIDE = {
    "contract_analysis": {
        "technique": "Self-Refine + legal criteria",
        "reason": "Precision and thoroughness are critical",
        "example": "Identifying problematic clauses"
    },
    "case_law_search": {
        "technique": "ReAct",
        "reason": "It needs to search case databases",
        "example": "Finding relevant precedents"
    },
    "document_classification": {
        "technique": "Few-shot with legal examples",
        "reason": "Classifying by document type is relatively straightforward",
        "example": "Telling contracts apart from invoices, rulings"
    }
}

E-commerce / Customer Support

ECOMMERCE_GUIDE = {
    "ticket_classification": {
        "technique": "Zero-shot or Few-shot",
        "reason": "High speed needed; simple classification",
        "example": "Return vs. inquiry vs. complaint vs. information"
    },
    "order_status_query": {
        "technique": "ReAct",
        "reason": "It requires querying the order system",
        "example": "Where is my package?"
    },
    "complex_answers": {
        "technique": "Self-Refine",
        "reason": "Answers to complex complaints have to be empathetic and precise",
        "example": "Handling complex returns, claims"
    }
}

The Full Flow Diagram with Code

def full_decision_framework(
    task_description: str,
    budget_usd_per_request: float = 0.005,
    max_latency_sec: float = 30.0,
    min_accuracy: float = 0.80
) -> dict:
    """
    A complete decision framework that takes budget, latency and accuracy into account.
    
    Returns:
        The full configuration: technique, parameters, estimated cost, fallback strategy
    """
    # Classify the task
    analysis = analyze_task_auto(task_description)
    
    # Estimated cost per technique (simplified)
    ESTIMATED_COSTS = {
        "zero_shot": 0.0002,
        "few_shot": 0.0003,
        "cot": 0.0004,
        "cot_sc_n3": 0.0012,
        "cot_sc_n5": 0.002,
        "react": 0.002,
        "tot_simplified": 0.003,
        "self_refine_n2": 0.0008,
        "self_refine_n3": 0.0012
    }
    
    # Select the base recommendation
    rec = select_technique(analysis)
    
    # Check whether the recommended technique fits in the budget
    technique = rec.main_technique.lower().replace(" + ", "_").replace(" ", "_")
    estimated_cost = ESTIMATED_COSTS.get(technique, ESTIMATED_COSTS["cot"])
    
    within_budget = estimated_cost <= budget_usd_per_request
    
    if not within_budget:
        # Look for a cheaper alternative
        alternatives_by_cost = sorted(
            [(t, c) for t, c in ESTIMATED_COSTS.items() if c <= budget_usd_per_request],
            key=lambda x: x[1], reverse=True
        )
        if alternatives_by_cost:
            alternative_technique = alternatives_by_cost[0][0]
            warning = f"The optimal technique ({rec.main_technique}) blows the budget. Using {alternative_technique}"
        else:
            alternative_technique = "zero_shot"
            warning = "Budget is very tight. Using zero-shot."
    else:
        alternative_technique = technique
        warning = None
    
    return {
        "recommended_technique": rec.main_technique,
        "technique_within_budget": alternative_technique,
        "params": rec.suggested_params,
        "rationale": rec.rationale,
        "estimated_cost_usd": estimated_cost,
        "within_budget": within_budget,
        "warning": warning,
        "fallback": rec.alternatives[0] if rec.alternatives else "zero_shot",
        "task_analysis": {
            "type": analysis.task_type.value,
            "accuracy_critical": analysis.accuracy_critical,
            "needs_external": analysis.needs_external_data
        }
    }

Case Studies

Case 1: An adaptive tutoring system

Task: A math tutoring system for high school.
Requirements:
- Always-correct answers (accuracy is critical)
- Step-by-step explanations
- Detect whether the student made a specific error

Analysis:
- Type: REASONING_MATH
- Accuracy critical: TRUE (you can't teach incorrect math)
- External data: FALSE
- Budget per query: $0.01

→ RECOMMENDATION: CoT + Self-Consistency N=5
  Parameters: n=5, temperature=0.7
  Rationale: Critical accuracy, math → SC to validate, CoT for the explanations
  Estimated cost: $0.002 (within budget)
  
IMPLEMENTATION:
```python
def math_tutoring(exercise: str, student_answer: str) -> dict:
    """
    Evaluates the student's answer and generates feedback.
    """
    from collections import Counter
    import re
    
    # Solve it correctly with CoT + SC
    answers = []
    for _ in range(5):
        resp = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": f"{exercise}\nSolve step by step. Answer: [number]"}],
            temperature=0.7, max_tokens=400
        ).choices[0].message.content
        nums = re.findall(r'-?\d+\.?\d*', resp)
        answers.append(nums[-1] if nums else resp.strip()[-10:])
    
    correct_answer = Counter(answers).most_common(1)[0][0]
    
    # Evaluate the student's answer
    try:
        is_correct = abs(float(student_answer) - float(correct_answer)) < 0.01
    except ValueError:
        is_correct = student_answer.strip() == correct_answer.strip()
    
    # Generate feedback
    feedback = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": f"""
Exercise: {exercise}
Correct answer: {correct_answer}
Student's answer: {student_answer}
Correct?: {is_correct}

{'Correct! Explain why the solution is right in 2-3 sentences.' if is_correct else "Incorrect. Identify the student's likely mistake and explain the correct process step by step."}
"""}],
        temperature=0
    ).choices[0].message.content
    
    return {
        "is_correct": is_correct,
        "correct_answer": correct_answer,
        "feedback": feedback
    }

Case 2: Sentiment analysis for a reviews app

Task: Classify product reviews as POSITIVE/NEGATIVE/MIXED/NEUTRAL
Volume: 10,000 reviews/day
Requirements:
- High speed (<2 sec per review)
- Low cost (limited budget)
- 85%+ accuracy (occasional errors are acceptable)

→ RECOMMENDATION: Few-shot with 2-3 examples
  Rationale: Simple classification, high volume → economize
  Cost: 1x (vs 5x if we used SC)
  
Do NOT use: CoT + Self-Consistency (it'd be 5x more expensive with no meaningful gain for classification)

Troubleshooting the Framework

Problem 1: The recommended technique gives bad results

Symptom: The framework recommends CoT for a task the model consistently fails.

Diagnosis: The task requires knowledge outside the training range, or the problem is one of complex planning.

Solution:

def quick_diagnosis(problem: str) -> str:
    """
    Runs plain CoT. If it fails, suggests an alternative technique.
    """
    resp = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": f"{problem}\nThink step by step."}],
        temperature=0, max_tokens=400
    ).choices[0].message.content
    
    # Signals that CoT is failing:
    failure_signals = [
        "i don't know", "i can't", "not sure", "unclear",
        "i need more information", "requires more context",
        "impossible", "cannot be determined"
    ]
    
    cot_failing = any(s in resp.lower() for s in failure_signals)
    
    if cot_failing:
        print("⚠ CoT is failing. Possible solutions:")
        print("  1. ReAct: if the problem needs external data")
        print("  2. ToT: if there are multiple possible strategies")
        print("  3. Few-shot with examples: if the model doesn't understand the format")
        print("  4. Rephrase the problem: make it more explicit")
    
    return resp

Problem 2: The budget is very tight

Symptom: Every advanced technique blows the allowed budget.

Solution: Use routing to apply advanced techniques only when necessary:

def budget_routing(
    problem: str,
    extra_budget: bool = False  # True if the client/case justifies it
) -> str:
    """
    Apply advanced techniques only to the cases that need them.
    """
    # First, try the cheapest technique
    simple_answer = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": problem}],
        temperature=0, max_tokens=200
    ).choices[0].message.content
    
    # Detect whether the simple answer is low quality
    needs_more = any(word in simple_answer.lower() for word in 
                     ["i don't know", "unclear", "depends", "complex", "multiple"])
    
    if needs_more and extra_budget:
        # Escalate to a more advanced technique
        return client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": f"{problem}\nThink very carefully, step by step."}],
            temperature=0, max_tokens=600
        ).choices[0].message.content
    
    return simple_answer

Exercises

Exercise 1: Classify 10 tasks

For each of the following tasks, use the decision tree to work out which technique you'd use:

  1. Translate a legal contract from Spanish to English
  2. Answer "What is Bitcoin worth right now?"
  3. Generate variants of a marketing email
  4. Check whether a number is prime
  5. Analyze the tone of a support email
  6. Compute the most efficient route between 5 cities
  7. Summarize a 50-page report
  8. Write code to sort an array
  9. Answer questions about a specific uploaded document
  10. Detect whether a tweet violates the usage policies
See solution
  1. Translation: Few-shot with examples from the legal domain → Zero-shot/Few-shot (1x)
  2. Bitcoin price: Real-time data → ReAct with a price tool (5x)
  3. Email variants: Generative, no single answer → Zero-shot with temperature 0.8 (1x)
  4. Is it prime?: Pure math → CoT (doesn't need SC for a simple number) (1.5x)
  5. Email tone: Classification → Few-shot (1x)
  6. Route between cities: Planning with multiple strategies → ToT or CoT (depends on the complexity)
  7. 50-page summary: Long text → Map-Reduce (chunking) + Zero-shot per chunk (N calls)
  8. Code to sort an array: Code → Self-Refine with correctness/efficiency criteria (3-5x)
  9. QA over a document: RAG (search + generation) → ReAct with an in-doc search tool (3-6x)
  10. Moderation: Classification → Few-shot with policy examples (1x)

Exercise 2: Design the complete system

You have a contract-analysis system where:

  • You receive 500 contracts/day
  • Each contract is ~20 pages (~15K tokens)
  • You need: an executive summary, risk clauses, the parties involved
  • Budget: $0.10 per contract

Design the complete architecture, stating which technique you use at each stage.

See solution
Architecture for contract analysis:

Stage 1: Preprocessing (no LLM)
  - Split into 4K-token chunks (keeping the section context)
  - Identify sections by their headers

Stage 2: Extraction per chunk (Few-shot, 1 call per chunk)
  - ~4 chunks per contract = 4 calls
  - Extract: parties, dates, obligations per section
  - Cost: ~4 × $0.0002 = $0.0008

Stage 3: Risk analysis (CoT, 1 call)
  - Input: the concatenated outputs from stage 2
  - Identify problematic clauses with reasoning
  - Cost: ~$0.0006

Stage 4: Executive summary (Self-Refine, 2 iterations)
  - Input: risk analysis + extraction
  - 2 iterations for quality
  - Cost: ~2 × $0.0004 = $0.0008

Total estimate: ~$0.002 per contract (well under $0.10)
→ You can afford more Self-Refine iterations, or use Self-Consistency N=3 for the risk analysis

Exercise 3: Build the router

Implement a function auto_router(task: str, text: str) -> str that:

  1. Classifies the task
  2. Selects the technique
  3. Runs the technique
  4. Returns the answer
See solution
def auto_router(task: str, text: str) -> str:
    """A router that automatically selects and runs the right technique."""
    problem = f"{task}\n\nText/Input:\n{text}"
    
    # Classify the task
    analysis = analyze_task_auto(task)
    rec = select_technique(analysis)
    technique = rec.main_technique.lower()
    
    print(f"[Router] Technique selected: {technique}")
    
    # Run it according to the technique
    if "self-consistency" in technique or "sc" in technique:
        from collections import Counter
        import re
        answers = []
        for _ in range(3):
            r = client.chat.completions.create(
                model="gpt-4o-mini",
                messages=[{"role": "user", "content": f"{problem}\nThink step by step."}],
                temperature=0.7, max_tokens=400
            ).choices[0].message.content
            nums = re.findall(r'-?\d+\.?\d*', r)
            answers.append(nums[-1] if nums else r.strip()[-20:])
        return Counter(answers).most_common(1)[0][0]
    
    elif "few-shot" in technique or "classification" in analysis.task_type.value:
        return client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": problem}],
            temperature=0, max_tokens=200
        ).choices[0].message.content
    
    else:  # CoT default
        return client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": f"{problem}\nThink step by step."}],
            temperature=0, max_tokens=500
        ).choices[0].message.content

Summary

The decision framework boils down to these key questions:

  1. Do you need external data? → Yes: ReAct. No: keep going.
  2. Is it reasoning (math/logic)? → Yes: CoT. If accuracy is critical: CoT + SC.
  3. Are there multiple possible strategies? → Yes: ToT (if the budget allows).
  4. Is it classification/extraction? → Few-shot or Zero-shot.
  5. Is the prompt's or the answer's quality suboptimal? → Meta-prompting / Self-Refine.

The golden rule: start with the simplest technique that works for your use case. Scale to more complex techniques only when you need to.


Additional resources

  1. Prompt Engineering Guide - Techniques Overview
  2. Chain-of-Thought Prompting (Wei et al.)
  3. ReAct (Yao et al., 2022)
  4. Self-Consistency (Wang et al., 2022)
  5. Tree of Thoughts (Yao et al., 2023)
  6. LLM evaluation best practices - OpenAI
  7. Cost optimization strategies - Anthropic