litellm - 💡(How to fix) Fix [Bug]: Credential routing `defaultconfig` provider matching fails when model_name has no provider prefix

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Error Message

BedrockException - {"Message":"Invalid API Key format: Must start with pre-defined prefix"}

Root Cause

This happens because the provider hint is extracted solely from the user's request model field (e.g. claude-sonnet-4.6), not from the deployment's litellm_params.model (e.g. bedrock/us.anthropic.claude-sonnet-4-6). When the request model name contains no /, the provider hint is None, and the fallback logic returns the first entry in the dict.

Fix Action

Workaround

Use model-specific overrides instead of relying on defaultconfig with multiple providers:

{
  "model_config": {
    "defaultconfig": {
      "gemini": { "litellm_credentials": "gemini-team-1" }
    },
    "claude-sonnet-4.6": {
      "bedrock": { "litellm_credentials": "bedrock-team-1" }
    },
    "claude-opus-4.6": {
      "bedrock": { "litellm_credentials": "bedrock-team-1" }
    }
  }
}

This works but defeats the purpose of defaultconfig — every new Bedrock model requires updating every team's model_config.

Code Example

model_name = data.get("model")  # This is the user-facing model name, e.g. "claude-sonnet-4.6"

# Extract provider hint from model name (e.g. "azure/gpt-4" -> "azure")
provider: Optional[str] = None
if "/" in model_name:
    provider = model_name.split("/", 1)[0]

---

# Prefer exact provider match when provider hint is available
if provider and provider in entry:
    # ... exact match (skipped because provider is None)

# Fall back to first available provider
for provider_config in entry.values():
    if isinstance(provider_config, dict):
        credential_name = provider_config.get("litellm_credentials")
        if credential_name:
            return credential_name  # Always returns the FIRST entry

---

BedrockException - {"Message":"Invalid API Key format: Must start with pre-defined prefix"}

---

# After: model_name = data.get("model")
provider: Optional[str] = None
if "/" in model_name:
    provider = model_name.split("/", 1)[0]
elif llm_router:
    # Resolve provider from deployment config
    deployments = llm_router.get_model_group(model_name)
    if deployments:
        litellm_model = deployments[0].get("litellm_params", {}).get("model", "")
        if "/" in litellm_model:
            provider = litellm_model.split("/", 1)[0]

---

{
  "model_config": {
    "defaultconfig": {
      "gemini": { "litellm_credentials": "gemini-team-1" }
    },
    "claude-sonnet-4.6": {
      "bedrock": { "litellm_credentials": "bedrock-team-1" }
    },
    "claude-opus-4.6": {
      "bedrock": { "litellm_credentials": "bedrock-team-1" }
    }
  }
}

---

model_list:
  - model_name: gemini-2.5-pro
    litellm_params:
      model: gemini/gemini-2.5-pro-preview-05-06
      api_key: os.environ/GEMINI_API_KEY

  - model_name: claude-sonnet-4.6
    litellm_params:
      model: bedrock/us.anthropic.claude-sonnet-4-6
      api_key: os.environ/AWS_BEARER_TOKEN_BEDROCK
      aws_region_name: us-east-1

litellm_settings:
  enable_model_config_credential_overrides: true

---

# Gemini credential for team-1
curl -X POST http://localhost:4000/credentials \
  -H "Authorization: Bearer $MASTER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "credential_name": "gemini-team-1",
    "credential_values": { "api_key": "gemini-key-for-team-1" },
    "credential_info": { "custom_llm_provider": "Google_AI_Studio" }
  }'

# Bedrock credential for team-1
curl -X POST http://localhost:4000/credentials \
  -H "Authorization: Bearer $MASTER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "credential_name": "bedrock-team-1",
    "credential_values": { "api_key": "ABSK-bedrock-key-for-team-1" },
    "credential_info": { "custom_llm_provider": "Bedrock" }
  }'

---

curl -X PATCH http://localhost:4000/team/update \
  -H "Authorization: Bearer $MASTER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "team_id": "team-1",
    "metadata": {
      "model_config": {
        "defaultconfig": {
          "gemini": {
            "litellm_credentials": "gemini-team-1"
          },
          "bedrock": {
            "litellm_credentials": "bedrock-team-1"
          }
        }
      }
    }
  }'

---

curl http://localhost:4000/v1/chat/completions \
  -H "Authorization: Bearer sk-team-1-virtual-key" \
  -H "Content-Type: application/json" \
  -d '{"model": "claude-sonnet-4.6", "messages": [{"role": "user", "content": "hi"}], "max_tokens": 5}'

---
RAW_BUFFERClick to expand / collapse

Check for existing issues

  • I have searched the existing issues and checked that my issue is not a duplicate.

What happened?

What happened

When using per-team credential routing with multiple providers in defaultconfig, the system always resolves to the first provider entry in the dict — regardless of which provider the model actually belongs to.

This happens because the provider hint is extracted solely from the user's request model field (e.g. claude-sonnet-4.6), not from the deployment's litellm_params.model (e.g. bedrock/us.anthropic.claude-sonnet-4-6). When the request model name contains no /, the provider hint is None, and the fallback logic returns the first entry in the dict.

Relevant code

litellm/proxy/litellm_pre_call_utils.py, inside _apply_credential_overrides_from_model_config:

model_name = data.get("model")  # This is the user-facing model name, e.g. "claude-sonnet-4.6"

# Extract provider hint from model name (e.g. "azure/gpt-4" -> "azure")
provider: Optional[str] = None
if "/" in model_name:
    provider = model_name.split("/", 1)[0]

When model_name = "claude-sonnet-4.6" (no /), provider is None.

Then in _extract_credential_from_entry:

# Prefer exact provider match when provider hint is available
if provider and provider in entry:
    # ... exact match (skipped because provider is None)

# Fall back to first available provider
for provider_config in entry.values():
    if isinstance(provider_config, dict):
        credential_name = provider_config.get("litellm_credentials")
        if credential_name:
            return credential_name  # Always returns the FIRST entry

Expected behavior

The request for claude-sonnet-4.6 should resolve to bedrock-team-1 credential because:

  • The deployment's litellm_params.model is bedrock/us.anthropic.claude-sonnet-4-6
  • The defaultconfig has a bedrock entry with litellm_credentials: bedrock-team-1

Actual behavior

The request resolves to gemini-team-1 credential (the first entry in defaultconfig), causing:

BedrockException - {"Message":"Invalid API Key format: Must start with pre-defined prefix"}

The Gemini API key is being sent to the Bedrock endpoint.

Why this happens

  1. User requests model: "claude-sonnet-4.6" — no / in the name
  2. provider is set to None
  3. _extract_credential_from_entry skips the exact provider match (since provider is None)
  4. Falls back to iterating entry.values() and returns the first credential found
  5. Dict iteration order determines which credential is used — gemini happens to be first

Suggested fix

The provider hint should be resolved from the deployment configuration (litellm_params.model), not just from the user's request model name. The router already knows which deployment(s) back a given model group.

Possible approaches:

Option A: Look up the model group's deployment and extract provider from litellm_params.model:

# After: model_name = data.get("model")
provider: Optional[str] = None
if "/" in model_name:
    provider = model_name.split("/", 1)[0]
elif llm_router:
    # Resolve provider from deployment config
    deployments = llm_router.get_model_group(model_name)
    if deployments:
        litellm_model = deployments[0].get("litellm_params", {}).get("model", "")
        if "/" in litellm_model:
            provider = litellm_model.split("/", 1)[0]

Option B: Store the provider on the model group metadata at config load time and pass it through.

Environment

  • LiteLLM version: v1.83.14-stable
  • Python: 3.13
  • Deployment: Docker

Workaround

Use model-specific overrides instead of relying on defaultconfig with multiple providers:

{
  "model_config": {
    "defaultconfig": {
      "gemini": { "litellm_credentials": "gemini-team-1" }
    },
    "claude-sonnet-4.6": {
      "bedrock": { "litellm_credentials": "bedrock-team-1" }
    },
    "claude-opus-4.6": {
      "bedrock": { "litellm_credentials": "bedrock-team-1" }
    }
  }
}

This works but defeats the purpose of defaultconfig — every new Bedrock model requires updating every team's model_config.

Steps to Reproduce

Steps to reproduce

1. Model config (config.yaml)

model_list:
  - model_name: gemini-2.5-pro
    litellm_params:
      model: gemini/gemini-2.5-pro-preview-05-06
      api_key: os.environ/GEMINI_API_KEY

  - model_name: claude-sonnet-4.6
    litellm_params:
      model: bedrock/us.anthropic.claude-sonnet-4-6
      api_key: os.environ/AWS_BEARER_TOKEN_BEDROCK
      aws_region_name: us-east-1

litellm_settings:
  enable_model_config_credential_overrides: true

2. Create credentials

# Gemini credential for team-1
curl -X POST http://localhost:4000/credentials \
  -H "Authorization: Bearer $MASTER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "credential_name": "gemini-team-1",
    "credential_values": { "api_key": "gemini-key-for-team-1" },
    "credential_info": { "custom_llm_provider": "Google_AI_Studio" }
  }'

# Bedrock credential for team-1
curl -X POST http://localhost:4000/credentials \
  -H "Authorization: Bearer $MASTER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "credential_name": "bedrock-team-1",
    "credential_values": { "api_key": "ABSK-bedrock-key-for-team-1" },
    "credential_info": { "custom_llm_provider": "Bedrock" }
  }'

3. Set team model_config with multiple providers in defaultconfig

curl -X PATCH http://localhost:4000/team/update \
  -H "Authorization: Bearer $MASTER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "team_id": "team-1",
    "metadata": {
      "model_config": {
        "defaultconfig": {
          "gemini": {
            "litellm_credentials": "gemini-team-1"
          },
          "bedrock": {
            "litellm_credentials": "bedrock-team-1"
          }
        }
      }
    }
  }'

4. Make a request using team-1's virtual key

curl http://localhost:4000/v1/chat/completions \
  -H "Authorization: Bearer sk-team-1-virtual-key" \
  -H "Content-Type: application/json" \
  -d '{"model": "claude-sonnet-4.6", "messages": [{"role": "user", "content": "hi"}], "max_tokens": 5}'

Relevant log output

What part of LiteLLM is this about?

Proxy

What LiteLLM version are you on ?

v1.83.14-stable.patch.3

Twitter / LinkedIn details

No response

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

The request for claude-sonnet-4.6 should resolve to bedrock-team-1 credential because:

  • The deployment's litellm_params.model is bedrock/us.anthropic.claude-sonnet-4-6
  • The defaultconfig has a bedrock entry with litellm_credentials: bedrock-team-1

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING