feat(fitness): objective ground-truth verifier for skill evolution (reference: arxiv) - #150
Open
MaxFreedomPollard wants to merge 2 commits into
Open
Conversation
…erence implementation
Phase 1 currently optimizes keyword overlap against the rubric text
(evolution/core/fitness.py), which rewards echoing the rubric's
vocabulary rather than being right. LLMJudge is imported but never
called, and even judge scoring never checks whether an answer is true.
That makes every reported improvement unfalsifiable.
This adds the missing signal: a pluggable Verifier interface that
grades outputs against checkable ground truth, plus a reference
verifier for the arxiv skill.
- evolution/core/verifier.py: Verifier ABC, registry, and a DSPy
metric adapter that returns floats for MIPROv2/holdout calls and
score-plus-feedback for GEPA's reflective form
- evolution/verifiers/arxiv_verifier.py: grades arXiv IDs, exact
titles, first authors, and submission years for 17 landmark papers.
Grading is pure Python (regex + normalization): zero API cost per
grade, and the same output always scores the same.
- evolve_skill.py: --fitness auto|verifier|keyword. Auto picks the
objective verifier when one is registered for the skill; the
verifier also supplies the eval dataset, since tasks and ground
truth must travel together.
Score shaping is monotone and anti-gaming: correct 1.0, hedging
across several candidates 0.75, confidently wrong 0.5, evasive or
empty 0.0. Conciseness only counts when an answer is present, so a
brief evasion can never outscore a verbose correct answer. Year
tasks are solvable purely from the arXiv ID's YYMM prefix, so they
measure whether the skill teaches the ID format, a property a skill
edit can actually flip.
Every embedded fact can be re-checked against the live arXiv API:
python -m evolution.verifiers.arxiv_verifier --validate
All 17 papers (51 facts) verified at commit time. No new
dependencies. 40 new tests, full suite passes (183).
…overrides a custom dataset Two review findings: - Title grading was too harsh on spacing variants: a response writing "Pretraining" for "Pre-training" or "LowRank" for "Low-Rank" lost full credit despite being right. A spaceless containment check now accepts fused variants while still requiring the exact letter sequence of the full title. - When a verifier is active, --eval-source and --dataset-path are ignored by design (verifier tasks carry their own ground truth). That was silent; now it prints a note pointing at --fitness keyword for evolving against a custom dataset.
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.
The problem
Phase 1's gate is "at least one skill measurably improved." Right now that measurement can't be trusted, because the metric the optimizer maximizes (and the holdout is scored with) is keyword overlap against the rubric text:
This rewards echoing the rubric's vocabulary, not being right.
LLMJudgeexists but is dead code (imported inevolve_skill.py, never called), and even judge scoring is subjective: it never checks whether an answer is actually true. The Phase 1 validation report is honest about this and lists replacing the heuristic as its own next step.So every reported improvement so far is unfalsifiable. Nothing downstream (benchmark gates, significance checks like #135/#136, Phase 2's tool-selection accuracy) means anything until the number being optimized measures reality.
What this adds
A third, strongest tier of fitness signal: objective verifiers that grade outputs against checkable ground truth, plus a working reference implementation for the
arxivskill (the same skill the Phase 1 validation report used).evolution/core/verifier.pyVerifierABC: a verifier owns both halves of the evaluation contract,build_dataset()(tasks whose answers are verifiable facts) andscore()(grade an output against those facts). They must travel together, since the grader can only score tasks it knows the truth for.@register_verifier,get_verifier(skill_name)).verifier_metric()adapts a verifier to every metric calling convention in the pipeline: plain float for MIPROv2 and holdout comparison, and score-plus-feedback (dspy.Prediction(score=..., feedback=...)) when GEPA asks about a specific predictor. The verifier's feedback text ("expected ID 1706.03762, response said 1706.03799") is exactly the reflection signal GEPA mutates on.evolution/verifiers/arxiv_verifier.pyGrades four kinds of verifiable facts across 17 landmark ML papers (word2vec through the GEPA paper itself): arXiv IDs, exact titles, first authors, and submission years. Grading is regex plus normalization, no LLM calls.
Score shaping is monotone and anti-gaming:
Details that matter:
2005.14165doesn't get misread as the year 2005.The embedded facts are historical constants, and every one is re-checkable against the live arXiv API:
All 51 facts (17 papers x title/author/year) passed at commit time.
Wiring (
evolve_skill.py)New
--fitness auto|verifier|keywordflag, defaultauto: use the objective verifier when one is registered for the skill, otherwise fall back to current behavior. When a verifier is active it also supplies the eval dataset (deterministic sample, 50/25/25 split, saved todatasets/skills/<skill>-verifier/).Deliberate scope boundaries
--fitnessselector leaves room for an LLM-as-judge mode (as in fix(phase1): GEPA-compatible SkillModule + LLM-as-judge metric + constraint fixes #137) for skills where no objective verifier is possible.Test plan
tests/verifiers/test_arxiv_verifier.py,tests/core/test_verifier_registry.py): grading correctness for all four fact kinds, the anti-gaming behaviors, dataset determinism and gradability, metric adapter under all three calling conventions, edge cases (empty output, unknown task, versioned IDs).--validateflag.--validate(17/17 papers verified),--demo, andevolve_skill --dry-runin both auto-verifier and forced-keyword modes.No new dependencies (stdlib
urllib+xml.etreefor the validation fetch;click/richalready in use).