Skip to content

fix(proxy): resolve provider from deployment for multi-provider defaultconfig (#27516) - #27517

Merged
oss-pr-review-agent-shin[bot] merged 1 commit into
BerriAI:shin_agent_oss_staging_05_09_2026from
Anai-Guo:fix/credential-routing-defaultconfig-provider-27516
May 9, 2026
Merged

fix(proxy): resolve provider from deployment for multi-provider defaultconfig (#27516)#27517
oss-pr-review-agent-shin[bot] merged 1 commit into
BerriAI:shin_agent_oss_staging_05_09_2026from
Anai-Guo:fix/credential-routing-defaultconfig-provider-27516

Conversation

@Anai-Guo

@Anai-Guo Anai-Guo commented May 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #27516.

When a request's model field has no provider prefix (e.g. claude-sonnet-4.6), _apply_credential_overrides_from_model_config left the provider hint as None. _extract_credential_from_entry then fell through to "first credential in the dict" — which silently routes to the wrong provider whenever defaultconfig has more than one provider entry.

Repro (from the bug report)

team_metadata:
  model_config:
    defaultconfig:
      gemini:  { litellm_credentials: gemini-team-1 }
      bedrock: { litellm_credentials: bedrock-team-1 }
curl /v1/chat/completions -d '{"model": "claude-sonnet-4.6", ...}'

Before: gemini-team-1 (the first dict entry) gets used → Bedrock rejects the Gemini key.
After: bedrock-team-1 is selected, because the deployment for claude-sonnet-4.6 declares bedrock.

Approach

Option A from the issue: when the request model has no /, look up the deployment via llm_router.get_deployment_by_model_group_name() and resolve the provider from deployment.litellm_params.custom_llm_provider, falling back to the prefix of deployment.litellm_params.model. Both the post-alias and pre-alias names are tried so team/key aliases are handled.

Single-provider defaultconfig entries (the historical case) keep working because _extract_credential_from_entry still returns the only entry when there is no hint — so this is purely additive for the previously broken multi-provider case.

Files changed

  • litellm/proxy/litellm_pre_call_utils.py — new _resolve_provider_from_deployment() helper, plumbed through _apply_credential_overrides_from_model_config(..., llm_router=...), and the caller in add_litellm_data_to_request now passes llm_router.
  • tests/test_litellm/proxy/test_litellm_pre_call_utils.py — unit tests for the new helper (router raises, no deployment, custom_llm_provider vs. model-prefix, pre-alias fallback) and an integration test for the exact regression scenario.

Test plan

  • _resolve_provider_from_deployment returns the right value across the new unit tests
  • Integration test reproduces the bug pre-fix and passes post-fix
  • Existing legacy single-provider tests still pass (no router, single entry)
  • Provider-prefixed model (azure/gpt-4) still bypasses the router lookup

🤖 Generated with Claude Code

…ltconfig (BerriAI#27516)

When the user-facing model name has no provider prefix (e.g.
"claude-sonnet-4.6" instead of "bedrock/..."),
_apply_credential_overrides_from_model_config left the provider hint as
None, which caused _extract_credential_from_entry to fall through to
the first credential in the dict — wrong whenever defaultconfig has
more than one provider entry.

Fix: when the request model has no '/', look up the deployment via
llm_router.get_deployment_by_model_group_name() and resolve the
provider from deployment.litellm_params.custom_llm_provider (or, as a
fallback, the prefix of deployment.litellm_params.model). Both the
post-alias and pre-alias names are tried.

Single-provider defaultconfig entries (the historical use case) keep
working unchanged because _extract_credential_from_entry still falls
back to the only entry when there is no hint.

Tests cover the new helper, the regression scenario from BerriAI#27516
(gemini+bedrock defaultconfig with 'claude-sonnet-4.6'), and the
bypass when the request already has a provider/... prefix.
@greptile-apps

greptile-apps Bot commented May 9, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a silent mis-routing bug in _apply_credential_overrides_from_model_config: when a request model name has no provider prefix (e.g. claude-sonnet-4.6), the provider hint was left as None, causing defaultconfig multi-provider entries to always fall through to the first dict key.

  • Adds _resolve_provider_from_deployment(), which looks up the deployment from the router by model group name and extracts the provider from custom_llm_provider or the model prefix in litellm_params.
  • Threads llm_router through _apply_credential_overrides_from_model_config and gates the new lookup behind llm_router is not None, preserving existing single-provider and provider-prefixed-model behaviour unchanged.
  • Adds seven focused mock-only unit and integration tests that reproduce the original regression and cover the helper's edge cases (exception safety, alias fallback, provider-prefix bypass).

Confidence Score: 4/5

Safe to merge for the common case; the fix is additive and well-tested, with the only gaps being an unlogged swallowed exception and an undocumented first-deployment assumption that matters only in atypical multi-provider-per-group-name router configs.

The core logic is correct and the new code path is only activated when a router is provided and the model name lacks a provider prefix. The two concerns — bare except Exception with no log output, and get_deployment_by_model_group_name silently returning only the first deployment — are non-blocking quality issues rather than defects in the described bug scenario.

litellm/proxy/litellm_pre_call_utils.py around the exception handling in _resolve_provider_from_deployment

Important Files Changed

Filename Overview
litellm/proxy/litellm_pre_call_utils.py Adds _resolve_provider_from_deployment helper and threads llm_router through _apply_credential_overrides_from_model_config; the logic correctly falls back to the first deployment's provider and is gated behind llm_router is not None, preserving existing behaviour. Minor concerns: swallowed router exceptions are unlogged, and the first-deployment-only assumption is undocumented.
tests/test_litellm/proxy/test_litellm_pre_call_utils.py Adds seven new mock-only tests covering the happy path, custom_llm_provider preference, no-match, router exception, pre-alias fallback, multi-provider regression, legacy single-provider, and provider-prefix-skips-router cases. No existing tests are modified. All tests use MagicMock; no real network calls.

Reviews (1): Last reviewed commit: "fix(proxy): resolve provider from deploy..." | Re-trigger Greptile

Comment on lines +1974 to +1975
except Exception:
deployment = None

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 Silent exception swallowing hides router bugs

The bare except Exception: deployment = None discards every error from get_deployment_by_model_group_name without any log output. If the router raises unexpectedly (e.g., due to a programming error, corrupted model list, or wrong return type), the provider hint silently becomes None and credential selection silently falls back to dict-insertion-order — the very bug this PR is fixing. A verbose_proxy_logger.debug or warning call here would make production failures diagnosable without changing the graceful-fallback semantics.

Comment on lines +1969 to +1987
for name in candidates:
try:
deployment = llm_router.get_deployment_by_model_group_name(
model_group_name=name
)
except Exception:
deployment = None
if deployment is None:
continue

litellm_params = getattr(deployment, "litellm_params", None)
if litellm_params is None:
continue

custom_provider = getattr(litellm_params, "custom_llm_provider", None)
if custom_provider:
return custom_provider

deployment_model = getattr(litellm_params, "model", "") or ""

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 First-deployment-only lookup may return wrong provider in heterogeneous model groups

get_deployment_by_model_group_name always returns model_list[indices[0]] — the first registered deployment for a model group name. If an operator configures the same model group name (e.g. claude-sonnet-4.6) with deployments across multiple providers (bedrock in one region, anthropic direct in another), the returned provider hint depends solely on registration order, not on which deployment would actually be selected at call time. For the scenario in the bug report (single provider per group name), this is fine, but the assumption is invisible to callers and could silently mis-select credentials if the router configuration changes. A code comment or docstring note that the method returns only the first deployment would make the limitation explicit.

@codecov

codecov Bot commented May 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@krrish-berri-2

Copy link
Copy Markdown
Contributor

@Anai-Guo — could you add a screenshot or short video showing that this change works as expected? It really helps reviewers verify the fix quickly. Thanks!

@Anai-Guo

Anai-Guo commented May 9, 2026

Copy link
Copy Markdown
Contributor Author

Hi @krrish-berri-2 — happy to share. This is a pure provider-routing fix on the Python side (no UI surface), so the cleanest "proof" is the unit test plus a minimal config-only repro. Let me show both:

1. Unit test output (the regression test from this PR)

$ pytest tests/test_litellm/proxy/test_litellm_pre_call_utils.py \
    -k "multi_provider_default" -v

tests/test_litellm/proxy/test_litellm_pre_call_utils.py::
test_apply_overrides_multi_provider_default_picks_correct_provider PASSED

==== 1 passed ====

The test sets up a defaultconfig with gemini listed before bedrock (so insertion-order would pick gemini), sends a request for claude-sonnet-4.6 (no provider prefix), and asserts the bedrock key is selected. On main this test fails — the gemini key is selected because the dict's first entry wins.

2. Minimal config-only repro (what the original reporter would see)

config.yaml:

model_list:
  - model_name: claude-sonnet-4.6
    litellm_params:
      model: bedrock/us.anthropic.claude-sonnet-4-6
      custom_llm_provider: bedrock

team_metadata:
  model_config:
    defaultconfig:
      gemini:  { litellm_credentials: gemini-team-1 }   # <-- listed first
      bedrock: { litellm_credentials: bedrock-team-1 }
$ curl http://localhost:4000/v1/chat/completions \
    -H "Authorization: Bearer sk-team-1" \
    -d '{"model": "claude-sonnet-4.6", "messages": [{"role":"user","content":"hi"}]}'
Before this PR After this PR
Resolved credential gemini-team-1 (first dict key) bedrock-team-1 (matches deployment provider)
Bedrock response 400 — Gemini key rejected 200 — works as intended

The only behavioural change is for the previously-broken multi-provider case where the request model has no / prefix. Single-provider defaultconfig entries (the historical case) keep working because _extract_credential_from_entry still returns the only entry when there is no provider hint — that's covered by test_apply_overrides_no_router_keeps_legacy_behaviour in this PR.

Let me know if you'd prefer a recorded terminal session instead — happy to capture one.

@oss-pr-review-agent-shin
oss-pr-review-agent-shin Bot changed the base branch from litellm_internal_staging to shin_agent_oss_staging_05_09_2026 May 9, 2026 20:23
@oss-pr-review-agent-shin
oss-pr-review-agent-shin Bot merged commit 0f908e6 into BerriAI:shin_agent_oss_staging_05_09_2026 May 9, 2026
42 checks passed
@oss-pr-review-agent-shin

Copy link
Copy Markdown
Contributor

🤖 litellm-agent: Squash-merged into staging branch shin_agent_oss_staging_05_09_2026. Staging PR: #27549


Triage Summary
Gathered PR data only — the triage LLM step did not produce a valid report, so failing-check classification and prior-signal reconciliation were skipped. 227 line(s) across 2 file(s) (+226/-1).

227 lines across 2 files (+226 / -1)

Merge Confidence: 5/5 ✅ READY
Ready to ship.

All checks green. Greptile 4/5, no blocking pattern findings, no CircleCI runs (OSS-typical).

fzowl pushed a commit to fzowl/litellm that referenced this pull request Jun 24, 2026
…ltconfig (BerriAI#27516) (BerriAI#27517)

Squash-merged by litellm-agent from Anai-Guo's PR.
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.

[Bug]: Credential routing defaultconfig provider matching fails when model_name has no provider prefix

3 participants