Skip to content

Commit d1d5935

Browse files
committed
Fix: propagate top-level llm.provider to per-model configs
The `provider` field (e.g. "claude_code") set at the top `llm:` level was omitted from the shared config that LLMConfig propagates down to each model, so it never reached the individual LLMModelConfig entries. As a result the ensemble ignored `provider: "claude_code"` and fell back to the OpenAI backend, crashing with "Missing credentials ... OPENAI_API_KEY" — which made the examples/claude_code_quickstart example unusable out of the box. Add `provider` to the shared_config dicts in both LLMConfig.__post_init__ and rebuild_models. Propagation uses update_model_params (overwrite=False), so an explicit per-model `provider` still takes precedence. Add regression tests asserting that a top-level provider reaches every evolution and evaluator model, that per-model overrides win, that the default remains None, and that the ensemble builds ClaudeCodeLLM end to end.
1 parent 499a64f commit d1d5935

2 files changed

Lines changed: 77 additions & 0 deletions

File tree

openevolve/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ def __post_init__(self):
174174

175175
# Update models with shared configuration values
176176
shared_config = {
177+
"provider": self.provider,
177178
"api_base": self.api_base,
178179
"api_key": self.api_key,
179180
"temperature": self.temperature,
@@ -228,6 +229,7 @@ def rebuild_models(self) -> None:
228229

229230
# Update models with shared configuration values
230231
shared_config = {
232+
"provider": self.provider,
231233
"api_base": self.api_base,
232234
"api_key": self.api_key,
233235
"temperature": self.temperature,

tests/test_claude_code_llm.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,81 @@ def test_max_budget_usd_from_dict(self):
133133
self.assertEqual(config.llm.models[0].max_budget_usd, 3.0)
134134

135135

136+
class TestProviderPropagation(unittest.TestCase):
137+
"""A top-level ``llm.provider`` must reach each model in the ensemble.
138+
139+
Regression test for a bug where ``provider`` was omitted from the shared
140+
config propagated to per-model configs, so ``provider: "claude_code"`` set
141+
at the ``llm:`` level was silently dropped and every model fell back to the
142+
OpenAI backend (crashing with "Missing credentials").
143+
"""
144+
145+
def test_top_level_provider_propagates_to_models(self):
146+
from openevolve.config import Config
147+
148+
config = Config.from_dict(
149+
{
150+
"llm": {
151+
"provider": "claude_code",
152+
"models": [
153+
{"name": "sonnet", "weight": 0.8},
154+
{"name": "haiku", "weight": 0.2},
155+
],
156+
}
157+
}
158+
)
159+
self.assertTrue(config.llm.models, "expected models to be configured")
160+
for model in config.llm.models:
161+
self.assertEqual(model.provider, "claude_code")
162+
# Evaluator models default to the evolution models and must inherit too.
163+
for model in config.llm.evaluator_models:
164+
self.assertEqual(model.provider, "claude_code")
165+
166+
def test_per_model_provider_overrides_top_level(self):
167+
from openevolve.config import Config
168+
169+
config = Config.from_dict(
170+
{
171+
"llm": {
172+
"provider": "claude_code",
173+
"models": [
174+
{"name": "sonnet", "weight": 0.5},
175+
{"name": "gpt-4o", "weight": 0.5, "provider": "openai"},
176+
],
177+
}
178+
}
179+
)
180+
providers = {m.name: m.provider for m in config.llm.models}
181+
self.assertEqual(providers["sonnet"], "claude_code")
182+
self.assertEqual(providers["gpt-4o"], "openai")
183+
184+
def test_default_provider_is_none(self):
185+
from openevolve.config import Config
186+
187+
config = Config.from_dict(
188+
{"llm": {"models": [{"name": "gpt-4o", "weight": 1.0}]}}
189+
)
190+
self.assertIsNone(config.llm.models[0].provider)
191+
192+
def test_ensemble_builds_claude_code_from_config(self):
193+
from openevolve.config import Config
194+
from openevolve.llm.ensemble import LLMEnsemble
195+
196+
config = Config.from_dict(
197+
{
198+
"llm": {
199+
"provider": "claude_code",
200+
"models": [{"name": "sonnet", "weight": 1.0}],
201+
}
202+
}
203+
)
204+
ensemble = LLMEnsemble(config.llm.models)
205+
self.assertTrue(
206+
all(isinstance(m, ClaudeCodeLLM) for m in ensemble.models),
207+
"ensemble should build ClaudeCodeLLM instances from top-level provider",
208+
)
209+
210+
136211
class TestProviderRegistry(unittest.TestCase):
137212
def test_claude_code_in_registry(self):
138213
from openevolve.llm.ensemble import _PROVIDER_REGISTRY

0 commit comments

Comments
 (0)