composio-gemini 0.8.15__tar.gz → 0.8.16__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: composio-gemini
3
- Version: 0.8.15
3
+ Version: 0.8.16
4
4
  Summary: Use Composio to get an array of tools with your Gemini agent.
5
5
  Home-page: https://github.com/ComposioHQ/composio
6
6
  Author: Composio
@@ -0,0 +1,271 @@
1
+ """Gemini provider for Composio SDK."""
2
+
3
+ import typing as t
4
+
5
+ from composio.client.types import Tool
6
+ from composio.core.provider import AgenticProvider
7
+ from composio.core.provider.agentic import AgenticProviderExecuteFn
8
+
9
+ # Try to import Google GenAI types
10
+ try:
11
+ from google.genai import types as genai_types
12
+
13
+ HAS_GENAI = True
14
+ except ImportError:
15
+ genai_types = None # type: ignore
16
+ HAS_GENAI = False
17
+
18
+
19
+ class GeminiTool:
20
+ """
21
+ Enhanced Gemini Tool wrapper that handles automatic function execution.
22
+ Acts as a drop-in replacement for genai_types.Tool with execution support.
23
+ """
24
+
25
+ def __init__(
26
+ self,
27
+ tool: Tool,
28
+ execute_tool: AgenticProviderExecuteFn,
29
+ function_declaration: t.Optional[t.Any] = None,
30
+ ):
31
+ self.tool = tool
32
+ self.execute_tool = execute_tool
33
+ self.function_declaration = function_declaration
34
+
35
+ # Create the actual Gemini Tool if we have genai support
36
+ self._genai_tool = None
37
+ if HAS_GENAI and function_declaration:
38
+ self._genai_tool = genai_types.Tool(
39
+ function_declarations=[function_declaration]
40
+ )
41
+
42
+ # Store executor for easy access
43
+ self._executors = {tool.slug: self}
44
+
45
+ # Copy function attributes for compatibility
46
+ self.__name__ = tool.slug
47
+ self.__module__ = "composio_gemini"
48
+ self.__doc__ = tool.description
49
+
50
+ def __call__(self, **kwargs: t.Any) -> t.Dict:
51
+ """Execute the tool when called."""
52
+ result = self.execute_tool(slug=self.tool.slug, arguments=kwargs)
53
+
54
+ # Process the result for Gemini
55
+ if not isinstance(result, dict):
56
+ return {"result": result}
57
+
58
+ # Extract data field if present and successful
59
+ if result.get("successful", True) and "data" in result:
60
+ data = result["data"]
61
+ return data if isinstance(data, dict) else {"result": data}
62
+
63
+ # Return error info if failed
64
+ if not result.get("successful", True):
65
+ return {
66
+ "error": result.get("error", "Tool execution failed"),
67
+ "details": result,
68
+ }
69
+
70
+ return result
71
+
72
+ @property
73
+ def function_declarations(self):
74
+ """Pass through to the underlying Gemini tool."""
75
+ return self._genai_tool.function_declarations if self._genai_tool else []
76
+
77
+ def to_dict(self):
78
+ """Convert to dict for Gemini API."""
79
+ return self._genai_tool.to_dict() if self._genai_tool else {}
80
+
81
+
82
+ class GeminiProvider(AgenticProvider[t.Any, list[t.Any]], name="gemini"):
83
+ """
84
+ Composio toolset for Google AI Python Gemini framework.
85
+ Supports automatic function calling with native Gemini tools.
86
+ """
87
+
88
+ __schema_skip_defaults__ = True
89
+
90
+ def _json_to_genai_schema(self, json_schema: dict) -> t.Optional[t.Any]:
91
+ """Convert JSON Schema to GenAI Schema format, handling composite types."""
92
+ if not HAS_GENAI:
93
+ return None
94
+
95
+ # Handle composite types (patterns from openapi.py)
96
+ if "oneOf" in json_schema:
97
+ # Gemini doesn't support unions directly, use first valid schema
98
+ # This is a limitation but better than failing
99
+ return self._json_to_genai_schema(json_schema["oneOf"][0])
100
+
101
+ if "anyOf" in json_schema:
102
+ # Similar to oneOf
103
+ return self._json_to_genai_schema(json_schema["anyOf"][0])
104
+
105
+ if "allOf" in json_schema:
106
+ # Merge all schemas (pattern from openapi.py's _all_of_to_parameter)
107
+ merged = {}
108
+ for subschema in json_schema["allOf"]:
109
+ merged.update(subschema)
110
+ return self._json_to_genai_schema(merged)
111
+
112
+ # Handle enum independently (can appear with or without type)
113
+ if "enum" in json_schema:
114
+ return self._handle_enum_schema(json_schema)
115
+
116
+ schema_type = json_schema.get("type", "string")
117
+
118
+ # Map type handlers
119
+ type_handlers = {
120
+ "array": self._handle_array_schema,
121
+ "object": self._handle_object_schema,
122
+ "string": self._handle_string_schema,
123
+ "number": lambda _: genai_types.Schema(type=genai_types.Type.NUMBER),
124
+ "integer": lambda _: genai_types.Schema(type=genai_types.Type.INTEGER),
125
+ "boolean": lambda _: genai_types.Schema(type=genai_types.Type.BOOLEAN),
126
+ "null": lambda _: genai_types.Schema(
127
+ type=genai_types.Type.STRING
128
+ ), # Gemini doesn't have null type
129
+ }
130
+
131
+ handler = type_handlers.get(
132
+ schema_type, lambda _: genai_types.Schema(type=genai_types.Type.STRING)
133
+ )
134
+ return handler(json_schema)
135
+
136
+ def _handle_array_schema(self, json_schema: dict) -> t.Any:
137
+ """Handle array type schema conversion."""
138
+ items = json_schema.get("items", {})
139
+ return genai_types.Schema(
140
+ type=genai_types.Type.ARRAY,
141
+ items=self._json_to_genai_schema(items)
142
+ if items
143
+ else genai_types.Schema(type=genai_types.Type.STRING),
144
+ )
145
+
146
+ def _handle_object_schema(self, json_schema: dict) -> t.Any:
147
+ """Handle object type schema conversion."""
148
+ properties = {
149
+ name: self._json_to_genai_schema(schema)
150
+ for name, schema in json_schema.get("properties", {}).items()
151
+ }
152
+ return genai_types.Schema(
153
+ type=genai_types.Type.OBJECT,
154
+ properties=properties,
155
+ required=json_schema.get("required", []),
156
+ )
157
+
158
+ def _handle_string_schema(self, json_schema: dict) -> t.Any:
159
+ """Handle string type schema conversion."""
160
+ schema_dict = {"type": genai_types.Type.STRING}
161
+ if enum_values := json_schema.get("enum"):
162
+ schema_dict["enum"] = enum_values
163
+ return genai_types.Schema(**schema_dict)
164
+
165
+ def _handle_enum_schema(self, json_schema: dict) -> t.Any:
166
+ """Handle enum schema conversion (pattern from openapi.py)."""
167
+ # Enum can be of any type, default to string if no type specified
168
+ base_type = json_schema.get("type", "string")
169
+ type_map = {
170
+ "string": genai_types.Type.STRING,
171
+ "integer": genai_types.Type.INTEGER,
172
+ "number": genai_types.Type.NUMBER,
173
+ }
174
+ schema_type = type_map.get(base_type, genai_types.Type.STRING)
175
+ return genai_types.Schema(type=schema_type, enum=json_schema["enum"])
176
+
177
+ def _create_function_declaration(self, tool: Tool) -> t.Optional[t.Any]:
178
+ """Create a native Gemini FunctionDeclaration if possible."""
179
+ if not HAS_GENAI:
180
+ return None
181
+
182
+ try:
183
+ genai_schema = self._json_to_genai_schema(tool.input_parameters)
184
+ return genai_types.FunctionDeclaration(
185
+ name=tool.slug,
186
+ description=tool.description or f"Execute {tool.slug}",
187
+ parameters=genai_schema,
188
+ )
189
+ except Exception as e:
190
+ print(f"Warning: Could not create FunctionDeclaration for {tool.slug}: {e}")
191
+ return None
192
+
193
+ def wrap_tool(
194
+ self,
195
+ tool: Tool,
196
+ execute_tool: AgenticProviderExecuteFn,
197
+ ) -> GeminiTool:
198
+ """Wrap a Composio tool for Gemini compatibility."""
199
+ # Create native FunctionDeclaration
200
+ function_declaration = self._create_function_declaration(tool)
201
+
202
+ # Return the GeminiTool wrapper
203
+ return GeminiTool(
204
+ tool=tool,
205
+ execute_tool=execute_tool,
206
+ function_declaration=function_declaration,
207
+ )
208
+
209
+ def wrap_tools(
210
+ self,
211
+ tools: t.Sequence[Tool],
212
+ execute_tool: AgenticProviderExecuteFn,
213
+ ) -> list[GeminiTool]:
214
+ """Wrap multiple tools for Gemini compatibility."""
215
+ return [self.wrap_tool(tool, execute_tool) for tool in tools]
216
+
217
+ @staticmethod
218
+ def handle_response(response, tools: list[GeminiTool]) -> tuple[list, bool]:
219
+ """
220
+ Automatically handle function calls in a Gemini response.
221
+
222
+ Args:
223
+ response: The response from Gemini chat
224
+ tools: The list of GeminiTool objects passed to the chat
225
+
226
+ Returns:
227
+ tuple: (function_responses, executed) where function_responses are ready
228
+ to send back and executed is True if functions were executed
229
+ """
230
+ # Check if we have a valid response with candidates
231
+ if not (hasattr(response, "candidates") and response.candidates):
232
+ return [], False
233
+
234
+ candidate = response.candidates[0]
235
+ if not (hasattr(candidate, "content") and candidate.content.parts):
236
+ return [], False
237
+
238
+ # Build executor map from tools
239
+ executors = {}
240
+ for tool in tools:
241
+ if isinstance(tool, GeminiTool):
242
+ executors.update(tool._executors)
243
+
244
+ # Process function calls
245
+ function_responses = []
246
+ executed = False
247
+
248
+ for part in candidate.content.parts:
249
+ if not (hasattr(part, "function_call") and part.function_call):
250
+ continue
251
+
252
+ fc = part.function_call
253
+ if fc.name not in executors:
254
+ continue
255
+
256
+ # Execute the function
257
+ executor = executors[fc.name]
258
+ result = executor(**fc.args)
259
+
260
+ # Create function response
261
+ if HAS_GENAI:
262
+ function_responses.append(
263
+ genai_types.Part(
264
+ function_response=genai_types.FunctionResponse(
265
+ name=fc.name, response=result
266
+ )
267
+ )
268
+ )
269
+ executed = True
270
+
271
+ return function_responses, executed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: composio-gemini
3
- Version: 0.8.15
3
+ Version: 0.8.16
4
4
  Summary: Use Composio to get an array of tools with your Gemini agent.
5
5
  Home-page: https://github.com/ComposioHQ/composio
6
6
  Author: Composio
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "composio-gemini"
3
- version = "0.8.15"
3
+ version = "0.8.16"
4
4
  description = "Use Composio to get an array of tools with your Gemini agent."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.9,<4"
@@ -8,7 +8,7 @@ from setuptools import setup
8
8
 
9
9
  setup(
10
10
  name="composio_gemini",
11
- version="0.8.15",
11
+ version="0.8.16",
12
12
  author="Composio",
13
13
  author_email="tech@composio.dev",
14
14
  description="Use Composio to get an array of tools with your Gemini agent.",
@@ -1,62 +0,0 @@
1
- import types
2
- import typing as t
3
- from inspect import Signature
4
-
5
- from composio.client.types import Tool
6
- from composio.core.provider import AgenticProvider
7
- from composio.core.provider.agentic import AgenticProviderExecuteFn
8
- from composio.utils.openapi import function_signature_from_jsonschema
9
-
10
-
11
- class GeminiProvider(AgenticProvider[t.Callable, list[t.Callable]], name="gemini"):
12
- """
13
- Composio toolset for Google AI Python Gemini framework.
14
- """
15
-
16
- __schema_skip_defaults__ = True
17
-
18
- def wrap_tool(
19
- self,
20
- tool: Tool,
21
- execute_tool: AgenticProviderExecuteFn,
22
- ) -> t.Callable:
23
- """Wraps composio tool as Google Genai SDK compatible function calling object."""
24
-
25
- docstring = tool.description
26
- docstring += "\nArgs:"
27
- for _param, _schema in tool.input_parameters["properties"].items(): # type: ignore
28
- docstring += "\n "
29
- docstring += _param + ": " + _schema.get("description", _param.title())
30
-
31
- docstring += "\nReturns:"
32
- docstring += "\n A dictionary containing response from the action"
33
-
34
- def _execute(**kwargs: t.Any) -> t.Dict:
35
- return execute_tool(slug=tool.slug, arguments=kwargs)
36
-
37
- function = types.FunctionType(
38
- code=_execute.__code__,
39
- name=tool.slug,
40
- globals=globals(),
41
- closure=_execute.__closure__,
42
- )
43
- parameters = function_signature_from_jsonschema(
44
- schema=tool.input_parameters,
45
- skip_default=self.skip_default,
46
- )
47
- setattr(function, "__signature__", Signature(parameters=parameters))
48
- setattr(
49
- function,
50
- "__annotations__",
51
- {p.name: p.annotation for p in parameters} | {"return": dict},
52
- )
53
- function.__doc__ = docstring
54
- return function
55
-
56
- def wrap_tools(
57
- self,
58
- tools: t.Sequence[Tool],
59
- execute_tool: AgenticProviderExecuteFn,
60
- ) -> list[t.Callable]:
61
- """Get composio tools wrapped as Google Genai SDK compatible function calling object."""
62
- return [self.wrap_tool(tool, execute_tool) for tool in tools]