Fix GEPA integration silently falling back to MIPROv2 on DSPy >=3.1 - #159
Open
pradeeps319 wants to merge 1 commit into
Open
Fix GEPA integration silently falling back to MIPROv2 on DSPy >=3.1#159pradeeps319 wants to merge 1 commit into
pradeeps319 wants to merge 1 commit into
Conversation
dspy.GEPA() raised on every run, and the surrounding try/except caught it
and dropped to MIPROv2 — so runs reported "Running GEPA optimization" and
completed successfully while never using GEPA at all. Three causes:
1. `max_steps` is not a DSPy >=3.1 argument; the budget is now expressed as
`max_metric_calls` (or `auto`/`max_full_evals`).
2. GEPA requires an explicit `reflection_lm` and none was passed, so it
raised even once the budget argument was correct. `--optimizer-model` was
documented as "Model for GEPA reflections" but never used for anything —
it was printed to the console and then dropped. It now drives the
reflection LM, which is what the flag describes.
3. GEPA requires a five-argument metric returning a score *and* textual
feedback. `skill_fitness_metric` has a three-argument signature and
returns a bare float derived from keyword overlap, leaving GEPA with no
trace-level signal to reflect on.
For (3), route the metric through the existing LLMJudge, whose `feedback`
field is already documented as "Textual feedback for GEPA's reflective
analysis" but was not wired to the optimizer. The heuristic metric remains
as a fallback when a judge call fails, so a flaky judge degrades the score
rather than killing the run.
Verified end-to-end against a local model: GEPA now engages
("Running GEPA for approx 20 metric calls") instead of falling through to
MIPROv2. Existing test suite (145 tests) still passes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review: PR #159This PR provides a thorough fix for GEPA silent fallback to MIPROv2. Key changes:
However, this PR does NOT fix:
Overlap: The GEPA constructor fix ( Suggestion: This has the best metric implementation (LLMJudge feedback), but needs to be combined with #155's SkillModule fix to actually work end-to-end. Consider coordinating with #155 author. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
dspy.GEPA()raises on every run with DSPy >= 3.1, and the surroundingtry/exceptinevolve_skill.pycatches it and drops to MIPROv2. Runs printRunning GEPA optimization, complete successfully, and write out an "evolved" skill — without GEPA ever being involved. The fallback message scrolls past in the middle of optimizer output, so this is easy to miss entirely.Reproduced on a clean install (
pip install -e ".[dev]", DSPy 3.2.1, gepa 0.0.27):and after fixing that, a second one:
Causes
max_stepsis not a DSPy >= 3.1 argument. The budget is nowmax_metric_calls(orauto/max_full_evals).reflection_lmis required and was never passed — GEPA raises without it. Separately,--optimizer-model(help text: "Model for GEPA reflections") was unused: printed to the console, then dropped. Only--eval-modelever reached DSPy. It now drives the reflection LM, which is what the flag already claimed to do.The metric signature and return type are wrong for GEPA. It needs five arguments and a score plus textual feedback.
skill_fitness_metrictakes three and returns a bare float from keyword overlap, so even once constructed, GEPA would have had no trace-level signal to reflect on — which is the mechanism the README describes as the reason to use GEPA.Fix
(1) and (2) are direct argument fixes. For (3), the metric now routes through the existing
LLMJudge, whosefeedbackfield is already documented infitness.pyas "Textual feedback for GEPA's reflective analysis" but was never connected to the optimizer.skill_fitness_metricis kept as a fallback when a judge call fails, so a flaky judge degrades the score instead of killing the run.Verification
GEPA now engages instead of falling through:
Existing suite passes unchanged (145 passed). Tested against a local Ollama-served model via litellm's
ollama_chat/prefix rather than an OpenAI endpoint, so the fix is not verified against the defaultopenai/gpt-4.1path — worth a second look from someone with those credentials.Note
You may want to reconsider the bare
except Exceptionaround the optimizer construction. It's what turned three straightforward API mismatches into a silent behavioral downgrade; narrowing it, or at least making the fallback loud, would surface the next DSPy break immediately.🤖 Generated with Claude Code