camel-ai 0.2.52__py3-none-any.whl → 0.2.54__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.
Potentially problematic release.
This version of camel-ai might be problematic. Click here for more details.
- camel/__init__.py +1 -1
- camel/agents/__init__.py +2 -0
- camel/agents/chat_agent.py +20 -0
- camel/agents/mcp_agent.py +233 -0
- camel/embeddings/__init__.py +2 -0
- camel/embeddings/gemini_embedding.py +115 -0
- camel/embeddings/openai_embedding.py +11 -11
- camel/embeddings/together_embedding.py +7 -7
- camel/models/mistral_model.py +1 -1
- camel/models/model_factory.py +6 -0
- camel/responses/agent_responses.py +1 -4
- camel/societies/role_playing.py +12 -0
- camel/toolkits/__init__.py +2 -0
- camel/toolkits/aci_toolkit.py +456 -0
- camel/toolkits/function_tool.py +5 -3
- camel/toolkits/mcp_toolkit.py +44 -1
- camel/types/__init__.py +2 -0
- camel/types/enums.py +28 -0
- camel/utils/__init__.py +2 -0
- camel/utils/commons.py +63 -0
- camel/utils/mcp.py +63 -0
- {camel_ai-0.2.52.dist-info → camel_ai-0.2.54.dist-info}/METADATA +6 -1
- {camel_ai-0.2.52.dist-info → camel_ai-0.2.54.dist-info}/RECORD +25 -22
- {camel_ai-0.2.52.dist-info → camel_ai-0.2.54.dist-info}/WHEEL +0 -0
- {camel_ai-0.2.52.dist-info → camel_ai-0.2.54.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
|
|
2
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
# you may not use this file except in compliance with the License.
|
|
4
|
+
# You may obtain a copy of the License at
|
|
5
|
+
#
|
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
#
|
|
8
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
# See the License for the specific language governing permissions and
|
|
12
|
+
# limitations under the License.
|
|
13
|
+
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
from typing import TYPE_CHECKING, Dict, List, Optional, Union
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from aci.types.app_configurations import AppConfiguration
|
|
20
|
+
from aci.types.apps import AppBasic, AppDetails
|
|
21
|
+
from aci.types.linked_accounts import LinkedAccount
|
|
22
|
+
|
|
23
|
+
from camel.logger import get_logger
|
|
24
|
+
from camel.toolkits import FunctionTool
|
|
25
|
+
from camel.toolkits.base import BaseToolkit
|
|
26
|
+
from camel.utils import api_keys_required, dependencies_required
|
|
27
|
+
|
|
28
|
+
logger = get_logger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@api_keys_required(
|
|
32
|
+
[
|
|
33
|
+
(None, 'ACI_API_KEY'),
|
|
34
|
+
]
|
|
35
|
+
)
|
|
36
|
+
class ACIToolkit(BaseToolkit):
|
|
37
|
+
r"""A toolkit for interacting with the ACI API."""
|
|
38
|
+
|
|
39
|
+
@dependencies_required('aci')
|
|
40
|
+
def __init__(
|
|
41
|
+
self,
|
|
42
|
+
api_key: Optional[str] = None,
|
|
43
|
+
base_url: Optional[str] = None,
|
|
44
|
+
linked_account_owner_id: Optional[str] = None,
|
|
45
|
+
timeout: Optional[float] = None,
|
|
46
|
+
) -> None:
|
|
47
|
+
r"""Initialize the ACI toolkit.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
api_key (Optional[str]): The API key for authentication.
|
|
51
|
+
(default: :obj: `None`)
|
|
52
|
+
base_url (Optional[str]): The base URL for the ACI API.
|
|
53
|
+
(default: :obj: `None`)
|
|
54
|
+
linked_account_owner_id (Optional[str]): ID of the owner of the
|
|
55
|
+
linked account, e.g., "johndoe"
|
|
56
|
+
(default: :obj: `None`)
|
|
57
|
+
timeout (Optional[float]): Request timeout.
|
|
58
|
+
(default: :obj: `None`)
|
|
59
|
+
"""
|
|
60
|
+
from aci import ACI
|
|
61
|
+
|
|
62
|
+
super().__init__(timeout)
|
|
63
|
+
|
|
64
|
+
self._api_key = api_key or os.getenv("ACI_API_KEY")
|
|
65
|
+
self._base_url = base_url or os.getenv("ACI_BASE_URL")
|
|
66
|
+
self.client = ACI(api_key=self._api_key, base_url=self._base_url)
|
|
67
|
+
self.linked_account_owner_id = linked_account_owner_id
|
|
68
|
+
|
|
69
|
+
def search_tool(
|
|
70
|
+
self,
|
|
71
|
+
intent: Optional[str] = None,
|
|
72
|
+
allowed_app_only: bool = True,
|
|
73
|
+
include_functions: bool = False,
|
|
74
|
+
categories: Optional[List[str]] = None,
|
|
75
|
+
limit: Optional[int] = 10,
|
|
76
|
+
offset: Optional[int] = 0,
|
|
77
|
+
) -> Union[List["AppBasic"], str]:
|
|
78
|
+
r"""Search for apps based on intent.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
intent (Optional[str]): Search results will be sorted by relevance
|
|
82
|
+
to this intent.
|
|
83
|
+
(default: :obj: `None`)
|
|
84
|
+
allowed_app_only (bool): If true, only return apps that
|
|
85
|
+
are allowed by the agent/accessor, identified by the api key.
|
|
86
|
+
(default: :obj: `True`)
|
|
87
|
+
include_functions (bool): If true, include functions
|
|
88
|
+
(name and description) in the search results.
|
|
89
|
+
(default: :obj: `False`)
|
|
90
|
+
categories (Optional[List[str]]): List of categories to filter the
|
|
91
|
+
search results. Defaults to an empty list.
|
|
92
|
+
(default: :obj: `None`)
|
|
93
|
+
limit (Optional[int]): Maximum number of results to return.
|
|
94
|
+
(default: :obj: `10`)
|
|
95
|
+
offset (Optional[int]): Offset for pagination.
|
|
96
|
+
(default: :obj: `0`)
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Optional[List[AppBasic]]: List of matching apps if successful,
|
|
100
|
+
error message otherwise.
|
|
101
|
+
"""
|
|
102
|
+
try:
|
|
103
|
+
apps = self.client.apps.search(
|
|
104
|
+
intent=intent,
|
|
105
|
+
allowed_apps_only=allowed_app_only,
|
|
106
|
+
include_functions=include_functions,
|
|
107
|
+
categories=categories,
|
|
108
|
+
limit=limit,
|
|
109
|
+
offset=offset,
|
|
110
|
+
)
|
|
111
|
+
return apps
|
|
112
|
+
except Exception as e:
|
|
113
|
+
logger.error(f"Error: {e}")
|
|
114
|
+
return str(e)
|
|
115
|
+
|
|
116
|
+
def list_configured_apps(
|
|
117
|
+
self,
|
|
118
|
+
app_names: Optional[List[str]] = None,
|
|
119
|
+
limit: Optional[int] = 10,
|
|
120
|
+
offset: Optional[int] = 0,
|
|
121
|
+
) -> Union[List["AppConfiguration"], str]:
|
|
122
|
+
r"""List all configured apps.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
app_names (Optional[List[str]]): List of app names to filter the
|
|
126
|
+
results. (default: :obj: `None`)
|
|
127
|
+
limit (Optional[int]): Maximum number of results to return.
|
|
128
|
+
(default: :obj: `10`)
|
|
129
|
+
offset (Optional[int]): Offset for pagination. (default: :obj: `0`)
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
Union[List[AppConfiguration], str]: List of configured apps if
|
|
133
|
+
successful, error message otherwise.
|
|
134
|
+
"""
|
|
135
|
+
try:
|
|
136
|
+
apps = self.client.app_configurations.list(
|
|
137
|
+
app_names=app_names, limit=limit, offset=offset
|
|
138
|
+
)
|
|
139
|
+
return apps
|
|
140
|
+
except Exception as e:
|
|
141
|
+
logger.error(f"Error: {e}")
|
|
142
|
+
return str(e)
|
|
143
|
+
|
|
144
|
+
def configure_app(self, app_name: str) -> Union[Dict, str]:
|
|
145
|
+
r"""Configure an app with specified authentication type.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
app_name (str): Name of the app to configure.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
Union[Dict, str]: Configuration result or error message.
|
|
152
|
+
"""
|
|
153
|
+
from aci.types.enums import SecurityScheme
|
|
154
|
+
|
|
155
|
+
try:
|
|
156
|
+
app_details = self.get_app_details(app_name)
|
|
157
|
+
if app_details and app_details.security_schemes[0] == "api_key":
|
|
158
|
+
security_scheme = SecurityScheme.API_KEY
|
|
159
|
+
elif app_details and app_details.security_schemes[0] == "oauth2":
|
|
160
|
+
security_scheme = SecurityScheme.OAUTH2
|
|
161
|
+
else:
|
|
162
|
+
security_scheme = SecurityScheme.NO_AUTH
|
|
163
|
+
configuration = self.client.app_configurations.create(
|
|
164
|
+
app_name=app_name, security_scheme=security_scheme
|
|
165
|
+
)
|
|
166
|
+
return configuration
|
|
167
|
+
except Exception as e:
|
|
168
|
+
logger.error(f"Error: {e}")
|
|
169
|
+
return str(e)
|
|
170
|
+
|
|
171
|
+
def get_app_configuration(
|
|
172
|
+
self, app_name: str
|
|
173
|
+
) -> Union["AppConfiguration", str]:
|
|
174
|
+
r"""Get app configuration by app name.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
app_name (str): Name of the app to get configuration for.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
Union[AppConfiguration, str]: App configuration if successful,
|
|
181
|
+
error message otherwise.
|
|
182
|
+
"""
|
|
183
|
+
try:
|
|
184
|
+
app = self.client.app_configurations.get(app_name=app_name)
|
|
185
|
+
return app
|
|
186
|
+
except Exception as e:
|
|
187
|
+
logger.error(f"Error: {e}")
|
|
188
|
+
return str(e)
|
|
189
|
+
|
|
190
|
+
def delete_app(self, app_name: str) -> Optional[str]:
|
|
191
|
+
r"""Delete an app configuration.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
app_name (str): Name of the app to delete.
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
Optional[str]: None if successful, error message otherwise.
|
|
198
|
+
"""
|
|
199
|
+
try:
|
|
200
|
+
self.client.app_configurations.delete(app_name=app_name)
|
|
201
|
+
return None
|
|
202
|
+
except Exception as e:
|
|
203
|
+
logger.error(f"Error: {e}")
|
|
204
|
+
return str(e)
|
|
205
|
+
|
|
206
|
+
def link_account(
|
|
207
|
+
self,
|
|
208
|
+
app_name: str,
|
|
209
|
+
) -> Union["LinkedAccount", str]:
|
|
210
|
+
r"""Link an account to a configured app.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
app_name (str): Name of the app to link the account to.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
Union[LinkedAccount, str]: LinkedAccount object if successful,
|
|
217
|
+
error message otherwise.
|
|
218
|
+
"""
|
|
219
|
+
from aci.types.enums import SecurityScheme
|
|
220
|
+
|
|
221
|
+
try:
|
|
222
|
+
security_scheme = self.client.app_configurations.get(
|
|
223
|
+
app_name=app_name
|
|
224
|
+
).security_scheme
|
|
225
|
+
|
|
226
|
+
if security_scheme == SecurityScheme.API_KEY:
|
|
227
|
+
return self.client.linked_accounts.link(
|
|
228
|
+
app_name=app_name,
|
|
229
|
+
linked_account_owner_id=self.linked_account_owner_id,
|
|
230
|
+
security_scheme=security_scheme,
|
|
231
|
+
api_key=self._api_key,
|
|
232
|
+
)
|
|
233
|
+
else:
|
|
234
|
+
return self.client.linked_accounts.link(
|
|
235
|
+
app_name=app_name,
|
|
236
|
+
linked_account_owner_id=self.linked_account_owner_id,
|
|
237
|
+
security_scheme=security_scheme,
|
|
238
|
+
)
|
|
239
|
+
except Exception as e:
|
|
240
|
+
logger.error(f"Error linking account: {e!s}")
|
|
241
|
+
return str(e)
|
|
242
|
+
|
|
243
|
+
def get_app_details(self, app_name: str) -> "AppDetails":
|
|
244
|
+
r"""Get details of an app.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
app_name (str): Name of the app to get details for.
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
AppDetails: App details.
|
|
251
|
+
"""
|
|
252
|
+
app = self.client.apps.get(app_name=app_name)
|
|
253
|
+
return app
|
|
254
|
+
|
|
255
|
+
def get_linked_accounts(
|
|
256
|
+
self, app_name: str
|
|
257
|
+
) -> Union[List["LinkedAccount"], str]:
|
|
258
|
+
r"""List all linked accounts for a specific app.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
app_name (str): Name of the app to get linked accounts for.
|
|
262
|
+
|
|
263
|
+
Returns:
|
|
264
|
+
Union[List[LinkedAccount], str]: List of linked accounts if
|
|
265
|
+
successful, error message otherwise.
|
|
266
|
+
"""
|
|
267
|
+
try:
|
|
268
|
+
accounts = self.client.linked_accounts.list(app_name=app_name)
|
|
269
|
+
return accounts
|
|
270
|
+
except Exception as e:
|
|
271
|
+
logger.error(f"Error: {e}")
|
|
272
|
+
return str(e)
|
|
273
|
+
|
|
274
|
+
def enable_linked_account(
|
|
275
|
+
self, linked_account_id: str
|
|
276
|
+
) -> Union["LinkedAccount", str]:
|
|
277
|
+
r"""Enable a linked account.
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
linked_account_id (str): ID of the linked account to enable.
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
Union[LinkedAccount, str]: Linked account if successful, error
|
|
284
|
+
message otherwise.
|
|
285
|
+
"""
|
|
286
|
+
try:
|
|
287
|
+
linked_account = self.client.linked_accounts.enable(
|
|
288
|
+
linked_account_id=linked_account_id
|
|
289
|
+
)
|
|
290
|
+
return linked_account
|
|
291
|
+
except Exception as e:
|
|
292
|
+
logger.error(f"Error: {e}")
|
|
293
|
+
return str(e)
|
|
294
|
+
|
|
295
|
+
def disable_linked_account(
|
|
296
|
+
self, linked_account_id: str
|
|
297
|
+
) -> Union["LinkedAccount", str]:
|
|
298
|
+
r"""Disable a linked account.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
linked_account_id (str): ID of the linked account to disable.
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
Union[LinkedAccount, str]: The updated linked account if
|
|
305
|
+
successful, error message otherwise.
|
|
306
|
+
"""
|
|
307
|
+
try:
|
|
308
|
+
linked_account = self.client.linked_accounts.disable(
|
|
309
|
+
linked_account_id=linked_account_id
|
|
310
|
+
)
|
|
311
|
+
return linked_account
|
|
312
|
+
except Exception as e:
|
|
313
|
+
logger.error(f"Error: {e}")
|
|
314
|
+
return str(e)
|
|
315
|
+
|
|
316
|
+
def delete_linked_account(self, linked_account_id: str) -> str:
|
|
317
|
+
r"""Delete a linked account.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
linked_account_id (str): ID of the linked account to delete.
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
str: Success message if successful, error message otherwise.
|
|
324
|
+
"""
|
|
325
|
+
try:
|
|
326
|
+
self.client.linked_accounts.delete(
|
|
327
|
+
linked_account_id=linked_account_id
|
|
328
|
+
)
|
|
329
|
+
return (
|
|
330
|
+
f"linked_account_id: {linked_account_id} deleted successfully"
|
|
331
|
+
)
|
|
332
|
+
except Exception as e:
|
|
333
|
+
logger.error(f"Error: {e}")
|
|
334
|
+
return str(e)
|
|
335
|
+
|
|
336
|
+
def function_definition(self, func_name: str) -> Dict:
|
|
337
|
+
r"""Get the function definition for an app.
|
|
338
|
+
|
|
339
|
+
Args:
|
|
340
|
+
app_name (str): Name of the app to get function definition for
|
|
341
|
+
|
|
342
|
+
Returns:
|
|
343
|
+
Dict: Function definition dictionary.
|
|
344
|
+
"""
|
|
345
|
+
return self.client.functions.get_definition(func_name)
|
|
346
|
+
|
|
347
|
+
def search_function(
|
|
348
|
+
self,
|
|
349
|
+
app_names: Optional[List[str]] = None,
|
|
350
|
+
intent: Optional[str] = None,
|
|
351
|
+
allowed_apps_only: bool = True,
|
|
352
|
+
limit: Optional[int] = 10,
|
|
353
|
+
offset: Optional[int] = 0,
|
|
354
|
+
) -> List[Dict]:
|
|
355
|
+
r"""Search for functions based on intent.
|
|
356
|
+
|
|
357
|
+
Args:
|
|
358
|
+
app_names (Optional[List[str]]): List of app names to filter the
|
|
359
|
+
search results. (default: :obj: `None`)
|
|
360
|
+
intent (Optional[str]): The search query/intent.
|
|
361
|
+
(default: :obj: `None`)
|
|
362
|
+
allowed_apps_only (bool): If true, only return
|
|
363
|
+
functions from allowed apps. (default: :obj: `True`)
|
|
364
|
+
limit (Optional[int]): Maximum number of results to return.
|
|
365
|
+
(default: :obj: `10`)
|
|
366
|
+
offset (Optional[int]): Offset for pagination.
|
|
367
|
+
(default: :obj: `0`)
|
|
368
|
+
|
|
369
|
+
Returns:
|
|
370
|
+
List[Dict]: List of matching functions
|
|
371
|
+
"""
|
|
372
|
+
return self.client.functions.search(
|
|
373
|
+
app_names=app_names,
|
|
374
|
+
intent=intent,
|
|
375
|
+
allowed_apps_only=allowed_apps_only,
|
|
376
|
+
limit=limit,
|
|
377
|
+
offset=offset,
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
def execute_function(
|
|
381
|
+
self,
|
|
382
|
+
function_name: str,
|
|
383
|
+
function_arguments: Dict,
|
|
384
|
+
linked_account_owner_id: str,
|
|
385
|
+
allowed_apps_only: bool = False,
|
|
386
|
+
) -> Dict:
|
|
387
|
+
r"""Execute a function call.
|
|
388
|
+
|
|
389
|
+
Args:
|
|
390
|
+
function_name (str): Name of the function to execute.
|
|
391
|
+
function_arguments (Dict): Arguments to pass to the function.
|
|
392
|
+
linked_account_owner_id (str): To specify the end-user (account
|
|
393
|
+
owner) on behalf of whom you want to execute functions
|
|
394
|
+
You need to first link corresponding account with the same
|
|
395
|
+
owner id in the ACI dashboard (https://platform.aci.dev).
|
|
396
|
+
allowed_apps_only (bool): If true, only returns functions/apps
|
|
397
|
+
that are allowed to be used by the agent/accessor, identified
|
|
398
|
+
by the api key. (default: :obj: `False`)
|
|
399
|
+
|
|
400
|
+
Returns:
|
|
401
|
+
Dict: Result of the function execution
|
|
402
|
+
"""
|
|
403
|
+
result = self.client.handle_function_call(
|
|
404
|
+
function_name,
|
|
405
|
+
function_arguments,
|
|
406
|
+
linked_account_owner_id,
|
|
407
|
+
allowed_apps_only,
|
|
408
|
+
)
|
|
409
|
+
return result
|
|
410
|
+
|
|
411
|
+
def get_tools(self) -> List[FunctionTool]:
|
|
412
|
+
r"""Get a list of tools (functions) available in the configured apps.
|
|
413
|
+
|
|
414
|
+
Returns:
|
|
415
|
+
List[FunctionTool]: List of FunctionTool objects representing
|
|
416
|
+
available functions
|
|
417
|
+
"""
|
|
418
|
+
_configure_app = [
|
|
419
|
+
app.app_name # type: ignore[union-attr]
|
|
420
|
+
for app in self.list_configured_apps() or []
|
|
421
|
+
]
|
|
422
|
+
_all_function = self.search_function(app_names=_configure_app)
|
|
423
|
+
tools = [
|
|
424
|
+
FunctionTool(self.search_tool),
|
|
425
|
+
FunctionTool(self.list_configured_apps),
|
|
426
|
+
FunctionTool(self.configure_app),
|
|
427
|
+
FunctionTool(self.get_app_configuration),
|
|
428
|
+
FunctionTool(self.delete_app),
|
|
429
|
+
FunctionTool(self.link_account),
|
|
430
|
+
FunctionTool(self.get_app_details),
|
|
431
|
+
FunctionTool(self.get_linked_accounts),
|
|
432
|
+
FunctionTool(self.enable_linked_account),
|
|
433
|
+
FunctionTool(self.disable_linked_account),
|
|
434
|
+
FunctionTool(self.delete_linked_account),
|
|
435
|
+
FunctionTool(self.function_definition),
|
|
436
|
+
FunctionTool(self.search_function),
|
|
437
|
+
]
|
|
438
|
+
|
|
439
|
+
for function in _all_function:
|
|
440
|
+
schema = self.client.functions.get_definition(
|
|
441
|
+
function['function']['name']
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
def dummy_func(*, schema=schema, **kwargs):
|
|
445
|
+
return self.execute_function(
|
|
446
|
+
function_name=schema['function']['name'],
|
|
447
|
+
function_arguments=kwargs,
|
|
448
|
+
linked_account_owner_id=self.linked_account_owner_id,
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
tool = FunctionTool(
|
|
452
|
+
func=dummy_func,
|
|
453
|
+
openai_tool_schema=schema,
|
|
454
|
+
)
|
|
455
|
+
tools.append(tool)
|
|
456
|
+
return tools
|
camel/toolkits/function_tool.py
CHANGED
|
@@ -458,9 +458,11 @@ class FunctionTool:
|
|
|
458
458
|
for param_name in properties.keys():
|
|
459
459
|
param_dict = properties[param_name]
|
|
460
460
|
if "description" not in param_dict:
|
|
461
|
-
warnings.warn(
|
|
462
|
-
|
|
463
|
-
|
|
461
|
+
warnings.warn(
|
|
462
|
+
f"Parameter description is missing "
|
|
463
|
+
f"for {param_dict}. This may affect the "
|
|
464
|
+
f"quality of tool calling."
|
|
465
|
+
)
|
|
464
466
|
|
|
465
467
|
def get_openai_tool_schema(self) -> Dict[str, Any]:
|
|
466
468
|
r"""Gets the OpenAI tool schema for this function.
|
camel/toolkits/mcp_toolkit.py
CHANGED
|
@@ -227,7 +227,7 @@ class MCPClient(BaseToolkit):
|
|
|
227
227
|
|
|
228
228
|
func_params.append(param_name)
|
|
229
229
|
|
|
230
|
-
async def dynamic_function(**kwargs):
|
|
230
|
+
async def dynamic_function(**kwargs) -> str:
|
|
231
231
|
r"""Auto-generated function for MCP Tool interaction.
|
|
232
232
|
|
|
233
233
|
Args:
|
|
@@ -351,6 +351,39 @@ class MCPClient(BaseToolkit):
|
|
|
351
351
|
for mcp_tool in self._mcp_tools
|
|
352
352
|
]
|
|
353
353
|
|
|
354
|
+
def get_text_tools(self) -> str:
|
|
355
|
+
r"""Returns a string containing the descriptions of the tools
|
|
356
|
+
in the toolkit.
|
|
357
|
+
|
|
358
|
+
Returns:
|
|
359
|
+
str: A string containing the descriptions of the tools
|
|
360
|
+
in the toolkit.
|
|
361
|
+
"""
|
|
362
|
+
return "\n".join(
|
|
363
|
+
f"tool_name: {tool.name}\n"
|
|
364
|
+
+ f"description: {tool.description or 'No description'}\n"
|
|
365
|
+
+ f"input Schema: {tool.inputSchema}\n"
|
|
366
|
+
for tool in self._mcp_tools
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
async def call_tool(
|
|
370
|
+
self, tool_name: str, tool_args: Dict[str, Any]
|
|
371
|
+
) -> Any:
|
|
372
|
+
r"""Calls the specified tool with the provided arguments.
|
|
373
|
+
|
|
374
|
+
Args:
|
|
375
|
+
tool_name (str): Name of the tool to call.
|
|
376
|
+
tool_args (Dict[str, Any]): Arguments to pass to the tool
|
|
377
|
+
(default: :obj:`{}`).
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
Any: The result of the tool call.
|
|
381
|
+
"""
|
|
382
|
+
if self._session is None:
|
|
383
|
+
raise RuntimeError("Session is not initialized.")
|
|
384
|
+
|
|
385
|
+
return await self._session.call_tool(tool_name, tool_args)
|
|
386
|
+
|
|
354
387
|
@property
|
|
355
388
|
def session(self) -> Optional["ClientSession"]:
|
|
356
389
|
return self._session
|
|
@@ -548,3 +581,13 @@ class MCPToolkit(BaseToolkit):
|
|
|
548
581
|
for server in self.servers:
|
|
549
582
|
all_tools.extend(server.get_tools())
|
|
550
583
|
return all_tools
|
|
584
|
+
|
|
585
|
+
def get_text_tools(self) -> str:
|
|
586
|
+
r"""Returns a string containing the descriptions of the tools
|
|
587
|
+
in the toolkit.
|
|
588
|
+
|
|
589
|
+
Returns:
|
|
590
|
+
str: A string containing the descriptions of the tools
|
|
591
|
+
in the toolkit.
|
|
592
|
+
"""
|
|
593
|
+
return "\n".join(server.get_text_tools() for server in self.servers)
|
camel/types/__init__.py
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
from .enums import (
|
|
15
15
|
AudioModelType,
|
|
16
16
|
EmbeddingModelType,
|
|
17
|
+
GeminiEmbeddingTaskType,
|
|
17
18
|
HuggingFaceRepoType,
|
|
18
19
|
ModelPlatformType,
|
|
19
20
|
ModelType,
|
|
@@ -75,6 +76,7 @@ __all__ = [
|
|
|
75
76
|
'UnifiedModelType',
|
|
76
77
|
'ParsedChatCompletion',
|
|
77
78
|
'HuggingFaceRepoType',
|
|
79
|
+
'GeminiEmbeddingTaskType',
|
|
78
80
|
'NOT_GIVEN',
|
|
79
81
|
'NotGiven',
|
|
80
82
|
]
|
camel/types/enums.py
CHANGED
|
@@ -1202,6 +1202,8 @@ class EmbeddingModelType(Enum):
|
|
|
1202
1202
|
|
|
1203
1203
|
MISTRAL_EMBED = "mistral-embed"
|
|
1204
1204
|
|
|
1205
|
+
GEMINI_EMBEDDING_EXP = "gemini-embedding-exp-03-07"
|
|
1206
|
+
|
|
1205
1207
|
@property
|
|
1206
1208
|
def is_openai(self) -> bool:
|
|
1207
1209
|
r"""Returns whether this type of models is an OpenAI-released model."""
|
|
@@ -1230,6 +1232,13 @@ class EmbeddingModelType(Enum):
|
|
|
1230
1232
|
EmbeddingModelType.MISTRAL_EMBED,
|
|
1231
1233
|
}
|
|
1232
1234
|
|
|
1235
|
+
@property
|
|
1236
|
+
def is_gemini(self) -> bool:
|
|
1237
|
+
r"""Returns whether this type of models is an Gemini-released model."""
|
|
1238
|
+
return self in {
|
|
1239
|
+
EmbeddingModelType.GEMINI_EMBEDDING_EXP,
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1233
1242
|
@property
|
|
1234
1243
|
def output_dim(self) -> int:
|
|
1235
1244
|
if self in {
|
|
@@ -1253,10 +1262,29 @@ class EmbeddingModelType(Enum):
|
|
|
1253
1262
|
return 3072
|
|
1254
1263
|
elif self is EmbeddingModelType.MISTRAL_EMBED:
|
|
1255
1264
|
return 1024
|
|
1265
|
+
elif self is EmbeddingModelType.GEMINI_EMBEDDING_EXP:
|
|
1266
|
+
return 3072
|
|
1256
1267
|
else:
|
|
1257
1268
|
raise ValueError(f"Unknown model type {self}.")
|
|
1258
1269
|
|
|
1259
1270
|
|
|
1271
|
+
class GeminiEmbeddingTaskType(str, Enum):
|
|
1272
|
+
r"""Task types for Gemini embedding models.
|
|
1273
|
+
|
|
1274
|
+
For more information, please refer to:
|
|
1275
|
+
https://ai.google.dev/gemini-api/docs/embeddings#task-types
|
|
1276
|
+
"""
|
|
1277
|
+
|
|
1278
|
+
SEMANTIC_SIMILARITY = "SEMANTIC_SIMILARITY"
|
|
1279
|
+
CLASSIFICATION = "CLASSIFICATION"
|
|
1280
|
+
CLUSTERING = "CLUSTERING"
|
|
1281
|
+
RETRIEVAL_DOCUMENT = "RETRIEVAL_DOCUMENT"
|
|
1282
|
+
RETRIEVAL_QUERY = "RETRIEVAL_QUERY"
|
|
1283
|
+
QUESTION_ANSWERING = "QUESTION_ANSWERING"
|
|
1284
|
+
FACT_VERIFICATION = "FACT_VERIFICATION"
|
|
1285
|
+
CODE_RETRIEVAL_QUERY = "CODE_RETRIEVAL_QUERY"
|
|
1286
|
+
|
|
1287
|
+
|
|
1260
1288
|
class TaskType(Enum):
|
|
1261
1289
|
AI_SOCIETY = "ai_society"
|
|
1262
1290
|
CODE = "code"
|
camel/utils/__init__.py
CHANGED
|
@@ -17,6 +17,7 @@ from .commons import (
|
|
|
17
17
|
BatchProcessor,
|
|
18
18
|
agentops_decorator,
|
|
19
19
|
api_keys_required,
|
|
20
|
+
browser_toolkit_save_auth_cookie,
|
|
20
21
|
check_server_running,
|
|
21
22
|
create_chunks,
|
|
22
23
|
dependencies_required,
|
|
@@ -92,4 +93,5 @@ __all__ = [
|
|
|
92
93
|
"with_timeout",
|
|
93
94
|
"MCPServer",
|
|
94
95
|
"sanitize_filename",
|
|
96
|
+
"browser_toolkit_save_auth_cookie",
|
|
95
97
|
]
|
camel/utils/commons.py
CHANGED
|
@@ -1040,3 +1040,66 @@ def with_timeout(timeout=None):
|
|
|
1040
1040
|
return decorator(func)
|
|
1041
1041
|
|
|
1042
1042
|
return decorator
|
|
1043
|
+
|
|
1044
|
+
|
|
1045
|
+
def browser_toolkit_save_auth_cookie(
|
|
1046
|
+
cookie_json_path: str, url: str, wait_time: int = 60
|
|
1047
|
+
):
|
|
1048
|
+
r"""Saves authentication cookies and browser storage state to a JSON file.
|
|
1049
|
+
|
|
1050
|
+
This function launches a browser window and navigates to the specified URL,
|
|
1051
|
+
allowing the user to manually authenticate (log in) during a 60-second
|
|
1052
|
+
wait period.After authentication, it saves all cookies, localStorage, and
|
|
1053
|
+
sessionStorage data to the specified JSON file path, which can be used
|
|
1054
|
+
later to maintain authenticated sessions without requiring manual login.
|
|
1055
|
+
|
|
1056
|
+
Args:
|
|
1057
|
+
cookie_json_path (str): Path where the authentication cookies and
|
|
1058
|
+
storage state will be saved as a JSON file. If the file already
|
|
1059
|
+
exists, it will be loaded first and then overwritten with updated
|
|
1060
|
+
state. The function checks if this file exists before attempting
|
|
1061
|
+
to use it.
|
|
1062
|
+
url (str): The URL to navigate to for authentication (e.g., a login
|
|
1063
|
+
page).
|
|
1064
|
+
wait_time (int): The time in seconds to wait for the user to manually
|
|
1065
|
+
authenticate.
|
|
1066
|
+
|
|
1067
|
+
Usage:
|
|
1068
|
+
1. The function opens a browser window and navigates to the specified
|
|
1069
|
+
URL
|
|
1070
|
+
2. User manually logs in during the wait_time wait period
|
|
1071
|
+
3. Browser storage state (including auth cookies) is saved to the
|
|
1072
|
+
specified file
|
|
1073
|
+
4. The saved state can be used in subsequent browser sessions to
|
|
1074
|
+
maintain authentication
|
|
1075
|
+
|
|
1076
|
+
Note:
|
|
1077
|
+
The wait_time sleep is intentional to give the user enough time to
|
|
1078
|
+
complete the manual authentication process before the storage state
|
|
1079
|
+
is captured.
|
|
1080
|
+
"""
|
|
1081
|
+
from playwright.sync_api import sync_playwright
|
|
1082
|
+
|
|
1083
|
+
playwright = sync_playwright().start()
|
|
1084
|
+
|
|
1085
|
+
# Launch visible browser window using Chromium
|
|
1086
|
+
browser = playwright.chromium.launch(headless=False, channel="chromium")
|
|
1087
|
+
|
|
1088
|
+
# Check if cookie file exists before using it
|
|
1089
|
+
storage_state = (
|
|
1090
|
+
cookie_json_path if os.path.exists(cookie_json_path) else None
|
|
1091
|
+
)
|
|
1092
|
+
|
|
1093
|
+
# Create browser context with proper typing
|
|
1094
|
+
context = browser.new_context(
|
|
1095
|
+
accept_downloads=True, storage_state=storage_state
|
|
1096
|
+
)
|
|
1097
|
+
page = context.new_page()
|
|
1098
|
+
page.goto(url) # Navigate to the authentication URL
|
|
1099
|
+
# Wait for page to fully load
|
|
1100
|
+
page.wait_for_load_state("load", timeout=1000)
|
|
1101
|
+
time.sleep(wait_time) # Wait 60 seconds for user to manually authenticate
|
|
1102
|
+
# Save browser storage state (cookies, localStorage, etc.) to JSON file
|
|
1103
|
+
context.storage_state(path=cookie_json_path)
|
|
1104
|
+
|
|
1105
|
+
browser.close() # Close the browser when finished
|