L'ottimizzazione dei prompt per i modelli linguistici di grandi dimensioni (LLM) sta evolvendo da un processo manuale basato su tentativi ed errori a un approccio scientifico e automatizzato. In questo tutorial, vedremo come utilizzare GEPA, un framework di evoluzione riflessiva, per trasformare prompt deboli in istruzioni altamente performanti per risolvere complessi problemi aritmetici.
Configurazione dell'ambiente: GEPA e LiteLLM
Il primo passo consiste nell'installazione delle librerie necessarie. Utilizzeremo GEPA per gestire il ciclo evolutivo del prompt e LiteLLM per standardizzare le chiamate alle API di diversi modelli linguistici, garantendo flessibilità tra i vari provider.
python!pip install -q gepa litellm
import os, re, json, random, getpass, textwrap
import litellm
import gepa.optimize_anything as oa
from gepa.optimize_anything import (
optimize_anything, GEPAConfig, EngineConfig, ReflectionConfig,
)
litellm.suppress_debug_info = True
if not os.environ.get("OPENAI_API_KEY"):
os.environ["OPENAI_API_KEY"] = getpass.getpass("Enter your OpenAI API key: ")
TASK_LM = "openai/gpt-4o-mini"
REFLECTION_LM = "openai/gpt-4.1"
MAX_METRIC_CALLS = 100In questa configurazione, distinguiamo due ruoli fondamentali:
Task LM: Il modello (es. GPT-4o-mini) incaricato di risolvere effettivamente il problema matematico.
Reflection LM: Un modello tipicamente più potente (es. GPT-4.1 o modelli di ragionamento avanzati) che analizza i fallimenti del Task LM per riscrivere e migliorare il prompt.
Costruzione di un benchmark aritmetico deterministico
Per ottimizzare un prompt in modo efficace, serve un set di dati affidabile. Creiamo un generatore di problemi aritmetici "deterministici" (ovvero con una soluzione univoca e calcolabile programmaticamente) che copra diverse casistiche: sconti commerciali, calcolo di distanze di viaggio, gestione del portafoglio e operazioni a catena.
pythondef make_problems(n, seed=0):
rng = random.Random(seed)
out = []
for _ in range(n):
t = rng.choice(["discount", "travel", "wallet", "chain"])
if t == "discount":
unit = rng.choice([40, 60, 80, 120])
qty = rng.choice([5, 6, 8, 10])
disc = rng.choice([10, 20, 25, 50])
total = unit * qty
gold = total - total * disc // 100
q = (f"A shop sells notebooks at {unit} rupees each. You buy {qty} "
f"notebooks and get a {disc}% discount on the total bill. "
f"How many rupees do you pay in total?")
elif t == "travel":
s1, h1 = rng.choice([40, 50, 60]), rng.choice([2, 3])
s2, h2 = rng.choice([30, 45, 70]), rng.choice([1, 2, 3])
gold = s1 * h1 + s2 * h2
q = (f"A car drives at {s1} km/h for {h1} hours, then at {s2} km/h "
f"for {h2} hours. What is the total distance travelled, in km?")
elif t == "wallet":
tens = rng.choice([3, 5, 7, 9])
fifties= rng.choice([2, 4, 6])
spent = rng.choice([50, 80, 110, 150])
gold = tens * 10 + fifties * 50 - spent
q = (f"You have {tens} ten-rupee notes and {fifties} fifty-rupee "
f"notes. You spend {spent} rupees. How many rupees are left?")
else:
x = rng.choice([6, 9, 12, 15]); y = rng.choice([4, 7, 10]); z = rng.choice([3, 8, 11])
gold = x * 2 - y + z
q = (f"Start with the number {x}. Double it, then subtract {y}, "
f"then add {z}. What number do you end with?")
out.append({"question": q, "answer": gold})
return out
all_problems = make_problems(18, seed=42)
random.Random(1).shuffle(all_problems)
trainset = all_problems[:12]
valset = all_problems[12:]
print(f"Dataset: {len(trainset)} train / {len(valset)} val problems\n")Dividiamo i dati in un training set (utilizzato da GEPA per l'ottimizzazione iterativa) e un validation set (testato solo alla fine per verificare che il prompt funzioni bene anche su problemi mai visti prima).
Definizione dell'evaluator e feedback strutturato
Il cuore dell'ottimizzazione riflessiva è la capacità di fornire feedback "azionabile". Non basta dire all'IA che la risposta è sbagliata; bisogna spiegare perché. L'evaluator analizza l'output del modello, estrae la risposta finale (identificata dal formato #### <numero>) e assegna un punteggio basato sulla precisione del calcolo e del formato.
pythondef build_system_prompt(candidate: dict) -> str:
return (f"{candidate['instructions']}\n\n"
f"OUTPUT FORMAT RULES:\n{candidate['format_rules']}")
def parse_answers(text: str):
formatted = re.search(r"####\s*(-?\d+)", text)
all_nums = re.findall(r"-?\d+", text)
fmt_val = int(formatted.group(1)) if formatted else None
last_val = int(all_nums[-1]) if all_nums else None
return fmt_val, last_val
def evaluate(candidate: dict, example: dict):
system = build_system_prompt(candidate)
raw = call_task_lm(system, example["question"])
gold = example["answer"]
fmt_val, last_val = parse_answers(raw)
if fmt_val is not None and fmt_val == gold:
score, fb = 1.0, "Correct and correctly formatted."
elif fmt_val is not None and fmt_val != gold:
score, fb = 0.0, (f"WRONG ANSWER. You output '#### {fmt_val}' but the correct answer is {gold}.")
elif last_val == gold:
score, fb = 0.5, (f"Right number but FORMAT VIOLATION. Use '#### <integer>'.")
else:
score, fb = 0.0, (f"WRONG. Correct answer is {gold}. Likely a reasoning slip.")
side_info = {"feedback": fb, "problem": example["question"], "model_output": raw[:500]}
return score, side_infoQuesto feedback strutturato permette a GEPA di capire se il modello fallisce per mancanza di logica (errori matematici) o per inosservanza del formato richiesto.
Esecuzione del ciclo evolutivo con GEPA
Iniziamo con un prompt "seme" estremamente semplice: "Risolvi il problema matematico. Dai la risposta.". È un prompt volutamente debole che funge da linea di base (baseline).
pythonseed_candidate = {
"instructions": "Solve the math problem.",
"format_rules": "Give the answer.",
}
objective = (
"Evolve a system prompt so a small LLM reliably solves multi-step arithmetic "
"word problems AND always ends with exactly '#### <integer>'."
)
config = GEPAConfig(
engine=EngineConfig(max_metric_calls=MAX_METRIC_CALLS, parallel=True),
reflection=ReflectionConfig(reflection_lm=REFLECTION_LM),
)
result = optimize_anything(
seed_candidate=seed_candidate,
evaluator=evaluate,
dataset=trainset,
valset=valset,
objective=objective,
config=config,
)Durante l'esecuzione, GEPA tenta diverse varianti del prompt. Se una variante migliora il punteggio medio, diventa la base per la "prole" successiva. Il processo continua fino al raggiungimento dell'obiettivo o all'esaurimento del budget di chiamate.
Processo di Ottimizzazione con GEPA
- 1InizializzazioneSi definisce un prompt di partenza e si valutano le prestazioni iniziali sui set di train e validazione.
- 2Ciclo EvolutivoGEPA chiama il Task LM sui problemi. L`evaluator analizza i risultati e produce feedback.
- 3RiflessioneIl Reflection LM usa il feedback per identificare punti deboli (es. mancanza di passaggi intermedi) e propone modifiche alle istruzioni.
- 4Selezione e ValidazioneIl framework seleziona il candidato migliore basato sui punteggi medi e lo valida sul set di dati `held-out` per evitare l`overfitting.
Analisi dei risultati e validazione finale
Al termine del processo, estraiamo il best_candidate. Spesso si nota come il Reflection LM aggiunga istruzioni che forzano il modello a mostrare il proprio ragionamento passo-passo (Chain of Thought) o a eseguire una fase di verifica interna prima di scrivere la riga finale con il formato rigoroso.
pythonbest = result.best_candidate
print(" Seed prompt score:", eval_set(seed_candidate, valset, "val-seed")[0])
print(" GEPA prompt score:", eval_set(best, valset, "val-gepa")[0])Il miglioramento delle prestazioni tra il prompt iniziale e quello ottimizzato sul set di validazione dimostra che il sistema ha imparato strategie di istruzione generalizzabili, anziché limitarsi a memorizzare le risposte del training set.
Verdetto Tecnico
GEPA si conferma uno strumento potente per chiunque debba gestire pipeline LLM complesse. La capacità di evolvere prompt multi-componente basandosi su feedback strutturato riduce drasticamente il tempo dedicato alla scrittura manuale delle istruzioni, portando a risultati più robusti e meno soggetti a errori di formato.
Posso usare GEPA con modelli locali?
Quanti esempi servono per una buona ottimizzazione?
GEPA funziona per compiti non matematici?
Fonti e riferimenti




