Skip to content

Commit f84c206

Browse files
ampagentIsaac Miller
andauthored
feat(gepa): upgrade engine to 0.1.4
Amp-Thread-ID: https://ampcode.com/threads/T-019ffe40-c240-70bb-8cb2-db33146ed8e6 Co-authored-by: Isaac Miller <isaac@cmpnd.ai>
1 parent 32bcd36 commit f84c206

5 files changed

Lines changed: 55 additions & 6 deletions

File tree

‎dspy/teleprompt/gepa/gepa.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,12 @@ def metric(
324324
MLflow can be used alongside Weights & Biases (WandB).
325325
- mlflow_tracking_uri: The tracking URI to use for MLflow (when use_mlflow=True).
326326
- mlflow_experiment_name: The experiment name to use for MLflow (when use_mlflow=True).
327+
- sampling_strategy, selection_strategy, acceptance_criterion: GEPA 0.1.4 proposal controls.
328+
DSPy supports these using GEPA's sequential candidate-evaluation fallback.
329+
- wandb_attach_existing, mlflow_attach_existing, tracking_key_prefix: GEPA 0.1.4 tracking controls.
330+
331+
`max_reflection_cost` is not supported yet because DSPy LMs do not expose the cumulative cost
332+
interface GEPA requires. Passing it raises an error instead of silently ignoring the budget.
327333
328334
Note: Parameters already handled by DSPy's GEPA class will be overridden by the direct parameters
329335
and should not be passed through gepa_kwargs.
@@ -451,6 +457,9 @@ def __init__(
451457
self.component_selector = component_selector
452458
self.gepa_kwargs = gepa_kwargs or {}
453459

460+
if "max_reflection_cost" in self.gepa_kwargs:
461+
raise ValueError("max_reflection_cost is not supported by dspy.GEPA yet.")
462+
454463
if "reflection_prompt_template" in self.gepa_kwargs:
455464
raise ValueError(
456465
"reflection_prompt_template cannot be passed via gepa_kwargs when using dspy.GEPA. "

‎dspy/teleprompt/gepa/gepa_utils.py‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,13 @@ def evaluate(self, batch, candidate, capture_traces=False):
268268
scores = [s["score"] if hasattr(s, "score") else s for s in scores]
269269
return EvaluationBatch(outputs=outputs, scores=scores, trajectories=None)
270270

271+
def get_adapter_state(self) -> dict[str, Any]:
272+
return {"rng_state": self.rng.getstate()}
273+
274+
def set_adapter_state(self, state: dict[str, Any]) -> None:
275+
if "rng_state" in state:
276+
self.rng.setstate(state["rng_state"])
277+
271278
def make_reflective_dataset(
272279
self, candidate, eval_batch, components_to_update
273280
) -> dict[str, list[ReflectiveExample]]:
@@ -415,4 +422,3 @@ def stripped_lm_call(self, x: str) -> list[str]:
415422
raise TypeError("Unexpected output type from the base LM! Expected str or dict")
416423

417424
return outputs
418-

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ dependencies = [
3434
"anyio",
3535
"cachetools>=5.5.0",
3636
"cloudpickle>=3.1.2",
37-
"gepa[dspy]==0.1.1",
37+
"gepa[dspy]==0.1.4",
3838
]
3939

4040
[project.optional-dependencies]

‎tests/teleprompt/test_gepa.py‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import json
2+
import random
23
import threading
34
from typing import Any
45
from unittest import mock
@@ -632,3 +633,24 @@ def test_track_best_outputs_result_structure():
632633
assert isinstance(entries, list)
633634
for cand_idx, output in entries:
634635
assert isinstance(cand_idx, int)
636+
637+
638+
def test_gepa_rejects_unsupported_reflection_cost_budget():
639+
with pytest.raises(ValueError, match="max_reflection_cost"):
640+
dspy.GEPA(
641+
metric=simple_metric,
642+
max_metric_calls=1,
643+
reflection_lm=DummyLM([]),
644+
gepa_kwargs={"max_reflection_cost": 1.0},
645+
)
646+
647+
648+
def test_adapter_state_round_trips_rng():
649+
from dspy.teleprompt.gepa.gepa_utils import DspyAdapter
650+
651+
adapter = DspyAdapter(SimpleModule("input -> output"), simple_metric, {}, rng=random.Random(42))
652+
state = adapter.get_adapter_state()
653+
expected = adapter.rng.random()
654+
adapter.rng.random()
655+
adapter.set_adapter_state(state)
656+
assert adapter.rng.random() == expected

0 commit comments

Comments
 (0)