MIPROv2 Optimizer

MIPROv2 (Multi-stage Instruction Proposal and Refinement Optimizer v2) is the primary optimization algorithm in DSPy.rb. It automatically improves your predictor’s performance through a three-phase optimization process: bootstrap training, instruction optimization, and few-shot example refinement.

Overview

MIPROv2 works by:

  • Bootstrap Phase: Generating training examples with reasoning traces
  • Instruction Phase: Optimizing the system instruction for better performance
  • Few-shot Phase: Selecting the best combination of few-shot examples

The optimizer uses a grounded proposer to generate high-quality candidate instructions and sophisticated example selection to create optimal few-shot demonstrations.

Basic Usage

Simple Optimization

# Define your signature
class ClassifyText < DSPy::Signature
  description "Classify the sentiment of the given text"
  
  input do
    const :text, String
  end
  
  output do
    const :sentiment, String
    const :confidence, Float
  end
end

# Create optimizer
optimizer = DSPy::MIPROv2.new(signature: ClassifyText)

# Run optimization
result = optimizer.optimize(examples: training_examples) do |predictor, examples|
  # Your evaluation logic
  evaluator = DSPy::Evaluate.new(metric: :exact_match)
  evaluation_result = evaluator.evaluate(examples: examples) do |example|
    predictor.call(text: example.text)
  end
  evaluation_result.score
end

# Use the optimized predictor
best_predictor = result.optimized_program
final_score = result.best_score_value

puts "Optimization complete!"
puts "Best score: #{final_score}"
puts "Best instruction: #{best_predictor.prompt.instruction}"

AutoMode Configuration

MIPROv2 provides preset configurations for different optimization scenarios:

# Light optimization - fastest, good for prototyping
optimizer = DSPy::MIPROv2.new(
  signature: ClassifyText,
  mode: :light
)

# Medium optimization - balanced performance and speed (default)
optimizer = DSPy::MIPROv2.new(
  signature: ClassifyText,
  mode: :medium
)

# Heavy optimization - most thorough, best results
optimizer = DSPy::MIPROv2.new(
  signature: ClassifyText,
  mode: :heavy
)

Custom Configuration

# Fine-tune optimization parameters
config = DSPy::MIPROv2::MIPROv2Config.new
config.bootstrap_examples = 4
config.max_bootstrap_examples = 8
config.num_candidate_instructions = 10
config.instruction_trials = 15
config.max_few_shot_examples = 6
config.few_shot_trials = 20

optimizer = DSPy::MIPROv2.new(
  signature: ClassifyText,
  config: config
)

Configuration Options

MIPROv2Config Parameters

config = DSPy::MIPROv2::MIPROv2Config.new

# Bootstrap phase settings
config.bootstrap_examples = 4          # Examples to generate initially
config.max_bootstrap_examples = 8      # Maximum examples to collect

# Instruction optimization
config.num_candidate_instructions = 10 # Instruction variants to try
config.instruction_trials = 15         # Evaluation trials per instruction

# Few-shot optimization  
config.max_few_shot_examples = 6       # Max examples in final prompt
config.few_shot_trials = 20           # Trials for few-shot selection

# Example selection
config.example_selection_strategy = :random  # or :diverse

# Display options
config.verbose = true                  # Show optimization progress

AutoMode Configurations

# Light mode values:
# - bootstrap_examples: 2
# - max_bootstrap_examples: 4  
# - num_candidate_instructions: 5
# - instruction_trials: 8
# - max_few_shot_examples: 3
# - few_shot_trials: 10

# Medium mode values (default):
# - bootstrap_examples: 4
# - max_bootstrap_examples: 8
# - num_candidate_instructions: 10  
# - instruction_trials: 15
# - max_few_shot_examples: 6
# - few_shot_trials: 20

# Heavy mode values:
# - bootstrap_examples: 8
# - max_bootstrap_examples: 16
# - num_candidate_instructions: 20
# - instruction_trials: 25
# - max_few_shot_examples: 10
# - few_shot_trials: 30

Optimization Phases

Phase 1: Bootstrap Training

The optimizer generates high-quality training examples:

# MIPROv2 automatically handles this, but you can observe the process
optimizer = DSPy::MIPROv2.new(
  signature: ClassifyText,
  config: config
)

# During optimization, bootstrap examples are generated using
# Chain of Thought reasoning to create examples with explanations

Phase 2: Instruction Optimization

Multiple instruction candidates are generated and tested:

# The grounded proposer generates instruction variations like:
# - "Classify the sentiment of the given text as positive, negative, or neutral."
# - "Analyze the emotional tone of the provided text and categorize it."
# - "Determine whether the text expresses positive, negative, or neutral sentiment."

# Each instruction is evaluated across multiple trials to find the best one

Phase 3: Few-shot Example Selection

The best combination of few-shot examples is selected:

# MIPROv2 tests different combinations of bootstrap examples
# to find the set that maximizes performance on validation data

Working with Results

MIPROv2Result Object

