camel-ai 0.2.51__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/runtime/__init__.py +2 -0
- camel/runtime/daytona_runtime.py +248 -0
- camel/societies/role_playing.py +12 -0
- camel/toolkits/__init__.py +4 -0
- camel/toolkits/aci_toolkit.py +456 -0
- camel/toolkits/function_tool.py +5 -3
- camel/toolkits/klavis_toolkit.py +228 -0
- camel/toolkits/mcp_toolkit.py +63 -4
- camel/types/__init__.py +2 -0
- camel/types/enums.py +28 -0
- camel/utils/commons.py +2 -0
- camel/utils/mcp.py +63 -0
- {camel_ai-0.2.51.dist-info → camel_ai-0.2.53.dist-info}/METADATA +8 -1
- {camel_ai-0.2.51.dist-info → camel_ai-0.2.53.dist-info}/RECORD +26 -21
- {camel_ai-0.2.51.dist-info → camel_ai-0.2.53.dist-info}/WHEEL +0 -0
- {camel_ai-0.2.51.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.
|
|
@@ -0,0 +1,228 @@
|
|
|
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 Any, Dict, List, Optional
|
|
17
|
+
|
|
18
|
+
import requests
|
|
19
|
+
|
|
20
|
+
from camel.logger import get_logger
|
|
21
|
+
from camel.toolkits.base import BaseToolkit
|
|
22
|
+
from camel.toolkits.function_tool import FunctionTool
|
|
23
|
+
from camel.utils import MCPServer, api_keys_required, dependencies_required
|
|
24
|
+
|
|
25
|
+
logger = get_logger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@MCPServer()
|
|
29
|
+
class KlavisToolkit(BaseToolkit):
|
|
30
|
+
r"""A class representing a toolkit for interacting with Klavis API.
|
|
31
|
+
|
|
32
|
+
This class provides methods for interacting with Klavis MCP server
|
|
33
|
+
instances, retrieving server information, managing tools, and handling
|
|
34
|
+
authentication.
|
|
35
|
+
|
|
36
|
+
Attributes:
|
|
37
|
+
api_key (str): The API key for authenticating with Klavis API.
|
|
38
|
+
base_url (str): The base URL for Klavis API endpoints.
|
|
39
|
+
timeout (Optional[float]): The timeout value for API requests
|
|
40
|
+
in seconds. If None, no timeout is applied.
|
|
41
|
+
(default: :obj:`None`)
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
@dependencies_required("requests")
|
|
45
|
+
@api_keys_required(
|
|
46
|
+
[
|
|
47
|
+
(None, "KLAVIS_API_KEY"),
|
|
48
|
+
]
|
|
49
|
+
)
|
|
50
|
+
def __init__(self, timeout: Optional[float] = None) -> None:
|
|
51
|
+
r"""Initialize the KlavisToolkit with API client. The API key is
|
|
52
|
+
retrieved from environment variables.
|
|
53
|
+
"""
|
|
54
|
+
super().__init__(timeout=timeout)
|
|
55
|
+
self.api_key = os.environ.get("KLAVIS_API_KEY")
|
|
56
|
+
self.base_url = "https://api.klavis.ai"
|
|
57
|
+
|
|
58
|
+
def _request(
|
|
59
|
+
self,
|
|
60
|
+
method: str,
|
|
61
|
+
endpoint: str,
|
|
62
|
+
payload: Optional[Dict[str, Any]] = None,
|
|
63
|
+
additional_headers: Optional[Dict[str, str]] = None,
|
|
64
|
+
) -> Dict[str, Any]:
|
|
65
|
+
r"""Make an HTTP request to the Klavis API.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
method (str): HTTP method (e.g., 'GET', 'POST', 'DELETE').
|
|
69
|
+
endpoint (str): API endpoint path.
|
|
70
|
+
payload (Optional[Dict[str, Any]]): JSON payload for POST
|
|
71
|
+
requests.
|
|
72
|
+
additional_headers (Optional[Dict[str, str]]): Additional
|
|
73
|
+
headers to include in the request.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Dict[str, Any]: The JSON response from the API or an error
|
|
77
|
+
dict.
|
|
78
|
+
"""
|
|
79
|
+
url = f"{self.base_url}{endpoint}"
|
|
80
|
+
headers = {
|
|
81
|
+
'accept': 'application/json',
|
|
82
|
+
'Authorization': f'Bearer {self.api_key}',
|
|
83
|
+
}
|
|
84
|
+
if additional_headers:
|
|
85
|
+
headers.update(additional_headers)
|
|
86
|
+
|
|
87
|
+
logger.debug(
|
|
88
|
+
f"Making {method} request to {url} with payload: {payload}"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
response = requests.request(
|
|
93
|
+
method,
|
|
94
|
+
url,
|
|
95
|
+
headers=headers,
|
|
96
|
+
json=payload,
|
|
97
|
+
timeout=self.timeout,
|
|
98
|
+
)
|
|
99
|
+
response.raise_for_status()
|
|
100
|
+
return response.json()
|
|
101
|
+
except requests.exceptions.RequestException as e:
|
|
102
|
+
logger.error(f"Request failed for {method} {endpoint}: {e!s}")
|
|
103
|
+
return {"error": f"Request failed: {e!s}"}
|
|
104
|
+
except ValueError:
|
|
105
|
+
logger.error(f"Response for {method} {endpoint} is not valid JSON")
|
|
106
|
+
return {"error": "Response is not valid JSON"}
|
|
107
|
+
|
|
108
|
+
def create_server_instance(
|
|
109
|
+
self, server_name: str, user_id: str, platform_name: str
|
|
110
|
+
) -> Dict[str, Any]:
|
|
111
|
+
r"""Create a Server-Sent Events (SSE) URL for a specified MCP server.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
server_name (str): The name of the target MCP server.
|
|
115
|
+
user_id (str): The ID for the user requesting the server URL.
|
|
116
|
+
platform_name (str): The name of the platform associated
|
|
117
|
+
with the user.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Dict[str, Any]: Response containing the server instance details.
|
|
121
|
+
"""
|
|
122
|
+
endpoint = "/mcp-server/instance/create"
|
|
123
|
+
payload = {
|
|
124
|
+
"serverName": server_name,
|
|
125
|
+
"userId": user_id,
|
|
126
|
+
"platformName": platform_name,
|
|
127
|
+
}
|
|
128
|
+
headers = {'Content-Type': 'application/json'}
|
|
129
|
+
return self._request(
|
|
130
|
+
'POST', endpoint, payload=payload, additional_headers=headers
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
def get_server_instance(self, instance_id: str) -> Dict[str, Any]:
|
|
134
|
+
r"""Get details of a specific server connection instance.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
instance_id (str): The ID of the connection instance whose status
|
|
138
|
+
is being checked.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
Dict[str, Any]: Details about the server instance.
|
|
142
|
+
"""
|
|
143
|
+
endpoint = f"/mcp-server/instance/get/{instance_id}"
|
|
144
|
+
return self._request('GET', endpoint)
|
|
145
|
+
|
|
146
|
+
def delete_auth_data(self, instance_id: str) -> Dict[str, Any]:
|
|
147
|
+
r"""Delete authentication metadata for a specific server
|
|
148
|
+
connection instance.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
instance_id (str): The ID of the connection instance to
|
|
152
|
+
delete auth for.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Dict[str, Any]: Status response for the operation.
|
|
156
|
+
"""
|
|
157
|
+
endpoint = f"/mcp-server/instance/delete-auth/{instance_id}"
|
|
158
|
+
return self._request('DELETE', endpoint)
|
|
159
|
+
|
|
160
|
+
def delete_server_instance(self, instance_id: str) -> Dict[str, Any]:
|
|
161
|
+
r"""Completely removes a server connection instance.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
instance_id (str): The ID of the connection instance to delete.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Dict[str, Any]: Status response for the operation.
|
|
168
|
+
"""
|
|
169
|
+
endpoint = f"/mcp-server/instance/delete/{instance_id}"
|
|
170
|
+
return self._request('DELETE', endpoint)
|
|
171
|
+
|
|
172
|
+
def get_server_tools(self, server_name: str) -> Dict[str, Any]:
|
|
173
|
+
r"""Get list of tool names for a specific MCP server.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
server_name (str): The name of the target MCP server.
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
Dict[str, Any]: List of tools available for the specified server.
|
|
180
|
+
"""
|
|
181
|
+
endpoint = f"/mcp-server/tools/{server_name}"
|
|
182
|
+
return self._request('GET', endpoint)
|
|
183
|
+
|
|
184
|
+
def get_all_servers(self) -> Dict[str, Any]:
|
|
185
|
+
r"""Get all MCP servers with their basic information.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
Dict[str, Any]: Information about all available MCP servers.
|
|
189
|
+
"""
|
|
190
|
+
endpoint = "/mcp-server/servers"
|
|
191
|
+
return self._request('GET', endpoint)
|
|
192
|
+
|
|
193
|
+
def set_auth_token(
|
|
194
|
+
self, instance_id: str, auth_token: str
|
|
195
|
+
) -> Dict[str, Any]:
|
|
196
|
+
r"""Sets an authentication token for a specific instance.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
instance_id (str): The ID for the connection instance.
|
|
200
|
+
auth_token (str): The authentication token to save.
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
Dict[str, Any]: Status response for the operation.
|
|
204
|
+
"""
|
|
205
|
+
endpoint = "/mcp-server/instance/set-auth-token"
|
|
206
|
+
payload = {"instanceId": instance_id, "authToken": auth_token}
|
|
207
|
+
headers = {'Content-Type': 'application/json'}
|
|
208
|
+
return self._request(
|
|
209
|
+
'POST', endpoint, payload=payload, additional_headers=headers
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
def get_tools(self) -> List[FunctionTool]:
|
|
213
|
+
r"""Returns a list of FunctionTool objects representing the functions
|
|
214
|
+
in the toolkit.
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
List[FunctionTool]: A list of FunctionTool objects representing
|
|
218
|
+
the functions in the toolkit.
|
|
219
|
+
"""
|
|
220
|
+
return [
|
|
221
|
+
FunctionTool(self.create_server_instance),
|
|
222
|
+
FunctionTool(self.get_server_instance),
|
|
223
|
+
FunctionTool(self.delete_auth_data),
|
|
224
|
+
FunctionTool(self.delete_server_instance),
|
|
225
|
+
FunctionTool(self.get_server_tools),
|
|
226
|
+
FunctionTool(self.get_all_servers),
|
|
227
|
+
FunctionTool(self.set_auth_token),
|
|
228
|
+
]
|