0Pricing
AI Prompt Engineering · Lesson

Compiling and Optimizing Prompts

BootstrapFewShot, MIPRO, and other DSPy optimizers in practice.

Compiling and Optimizing Prompts is a free AI Prompt Engineering lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the AI Prompt Engineering learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

What Optimization Means in DSPy

DSPy optimization (called compilation) finds the best prompt configuration for your program given a training set and a metric. The optimizer searches over possible few-shot examples, instructions, and reasoning demonstrations.

You run compilation once, save the result, and deploy the optimized program. At inference time it's just fast LLM calls — no more optimization overhead.

Defining a Metric Function

Every DSPy optimizer needs a metric function that scores a prediction given the expected output. It returns a number (or boolean) — higher is better.

The metric is the signal the optimizer uses to decide if a prompt configuration is good.

# Metric: exact match on answer field
def exact_match_metric(example, prediction, trace=None):
    """
    example: a training example with .answer
    prediction: the module's output with .answer
    Returns 1.0 if correct, 0.0 otherwise
    """
    expected = example.answer.strip().lower()
    predicted = prediction.answer.strip().lower()
    return float(expected == predicted)

# Metric: F1 score for token overlap (common in QA)
def token_f1_metric(example, prediction, trace=None):
    gold_tokens = set(example.answer.lower().split())
    pred_tokens = set(prediction.answer.lower().split())
    if not pred_tokens:
        return 0.0
    precision = len(gold_tokens & pred_tokens) / len(pred_tokens)
    recall = len(gold_tokens & pred_tokens) / len(gold_tokens)
    if precision + recall == 0:
        return 0.0
    return 2 * precision * recall / (precision + recall)

Preparing a Training Set

DSPy needs a training set of dspy.Example objects. Each example specifies the inputs and the expected output. Even 20-50 examples are often enough for BootstrapFewShot.

import dspy

# Build training examples
trainset = [
    dspy.Example(
        question='What is the capital of Germany?',
        answer='Berlin'
    ).with_inputs('question'),

    dspy.Example(
        question='Who wrote Romeo and Juliet?',
        answer='William Shakespeare'
    ).with_inputs('question'),

    dspy.Example(
        question='What year did World War II end?',
        answer='1945'
    ).with_inputs('question'),
    # ... add more examples
]

print(f'Training set size: {len(trainset)} examples')
print(trainset[0].question, '->', trainset[0].answer)

BootstrapFewShot Optimizer

BootstrapFewShot is the most common DSPy optimizer. It runs your program on the training set, collects successful traces (input-output pairs where the metric passes), and uses those traces as few-shot demonstrations in the compiled prompt.

import dspy
from dspy.teleprompt import BootstrapFewShot

# Define your program
class QA(dspy.Signature):
    """Answer factual questions."""
    question: str = dspy.InputField()
    answer: str = dspy.OutputField()

program = dspy.ChainOfThought(QA)

# Set up the optimizer
optimizer = BootstrapFewShot(
    metric=exact_match_metric,
    max_bootstrapped_demos=4,   # Up to 4 few-shot examples per predictor
    max_labeled_demos=4,        # Use labeled examples directly if available
)

# Compile!
compiled_program = optimizer.compile(program, trainset=trainset)
print('Compilation complete')

MIPRO Optimizer

MIPRO (Multi-prompt Instruction Proposal and Optimization) is a more powerful optimizer. It not only selects few-shot examples but also searches for better instruction text to include in the prompt.

MIPRO requires more LLM calls during compilation but often achieves significantly higher accuracy.

import dspy
from dspy.teleprompt import MIPROv2

class QA(dspy.Signature):
    """Answer factual questions."""
    question: str = dspy.InputField()
    answer: str = dspy.OutputField()

program = dspy.ChainOfThought(QA)

# MIPRO: optimizes both instructions AND few-shot examples
optimizer = MIPROv2(
    metric=exact_match_metric,
    auto='medium',      # 'light' / 'medium' / 'heavy' for optimization budget
    num_threads=4,      # Parallel evaluation threads
)

compiled_program = optimizer.compile(
    program,
    trainset=trainset,
    num_trials=20,       # Number of candidate prompts to evaluate
)
print('MIPRO compilation complete')

What a Compiled Prompt Looks Like

After compilation, DSPy embeds optimized few-shot demonstrations into the prompt. You can inspect this by examining the compiled program's predictors.

import dspy

# After compiling, inspect the optimized state
compiled_program = dspy.ChainOfThought('question -> answer')
# (Assume this was returned by optimizer.compile(...))

# Inspect the demos that were found
for demo in compiled_program.demos:
    print('Input:', demo.question)
    print('Reasoning:', demo.get('reasoning', 'N/A'))
    print('Answer:', demo.answer)
    print('---')

# Save the compiled state
compiled_program.save('compiled_qa_program.json')
print('Saved compiled program')

The teleprompter.compile() Interface

All DSPy optimizers share the same compile() interface. This consistency means you can swap optimizers without changing your program code.

from dspy.teleprompt import BootstrapFewShot, MIPROv2, COPRO

# All optimizers use the same interface:
# compiled = optimizer.compile(program, trainset=trainset)

# BootstrapFewShot: fast, uses successful traces as demos
opt1 = BootstrapFewShot(metric=exact_match_metric)

# MIPRO: slower, optimizes instructions too
opt2 = MIPROv2(metric=exact_match_metric, auto='light')

# COPRO: coordinate descent over instruction proposals
opt3 = COPRO(metric=exact_match_metric, depth=3)

# Swap between them with one line change:
compiled = opt1.compile(program, trainset=trainset)
# or: compiled = opt2.compile(program, trainset=trainset)

BootstrapFewShotWithRandomSearch

BootstrapFewShotWithRandomSearch extends BootstrapFewShot by generating many candidate demonstration sets and selecting the best-performing one on a validation set.

It's a good middle ground between the simplicity of BootstrapFewShot and the power of MIPRO.

from dspy.teleprompt import BootstrapFewShotWithRandomSearch

# Split data into train and validation
trainset = examples[:40]
devset = examples[40:60]

optimizer = BootstrapFewShotWithRandomSearch(
    metric=exact_match_metric,
    max_bootstrapped_demos=4,
    num_candidate_programs=8,  # Try 8 different demo sets
    num_threads=4,
)

compiled_program = optimizer.compile(
    program,
    trainset=trainset,
    valset=devset,  # Picks the best program based on validation
)
print('Best program selected from 8 candidates')

Compilation Cost Considerations

Compilation makes extra LLM calls to generate and evaluate candidate prompts. Budget accordingly:

  • BootstrapFewShot: ~1-2x your training set size in LLM calls
  • RandomSearch with 8 candidates: ~8-10x
  • MIPRO medium: ~30-50x

Run compilation offline, save the result, and deploy the saved program. Your production inference cost is unchanged.

Validating the Compiled Program

After compilation, always evaluate on a held-out test set to confirm the optimized program truly generalizes. Don't just check training set performance.

import dspy

# Evaluate on test set
evaluate = dspy.Evaluate(
    devset=testset,
    metric=exact_match_metric,
    num_threads=4,
    display_progress=True,
)

# Compare uncompiled vs compiled
uncompiled_score = evaluate(uncompiled_program)
compiled_score = evaluate(compiled_program)

print(f'Uncompiled accuracy: {uncompiled_score:.1%}')
print(f'Compiled accuracy:   {compiled_score:.1%}')
print(f'Improvement: +{compiled_score - uncompiled_score:.1%}')

Putting It All Together

A complete DSPy optimization workflow: define signature → build module → prepare training data → choose optimizer → compile → evaluate → save. This is the full cycle from idea to production-ready optimized prompt pipeline.

import dspy
from dspy.teleprompt import BootstrapFewShot

# 1. Configure LM
dspy.configure(lm=dspy.LM('openai/gpt-4o-mini', api_key='sk-...'))

# 2. Define signature and module
class QA(dspy.Signature):
    """Answer questions accurately."""
    question: str = dspy.InputField()
    answer: str = dspy.OutputField()

program = dspy.ChainOfThought(QA)

# 3. Compile
optimizer = BootstrapFewShot(metric=exact_match_metric)
compiled = optimizer.compile(program, trainset=trainset)

# 4. Save
compiled.save('production_qa.json')
print('Production program ready')

Knowledge Check: BootstrapFewShot

What does BootstrapFewShot use from your training set to improve the compiled prompt?

Recap: Compiling and Optimizing

DSPy optimization searches for the best prompt configuration using a metric function and a training set. BootstrapFewShot finds good few-shot demonstrations from successful traces. MIPROv2 additionally searches for better instruction text. All optimizers share a compile(program, trainset=...) interface. Compilation is a one-time offline cost — save the result with program.save() and deploy the optimized program for free at inference time.

Frequently asked questions

Is the “Compiling and Optimizing Prompts” lesson free?

Yes — the full text of “Compiling and Optimizing Prompts” is free to read here on the web, and the AI Prompt Engineering course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the AI Prompt Engineering course, upgrade to CoddyKit PRO.

What will I learn in “Compiling and Optimizing Prompts”?

BootstrapFewShot, MIPRO, and other DSPy optimizers in practice. You practise AI Prompt Engineering with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start AI Prompt Engineering?

No prior experience is required. AI Prompt Engineering on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Compiling and Optimizing Prompts” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this AI Prompt Engineering lesson?

Yes. Every AI Prompt Engineering lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Introduction to DSPy Framework
  2. Defining Signatures and Modules
  3. Compiling and Optimizing Prompts
  4. Evaluating DSPy Pipelines
← Back to AI Prompt Engineering