AI Blog

BootstrapFewShot vs MIPROv2 vs GEPA vs TextGrad: your metric picks the optimizer

GEPA's reported margins — up to 20% over GRPO, 13% over MIPROv2 — were all measured where an automatic checker was free and a failed run could be described in words. Two of these four optimizers run on a bare scalar; two need a sentence. What your eval function returns decides which half of the field you can use, so change the metric before you change the optimizer.

By Agentic AI Wiki 12 min read

Every published comparison of these four optimizers ranks them by how much accuracy they buy, and that ranking is unavailable to you — because each one consumes a different shape of feedback, and the shape you can supply was fixed the day you wrote your eval function. Before you read another benchmark table, answer one question about your metric: does it return a number, or a number and a sentence? That answer eliminates half this field.

At a glance

Three of these live in DSPy and optimize a compiled program; one is a standalone framework that treats any text in your system as a variable. What separates them is not the search algorithm but what they can learn from.

OptimizerWhat it changesWhat it learns fromHome
BootstrapFewShot Demonstrations only Whether a trace passed DSPy
MIPROv2 Instructions and demonstrations, jointly A scalar score over many rollouts DSPy
GEPA Instruction text, by reflection The trajectory, plus any diagnostic text DSPy; standalone library
TextGrad Any text variable in the graph A written critique from a second model Standalone
What each prompt optimizer mutates, and what signal it consumes A program box on the left feeds an evaluation box on the right, and a return path runs back to four optimizers. BootstrapFewShot mutates only the demonstration examples and consumes a pass or fail score. MIPROv2 mutates instructions and demonstrations together and consumes a scalar score over many rollouts. GEPA mutates instruction text by reflecting on the run trajectory and consumes a score plus any diagnostic text the metric returns. TextGrad mutates any text variable in the graph and consumes a natural-language critique written by a second model. The return arrow carries a different payload for each. Same loop, four different return payloads Your program instructions + demos + calls Your metric what it returns is the choice Score, and maybe a sentence BootstrapFewShot Mutates: demos keeps traces that passed needs: pass / fail MIPROv2 Mutates: both joint instruction + demo search needs: a scalar, many rollouts GEPA Mutates: instructions reflects on the trajectory wants: score + diagnosis TextGrad Mutates: any text var critique flows back through needs: a written critique A new program fitted to this model version THE LEFT TWO CAN RUN ON A BARE NUMBER. THE RIGHT TWO WANT WORDS, AND LOSE MOST OF THEIR EDGE WITHOUT THEM THE OUTPUT IS A FITTED ARTEFACT: SWAP THE MODEL AND THE SEARCH HAS TO RUN AGAIN
The loop is identical in all four. What differs is the payload on the return arrow — and that payload is written by your metric.
Which optimizer your evaluation function can actually feed A four by four heatmap. Rows are BootstrapFewShot, MIPROv2, GEPA and TextGrad. Columns are works on a bare scalar, exploits text feedback, small labelled set is enough, and cheap to run. BootstrapFewShot is strong on the scalar, weak on text feedback, and strong on both set size and cost. MIPROv2 is strong on the scalar, weak on text feedback, and medium on set size and cost. GEPA is medium on the scalar, strong on text feedback and set size, medium on cost. TextGrad is weak on the scalar, strong on text feedback and set size, medium on cost. The left column and the second column are close to mutually exclusive. The first two columns are nearly mutually exclusive Runs on a bare scalar Exploits text feedback Small labelled set OK Cheap to run BootstrapFewShot Strong (all it needs) Weak (ignores it) Strong (tens) Strong (one pass) MIPROv2 Strong (designed for) Weak (ignores it) Medium (hundreds) Medium (search) GEPA Medium (works, dulled) Strong (its edge) Strong (sample-efficient) Medium (reflection calls) TextGrad Weak (needs a critic) Strong (is the gradient) Strong (few examples) Medium (critic calls) Strong Medium Weak READ THE FIRST TWO COLUMNS AGAINST YOUR OWN METRIC BEFORE READING ANY BENCHMARK TABLE
Read the first two columns against your own eval function before you read anyone's accuracy table.

The metric is the interface, and almost nobody designs it that way

In a DSPy program the metric is a function you write that scores a prediction against an example. Most people write it once, return True or a float, and never think about it again — it is plumbing. It is not plumbing. It is the only channel through which any of these optimizers learns anything, and its return type decides which of them can do useful work.

A bare float is enough for the search-based optimizers

BootstrapFewShot runs your program over training examples, keeps the traces where the metric said yes, and installs them as few-shot demonstrations. It needs a pass/fail verdict and nothing else, it terminates fast, and on a program whose failures are mostly formatting or missing pattern, it is a startling amount of the available gain for an hour of work.

MIPROv2 does something more ambitious with the same signal: it proposes candidate instructions, pairs them with candidate demonstration sets, and searches that joint space against your scalar over a budget of rollouts. It is the sensible default when you have a few hundred labelled examples and an automatic checker. What it cannot do is know why a candidate lost — the score is a scalar, so the search is guided by the shape of the surface rather than by any understanding of the failure.

The reflective optimizers want words, and get sharply worse without them

GEPA, introduced at ICLR 2026 as a gradient-free reflective optimizer, inverts that. Rather than sampling the space and following the score, it reads the program's own trajectory — the inputs, the intermediate outputs, what the metric said — and writes an improved instruction the way a person would after reading a failed run. It then evolves a population of these along a Pareto frontier so that a candidate strong on one slice of the data is not discarded for being weak on another.

This is why its reported numbers are what they are. Across six tasks on Qwen3 8B the paper reports GEPA beating GRPO — a serious reinforcement-learning baseline — by up to 20%, and MIPROv2 by 13% aggregate. On MATH, GEPA-optimized programs are reported at 93% against 67% for a plain chain-of-thought module. On AIME 2025 with GPT-4.1 Mini it lands about 10% over the unoptimized baseline and about 12% over MIPROv2.

Now notice what every one of those tasks has in common: a free, automatic, unambiguous checker. A maths answer is right or wrong, and — this is the load-bearing part — a failed maths trace can be described. The metric can say the model dropped the constraint from line two. That sentence is GEPA's fuel. Hand it a metric that returns 0.0 and you have not disabled it, but you have taken away the thing it beats MIPROv2 with.

TextGrad takes the same idea further and makes it the whole system. It treats a compound LLM application as a computation graph in which the "gradient" is a natural-language critique written by an LLM, propagated backwards through the graph by the chain rule with text in place of derivatives. There is no scalar loss at all in the usual sense; there is a critic that says what was wrong and which way to push. The honesty of the design is that its authors are clear about the limits — the gradients are approximate and stochastic, they can contradict each other across a batch, and there is no learning rate and no convergence guarantee.

Reported gains for GEPA over its published baselines Horizontal bar chart of four reported margins from the GEPA literature. GEPA over GRPO reaches up to twenty percent. GEPA over MIPROv2 is thirteen percent aggregate on Qwen3 8B across six tasks. On AIME 2025 with GPT-4.1 Mini, GEPA is twelve percent over MIPROv2 and ten percent over the unoptimized baseline. Every one of these margins was measured on a task with a free automatic checker, which is the condition most production tasks do not meet. Reported margins, and what they were measured on GEPA over GRPO six tasks, Qwen3 8B up to +20% GEPA over MIPROv2 aggregate, same setup +13% GEPA over MIPROv2 AIME 2025, GPT-4.1 Mini +12% GEPA over no optimizer AIME 2025, GPT-4.1 Mini +10% 0 +5% +10% +15% +20% ALL FOUR MARGINS COME FROM TASKS WITH A FREE AUTOMATIC CHECKER — MATH ANSWERS, NOT HUMAN JUDGEMENT THE SPREAD BETWEEN THE TOP AND BOTTOM ROW IS THE BASELINE CHANGING, NOT THE OPTIMIZER
Four reported margins, all measured where an automatic checker was free. The bottom two rows differ only in what they were compared against.

Three things the benchmark tables will not tell you

1. Your task probably has no free checker

