From 250a69486423af42eeeb1bb6183886d233cd2750 Mon Sep 17 00:00:00 2001 From: 72004 Date: Sun, 11 Oct 2026 19:26:21 +0500 Subject: [PATCH 1/4] fix(databricks): bridge GPT-5.4+ function tools to the Responses API Databricks forwards its OpenAI GPT endpoints (databricks-gpt-5-4, -5-6-sol, ...) to OpenAI, whose Chat Completions rejects function tools while reasoning is on. The version check never matched the hyphenated Databricks names, so these requests were sent to /chat/completions and failed. --- litellm/responses/bridge_check.py | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/litellm/responses/bridge_check.py b/litellm/responses/bridge_check.py index 3b1e958ff86..e4b5dc02828 100644 --- a/litellm/responses/bridge_check.py +++ b/litellm/responses/bridge_check.py @@ -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 @@ -15,6 +16,15 @@ from litellm.utils import get_model_info_helper _OPENAI_DEFAULT_API_BASE: Final = "https://api.openai.com/v1" _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. +_DATABRICKS_GPT_NAME: Final = re.compile(r"^databricks-gpt-(\d+)-(\d+)(?=-|$)") + + +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(r"gpt-\1.\2", model_name) def _is_openai_backed_api_base(api_base: str) -> bool: @@ -122,8 +132,15 @@ 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 + ) on_constraint_enforcing_endpoint: Final = ( - custom_llm_provider == "azure" or resolved_api_base == "" or _is_openai_backed_api_base(resolved_api_base) + custom_llm_provider in ("azure", "databricks") + or resolved_api_base == "" + or _is_openai_backed_api_base(resolved_api_base) ) chat_rejects_function_tools: Final = ( has_function_tool @@ -132,7 +149,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(model) + OpenAIGPT5Config.is_model_gpt_5_4_plus_model( + _openai_name_for_databricks_model(model) if on_databricks_gpt_endpoint else model + ) and (reasoning_effort is not None or on_constraint_enforcing_endpoint) ) ) @@ -143,7 +162,7 @@ def responses_api_bridge_check( and OpenAIGPT5Config.is_model_gpt_5_model(model) and not OpenAIGPT5Config.is_model_gpt_5_search_model(model) and ((reasoning_effort is not None and reasoning_summary is not None) or chat_rejects_function_tools) - ): + ) or (on_databricks_gpt_endpoint and model_info.get("mode") != "responses" and chat_rejects_function_tools): model_info["mode"] = "responses" model = model.replace("responses/", "") From 1d2f37b6b4bf5bdf5b9ab6202e1290b4758ef57c Mon Sep 17 00:00:00 2001 From: 72004 Date: Sun, 11 Oct 2026 19:26:21 +0500 Subject: [PATCH 2/4] test(databricks): cover Responses bridge routing for Databricks GPT models --- tests/unit/test_main.py | 68 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/tests/unit/test_main.py b/tests/unit/test_main.py index 635ee98549e..f79dcd695a4 100644 --- a/tests/unit/test_main.py +++ b/tests/unit/test_main.py @@ -1492,6 +1492,74 @@ 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-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-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 From fffdf3eeb3d9a7108171625522f6651f037caa9d Mon Sep 17 00:00:00 2001 From: 72004 Date: Sun, 11 Oct 2026 21:52:49 +0500 Subject: [PATCH 3/4] fix(databricks): map major-only GPT names and skip search models in the bridge check --- litellm/responses/bridge_check.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/litellm/responses/bridge_check.py b/litellm/responses/bridge_check.py index e4b5dc02828..bd6b4686cfb 100644 --- a/litellm/responses/bridge_check.py +++ b/litellm/responses/bridge_check.py @@ -17,14 +17,21 @@ from litellm.utils import get_model_info_helper _OPENAI_DEFAULT_API_BASE: Final = "https://api.openai.com/v1" _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. -_DATABRICKS_GPT_NAME: Final = re.compile(r"^databricks-gpt-(\d+)-(\d+)(?=-|$)") +# 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(r"gpt-\1.\2", model_name) + return _DATABRICKS_GPT_NAME.sub(_openai_gpt_name, model_name) def _is_openai_backed_api_base(api_base: str) -> bool: @@ -134,8 +141,10 @@ def responses_api_bridge_check( ) # 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 + 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) ) on_constraint_enforcing_endpoint: Final = ( custom_llm_provider in ("azure", "databricks") From 1572bafb097340a8a21669f6123b771b6bcc1fa2 Mon Sep 17 00:00:00 2001 From: 72004 Date: Sun, 11 Oct 2026 21:52:49 +0500 Subject: [PATCH 4/4] test(databricks): cover major-only names and search models --- tests/unit/test_main.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/unit/test_main.py b/tests/unit/test_main.py index f79dcd695a4..15592fe95ab 100644 --- a/tests/unit/test_main.py +++ b/tests/unit/test_main.py @@ -1526,6 +1526,8 @@ def test_responses_api_bridge_check_databricks_gpt_5_4_plus_tools_route_to_respo 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"), ], @@ -1550,6 +1552,9 @@ def test_responses_api_bridge_check_databricks_without_rejected_tools_stays_chat ("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"), ],