result = optimizer.optimize(examples: examples) do |predictor, val_examples|
  # evaluation logic
end

# Access optimization results
puts "Best score: #{result.best_score_value}"
puts "Score name: #{result.best_score_name}"
puts "Total trials: #{result.total_trials}"

# Get the optimized predictor
optimized_predictor = result.optimized_program

# Access optimization history
result.history[:trials].each do |trial|
  puts "Trial #{trial[:trial_number]}: #{trial[:score]}"
end

# Check timing information
puts "Bootstrap time: #{result.history[:bootstrap_time]}"
puts "Instruction time: #{result.history[:instruction_time]}" 
puts "Few-shot time: #{result.history[:few_shot_time]}"
puts "Total time: #{result.total_time}"

Best Configuration Access

best_config = result.best_config

puts "Best instruction: #{best_config.instruction}"
puts "Number of few-shot examples: #{best_config.few_shot_examples.size}"

# Inspect few-shot examples
best_config.few_shot_examples.each_with_index do |example, i|
  puts "Example #{i+1}:"
  puts "  Input: #{example.input}"
  puts "  Output: #{example.output}"
end

Integration with Storage and Registry

Saving Optimization Results

# Save to storage system
storage = DSPy::Storage::StorageManager.new
saved_program = storage.save_optimization_result(
  result,
  metadata: {
    signature: 'text_classifier',
    optimization_method: 'MIPROv2',
    mode: 'medium'
  }
)

puts "Saved with ID: #{saved_program.program_id}"

Integration with Registry

# Auto-register with registry
registry_manager = DSPy::Registry::RegistryManager.new
registry_manager.integration_config.auto_register_optimizations = true

# This will automatically register the result
version = registry_manager.register_optimization_result(
  result,
  signature_name: 'text_classifier'
)

puts "Registered as version: #{version.version}"

Advanced Usage

Custom Evaluation Logic

result = optimizer.optimize(examples: training_examples) do |predictor, val_examples|
  total_score = 0.0
  
  val_examples.each do |example|
    prediction = predictor.call(text: example.text)
    
    # Custom scoring logic
    if prediction.sentiment == example.expected_sentiment
      # Base score for correct classification
      score = 1.0
      
      # Bonus for high confidence on correct predictions
      if prediction.confidence > 0.8
        score += 0.2
      end
      
      total_score += score
    end
  end
  
  total_score / val_examples.size
end

Validation Split

# Use separate validation set for unbiased evaluation
result = optimizer.optimize(
  examples: training_examples,
  val_examples: validation_examples
) do |predictor, val_examples|
  # Evaluation on held-out validation set
  evaluator = DSPy::Evaluate.new(metric: :exact_match)
  evaluator.evaluate(examples: val_examples) do |example|
    predictor.call(text: example.text)
  end.score
end

Monitoring Progress

config = DSPy::MIPROv2::MIPROv2Config.new
config.verbose = true  # Show detailed progress

optimizer = DSPy::MIPROv2.new(
  signature: ClassifyText,
  config: config
)

# Progress information is printed during optimization:
# - Bootstrap phase progress
# - Instruction candidate evaluation
# - Few-shot selection progress
# - Best scores and configurations

Best Practices

1. Choose Appropriate Mode

# For quick experimentation
optimizer = DSPy::MIPROv2.new(signature: YourSignature, mode: :light)

# For production optimization
optimizer = DSPy::MIPROv2.new(signature: YourSignature, mode: :heavy)

# For balanced optimization
optimizer = DSPy::MIPROv2.new(signature: YourSignature, mode: :medium)

2. Provide Quality Examples

# Use diverse, high-quality training examples
training_examples = [
  DSPy::Example.new(
    text: "I love this product! It's amazing.",
    expected_sentiment: "positive"
  ),
  DSPy::Example.new(
    text: "This is the worst experience I've ever had.",
    expected_sentiment: "negative"
  ),
  DSPy::Example.new(
    text: "The product is okay, nothing special.",
    expected_sentiment: "neutral"
  )
  # ... more diverse examples
]

3. Robust Evaluation

result = optimizer.optimize(examples: examples) do |predictor, val_examples|
  total_correct = 0
  total_attempted = 0
  
  val_examples.each do |example|
    begin
      prediction = predictor.call(text: example.text)
      total_attempted += 1
      
      if prediction.sentiment.downcase == example.expected_sentiment.downcase
        total_correct += 1
      end
    rescue => e
      # Handle prediction errors gracefully
      puts "Prediction failed: #{e.message}"
    end
  end
  
  return 0.0 if total_attempted == 0
  total_correct.to_f / total_attempted
end

4. Save Your Results

# Always save successful optimizations
if result.best_score_value > 0.8  # Your quality threshold
  storage_manager = DSPy::Storage::StorageManager.new
  storage_manager.save_optimization_result(
    result,
    tags: ['production', 'validated'],
    metadata: {
      dataset: 'customer_reviews_v2',
      optimization_date: Date.current,
      minimum_score: 0.8
    }
  )
end