This commit is contained in:
Syed Ahnaf Raza 2026-10-21 03:35:55 +00:00 • committed by GitHub
commit 44bb2c58ab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 102 additions and 3 deletions

View file

@ -1,3 +1,4 @@
import re
from collections.abc import Mapping, Sequence
from typing import Final, cast # noqa: TID251 # moved unchanged from litellm/main.py
from urllib.parse import urlsplit
@ -61,6 +62,22 @@ def keep_registered_mode(backend_key: str, shared_model_info: Mapping[str, objec
_OPENAI_API_HOST: Final = "api.openai.com"
# Databricks serves OpenAI GPT models as e.g. ``databricks-gpt-5-6-sol``: the minor version is
# hyphenated, so the OpenAI version parser does not recognise it. The minor version is optional
# so major-only names (``databricks-gpt-5``) map as well.
_DATABRICKS_GPT_NAME: Final = re.compile(r"^databricks-gpt-(\d+)(?:-(\d+))?(?=-|$)")
def _openai_gpt_name(match: "re.Match[str]") -> str:
major: Final = match.group(1)
minor: Final = match.group(2)
return f"gpt-{major}" if minor is None else f"gpt-{major}.{minor}"
def _openai_name_for_databricks_model(model: str) -> str:
"""``databricks-gpt-5-6-sol`` -> ``gpt-5.6-sol``; other names are returned unchanged."""
model_name: Final = model.split("/")[-1]
return _DATABRICKS_GPT_NAME.sub(_openai_gpt_name, model_name)
def _is_openai_backed_api_base(api_base: str) -> bool:
@ -243,6 +260,13 @@ def responses_api_bridge_check(
on_foundry_openai_endpoint: Final = custom_llm_provider == "azure_ai" and azure_ai_supports_native_responses(
model, api_base
)
# Databricks proxies OpenAI GPT models to the real OpenAI backend, which enforces the
# reasoning+tools constraint, and serves them on its own /responses route as well.
on_databricks_gpt_endpoint: Final = (
custom_llm_provider == "databricks"
and OpenAIGPT5Config.is_model_gpt_5_model(model)
and not OpenAIGPT5Config.is_model_gpt_5_search_model(model)
)
mantle_series_name: Final = (
_mantle_responses_series_name(model) if custom_llm_provider == "bedrock_mantle" else None
)
@ -254,7 +278,7 @@ def responses_api_bridge_check(
)
gpt_series_name: Final = mantle_series_name or model
on_constraint_enforcing_endpoint: Final = (
custom_llm_provider == "azure"
custom_llm_provider in ("azure", "databricks")
or on_mantle_responses_endpoint
or resolved_api_base == ""
or _is_openai_backed_api_base(resolved_api_base)
@ -266,7 +290,9 @@ def responses_api_bridge_check(
foundry_chat_rejects_function_tools_while_reasoning(model, reasoning_effort)
if on_foundry_openai_endpoint
else (
OpenAIGPT5Config.is_model_gpt_5_4_plus_model(gpt_series_name)
OpenAIGPT5Config.is_model_gpt_5_4_plus_model(
_openai_name_for_databricks_model(model) if on_databricks_gpt_endpoint else gpt_series_name
)
and (reasoning_effort is not None or on_constraint_enforcing_endpoint)
)
)
@ -282,7 +308,7 @@ def responses_api_bridge_check(
or (carries_tool_search and on_constraint_enforcing_endpoint)
or (web_search_options is not None and on_mantle_responses_endpoint)
)
):
) or (on_databricks_gpt_endpoint and model_info.get("mode") != "responses" and chat_rejects_function_tools):
model_info["mode"] = "responses"
model = model.replace("responses/", "")

View file

@ -1810,6 +1810,79 @@ def test_responses_api_bridge_check_azure_ai_without_foundry_responses_route_sta
assert model_info.get("mode") != "responses"
_DATABRICKS_FUNCTION_TOOL: Final = ({"type": "function", "function": {"name": "get_capital"}},)
@pytest.mark.parametrize(
"model_name, reasoning_effort",
[
pytest.param("databricks-gpt-5-6-sol", None, id="gpt-5.6-unset-effort"),
pytest.param("databricks-gpt-5-6-luna", "high", id="gpt-5.6-explicit-effort"),
pytest.param("databricks-gpt-5-4", None, id="gpt-5.4-unset-effort"),
pytest.param("databricks-gpt-5-4-mini", "low", id="gpt-5.4-mini-explicit-effort"),
pytest.param("databricks/databricks-gpt-5-6-terra", None, id="provider-prefixed"),
],
)
def test_responses_api_bridge_check_databricks_gpt_5_4_plus_tools_route_to_responses(model_name, reasoning_effort):
"""Databricks forwards GPT models to OpenAI, whose Chat Completions rejects function tools while reasoning."""
from litellm.main import responses_api_bridge_check
model_info, model = responses_api_bridge_check(
model=model_name,
custom_llm_provider="databricks",
tools=_DATABRICKS_FUNCTION_TOOL,
reasoning_effort=reasoning_effort,
)
assert model == model_name
assert model_info.get("mode") == "responses"
@pytest.mark.parametrize(
"model_name, tools, reasoning_effort",
[
pytest.param("databricks-gpt-5-6-sol", _DATABRICKS_FUNCTION_TOOL, "none", id="explicit-none"),
pytest.param("databricks-gpt-5-6-sol", None, None, id="no-tools"),
pytest.param("databricks-gpt-5-2", _DATABRICKS_FUNCTION_TOOL, None, id="pre-5.4"),
pytest.param("databricks-gpt-5", _DATABRICKS_FUNCTION_TOOL, None, id="major-only-pre-5.4"),
pytest.param("databricks-gpt-5-6-search-api", _DATABRICKS_FUNCTION_TOOL, None, id="search-model"),
pytest.param("databricks-gpt-oss-120b", _DATABRICKS_FUNCTION_TOOL, None, id="gpt-oss"),
pytest.param("databricks-claude-sonnet-4-5", _DATABRICKS_FUNCTION_TOOL, None, id="non-gpt"),
],
)
def test_responses_api_bridge_check_databricks_without_rejected_tools_stays_chat(model_name, tools, reasoning_effort):
from litellm.main import responses_api_bridge_check
model_info, model = responses_api_bridge_check(
model=model_name,
custom_llm_provider="databricks",
tools=tools,
reasoning_effort=reasoning_effort,
)
assert model == model_name
assert model_info.get("mode") != "responses"
@pytest.mark.parametrize(
"model_name, expected",
[
("databricks-gpt-5-6-sol", "gpt-5.6-sol"),
("databricks/databricks-gpt-5-4", "gpt-5.4"),
("databricks-gpt-5-4-mini", "gpt-5.4-mini"),
("databricks-gpt-5", "gpt-5"),
("databricks-gpt-5-mini", "gpt-5-mini"),
("databricks-gpt-6", "gpt-6"),
("databricks-gpt-oss-120b", "databricks-gpt-oss-120b"),
("databricks-claude-sonnet-4-5", "databricks-claude-sonnet-4-5"),
],
)
def test_openai_name_for_databricks_model(model_name, expected):
from litellm.responses.bridge_check import _openai_name_for_databricks_model
assert _openai_name_for_databricks_model(model_name) == expected
def test_responses_api_bridge_check_older_gpt_5_tools_without_reasoning_stays_chat():
"""Pre-5.4 GPT-5 names keep the old boundary: tools alone never bridge."""
from litellm.main import responses_api_bridge_check