mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-06 08:16:43 +00:00
fix(azure): flatten top-level tool schema combinators for Azure Responses GPT-4-family deployments
This commit is contained in:
parent
9448293903
commit
af186eaaf3
2 changed files with 96 additions and 4 deletions
|
|
@ -33,6 +33,7 @@ else:
|
|||
|
||||
_NO_TOOL_UPDATE: Final[Mapping[str, object]] = MappingProxyType({})
|
||||
_MODEL_FAMILIES_REJECTING_TOP_LEVEL_SCHEMA_COMBINATORS: Final = ("gpt-4", "gpt-3.5", "chatgpt-4o", "o1", "o3", "o4")
|
||||
_PROVIDERS_WITH_COMBINATOR_REJECTING_VALIDATOR: Final = frozenset({LlmProviders.AZURE, LlmProviders.OPENAI})
|
||||
|
||||
|
||||
class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig):
|
||||
|
|
@ -172,7 +173,9 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig):
|
|||
input = self._validate_input_param(input)
|
||||
tools = response_api_optional_request_params.get("tools")
|
||||
input, tools = self.remove_cache_control_flag_from_input_and_tools(model=model, input=input, tools=tools)
|
||||
sanitized_tools: Final = self._flatten_tool_schema_combinators_for_openai(model=model, tools=tools)
|
||||
sanitized_tools: Final = self._flatten_tool_schema_combinators_for_openai(
|
||||
model=model, tools=tools, litellm_params=litellm_params
|
||||
)
|
||||
if sanitized_tools is not None:
|
||||
response_api_optional_request_params["tools"] = sanitized_tools
|
||||
final_request_params: Final = dict(
|
||||
|
|
@ -217,6 +220,7 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig):
|
|||
self,
|
||||
model: str,
|
||||
tools: list[ALL_RESPONSES_API_TOOL_PARAMS] | None, # mutable-ok: request tools are a JSON list
|
||||
litellm_params: GenericLiteLLMParams,
|
||||
) -> list[ALL_RESPONSES_API_TOOL_PARAMS] | None: # mutable-ok: request tools are a JSON list
|
||||
"""Flatten top-level schema combinators only where OpenAI's validator rejects them.
|
||||
|
||||
|
|
@ -224,10 +228,14 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig):
|
|||
Codex talks to natively) accept them, and so do GPT-5 and later models,
|
||||
which also call tools better with the union intact. Codex wraps MCP tools
|
||||
inside namespace entries, so nested ``tools`` arrays are walked too.
|
||||
Azure OpenAI shares the validator but names deployments arbitrarily, so
|
||||
the router's declared ``model_info.base_model`` wins over the deployment
|
||||
name and an unrecognized name without one is left untouched.
|
||||
"""
|
||||
if tools is None or self.custom_llm_provider != LlmProviders.OPENAI:
|
||||
if tools is None or self.custom_llm_provider not in _PROVIDERS_WITH_COMBINATOR_REJECTING_VALIDATOR:
|
||||
return tools
|
||||
if not self._rejects_top_level_schema_combinators(model):
|
||||
gate_model: Final = self._combinator_gate_model(model=model, litellm_params=litellm_params)
|
||||
if not self._rejects_top_level_schema_combinators(gate_model):
|
||||
return tools
|
||||
flattened: Final = [ # mutable-ok: request tools are a JSON list
|
||||
self._flattened_tool_or_passthrough(tool) for tool in tools
|
||||
|
|
@ -244,6 +252,12 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig):
|
|||
base_model: Final = bare_model.split(":")[1] if bare_model.startswith("ft:") else bare_model
|
||||
return base_model.startswith(_MODEL_FAMILIES_REJECTING_TOP_LEVEL_SCHEMA_COMBINATORS)
|
||||
|
||||
@staticmethod
|
||||
def _combinator_gate_model(model: str, litellm_params: GenericLiteLLMParams) -> str:
|
||||
model_info: Final[object] = getattr(litellm_params, "model_info", None)
|
||||
base_model: Final[object] = model_info.get("base_model") if isinstance(model_info, dict) else None
|
||||
return base_model if isinstance(base_model, str) and base_model else model
|
||||
|
||||
@staticmethod
|
||||
def _flattened_tool_entry(
|
||||
entry: Mapping[str, object],
|
||||
|
|
@ -714,7 +728,9 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig):
|
|||
input = self._validate_input_param(input)
|
||||
tools = response_api_optional_request_params.get("tools")
|
||||
input, tools = self.remove_cache_control_flag_from_input_and_tools(model=model, input=input, tools=tools)
|
||||
sanitized_tools: Final = self._flatten_tool_schema_combinators_for_openai(model=model, tools=tools)
|
||||
sanitized_tools: Final = self._flatten_tool_schema_combinators_for_openai(
|
||||
model=model, tools=tools, litellm_params=litellm_params
|
||||
)
|
||||
if sanitized_tools is not None:
|
||||
response_api_optional_request_params["tools"] = sanitized_tools
|
||||
data: Final = dict(ResponsesAPIRequestParams(model=model, input=input, **response_api_optional_request_params))
|
||||
|
|
|
|||
|
|
@ -537,3 +537,79 @@ class TestAzureResponsesAPIConfig:
|
|||
"""
|
||||
supported = self.config.get_supported_openai_params(self.model)
|
||||
assert "context_management" not in supported
|
||||
|
||||
def _anyof_tool(self):
|
||||
return {
|
||||
"type": "function",
|
||||
"name": "automation_update",
|
||||
"description": "Update an automation",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"anyOf": [
|
||||
{
|
||||
"properties": {"id": {"type": "string"}, "enabled": {"type": "boolean"}},
|
||||
"required": ["id", "enabled"],
|
||||
},
|
||||
{
|
||||
"properties": {"id": {"type": "string"}, "schedule": {"type": "string"}},
|
||||
"required": ["id", "schedule"],
|
||||
},
|
||||
],
|
||||
"properties": {"id": {"type": "string"}},
|
||||
"required": ["id"],
|
||||
},
|
||||
}
|
||||
|
||||
def test_azure_flattens_top_level_anyof_for_gpt4_family_deployment_name(self):
|
||||
result = self.config.transform_responses_api_request(
|
||||
model="gpt-4o",
|
||||
input="hi",
|
||||
response_api_optional_request_params={"tools": [self._anyof_tool()]},
|
||||
litellm_params=GenericLiteLLMParams(),
|
||||
headers={},
|
||||
)
|
||||
|
||||
parameters = result["tools"][0]["parameters"]
|
||||
assert "anyOf" not in parameters
|
||||
assert parameters["type"] == "object"
|
||||
assert set(parameters["properties"]) == {"id", "enabled", "schedule"}
|
||||
assert parameters["required"] == ["id"]
|
||||
|
||||
def test_azure_flattens_via_base_model_for_arbitrary_deployment_name(self):
|
||||
result = self.config.transform_responses_api_request(
|
||||
model="my-eastus-deployment",
|
||||
input="hi",
|
||||
response_api_optional_request_params={"tools": [self._anyof_tool()]},
|
||||
litellm_params=GenericLiteLLMParams(model_info={"base_model": "azure/gpt-4o"}),
|
||||
headers={},
|
||||
)
|
||||
|
||||
assert "anyOf" not in result["tools"][0]["parameters"]
|
||||
|
||||
def test_azure_keeps_combinators_for_gpt5_base_model(self):
|
||||
tool = self._anyof_tool()
|
||||
|
||||
result = self.config.transform_responses_api_request(
|
||||
model="my-eastus-deployment",
|
||||
input="hi",
|
||||
response_api_optional_request_params={"tools": [tool]},
|
||||
litellm_params=GenericLiteLLMParams(model_info={"base_model": "azure/gpt-5.4-mini"}),
|
||||
headers={},
|
||||
)
|
||||
|
||||
assert result["tools"][0] is tool
|
||||
assert "anyOf" in result["tools"][0]["parameters"]
|
||||
|
||||
def test_azure_keeps_combinators_for_unrecognized_deployment_without_base_model(self):
|
||||
tool = self._anyof_tool()
|
||||
|
||||
result = self.config.transform_responses_api_request(
|
||||
model="my-eastus-deployment",
|
||||
input="hi",
|
||||
response_api_optional_request_params={"tools": [tool]},
|
||||
litellm_params=GenericLiteLLMParams(),
|
||||
headers={},
|
||||
)
|
||||
|
||||
assert result["tools"][0] is tool
|
||||
assert "anyOf" in result["tools"][0]["parameters"]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue