fix: move custom provider dispatch before model-name-based matching

Custom LLM providers registered via `custom_provider_map` are silently
bypassed when the underlying model name (after prefix stripping by
`get_llm_provider`) matches a known built-in model like `gpt-4o-mini`.

Root cause: The `elif` chain in `completion()` checks
`model in litellm.open_ai_chat_completion_models` (line ~2528) BEFORE
`custom_llm_provider in litellm._custom_providers` (line ~4285).
Since `get_llm_provider()` strips the provider prefix
(e.g. `my-provider/gpt-4o-mini` → `gpt-4o-mini`), the naked model name
matches the OpenAI catch-all branch, and the custom provider handler is
never reached.

Fix: Move the `_custom_providers` dispatch block before the
model-name-based OpenAI catch-all, ensuring explicitly registered custom
providers always take priority.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Danny Gerst 2026-03-11 16:20:38 +01:00
parent cbbd51a5ce
commit e8327754e0

View file

@ -1618,7 +1618,57 @@ def completion( # type: ignore # noqa: PLR0915
stream=stream,
)
if custom_llm_provider == "azure":
if (
custom_llm_provider in litellm._custom_providers
): # Explicitly registered custom providers always take priority
# over built-in model-name or provider-name matching below.
# Get the Custom Handler
custom_handler: Optional[CustomLLM] = None
for item in litellm.custom_provider_map:
if item["provider"] == custom_llm_provider:
custom_handler = item["custom_handler"]
if custom_handler is None:
raise LiteLLMUnknownProvider(
model=model, custom_llm_provider=custom_llm_provider
)
## ROUTE LLM CALL ##
handler_fn = custom_chat_llm_router(
async_fn=acompletion, stream=stream, custom_llm=custom_handler
)
headers = headers or litellm.headers or {}
## CALL FUNCTION
response = handler_fn(
model=model,
messages=messages,
headers=headers,
model_response=model_response,
print_verbose=print_verbose,
api_key=api_key,
api_base=api_base,
acompletion=acompletion,
logging_obj=logging,
optional_params=optional_params,
litellm_params=litellm_params,
logger_fn=logger_fn,
timeout=timeout, # type: ignore
custom_prompt_dict=custom_prompt_dict,
client=client, # pass AsyncOpenAI, OpenAI client
encoding=_get_encoding(),
)
if stream is True:
return CustomStreamWrapper(
completion_stream=response,
model=model,
custom_llm_provider=custom_llm_provider,
logging_obj=logging,
)
return response
elif custom_llm_provider == "azure":
# azure configs
## check dynamic params ##
dynamic_params = False
@ -4278,54 +4328,6 @@ def completion( # type: ignore # noqa: PLR0915
model_response.model = model
response = model_response
elif (
custom_llm_provider in litellm._custom_providers
): # Assume custom LLM provider
# Get the Custom Handler
custom_handler: Optional[CustomLLM] = None
for item in litellm.custom_provider_map:
if item["provider"] == custom_llm_provider:
custom_handler = item["custom_handler"]
if custom_handler is None:
raise LiteLLMUnknownProvider(
model=model, custom_llm_provider=custom_llm_provider
)
## ROUTE LLM CALL ##
handler_fn = custom_chat_llm_router(
async_fn=acompletion, stream=stream, custom_llm=custom_handler
)
headers = headers or litellm.headers or {}
## CALL FUNCTION
response = handler_fn(
model=model,
messages=messages,
headers=headers,
model_response=model_response,
print_verbose=print_verbose,
api_key=api_key,
api_base=api_base,
acompletion=acompletion,
logging_obj=logging,
optional_params=optional_params,
litellm_params=litellm_params,
logger_fn=logger_fn,
timeout=timeout, # type: ignore
custom_prompt_dict=custom_prompt_dict,
client=client, # pass AsyncOpenAI, OpenAI client
encoding=_get_encoding(),
)
if stream is True:
return CustomStreamWrapper(
completion_stream=response,
model=model,
custom_llm_provider=custom_llm_provider,
logging_obj=logging,
)
elif custom_llm_provider == "langgraph":
# LangGraph - Agent Runtime Provider
from litellm.llms.langgraph.chat.transformation import LangGraphConfig