From 3060c6f9197907de11df7e4edf97fd805257b413 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 24 Mar 2025 11:32:29 +0000 Subject: [PATCH] Fix Gemini API additionalProperties error (issue #2457) Co-Authored-By: Joe Moura --- src/crewai/llm.py | 14 +++++++++++++- src/crewai/tools/structured_tool.py | 20 ++++++++++++++++++++ tests/tools/test_structured_tool.py | 29 ++++++++++++++++++++++++++++- 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/src/crewai/llm.py b/src/crewai/llm.py index 68ddbacc7..6b0ce8ac0 100644 --- a/src/crewai/llm.py +++ b/src/crewai/llm.py @@ -316,7 +316,19 @@ class LLM: messages = [{"role": "user", "content": messages}] formatted_messages = self._format_messages_for_provider(messages) - # --- 2) Prepare the parameters for the completion call + # --- 2) If using Gemini, ensure additionalProperties is not in tool schemas + if tools and "gemini" in self.model.lower(): + for i, tool in enumerate(tools): + if ( + isinstance(tool, dict) + and "function" in tool + and "parameters" in tool["function"] + ): + params = tool["function"]["parameters"] + if "additionalProperties" in params: + del params["additionalProperties"] + + # --- 3) Prepare the parameters for the completion call params = { "model": self.model, "messages": formatted_messages, diff --git a/src/crewai/tools/structured_tool.py b/src/crewai/tools/structured_tool.py index dfd23a9cb..200a4ee4a 100644 --- a/src/crewai/tools/structured_tool.py +++ b/src/crewai/tools/structured_tool.py @@ -239,6 +239,26 @@ class CrewStructuredTool: def args(self) -> dict: """Get the tool's input arguments schema.""" return self.args_schema.model_json_schema()["properties"] + + def to_openai_function(self) -> dict: + """Convert the tool to an OpenAI function format. + + Returns: + dict: A dictionary in the OpenAI function format. + """ + schema = self.args_schema.model_json_schema() + # Remove additionalProperties field to prevent Gemini API errors + if "additionalProperties" in schema: + del schema["additionalProperties"] + + return { + "type": "function", + "function": { + "name": self.name, + "description": self.description, + "parameters": schema + } + } def __repr__(self) -> str: return ( diff --git a/tests/tools/test_structured_tool.py b/tests/tools/test_structured_tool.py index 333220486..de4a5ee64 100644 --- a/tests/tools/test_structured_tool.py +++ b/tests/tools/test_structured_tool.py @@ -25,7 +25,7 @@ def schema_class(): return TestSchema -class InternalCrewStructuredTool: +class TestInternalCrewStructuredTool: def test_initialization(self, basic_function, schema_class): """Test basic initialization of CrewStructuredTool""" tool = CrewStructuredTool( @@ -144,3 +144,30 @@ class InternalCrewStructuredTool: {"required_param": "test", "optional_param": "custom", "nullable_param": 42} ) assert result == "test custom 42" + + def test_to_openai_function_no_additional_properties(self): + """Test that the to_openai_function method doesn't include additionalProperties.""" + + class TestSchema(BaseModel): + test_field: str = Field(..., description="A test field") + + def test_func(test_field: str) -> str: + """Test function that returns the input.""" + return f"Test function received: {test_field}" + + tool = CrewStructuredTool( + name="test_tool", + description="A test tool", + args_schema=TestSchema, + func=test_func + ) + + function_dict = tool.to_openai_function() + assert "additionalProperties" not in function_dict["function"]["parameters"] + + # Verify other properties are correct + assert function_dict["type"] == "function" + assert function_dict["function"]["name"] == "test_tool" + assert function_dict["function"]["description"] == "A test tool" + assert "properties" in function_dict["function"]["parameters"] + assert "test_field" in function_dict["function"]["parameters"]["properties"]