camel-ai 0.2.52__py3-none-any.whl → 0.2.53__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/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/mcp.py +63 -0
- {camel_ai-0.2.52.dist-info → camel_ai-0.2.53.dist-info}/METADATA +6 -1
- {camel_ai-0.2.52.dist-info → camel_ai-0.2.53.dist-info}/RECORD +22 -19
- {camel_ai-0.2.52.dist-info → camel_ai-0.2.53.dist-info}/WHEEL +0 -0
- {camel_ai-0.2.52.dist-info → camel_ai-0.2.53.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/mcp.py
CHANGED
|
@@ -17,6 +17,42 @@ from typing import Any, Callable, Optional
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class MCPServer:
|
|
20
|
+
r"""Decorator class for registering functions of a class as tools in an MCP
|
|
21
|
+
(Model Context Protocol) server.
|
|
22
|
+
|
|
23
|
+
This class is typically used to wrap a toolkit or service class and
|
|
24
|
+
automatically register specified methods (or methods derived from
|
|
25
|
+
`BaseToolkit`) with a FastMCP server.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
function_names (Optional[list[str]]): A list of method names to expose
|
|
29
|
+
via the MCP server. If not provided and the class is a subclass of
|
|
30
|
+
`BaseToolkit`, method names will be inferred from the tools
|
|
31
|
+
returned by `get_tools()`.
|
|
32
|
+
server_name (Optional[str]): A name for the MCP server. If not
|
|
33
|
+
provided, the class name of the decorated object is used.
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
```
|
|
37
|
+
@MCPServer(function_names=["run", "status"])
|
|
38
|
+
class MyTool:
|
|
39
|
+
def run(self): ...
|
|
40
|
+
def status(self): ...
|
|
41
|
+
```
|
|
42
|
+
Or, with a class inheriting from BaseToolkit (no need to specify
|
|
43
|
+
`function_names`):
|
|
44
|
+
```
|
|
45
|
+
@MCPServer()
|
|
46
|
+
class MyToolkit(BaseToolkit):
|
|
47
|
+
...
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Raises:
|
|
51
|
+
ValueError: If no function names are provided and the class does not
|
|
52
|
+
inherit from BaseToolkit, or if any specified method is not found
|
|
53
|
+
or not callable.
|
|
54
|
+
"""
|
|
55
|
+
|
|
20
56
|
def __init__(
|
|
21
57
|
self,
|
|
22
58
|
function_names: Optional[list[str]] = None,
|
|
@@ -26,6 +62,19 @@ class MCPServer:
|
|
|
26
62
|
self.server_name = server_name
|
|
27
63
|
|
|
28
64
|
def make_wrapper(self, func: Callable[..., Any]) -> Callable[..., Any]:
|
|
65
|
+
r"""Wraps a function (sync or async) to preserve its signature and
|
|
66
|
+
metadata.
|
|
67
|
+
|
|
68
|
+
This is used to ensure the MCP server can correctly call and introspect
|
|
69
|
+
the method.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
func (Callable[..., Any]): The function to wrap.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
Callable[..., Any]: The wrapped function, with preserved signature
|
|
76
|
+
and async support.
|
|
77
|
+
"""
|
|
29
78
|
if inspect.iscoroutinefunction(func):
|
|
30
79
|
|
|
31
80
|
@functools.wraps(func)
|
|
@@ -41,6 +90,20 @@ class MCPServer:
|
|
|
41
90
|
return wrapper
|
|
42
91
|
|
|
43
92
|
def __call__(self, cls):
|
|
93
|
+
r"""Decorates a class by injecting an MCP server instance and
|
|
94
|
+
registering specified methods.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
cls (type): The class being decorated.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
type: The modified class with MCP integration.
|
|
101
|
+
|
|
102
|
+
Raises:
|
|
103
|
+
ValueError: If function names are missing and the class is not a
|
|
104
|
+
`BaseToolkit` subclass,
|
|
105
|
+
or if a specified method cannot be found or is not callable.
|
|
106
|
+
"""
|
|
44
107
|
from mcp.server.fastmcp import FastMCP
|
|
45
108
|
|
|
46
109
|
from camel.toolkits.base import BaseToolkit
|