diff --git a/litellm/llms/azure/chat/gpt_transformation.py b/litellm/llms/azure/chat/gpt_transformation.py index 3f450fefa5a..438766fa52f 100644 --- a/litellm/llms/azure/chat/gpt_transformation.py +++ b/litellm/llms/azure/chat/gpt_transformation.py @@ -176,13 +176,10 @@ class AzureOpenAIConfig(BaseConfig): optional_params["tool_choice"] = value elif param == "response_format" and isinstance(value, dict): json_schema: Optional[dict] = None - schema_name: str = "" if "response_schema" in value: json_schema = value["response_schema"] - schema_name = "json_tool_call" elif "json_schema" in value: json_schema = value["json_schema"]["schema"] - schema_name = value["json_schema"]["name"] """ Follow similar approach to anthropic - translate to a single tool call. @@ -213,11 +210,14 @@ class AzureOpenAIConfig(BaseConfig): ), ) - optional_params["tools"] = [_tool] - optional_params["tool_choice"] = _tool_choice - optional_params["json_mode"] = True + optional_params = self._add_response_format_to_tools( + optional_params, _tool, _tool_choice + ) else: optional_params["response_format"] = value + elif param == "tools" and isinstance(value, list): + optional_params.setdefault("tools", []) + optional_params["tools"].extend(value) elif param in supported_openai_params: optional_params[param] = value diff --git a/litellm/llms/base_llm/chat/transformation.py b/litellm/llms/base_llm/chat/transformation.py index 76da38481d4..23aea313db0 100644 --- a/litellm/llms/base_llm/chat/transformation.py +++ b/litellm/llms/base_llm/chat/transformation.py @@ -19,7 +19,11 @@ import httpx from pydantic import BaseModel from litellm._logging import verbose_logger -from litellm.types.llms.openai import AllMessageValues +from litellm.types.llms.openai import ( + AllMessageValues, + ChatCompletionToolChoiceObjectParam, + ChatCompletionToolParam, +) from litellm.types.utils import ModelResponse from ..base_utils import ( @@ -148,6 +152,23 @@ class BaseConfig(ABC): def get_supported_openai_params(self, model: str) -> list: pass + def _add_response_format_to_tools( + self, + optional_params: dict, + _tool: ChatCompletionToolParam, + _tool_choice: ChatCompletionToolChoiceObjectParam, + ) -> dict: + """ + Add response format to tools + + This is used to translate response_format to a tool call, for models/APIs that don't support response_format directly. + """ + optional_params.setdefault("tools", []) + optional_params["tools"].append(_tool) + optional_params["tool_choice"] = _tool_choice + optional_params["json_mode"] = True + return optional_params + @abstractmethod def map_openai_params( self, diff --git a/tests/llm_translation/test_azure_openai.py b/tests/llm_translation/test_azure_openai.py index 1306c536501..6306f58cfbd 100644 --- a/tests/llm_translation/test_azure_openai.py +++ b/tests/llm_translation/test_azure_openai.py @@ -341,3 +341,98 @@ def test_azure_gpt_4o_with_tool_call_and_response_format(): assert response.choices[0].finish_reason == "tool_calls" print(response.to_json()) + + +def test_map_openai_params(): + """ + Ensure response_format does not override tools + """ + from litellm.llms.azure.chat.gpt_transformation import AzureOpenAIConfig + + azure_openai_config = AzureOpenAIConfig() + tools = [ + { + "type": "function", + "function": { + "name": "get_current_time", + "description": "Returns the current date and time", + "strict": True, + "parameters": { + "properties": { + "timezone": { + "type": "string", + "description": "The timezone to get the current time for (e.g., 'UTC', 'America/New_York')", + } + }, + "required": ["timezone"], + "type": "object", + "additionalProperties": False, + }, + }, + } + ] + received_args = { + "non_default_params": { + "temperature": 1e-08, + "response_format": { + "type": "json_schema", + "json_schema": { + "schema": { + "properties": { + "alert_explanation": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Alert Explanation", + }, + "investigation": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Investigation", + }, + "conclusions_and_possible_root_causes": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Conclusions And Possible Root Causes", + }, + "next_steps": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Next Steps", + }, + "related_logs": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Related Logs", + }, + "app_or_infra": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "App Or Infra", + }, + "external_links": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "External Links", + }, + }, + "title": "InvestigationOutput", + "type": "object", + "additionalProperties": False, + "required": [ + "alert_explanation", + "investigation", + "conclusions_and_possible_root_causes", + "next_steps", + "related_logs", + "app_or_infra", + "external_links", + ], + }, + "name": "InvestigationOutput", + "strict": True, + }, + }, + "tools": tools, + "tool_choice": "auto", + }, + "optional_params": {}, + "model": "gpt-4o", + "drop_params": True, + "api_version": "2024-02-15-preview", + } + optional_params = azure_openai_config.map_openai_params(**received_args) + assert "tools" in optional_params + assert len(optional_params["tools"]) > 1