Skip to content

fix(health): stop treating custom-pricing fields as a connection override on test_connection - #41024

Open
mihidumh wants to merge 1 commit into
BerriAI:mainfrom
mihidumh:fix/health-test-connection-pricing-params
Open

fix(health): stop treating custom-pricing fields as a connection override on test_connection#41024
mihidumh wants to merge 1 commit into
BerriAI:mainfrom
mihidumh:fix/health-test-connection-pricing-params

Conversation

@mihidumh

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • "Test Connection" fails for a working model when a price is also set
  • Any custom-pricing field in the request drops the configured credentials
  • The probe then runs with no API key and reports an auth error
  • It hits the Add Model wizard, where price and test sit together

How it solves it:

  • Split out the banned params that describe a connection
  • That list is the banned list minus the custom-pricing fields
  • Gate credential inheritance on that list, not the full banned list
  • Pricing stays banned from request bodies; nothing else changes

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

  1. The admin has a configured model whose provider model string is openai/gemini-2.5-flash-lite, with its API key in the proxy config
  2. They send POST https://litellm-domain/health/test_connection with {"litellm_params": {"model": "openai/gemini-2.5-flash-lite"}, "mode": "chat"} and get "status": "success"
  3. They send the same request with "input_cost_per_token": 1e-9 added, as the Add Model wizard does when a price is filled in
  4. They get "status": "error" with AuthenticationError: … The api_key client option must be set, for a model that works

After: the price no longer changes which credentials the test uses

  1. Same configured model, same API key in the proxy config
  2. POST https://litellm-domain/health/test_connection with only the model still returns "status": "success"
  3. The same request with "input_cost_per_token": 1e-9 added now also returns "status": "success"
  4. A request that brings its own api_base still inherits no configured credentials, as before

Relevant 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

  • I have added meaningful tests
  • The handful of test files covering my change pass locally, e.g. 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
  • My PR passes all required CI/CD checks (e.g., lint, schema.d.ts sync check, etc.)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Local checks on b9eff59e2e: tests/test_litellm/proxy/health_endpoints/test_health_endpoints.py 197 passed; the three new pricing cases fail with only the one-line gate swap reverted; ruff check finds nothing new against the base files and ruff format --check (pinned 0.15.3) is clean; ruff_strict_gate, type_discipline_gate, test_quality_gate and type_check_gate all OK against main.

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.

model_list:
  - model_name: pricing-probe
    litellm_params:
      model: openai/gemini-2.5-flash-lite
      api_base: os.environ/DEV_PROXY_BASE
      api_key: os.environ/DEV_PROXY_KEY
general_settings:
  master_key: sk-local-proof-1234

Each case is POST http://127.0.0.1:21431/health/test_connection with Authorization: Bearer sk-local-proof-1234.

Before (30f33a9)

Model only

  1. Body: {"litellm_params":{"model":"openai/gemini-2.5-flash-lite"},"mode":"chat"}
  2. HTTP 200, "status": "success"

Model plus a price

  1. Body: {"litellm_params":{"model":"openai/gemini-2.5-flash-lite","input_cost_per_token":1e-9},"mode":"chat"}
  2. HTTP 200, "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

  1. Body: {"litellm_params":{"model":"openai/gemini-2.5-flash-lite","api_base":"https://example.invalid/v1","input_cost_per_token":1e-9},"mode":"chat"}
  2. HTTP 200, "status": "error", litellm.AuthenticationError: … The api_key client option must be set …

After (b9eff59)

Model only

  1. Body: {"litellm_params":{"model":"openai/gemini-2.5-flash-lite"},"mode":"chat"}
  2. HTTP 200, "status": "success"

Model plus a price

  1. Body: {"litellm_params":{"model":"openai/gemini-2.5-flash-lite","input_cost_per_token":1e-9},"mode":"chat"}
  2. HTTP 200, "status": "success"

Own api_base plus a price

  1. Body: {"litellm_params":{"model":"openai/gemini-2.5-flash-lite","api_base":"https://example.invalid/v1","input_cost_per_token":1e-9},"mode":"chat"}
  2. HTTP 200, "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

  • Probing by public model name overrides the configured provider model string
    • Pre-existing and unrelated; the proof uses the provider string the wizard sends

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

/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>
@mihidumh
mihidumh requested a review from a team September 13, 2026 23:15
@codspeed-hq

codspeed-hq Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing mihidumh:fix/health-test-connection-pricing-params (b9eff59) with main (30f33a9)

Open in CodSpeed

@greptile-apps

greptile-apps Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR separates pricing metadata from connection overrides when deciding whether a health probe may inherit configured credentials

  • Pricing-only test-connection requests now retain the configured endpoint and credentials
  • Real endpoint or credential overrides still prevent inheritance
  • Regression coverage was added, but two tests assert private registry structure instead of observable behavior

Confidence Score: 4/5

The 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

Important Files Changed

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

Comment on lines +3709 to +3718
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."
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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

codecov Bot commented Sep 13, 2026

Copy link
Copy Markdown

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(
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants