Skip to content

What is GEPA? Reflective Prompt Evolution That Outperforms Reinforcement Learning

The Problem

When I tried to optimize prompts for my AI agents, I hit a wall. Reinforcement learning methods like GRPO require thousands of rollouts - 5,000 to 25,000+ evaluations. My API costs exploded. Worse, the optimization traces were opaque. I couldn’t understand why a particular prompt change improved (or worsened) performance.

I spent weeks watching scalar rewards bounce around without any insight into what was actually happening inside my system.

What I Tried First

I started with GRPO (Group Relative Policy Optimization), the RL method behind many prompt optimization systems:

GRPO Workflow
Run 10,000 rollouts
-> Compute scalar reward for each (success=1, failure=0)
-> Use policy gradients to update parameters
-> Repeat
Information preserved: Just the reward number
Information lost: Error messages, reasoning traces, tool call history

This approach has critical flaws:

  1. Information loss: Scalar rewards don’t explain why something failed
  2. Sample inefficiency: I burned through API credits with 5,000+ rollouts
  3. Black-box optimization: I couldn’t interpret why certain prompts worked better

What I Found

Then I discovered GEPA (Genetic-Pareto) - an ICLR 2026 Oral paper that fundamentally rethinks prompt optimization. The core insight struck me:

Language optimization should use language feedback, not scalar gradients.

GEPA reads full execution traces to diagnose why a candidate failed, then uses natural language reflection to propose targeted fixes. This approach achieves:

  • 35x fewer evaluations: 100-500 vs 5,000-25,000+ for RL
  • 6% average improvement over GRPO, up to 20% on specific tasks
  • Full interpretability: Every optimization trace is human-readable

How GEPA Works

GEPA takes a completely different approach than RL methods:

GEPA Reflective Evolution Cycle
+------------------------+
| Pareto Frontier | <-- Pool of best candidates
+------------------------+
|
v
+------------------------+
| Select Candidate | <-- Best on some task subset
+------------------------+
|
v
+------------------------+
| Execute on Minibatch | <-- Capture full traces
+------------------------+
|
v
+------------------------+
| Reflect with LLM | <-- Read traces, diagnose failures
+------------------------+ (uses Actionable Side Information)
|
v
+------------------------+
| Mutate Candidate | <-- Apply learned lessons
+------------------------+
|
v
+------------------------+
| Evaluate & Accept | <-- Update Pareto front if improved
+------------------------+

The key innovation is Actionable Side Information (ASI) - diagnostic feedback that serves as the text-optimization analogue of a gradient.

Why Reflection Beats Gradients

When a prompt fails, the execution trace contains rich diagnostic information:

What Execution Traces Reveal
- Error messages show constraint violations
- Reasoning logs reveal logical gaps
- Tool outputs indicate retrieval failures
- Profiler data shows computational bottlenecks

RL collapses all this into a single number (reward = 0.7). GEPA preserves the full context and lets an LLM reflect on it in natural language - the same medium the optimization target (prompts) exists in.

This is why GEPA needs 35x fewer evaluations. Each reflection provides more actionable insight than hundreds of scalar reward signals.

The Pareto Frontier Advantage

GEPA doesn’t optimize a single candidate. It maintains a Pareto frontier of candidates excelling on different task subsets:

Pareto Frontier Example
Candidate A: 90% accuracy on math problems, 60% on coding
Candidate B: 70% accuracy on math problems, 85% on coding
Candidate C: 80% accuracy on math problems, 75% on coding
No single metric collapses this multi-dimensional performance.
GEPA merges strengths from complementary candidates.

This preserves diverse strategies instead of converging to a single “optimal” prompt that might fail on edge cases.

Basic GEPA Usage

I tested GEPA with the official library. Here’s a minimal example:

basic_gepa_example.py
import gepa
# Initialize dataset (AIME math problems)
trainset, valset, _ = gepa.examples.aime.init_dataset()
# Seed prompt to optimize
seed_prompt = {
"system_prompt": "You are a helpful assistant. Answer the question. "
"Put your final answer in the format '### &lt;answer&gt;'"
}
# Run GEPA optimization - only 150 evaluations!
result = gepa.optimize(
seed_candidate=seed_prompt,
trainset=trainset,
valset=valset,
task_lm="openai/gpt-4.1-mini",
max_metric_calls=150, # Compare: GRPO needs 5,000+
reflection_lm="openai/gpt-5",
)
print("Optimized prompt:", result.best_candidate['system_prompt'])
# Result: GPT-4.1 Mini goes from 46.6% -> 56.6% on AIME 2025

