How it works
MIPROv2 works in four phases:Phase 1: Trace collection
One traced teacher pass over the trainset. Every example runs through your module under trace capture, collecting whole-program scores plus per-Predict input/output spans.
Phase 2: Demo bootstrapping
Successful spans scoring at leastmin_demo_score become few-shot demos on the predictor that produced them (top max_bootstrapped_demos by score, deduplicated on inputs). A span scores as its rollout does, unless the metric attached a span-level eval via TypedMetric::evaluate_spans — that score then takes precedence (see Evaluation). Demos are installed before instruction search, so candidates are scored against the module as it will actually run.
Phase 3: Candidate generation
Uses the traces and a rotation of prompting tips to generatenum_candidates instruction variants per predictor. The prompting tips library includes:
- Use clear, specific language
- Consider chain-of-thought for complex tasks
- Specify output formats
- Use role-playing when appropriate
- Handle edge cases explicitly
- Request structured outputs when needed
Phase 4: Trial evaluation
- Evaluates up to
num_trialscandidates per predictor on one sampled minibatch (candidates injected ambiently — the module is never touched during evaluation) - Computes performance scores
- Selects the best performing candidate
- Installs the accumulated winner (demos + best instructions) once at the end through
OptimizeTarget::install
Configuration
Default settings:eval_concurrency (concurrent LM calls during evaluation, default 16) and seed (fixes minibatch sampling for reproducible runs). The full field table is in the optimizers reference.
Usage example
TypedMetric used by every optimizer: evaluate(&self, example, prediction, trace) -> Result<Eval>, where example is your full trainset row. MIPROv2 only reads the numerical score; feedback is ignored. See the evaluation reference for the trait.
train_examples is a slice of any row type implementing ToInput toward the module’s input — a #[derive(Example)] struct or (Input, Output) tuples; see Data. The module must declare its leaves with predictors!; see Modules.
Typed data loading
Use the shared data ingress reference:DataLoader.
Comparison: COPRO vs MIPROv2 vs GEPA
GEPA is the only optimizer that requires textual feedback from the metric (
Eval::with_feedback). The others use numerical scores alone. Full configuration tables for all six live in the optimizers reference.
When to use MIPROv2
- You have decent training data (15+ examples recommended)
- Quality matters more than speed
- Task benefits from prompting best practices and few-shot demos
- Need trace-informed candidate generation
When to use COPRO
- You need fast iteration
- Compute budget is limited
- Task is straightforward
When to use GEPA
- Complex tasks with subtle failure modes
- You can provide rich feedback
- Multi-objective optimization
- Need diverse solutions
Implementation notes
The code follows standard Rust practices:- No unsafe blocks
- Results for error handling with context via anyhow
- Strong types (
Candidate,PromptingTips) - Builder pattern for configuration
- Async throughout, no blocking calls
PromptingTips- the library of best practices (default_tips(),format_for_prompt())Candidate- the shared engine currency MIPROv2 registers its instruction variants as (see Optimizer engine)
compile_module returns (): MIPROv2 installs the winner on the module and reports nothing further (Report::None through the trait).
Cost is roughly num_predictors × (trainset_size + num_trials × minibatch_size) LM calls, minus rollout-cache hits.
Testing
Run tests:Example
MIPROv2 Example
Complete working example with HuggingFace data loading
