dspy.GEPA: optimizare reflexivă a instrucțiunilor în DSPy & Programare Algoritmică LLM

Optimizare și Compilare Nivel: avansat

GEPA rescrie instrucțiunile fiecărui predictor: un model de reflecție citește exemplele ratate și feedback-ul text al metricii și propune instrucțiuni noi, păstrate dacă scorul crește.

Sintaxă & invocare
optimizator = dspy.GEPA(metric=metrica, auto="light", reflection_lm=dspy.LM("openai/gpt-6-astra"))
optimizat = optimizator.compile(program, trainset=train, valset=val)

GEPA (Genetic-Pareto) a fost propus în lucrarea „GEPA: Reflective Prompt Evolution Can Outperform Reinforcement Learning” (Agrawal et al., 2025, arXiv:2507.19457). În DSPy este un optimizator de instrucțiuni: rescrie docstring-ul semnăturii fiecărui predictor și lasă neschimbate demonstrațiile, numele și descrierile câmpurilor. Ghidul de început al DSPy îl folosește ca prim optimizator.

GEPA ține o populație de programe candidate. La fiecare pas alege un predictor (implicit pe rând, component_selector="round_robin"), rulează un mini-lot de exemple, iar reflection_lm citește intrările, ieșirile și feedback-ul exemplelor slabe și propune o instrucțiune nouă. Candidatul următor este ales din frontiera Pareto pe exemple (programele care au cel mai bun scor pe cel puțin un exemplu); la final se întoarce programul cu cel mai bun scor agregat pe valset. Fără valset, GEPA folosește trainset și pentru selecție, ceea ce permite potrivirea excesivă pe acele exemple.

Metrica poate întoarce un număr, dar GEPA folosește pe deplin doar forma dspy.Prediction(score=..., feedback="..."): textul din feedback ajunge direct în promptul de reflecție. Bugetul se dă prin exact unul dintre auto ("light" / "medium" / "heavy", adică 6 / 12 / 18 candidați), max_full_evals sau max_metric_calls. În dspy 3.4.1, reflection_lm (sau un instruction_proposer propriu) este obligatoriu. Pachetul gepa se instalează automat doar pe Python sub 3.15.

Concepte cheie și caracteristici

Concept / Parametru Descriere practică
metric metric(gold, pred, trace=None, pred_name=None, pred_trace=None), de preferință cu dspy.Prediction(score, feedback).
auto / max_full_evals / max_metric_calls Bugetul; exact unul trebuie setat, altfel constructorul dă eroare.
reflection_lm Modelul care propune instrucțiuni; documentația recomandă unul mai puternic decât cel al programului.
reflection_minibatch_size Exemple arătate modelului de reflecție la o propunere; implicit 3.
candidate_selection_strategy "pareto" (implicit) sau "current_best".
skip_perfect_score / perfect_score Implicit True / 1.0: exemplele cu scor maxim nu intră în mini-lotul de reflecție.
use_merge / max_merge_invocations Implicit True / 5: combină instrucțiunile a doi candidați buni.
num_threads Paralelizează evaluările; reflecția rămâne serială.
track_stats / log_dir Implicit False; cu True, programul întors are detailed_results (toți candidații, scorurile, descendența).
compile(student, trainset, valset=None) Întoarce o copie a programului cu instrucțiunile celui mai bun candidat.

Exemple practice și scenarii reale

Exemplul 1: Optimizarea unui clasificator de tichete

from typing import Literal
import dspy

dspy.configure(lm=dspy.LM("openai/gpt-6-luna"))

class ClasificaTichet(dspy.Signature):
    """Clasifică tichetul de suport."""
    tichet: str = dspy.InputField()
    categorie: Literal["facturare", "tehnic", "cont", "altele"] = dspy.OutputField()

program = dspy.ChainOfThought(ClasificaTichet)
# train, val: liste de dspy.Example(tichet=..., categorie=...).with_inputs("tichet")

def metrica(gold, pred, trace=None, pred_name=None, pred_trace=None):
    if gold.categorie == pred.categorie:
        return dspy.Prediction(score=1.0, feedback="Categoria este corectă.")
    return dspy.Prediction(
        score=0.0,
        feedback=f"Ai ales {pred.categorie}, dar tichetul este de tip {gold.categorie}.",
    )

optimizator = dspy.GEPA(
    metric=metrica,
    auto="light",
    reflection_lm=dspy.LM("openai/gpt-6-astra"),
    num_threads=8,
    track_stats=True,
)
optimizat = optimizator.compile(program, trainset=train, valset=val)
optimizat.save("clasificator_gepa.json")

Exemplul 2: Instrucțiunea găsită și istoricul căutării

print(optimizat.predict.signature.instructions)   # ChainOfThought ține predictorul în .predict
rezultate = optimizat.detailed_results              # doar cu track_stats=True
print(rezultate.best_idx, rezultate.val_aggregate_scores[rezultate.best_idx])

Exemplul 3: Buget fix în apeluri de metrică

optimizator = dspy.GEPA(
    metric=metrica,
    max_metric_calls=500,
    reflection_lm=dspy.LM("anthropic/claude-sonnet-5-5"),
)

Greșeli frecvente și capcane

  • Construiești dspy.GEPA(metric=..., auto="light") fără reflection_lm. În 3.4.1 constructorul se oprește cu eroare de aserțiune.
  • Setezi și auto, și max_metric_calls. GEPA acceptă exact un tip de buget.
  • Întorci doar un număr din metrică. Merge, dar modelul de reflecție vede doar scorul, nu și motivul eșecului; scrie un feedback concret.
  • Omiți valset și raportezi scorul pe aceleași date. Ține un set de test separat, nefolosit nici la reflecție, nici la selecție.
  • Pornești cu auto="heavy". Documentația avertizează că o rulare GEPA sau MIPROv2 poate costa sute de dolari în apeluri; începe cu "light".

Noțiuni și module înrudite