The published margins come from maths, code and retrieval — domains chosen precisely because ground truth is cheap. If your task's ground truth is a support lead's judgement collected weekly, you cannot run any of these at their published rollout budgets, and your binding constraint is the labelled set rather than the algorithm. The correct first move is not to pick an optimizer; it is to buy a checker, which usually means a rubric-driven judge, calibrated against human labels, of the kind argued for in judge calibration & meta-evaluation. Optimizing against an uncalibrated judge is the fastest way to fit your prompt to that judge's biases.

2. An optimized prompt is a fitted artefact, not an asset

What comes out of these is a set of instructions and demonstrations tuned to one model at one version. Swap the model — because the provider deprecated it, because a cheaper one appeared, because you moved to a smaller one for cost — and the search has to run again. That is a genuine and under-budgeted operational cost, and it belongs in the same migration plan as everything else in model deprecation & migration. It also means the optimizer's own run cost is recurring, not one-off.

3. The gain frequently lands where you didn't want it

An optimizer maximises the metric. If the metric rewards final-answer correctness, it will happily discover instructions that get there through reasoning you would not endorse, or that overfit to a quirk of your training slice. This is ordinary reward hacking with a text-shaped action space, and the defences are the same: a held-out set the optimizer never sees, a look at the prompts it produced rather than only the score, and the process-versus-outcome distinction from reward design & hacking. Read the optimized instruction before you ship it. It is short, it is in English, and it will occasionally say something alarming.

When to pick which

Your situationStart withWhy
Metric returns pass/fail, you have a few dozen examples BootstrapFewShot Cheapest possible experiment; often most of the available gain
Scalar metric, hundreds of examples, budget for rollouts MIPROv2 The mature default for joint instruction and demo search
Your metric can explain a failure in a sentence GEPA The diagnostic text is exactly what its reflection step consumes
Few examples, but a strong model available as a critic TextGrad Needs a critique rather than a labelled set
Multi-module program where you don't know which stage is failing GEPA or TextGrad Both attribute credit through the trace; scalar search cannot
No reliable metric at all None of them, yet Build the eval first — every one of these amplifies whatever it measures

One move dominates all of the above and costs an afternoon: change your metric to return a score and a short reason string. Search-based optimizers ignore the string harmlessly; reflective ones get their main input. You do not have to choose an optimizer to make that change, and you cannot use half this field without it.

FAQ

Is prompt optimization just a worse version of fine-tuning?

They answer different questions. Fine-tuning changes the weights and needs a training set, a serving story and a repeat of the whole exercise per model. Prompt optimization changes text, runs in minutes to hours, and transfers across providers about as well as any prompt does — which is to say, badly, but recoverably. The decision framework is in prompt, fine-tune or RL; in practice most teams have not exhausted prompt optimization before reaching for weights.

Does GEPA replace MIPROv2 in DSPy?

Not automatically. It reports better results on the paper's tasks under the paper's conditions, which include a rich feedback signal. On a program whose metric is a bare boolean and whose failures are demonstration-shaped, MIPROv2 or even BootstrapFewShot can match it for less money. Run the cheap one first — it takes an hour, and it tells you how much headroom there is before you spend on reflection calls.

How many examples do I actually need?

Fewer than people expect for the demonstration-based optimizers — tens is a real starting point — and the harder constraint is a held-out set large enough that the improvement you measure is not noise. This is the same statistical-power question as any other eval, treated properly in eval variance & statistical power. A 3% gain on 50 held-out examples is not a gain.

Can I optimize a multi-agent system this way?

Partially, and the credit-assignment problem is the reason it is hard: when a five-step pipeline produces a wrong answer, which module's instruction was at fault? Reflective optimizers have a real advantage here because the trajectory names the intermediate outputs, but the failure attribution is still the bottleneck — the same one described in multi-agent failure modes.

What about COPRO, and the other optimizers in DSPy?

COPRO does instruction-only coordinate ascent and is a reasonable middle option when your demonstrations are already good and only the instruction is weak. It sits on the same scalar-signal side of the split as MIPROv2, so nothing in this post's central argument changes for it. The DSPy optimizer set moves quickly; the question to carry forward is always what shape of feedback a new one consumes.

Further reading

On this wiki:

Project sources: