|
8 | 8 |
|
9 | 9 | import pytest |
10 | 10 | from nemo_experimentalist_plugin.config import EvolutionaryOptimizerConfig |
11 | | -from nemo_experimentalist_plugin.entities import Candidate, ExperimentRun |
| 11 | +from nemo_experimentalist_plugin.entities import Candidate, EvaluationResult, ExperimentRun |
12 | 12 | from nemo_experimentalist_plugin.experimentalist.components import loop as loop_module |
13 | 13 | from nemo_experimentalist_plugin.experimentalist.components.loop import EvolutionaryOptimizer |
| 14 | +from nemo_experimentalist_plugin.experimentalist.components.models import EvolutionTree |
| 15 | +from nemo_experimentalist_plugin.experimentalist.components.terminator import TerminationDecision |
14 | 16 | from nemo_experimentalist_plugin.experimentalist.experimentalist_backend import LocalExperimentalistBackend |
15 | 17 |
|
16 | 18 |
|
@@ -127,3 +129,80 @@ async def run(self, *args, **kwargs): |
127 | 129 | candidates=[candidate], |
128 | 130 | config=EvolutionaryOptimizerConfig(), |
129 | 131 | ) |
| 132 | + |
| 133 | + |
| 134 | +@pytest.mark.asyncio |
| 135 | +async def test_no_proposals_finalizes_evaluated_candidates(monkeypatch, tmp_path): |
| 136 | + """An exhausted proposer ends successfully with the best evaluated candidate.""" |
| 137 | + baseline = Candidate(run_id="run-1", label="agent-0", round=0, optimization="baseline") |
| 138 | + tree = EvolutionTree() |
| 139 | + tree.add(baseline) |
| 140 | + run = ExperimentRun( |
| 141 | + id="run-1", |
| 142 | + workspace="default", |
| 143 | + agent="agent", |
| 144 | + config_snapshot={}, |
| 145 | + status="running", |
| 146 | + rounds_completed=0, |
| 147 | + ) |
| 148 | + evaluation = EvaluationResult(id="agent-0-validation", aggregate_metrics={"reward": 1.0}) |
| 149 | + backend = SimpleNamespace( |
| 150 | + client=None, |
| 151 | + get_agent_code=AsyncMock(), |
| 152 | + persist_evaluation=AsyncMock(), |
| 153 | + update_run=AsyncMock(), |
| 154 | + persist_result=AsyncMock(), |
| 155 | + ) |
| 156 | + finalized = AsyncMock(return_value=baseline) |
| 157 | + |
| 158 | + monkeypatch.setattr( |
| 159 | + loop_module, |
| 160 | + "EvaluatorFactory", |
| 161 | + lambda: SimpleNamespace(build_evaluator=lambda *args, **kwargs: object()), |
| 162 | + ) |
| 163 | + monkeypatch.setattr( |
| 164 | + loop_module, |
| 165 | + "DatasetFactory", |
| 166 | + lambda: SimpleNamespace(build_dataset=lambda *args, **kwargs: object()), |
| 167 | + ) |
| 168 | + monkeypatch.setattr(EvolutionaryOptimizer, "_init_structure", lambda self: (tmp_path / "agents", tmp_path / "analysis", tmp_path / "results")) |
| 169 | + monkeypatch.setattr(EvolutionaryOptimizer, "_detect_last_round", lambda self: None) |
| 170 | + monkeypatch.setattr(EvolutionaryOptimizer, "_create_experiment_run", AsyncMock(return_value=run)) |
| 171 | + monkeypatch.setattr(EvolutionaryOptimizer, "_create_baseline_agent", AsyncMock(return_value=baseline)) |
| 172 | + monkeypatch.setattr(EvolutionaryOptimizer, "_update_candidate", AsyncMock()) |
| 173 | + monkeypatch.setattr(EvolutionaryOptimizer, "_evaluate_validation_candidates", AsyncMock(return_value={"agent-0": evaluation})) |
| 174 | + monkeypatch.setattr(EvolutionaryOptimizer, "_generate_initial_goal_tree", AsyncMock()) |
| 175 | + monkeypatch.setattr(EvolutionaryOptimizer, "_select_survivors", AsyncMock(return_value=[baseline])) |
| 176 | + monkeypatch.setattr(EvolutionaryOptimizer, "_evaluate_train_candidates", AsyncMock(return_value={"agent-0": evaluation})) |
| 177 | + monkeypatch.setattr(EvolutionaryOptimizer, "_analyze_round", AsyncMock(return_value="analysis")) |
| 178 | + monkeypatch.setattr(EvolutionaryOptimizer, "_update_goal_tree", AsyncMock()) |
| 179 | + propose = AsyncMock(return_value=[]) |
| 180 | + monkeypatch.setattr(EvolutionaryOptimizer, "_propose_improvements", propose) |
| 181 | + monkeypatch.setattr(EvolutionaryOptimizer, "_implement_candidates", AsyncMock()) |
| 182 | + monkeypatch.setattr(EvolutionaryOptimizer, "_finalize", finalized) |
| 183 | + monkeypatch.setattr(loop_module.EvolutionTree, "from_dir", lambda path: tree) |
| 184 | + |
| 185 | + optimizer = object.__new__(EvolutionaryOptimizer) |
| 186 | + optimizer.working_dir = tmp_path |
| 187 | + optimizer.config = EvolutionaryOptimizerConfig() |
| 188 | + optimizer.shell = SimpleNamespace(close=AsyncMock()) |
| 189 | + optimizer.terminator = SimpleNamespace(run=AsyncMock(return_value=TerminationDecision(stop=False))) |
| 190 | + optimizer._framework_skills_dirs = [] |
| 191 | + deps = SimpleNamespace( |
| 192 | + backend=backend, |
| 193 | + workspace="default", |
| 194 | + config=EvolutionaryOptimizerConfig(), |
| 195 | + evaluator_type="harbor", |
| 196 | + train_dataset=object(), |
| 197 | + validation_dataset=object(), |
| 198 | + insight=None, |
| 199 | + agent=tmp_path / "agent", |
| 200 | + agent_spec=None, |
| 201 | + task_template=None, |
| 202 | + ) |
| 203 | + |
| 204 | + result = await optimizer.run(deps) |
| 205 | + |
| 206 | + assert result.winner is baseline |
| 207 | + propose.assert_awaited_once() |
| 208 | + finalized.assert_awaited_once() |
0 commit comments