fix(health): stop treating custom-pricing fields as a connection override on test_connection - #41024
Conversation
/health/test_connection returns "Missing credentials. Please pass one of
api_key, azure_ad_token, ..." for a perfectly healthy configured model, as
soon as the request also carries any custom-pricing field.
_config_base_for_health_check strips the configured connection fields when
the request sets one of _BANNED_REQUEST_BODY_PARAMS, on the reasoning that
such a request describes a connection of its own. That tuple ends with
every CustomPricingLiteLLMParams field, which is correct for the request-body
check it was built for — those fields are banned because they poison the
shared model-cost registry — but they are not connection parameters. A test
that names a configured model and its price therefore has the configuration
emptied out from under it, litellm_credential_name included, and the probe
fails with no credential at all.
It lands on the Admin UI's Add Model wizard, where "Test Connection" sits
next to the pricing fields that a model missing from the cost map has to
have, so the two are filled in together and the button reports a working
deployment as broken.
Splits the connection-relevant subset out as
_CONNECTION_OVERRIDE_REQUEST_PARAMS — the same tuple minus the pricing
fields — and gates the health-check merge on that. The request-body check is
unchanged, so pricing fields stay banned there.
Measured against a configured azure deployment: {"model": ...} succeeds,
{"model": ..., "input_cost_per_token": 1e-9} failed and now succeeds, and
{"api_base": ..., "input_cost_per_token": 1e-9} still drops the configured
credentials.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Greptile SummaryThis PR separates pricing metadata from connection overrides when deciding whether a health probe may inherit configured credentials
Confidence Score: 4/5The credential-inheritance fix appears correct, but the repository’s behavior-only testing requirement must be satisfied before merging Pricing fields no longer suppress configured credentials, while genuine connection overrides still do; the only accepted issue is structural testing of a private registry Files Needing Attention: tests/test_litellm/proxy/health_endpoints/test_health_endpoints.py
|
| Filename | Overview |
|---|---|
| litellm/proxy/auth/auth_utils.py | Derives connection override parameters by excluding custom-pricing fields from the complete banned request-field registry |
| litellm/proxy/health_endpoints/_health_endpoints.py | Uses the narrower connection override registry when deciding whether health probes inherit configured credentials |
| tests/test_litellm/proxy/health_endpoints/test_health_endpoints.py | Adds pricing and endpoint-override coverage, including two private-registry assertions that violate behavior-only testing guidance |
Reviews (1): Last reviewed commit: "fix(health): don't treat a custom-pricin..." | Re-trigger Greptile
| def test_every_custom_pricing_field_is_excluded_from_the_connection_list(self): | ||
| from litellm.proxy.auth.auth_utils import _CONNECTION_OVERRIDE_REQUEST_PARAMS | ||
| from litellm.types.utils import CustomPricingLiteLLMParams | ||
|
|
||
| connection_params = set(_CONNECTION_OVERRIDE_REQUEST_PARAMS) | ||
| for field in CustomPricingLiteLLMParams.model_fields: | ||
| assert field not in connection_params, ( | ||
| f"CustomPricingLiteLLMParams.{field} is treated as a connection override, " | ||
| "so a connection test that sets it loses the configured credentials." | ||
| ) |
There was a problem hiding this comment.
Tests Assert Internal Structure
These tests inspect a private registry instead of health-check behavior. Repository rules require behavioral tests before merging.
Context Used: CLAUDE.md (source)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
| ) | ||
| from litellm.proxy.auth.auth_utils import ( | ||
| _BANNED_REQUEST_BODY_PARAMS, # pyright: ignore[reportPrivateUsage] # one canonical list, shared with the request-body check | ||
| _CONNECTION_OVERRIDE_REQUEST_PARAMS, # pyright: ignore[reportPrivateUsage] # one canonical list, shared with the request-body check |
| # or re-authenticate the outbound call. A caller that needs to reason about | ||
| # "did this request bring its own connection?" must use this list; the full | ||
| # tuple would treat `input_cost_per_token` as a credential. | ||
| _CONNECTION_OVERRIDE_REQUEST_PARAMS: Final[tuple[str, ...]] = tuple( |
TLDR
Problem this solves:
How it solves it:
User Flow
Before: an admin adds a model that is not in the cost map, sets its price, and "Test Connection" says the working model is broken
openai/gemini-2.5-flash-lite, with its API key in the proxy config{"litellm_params": {"model": "openai/gemini-2.5-flash-lite"}, "mode": "chat"}and get"status": "success""input_cost_per_token": 1e-9added, as the Add Model wizard does when a price is filled in"status": "error"withAuthenticationError: … The api_key client option must be set, for a model that worksAfter: the price no longer changes which credentials the test uses
"status": "success""input_cost_per_token": 1e-9added now also returns"status": "success"api_basestill inherits no configured credentials, as beforeRelevant issues
No issue filed. Regression from the credential-inheritance guard that now lives in
_request_inherits_config_credentials(#39801 and earlier).Affected release
Linear ticket
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
uv run pytest tests/test_litellm/<your_test_file>.py -v. Leave the suites (make test-unit-*,make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more@greptileaito re-request a review after pushing changes)Local checks on
b9eff59e2e:tests/test_litellm/proxy/health_endpoints/test_health_endpoints.py197 passed; the three new pricing cases fail with only the one-line gate swap reverted;ruff checkfinds nothing new against the base files andruff format --check(pinned 0.15.3) is clean;ruff_strict_gate,type_discipline_gate,test_quality_gateandtype_check_gateall OK againstmain.Screenshots / Proof of Fix
Shared setup, the same for both runs: a local proxy with a Postgres database and this config. The model routes to a real OpenAI-compatible endpoint, so each successful probe is a real LLM call.
Each case is
POST http://127.0.0.1:21431/health/test_connectionwithAuthorization: Bearer sk-local-proof-1234.Before (30f33a9)
Model only
{"litellm_params":{"model":"openai/gemini-2.5-flash-lite"},"mode":"chat"}"status": "success"Model plus a price
{"litellm_params":{"model":"openai/gemini-2.5-flash-lite","input_cost_per_token":1e-9},"mode":"chat"}"status": "error",litellm.AuthenticationError: AuthenticationError: OpenAIException - The api_key client option must be set either by passing api_key to the client or b…Own api_base plus a price
{"litellm_params":{"model":"openai/gemini-2.5-flash-lite","api_base":"https://example.invalid/v1","input_cost_per_token":1e-9},"mode":"chat"}"status": "error",litellm.AuthenticationError: … The api_key client option must be set …After (b9eff59)
Model only
{"litellm_params":{"model":"openai/gemini-2.5-flash-lite"},"mode":"chat"}"status": "success"Model plus a price
{"litellm_params":{"model":"openai/gemini-2.5-flash-lite","input_cost_per_token":1e-9},"mode":"chat"}"status": "success"Own api_base plus a price
{"litellm_params":{"model":"openai/gemini-2.5-flash-lite","api_base":"https://example.invalid/v1","input_cost_per_token":1e-9},"mode":"chat"}"status": "error",litellm.AuthenticationError: … The api_key client option must be set …(unchanged: a request with its own endpoint still gets no configured key)Type
🐛 Bug Fix
Caveats (if any)
Low
Final Attestation