mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-28 06:23:07 +00:00
Merge fec5b3d139 into 8d6b8dab3d
This commit is contained in:
commit
44bb2c58ab
2 changed files with 102 additions and 3 deletions
|
|
@ -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/", "")
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue