GEPA Optimization
Run agent-opt's GEPAOptimizer to evolve a prompt through reflection and mutation, then read out the best prompt found.
Run agent-opt’s GEPAOptimizer on a summarization prompt: it evaluates the current prompt, reflects on the failures with a teacher model, mutates the prompt, and repeats within a fixed evaluation budget. You end with a best-scoring prompt and its score.
| Time | Difficulty | Package |
|---|---|---|
| 20 min | Intermediate | agent-opt + gepa |
- Future AGI account → app.futureagi.com
- API keys:
FI_API_KEYandFI_SECRET_KEY(see Get your API keys) - An OpenAI API key (the reflection and generator models call OpenAI through LiteLLM)
- Python 3.11
Install
pip install agent-opt gepa
export FI_API_KEY="your-api-key"
export FI_SECRET_KEY="your-secret-key"
export OPENAI_API_KEY="your-openai-api-key"
Note
GEPAOptimizer wraps the external gepa library. Without pip install gepa, importing GEPAOptimizer raises ImportError: To use GEPAOptimizer, please install the 'gepa' library with: pip install gepa.
Tutorial
Prepare a dataset and a starting prompt
GEPA needs a dataset of inputs to score candidate prompts against. Each row is a plain dict.
dataset = [
{
"article": "The James Webb Space Telescope has captured stunning new images of the Pillars of Creation, revealing intricate details of gas and dust clouds where new stars are forming.",
},
{
"article": "Researchers at the University of Austin have discovered a new enzyme capable of breaking down PET plastic, the material commonly found in beverage bottles, in a matter of hours.",
},
]
initial_prompt = "Summarize this article concisely: {article}"You should see nothing yet, this just defines the dataset and starting prompt in memory. Two rows is enough to run the tutorial; use 30+ for a result you’d trust.
Configure the Evaluator
The Evaluator scores each candidate prompt’s output using a Future AGI eval template.
from fi.opt.base import Evaluator
evaluator = Evaluator(
eval_template="summary_quality",
eval_model_name="turing_flash",
)You should see nothing yet, this just constructs the evaluator. If FI_API_KEY or FI_SECRET_KEY isn’t set, construction raises ValueError naming the missing key.
Map dataset fields with BasicDataMapper
BasicDataMapper tells the optimizer which dataset column is the input and which generated field is the output to score.
from fi.opt.datamappers import BasicDataMapper
data_mapper = BasicDataMapper(
key_map={"input": "article", "output": "generated_output"}
)generated_output is the field name the generator writes to during a run, not a column in dataset.
You should see nothing yet, this just builds the mapper; it does no work until optimize() runs.
Configure GEPAOptimizer
GEPA needs two models: a reflection model that critiques failures and rewrites the prompt, and a generator model whose prompt is being optimized.
from fi.opt.optimizers import GEPAOptimizer
optimizer = GEPAOptimizer(
reflection_model="gpt-4o", # critiques failures and proposes mutations
generator_model="gpt-4o-mini", # the "student" model whose prompt is optimized
)A stronger reflection model produces more useful critiques and better mutations. It’s worth spending more here than on the generator model.
You should see nothing yet, this just constructs the optimizer; no API calls happen until optimize() runs.
Run the optimization
max_metric_calls is the total evaluation budget for the whole run, including the calls spent scoring the initial prompt.
result = optimizer.optimize(
evaluator=evaluator,
data_mapper=data_mapper,
dataset=dataset,
initial_prompts=[initial_prompt],
max_metric_calls=20,
)Note
If your dataset has 300 rows and max_metric_calls is 200, the budget runs out scoring the starting prompt alone, with nothing left for actual mutation. Keep max_metric_calls well above your dataset size; the SDK default is 150.
This calls the reflection model to analyze failures, mutates the prompt, and scores each new candidate with the evaluator. On a 2-row dataset with a small budget it typically finishes in under a minute; the default budget of 150 takes longer.
Inspect the results
seed_score = result.history[0].average_score
print(f"Seed Score: {seed_score:.4f}")
print(f"Best Score: {result.final_score:.4f}")
print(f"Initial Prompt:\n{initial_prompt}")
print(f"Best Prompt Found:\n{result.best_generator.get_prompt_template()}")result.history[0] is the seed candidate’s evaluation, appended before any mutation, so seed_score and result.final_score give you a before/after delta rather than a single number. Example output, illustrative:
Seed Score: 0.7200
Best Score: 0.8400
Initial Prompt:
Summarize this article concisely: {article}
Best Prompt Found:
Summarize this article in 1-2 sentences, naming the specific finding or event and its concrete outcome...The exact prompt and score depend on your dataset and models, so don’t expect to match this run.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
ImportError: To use GEPAOptimizer, please install the 'gepa' library | The gepa package isn’t installed alongside agent-opt | pip install gepa |
TypeError: unexpected keyword argument 'max_metric_calls' | max_metric_calls passed to GEPAOptimizer(...) instead of .optimize(...) | Move max_metric_calls to the optimize() call |
AuthenticationError on the first evaluator call | FI_API_KEY or FI_SECRET_KEY wasn’t exported before the script started | Export both keys in the same shell session, then rerun |
| LiteLLM raises an OpenAI auth error | OPENAI_API_KEY missing; reflection_model and generator_model both route through LiteLLM to OpenAI | Export OPENAI_API_KEY alongside the FI keys |
ValueError: Initial prompts list cannot be empty | initial_prompts=[] or omitted | Pass at least one prompt string, e.g. initial_prompts=[initial_prompt] |
optimize() exhausts its budget on the first evaluation, result.final_score never changes | max_metric_calls is smaller than or close to the dataset size | Raise max_metric_calls well above the dataset row count |
optimize() runs for many minutes | max_metric_calls left at a large value (150-300) while testing, and each call round-trips the reflection and generator models | Lower max_metric_calls to 10-20 for a test run, raise it only for a real optimization pass |
Once you have a GEPA baseline, Comparing Prompt Optimizers runs it alongside ProTeGi and PromptWizard on the same task.
Questions & Discussion