composio-gemini 0.8.14__py3-none-any.whl → 0.8.16__py3-none-any.whl
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.
- composio_gemini/provider.py +247 -38
- {composio_gemini-0.8.14.dist-info → composio_gemini-0.8.16.dist-info}/METADATA +1 -1
- composio_gemini-0.8.16.dist-info/RECORD +7 -0
- composio_gemini-0.8.14.dist-info/RECORD +0 -7
- {composio_gemini-0.8.14.dist-info → composio_gemini-0.8.16.dist-info}/WHEEL +0 -0
- {composio_gemini-0.8.14.dist-info → composio_gemini-0.8.16.dist-info}/top_level.txt +0 -0
composio_gemini/provider.py
CHANGED
@@ -1,62 +1,271 @@
|
|
1
|
-
|
1
|
+
"""Gemini provider for Composio SDK."""
|
2
|
+
|
2
3
|
import typing as t
|
3
|
-
from inspect import Signature
|
4
4
|
|
5
5
|
from composio.client.types import Tool
|
6
6
|
from composio.core.provider import AgenticProvider
|
7
7
|
from composio.core.provider.agentic import AgenticProviderExecuteFn
|
8
|
-
from composio.utils.openapi import function_signature_from_jsonschema
|
9
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
|
10
49
|
|
11
|
-
|
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"):
|
12
83
|
"""
|
13
84
|
Composio toolset for Google AI Python Gemini framework.
|
85
|
+
Supports automatic function calling with native Gemini tools.
|
14
86
|
"""
|
15
87
|
|
16
88
|
__schema_skip_defaults__ = True
|
17
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
|
+
|
18
193
|
def wrap_tool(
|
19
194
|
self,
|
20
195
|
tool: Tool,
|
21
196
|
execute_tool: AgenticProviderExecuteFn,
|
22
|
-
) ->
|
23
|
-
"""
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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},
|
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,
|
52
207
|
)
|
53
|
-
function.__doc__ = docstring
|
54
|
-
return function
|
55
208
|
|
56
209
|
def wrap_tools(
|
57
210
|
self,
|
58
211
|
tools: t.Sequence[Tool],
|
59
212
|
execute_tool: AgenticProviderExecuteFn,
|
60
|
-
) -> list[
|
61
|
-
"""
|
213
|
+
) -> list[GeminiTool]:
|
214
|
+
"""Wrap multiple tools for Gemini compatibility."""
|
62
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
|
@@ -0,0 +1,7 @@
|
|
1
|
+
composio_gemini/__init__.py,sha256=1E2vHF2vpRqgO9hEHJlgaePVhmlLSmNB6irwy2wh8hM,68
|
2
|
+
composio_gemini/provider.py,sha256=mMnrGDRIsiRSJLHJjDFogp5q8GROxlrqektlwJxpFkk,9745
|
3
|
+
composio_gemini/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
|
+
composio_gemini-0.8.16.dist-info/METADATA,sha256=uaa04kdE46q8m3rsqbR2apZFs6Qyho-UsGOZ3e8BwoU,2610
|
5
|
+
composio_gemini-0.8.16.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
6
|
+
composio_gemini-0.8.16.dist-info/top_level.txt,sha256=OXlT1cwZ_H8zLnD-WjZOokKdqRgsq5cqhx0uCnxCikk,16
|
7
|
+
composio_gemini-0.8.16.dist-info/RECORD,,
|
@@ -1,7 +0,0 @@
|
|
1
|
-
composio_gemini/__init__.py,sha256=1E2vHF2vpRqgO9hEHJlgaePVhmlLSmNB6irwy2wh8hM,68
|
2
|
-
composio_gemini/provider.py,sha256=4nXAUEICCVQHykxrjeSZxFwMHvI7Q9V3_tM3Zgmh-QA,2154
|
3
|
-
composio_gemini/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
|
-
composio_gemini-0.8.14.dist-info/METADATA,sha256=GkDIYynejo5kHOE-aFpT922UjKsO4YOqaYtvLNpRrvw,2610
|
5
|
-
composio_gemini-0.8.14.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
6
|
-
composio_gemini-0.8.14.dist-info/top_level.txt,sha256=OXlT1cwZ_H8zLnD-WjZOokKdqRgsq5cqhx0uCnxCikk,16
|
7
|
-
composio_gemini-0.8.14.dist-info/RECORD,,
|
File without changes
|
File without changes
|