Note the max_metric_calls=150. That’s the key difference. GEPA achieves better results with 35x fewer evaluations than RL.

Using GEPA with DSPy

DSPy integration makes GEPA even easier. This is my recommended approach:

dspy_gepa_optimizer.py
import dspy
# Define your DSPy program
class MyProgram(dspy.Module):
def __init__(self):
self.generate = dspy.ChainOfThought("question -> answer")
def forward(self, question):
return self.generate(question=question)
# Create GEPA optimizer
optimizer = dspy.GEPA(
metric=your_metric,
max_metric_calls=150,
reflection_lm="openai/gpt-5",
)
# Optimize the entire program
optimized_program = optimizer.compile(
student=MyProgram(),
trainset=trainset,
valset=valset
)

DSPy handles the adapter pattern automatically. You define your program, specify a metric, and GEPA learns the optimal prompts.

Beyond Prompts: optimize_anything

GEPA can optimize any text artifact - code, configuration files, SVG graphics:

optimize_anything_example.py
import gepa.optimize_anything as oa
from gepa.optimize_anything import optimize_anything, GEPAConfig, EngineConfig
def evaluate(candidate: str) -> float:
"""Evaluate any text artifact - code, config, SVG, etc."""
result = run_my_system(candidate)
# Log ASI for reflection - this is crucial!
oa.log(f"Output: {result.output}")
oa.log(f"Error: {result.error}")
oa.log(f"Profiler: {result.profile_data}")
return result.score
result = optimize_anything(
seed_candidate="", # Starting artifact
evaluator=evaluate,
objective="Describe what you want to optimize for.",
config=GEPAConfig(engine=EngineConfig(max_metric_calls=100)),
)

The oa.log() calls provide ASI (Actionable Side Information). This diagnostic feedback is what makes GEPA work.

Benchmark Comparison

I compiled the key metrics from the ICLR 2026 paper:

MetricGEPAGRPO (RL)MIPROv2
Evaluations needed100-5005,000-25,000+500-1000
AIME-2025 accuracy+12% over MIPROv2LowerBaseline
Average improvement vs GRPO+6%Baseline-
Maximum improvement vs GRPO+20%Baseline-
InterpretabilityFull tracesScalar rewardsPartial
API-only model supportYesNo (needs weights)Yes

The 35x efficiency gap is what convinced me to switch. With expensive API calls, this translates to significant cost savings.

Real-World Results

Production deployments show GEPA’s practical impact:

  • Databricks Enterprise Agents: 90x cheaper than Claude Opus 4.1 using open-source models + GEPA
  • ARC-AGI Agent: 32% to 89% accuracy via architecture discovery
  • Jinja Coding Agent: 55% to 82% resolve rate via auto-learned skills
  • Cloud Scheduling: 40.2% cost savings, beating expert heuristics

Shopify’s CEO Tobi Lutke noted: “Both DSPy and GEPA are currently severely under hyped in the AI context engineering world.”

When GEPA Shines

GEPA works best in these scenarios:

  1. Expensive rollouts: Scientific simulations, complex agents with tool calls - GEPA needs 100-500 evals
  2. Scarce data: Works with as few as 3 examples, no large training sets required
  3. API-only models: No weights access needed - optimize GPT-5, Claude, Gemini through APIs
  4. Interpretability requirement: Human-readable optimization traces show why each prompt changed

When to Use RL Instead

GEPA complements RL, not replaces it entirely:

  • Use GEPA for rapid initial optimization (100-500 evals)
  • Then apply RL/fine-tuning for additional gains if you have model weight access
  • RL works better when you need to optimize non-text parameters (embeddings, weights)

What I Learned

After switching to GEPA, I realized three things:

  1. Reflection is the right medium for language optimization - Reading error messages and reasoning traces provides more insight than scalar rewards
  2. ASI is the new gradient - Actionable Side Information gives direction for text optimization
  3. 35x efficiency matters - With API costs, fewer evaluations directly translates to savings

Key Takeaways

GEPA represents a paradigm shift: instead of treating prompts as black-box parameters optimized via scalar rewards, it recognizes that language is inherently interpretable and can be improved through natural language reflection.

If you’re optimizing prompts, agents, or any text-based AI systems:

  1. Start with DSPy GEPA integration (dspy.GEPA)
  2. Provide rich ASI in your evaluators - log error messages, reasoning traces, tool history
  3. Use a strong reflection LLM (GPT-5 or similar)
  4. Limit evaluations to 150-300 for most tasks
  5. Monitor the Pareto frontier to preserve diverse strategies

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments