Skip to content

Commit 7b70c4b

Browse files
CarlesUIPathclaude
andcommitted
fix(routing): validate LITELLM settings in resolve_route (evaluate-only path, -O safe)
resolve_route is reached on the evaluate-only path without a preceding validate_api_keys(), so a scheme-less/missing LITELLM_BASE_URL there escaped validation and recorded an empty host in environment_info; the arm's asserts were also strippable under 'python -O'. Call _validate_litellm_settings() in the LiteLLM arm (raise, not assert) so both the normal and evaluate-only paths validate presence + URL scheme with a field-named error; narrowing asserts kept for pyright only. Closes the blocker-2 evaluate-only edge + review non-blocking #11. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 46ff829 commit 7b70c4b

2 files changed

Lines changed: 21 additions & 4 deletions

File tree

‎src/coder_eval/models/routing.py‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,10 +165,14 @@ def resolve_route(settings: Settings) -> ApiRoute:
165165
case ApiBackend.DIRECT:
166166
return DirectRoute(judge_transport=_resolve_direct_judge_transport(settings))
167167
case ApiBackend.LITELLM:
168-
# base_url/auth_token are guaranteed by validate_api_keys (run first);
169-
# asserts narrow the Optional types, mirroring the Bedrock arm.
170-
assert settings.litellm_base_url is not None, "LiteLLM backend requires litellm_base_url"
171-
assert settings.litellm_auth_token is not None, "LiteLLM backend requires litellm_auth_token"
168+
# Validate here (raise, not assert): resolve_route is reached on the
169+
# evaluate-only path WITHOUT a preceding validate_api_keys(), so this is
170+
# the only guard there and must survive `python -O`. Checks presence +
171+
# URL scheme, raising a field-named ValueError (review non-blocking #11).
172+
settings._validate_litellm_settings()
173+
# Narrowing for pyright only — _validate_litellm_settings guarantees these.
174+
assert settings.litellm_base_url is not None
175+
assert settings.litellm_auth_token is not None
172176
# No inference-profile qualification: the id is passed verbatim to the gateway.
173177
small_model = settings.litellm_small_model or settings.litellm_model
174178
return LiteLLMRoute(

‎tests/test_litellm_route.py‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,19 @@ def test_resolves_custom_route_with_all_fields(self):
144144
assert route.auth_token == "sk-master"
145145
assert route.model == "deepseek.v3.2"
146146

147+
def test_rejects_scheme_less_base_url(self):
148+
# resolve_route is the ONLY validation on the evaluate-only path (which
149+
# skips validate_api_keys), so it must reject a malformed URL itself —
150+
# otherwise environment_info records an empty host silently.
151+
settings = Settings(
152+
api_backend=ApiBackend.LITELLM,
153+
litellm_base_url="localhost:4000",
154+
litellm_auth_token="sk-master",
155+
litellm_model="zai.glm-5",
156+
)
157+
with pytest.raises(ValueError, match="LITELLM_BASE_URL must be an http"):
158+
resolve_route(settings)
159+
147160
def test_small_model_falls_back_to_model(self):
148161
settings = Settings(
149162
api_backend=ApiBackend.LITELLM,

0 commit comments

Comments
 (0)