From f759402d3ec41be68ddfeca1b14377aac4c3c839 Mon Sep 17 00:00:00 2001 From: yunyv <62236062+yunyv@users.noreply.github.com> Date: Thu, 15 Oct 2026 07:49:16 -0600 Subject: [PATCH] fix: preserve Claude Code default budget with real configs --- openevolve/llm/claude_code.py | 2 ++ tests/test_claude_code_llm.py | 39 +++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/openevolve/llm/claude_code.py b/openevolve/llm/claude_code.py index 5bd3c9fb3..fe1a6eeaf 100644 --- a/openevolve/llm/claude_code.py +++ b/openevolve/llm/claude_code.py @@ -46,6 +46,8 @@ def __init__(self, model_cfg=None): self.retries = getattr(model_cfg, "retries", 3) self.retry_delay = getattr(model_cfg, "retry_delay", 5) self.max_budget_usd = getattr(model_cfg, "max_budget_usd", 1.0) + if self.max_budget_usd is None: + self.max_budget_usd = 1.0 self.cwd = getattr(model_cfg, "cwd", None) logger.info(f"Initialized ClaudeCodeLLM with model: {self.model}") diff --git a/tests/test_claude_code_llm.py b/tests/test_claude_code_llm.py index d803aa4a6..4590b0e34 100644 --- a/tests/test_claude_code_llm.py +++ b/tests/test_claude_code_llm.py @@ -168,5 +168,44 @@ def test_ensemble_creates_claude_code(self): self.assertIsInstance(model, ClaudeCodeLLM) +class TestRealConfigBudget(unittest.TestCase): + def _assert_cli_budget(self, model_options, expected, **call_options): + from openevolve.config import Config + from openevolve.llm.ensemble import _create_model + + config = Config.from_dict( + { + "llm": { + "provider": "claude_code", + "retries": 0, + "models": [{"name": "sonnet", **model_options}], + } + } + ) + model = _create_model(config.llm.models[0]) + with patch("openevolve.llm.claude_code.subprocess.run") as mock_run: + mock_run.return_value = MagicMock(returncode=0, stdout="response", stderr="") + self.assertEqual(asyncio.run(model.generate("prompt", **call_options)), "response") + mock_run.assert_called_once() + cmd = mock_run.call_args.args[0] + self.assertEqual(cmd[cmd.index("--max-budget-usd") + 1], str(expected)) + + def test_omitted_budget_uses_documented_default(self): + self._assert_cli_budget({}, 1.0) + + def test_null_budget_uses_documented_default(self): + self._assert_cli_budget({"max_budget_usd": None}, 1.0) + + def test_explicit_budget_is_preserved(self): + self._assert_cli_budget({"max_budget_usd": 0.5}, 0.5) + + def test_zero_is_not_replaced_by_default(self): + # Keep explicit values unchanged; CLI validation remains the CLI's responsibility. + self._assert_cli_budget({"max_budget_usd": 0.0}, 0.0) + + def test_per_call_budget_overrides_configured_budget(self): + self._assert_cli_budget({"max_budget_usd": 0.5}, 0.25, max_budget_usd=0.25) + + if __name__ == "__main__": unittest.main()