camel-ai 0.2.58__py3-none-any.whl → 0.2.60__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/chat_agent.py +126 -9
- camel/agents/critic_agent.py +73 -8
- camel/benchmarks/__init__.py +2 -0
- camel/benchmarks/browsecomp.py +854 -0
- camel/configs/cohere_config.py +1 -1
- camel/configs/mistral_config.py +1 -1
- camel/configs/openai_config.py +3 -0
- camel/configs/reka_config.py +1 -1
- camel/configs/samba_config.py +2 -2
- camel/datagen/cot_datagen.py +29 -34
- camel/embeddings/jina_embedding.py +8 -1
- camel/embeddings/sentence_transformers_embeddings.py +2 -2
- camel/embeddings/vlm_embedding.py +9 -2
- camel/human.py +14 -0
- camel/memories/records.py +3 -0
- camel/messages/base.py +15 -3
- camel/models/azure_openai_model.py +1 -0
- camel/models/model_factory.py +2 -2
- camel/retrievers/bm25_retriever.py +1 -2
- camel/retrievers/hybrid_retrival.py +2 -2
- camel/societies/role_playing.py +50 -0
- camel/societies/workforce/role_playing_worker.py +17 -8
- camel/societies/workforce/workforce.py +70 -14
- camel/storages/vectordb_storages/oceanbase.py +1 -2
- camel/toolkits/async_browser_toolkit.py +5 -1
- camel/toolkits/base.py +4 -2
- camel/toolkits/browser_toolkit.py +6 -3
- camel/toolkits/dalle_toolkit.py +4 -0
- camel/toolkits/excel_toolkit.py +11 -3
- camel/toolkits/github_toolkit.py +43 -25
- camel/toolkits/image_analysis_toolkit.py +3 -0
- camel/toolkits/jina_reranker_toolkit.py +194 -77
- camel/toolkits/mcp_toolkit.py +60 -16
- camel/toolkits/page_script.js +40 -28
- camel/toolkits/twitter_toolkit.py +6 -1
- camel/toolkits/video_analysis_toolkit.py +3 -0
- camel/toolkits/video_download_toolkit.py +3 -0
- camel/toolkits/wolfram_alpha_toolkit.py +46 -22
- camel/types/enums.py +14 -5
- {camel_ai-0.2.58.dist-info → camel_ai-0.2.60.dist-info}/METADATA +7 -9
- {camel_ai-0.2.58.dist-info → camel_ai-0.2.60.dist-info}/RECORD +44 -43
- {camel_ai-0.2.58.dist-info → camel_ai-0.2.60.dist-info}/WHEEL +0 -0
- {camel_ai-0.2.58.dist-info → camel_ai-0.2.60.dist-info}/licenses/LICENSE +0 -0
|
@@ -11,7 +11,11 @@
|
|
|
11
11
|
# See the License for the specific language governing permissions and
|
|
12
12
|
# limitations under the License.
|
|
13
13
|
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
|
|
14
|
-
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
from typing import Any, Dict, List, Optional
|
|
17
|
+
|
|
18
|
+
import requests
|
|
15
19
|
|
|
16
20
|
from camel.toolkits import FunctionTool
|
|
17
21
|
from camel.toolkits.base import BaseToolkit
|
|
@@ -30,7 +34,9 @@ class JinaRerankerToolkit(BaseToolkit):
|
|
|
30
34
|
def __init__(
|
|
31
35
|
self,
|
|
32
36
|
timeout: Optional[float] = None,
|
|
37
|
+
model_name: Optional[str] = "jinaai/jina-reranker-m0",
|
|
33
38
|
device: Optional[str] = None,
|
|
39
|
+
use_api: bool = True,
|
|
34
40
|
) -> None:
|
|
35
41
|
r"""Initializes a new instance of the JinaRerankerToolkit class.
|
|
36
42
|
|
|
@@ -38,31 +44,57 @@ class JinaRerankerToolkit(BaseToolkit):
|
|
|
38
44
|
timeout (Optional[float]): The timeout value for API requests
|
|
39
45
|
in seconds. If None, no timeout is applied.
|
|
40
46
|
(default: :obj:`None`)
|
|
47
|
+
model_name (Optional[str]): The reranker model name. If None,
|
|
48
|
+
will use the default model.
|
|
49
|
+
(default: :obj:`None`)
|
|
41
50
|
device (Optional[str]): Device to load the model on. If None,
|
|
42
51
|
will use CUDA if available, otherwise CPU.
|
|
52
|
+
Only effective when use_api=False.
|
|
43
53
|
(default: :obj:`None`)
|
|
54
|
+
use_api (bool): A flag to switch between local model and API.
|
|
55
|
+
(default: :obj:`True`)
|
|
44
56
|
"""
|
|
45
|
-
import torch
|
|
46
|
-
from transformers import AutoModel
|
|
47
57
|
|
|
48
58
|
super().__init__(timeout=timeout)
|
|
49
59
|
|
|
50
|
-
self.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
self.use_api = use_api
|
|
61
|
+
self.model_name = model_name
|
|
62
|
+
|
|
63
|
+
if self.use_api:
|
|
64
|
+
self.model = None
|
|
65
|
+
self._api_key = os.environ.get("JINA_API_KEY", "None")
|
|
66
|
+
if self._api_key == "None":
|
|
67
|
+
raise ValueError(
|
|
68
|
+
"Missing or empty required API keys in "
|
|
69
|
+
"environment variables\n"
|
|
70
|
+
"You can obtain the API key from https://jina.ai/reranker/"
|
|
71
|
+
)
|
|
72
|
+
self.url = 'https://api.jina.ai/v1/rerank'
|
|
73
|
+
self.headers = {
|
|
74
|
+
'Content-Type': 'application/json',
|
|
75
|
+
'Accept': 'application/json',
|
|
76
|
+
'Authorization': f'Bearer {self._api_key}',
|
|
77
|
+
}
|
|
78
|
+
else:
|
|
79
|
+
import torch
|
|
80
|
+
from transformers import AutoModel
|
|
81
|
+
|
|
82
|
+
self.model = AutoModel.from_pretrained(
|
|
83
|
+
self.model_name,
|
|
84
|
+
torch_dtype="auto",
|
|
85
|
+
trust_remote_code=True,
|
|
86
|
+
)
|
|
87
|
+
self.device = (
|
|
88
|
+
device
|
|
89
|
+
if device is not None
|
|
90
|
+
else ("cuda" if torch.cuda.is_available() else "cpu")
|
|
91
|
+
)
|
|
92
|
+
self.model.to(self.device)
|
|
93
|
+
self.model.eval()
|
|
62
94
|
|
|
63
95
|
def _sort_documents(
|
|
64
96
|
self, documents: List[str], scores: List[float]
|
|
65
|
-
) -> List[
|
|
97
|
+
) -> List[Dict[str, object]]:
|
|
66
98
|
r"""Sort documents by their scores in descending order.
|
|
67
99
|
|
|
68
100
|
Args:
|
|
@@ -70,7 +102,7 @@ class JinaRerankerToolkit(BaseToolkit):
|
|
|
70
102
|
scores (List[float]): Corresponding scores for each document.
|
|
71
103
|
|
|
72
104
|
Returns:
|
|
73
|
-
List[
|
|
105
|
+
List[Dict[str, object]]: Sorted list of (document, score) pairs.
|
|
74
106
|
|
|
75
107
|
Raises:
|
|
76
108
|
ValueError: If documents and scores have different lengths.
|
|
@@ -80,14 +112,45 @@ class JinaRerankerToolkit(BaseToolkit):
|
|
|
80
112
|
doc_score_pairs = list(zip(documents, scores))
|
|
81
113
|
doc_score_pairs.sort(key=lambda x: x[1], reverse=True)
|
|
82
114
|
|
|
83
|
-
|
|
115
|
+
results = [
|
|
116
|
+
{'document': {'text': doc}, 'relevance_score': score}
|
|
117
|
+
for doc, score in doc_score_pairs
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
return results
|
|
121
|
+
|
|
122
|
+
def _call_jina_api(self, data: Dict[str, Any]) -> List[Dict[str, object]]:
|
|
123
|
+
r"""Makes a call to the JINA API for reranking.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
data (Dict[str]): The data to be passed into the api body.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
List[Dict[str, object]]: A list of dictionary containing
|
|
130
|
+
the reranked documents and their relevance scores.
|
|
131
|
+
"""
|
|
132
|
+
try:
|
|
133
|
+
response = requests.post(
|
|
134
|
+
self.url,
|
|
135
|
+
headers=self.headers,
|
|
136
|
+
data=json.dumps(data),
|
|
137
|
+
timeout=self.timeout,
|
|
138
|
+
)
|
|
139
|
+
response.raise_for_status()
|
|
140
|
+
results = [
|
|
141
|
+
{key: value for key, value in _res.items() if key != 'index'}
|
|
142
|
+
for _res in response.json()['results']
|
|
143
|
+
]
|
|
144
|
+
return results
|
|
145
|
+
except requests.exceptions.RequestException as e:
|
|
146
|
+
raise RuntimeError(f"Failed to get response from Jina AI: {e}")
|
|
84
147
|
|
|
85
148
|
def rerank_text_documents(
|
|
86
149
|
self,
|
|
87
150
|
query: str,
|
|
88
151
|
documents: List[str],
|
|
89
152
|
max_length: int = 1024,
|
|
90
|
-
) -> List[
|
|
153
|
+
) -> List[Dict[str, object]]:
|
|
91
154
|
r"""Reranks text documents based on their relevance to a text query.
|
|
92
155
|
|
|
93
156
|
Args:
|
|
@@ -97,21 +160,34 @@ class JinaRerankerToolkit(BaseToolkit):
|
|
|
97
160
|
(default: :obj:`1024`)
|
|
98
161
|
|
|
99
162
|
Returns:
|
|
100
|
-
List[
|
|
163
|
+
List[Dict[str, object]]: A list of dictionary containing
|
|
101
164
|
the reranked documents and their relevance scores.
|
|
102
165
|
"""
|
|
103
|
-
import torch
|
|
104
166
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
)
|
|
167
|
+
data = {
|
|
168
|
+
'model': self.model_name,
|
|
169
|
+
'query': query,
|
|
170
|
+
'top_n': len(documents),
|
|
171
|
+
'documents': documents,
|
|
172
|
+
'return_documents': True,
|
|
173
|
+
}
|
|
109
174
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
175
|
+
if self.use_api:
|
|
176
|
+
return self._call_jina_api(data)
|
|
177
|
+
|
|
178
|
+
else:
|
|
179
|
+
import torch
|
|
180
|
+
|
|
181
|
+
if self.model is None:
|
|
182
|
+
raise ValueError(
|
|
183
|
+
"Model has not been initialized or failed to initialize."
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
with torch.inference_mode():
|
|
187
|
+
text_pairs = [[query, doc] for doc in documents]
|
|
188
|
+
scores = self.model.compute_score(
|
|
189
|
+
text_pairs, max_length=max_length, doc_type="text"
|
|
190
|
+
)
|
|
115
191
|
|
|
116
192
|
return self._sort_documents(documents, scores)
|
|
117
193
|
|
|
@@ -120,7 +196,7 @@ class JinaRerankerToolkit(BaseToolkit):
|
|
|
120
196
|
query: str,
|
|
121
197
|
documents: List[str],
|
|
122
198
|
max_length: int = 2048,
|
|
123
|
-
) -> List[
|
|
199
|
+
) -> List[Dict[str, object]]:
|
|
124
200
|
r"""Reranks image documents based on their relevance to a text query.
|
|
125
201
|
|
|
126
202
|
Args:
|
|
@@ -130,21 +206,33 @@ class JinaRerankerToolkit(BaseToolkit):
|
|
|
130
206
|
(default: :obj:`2048`)
|
|
131
207
|
|
|
132
208
|
Returns:
|
|
133
|
-
List[
|
|
209
|
+
List[Dict[str, object]]: A list of dictionary containing
|
|
134
210
|
the reranked image URLs/paths and their relevance scores.
|
|
135
211
|
"""
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
212
|
+
data = {
|
|
213
|
+
'model': self.model_name,
|
|
214
|
+
'query': query,
|
|
215
|
+
'top_n': len(documents),
|
|
216
|
+
'documents': documents,
|
|
217
|
+
'return_documents': True,
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if self.use_api:
|
|
221
|
+
return self._call_jina_api(data)
|
|
222
|
+
|
|
223
|
+
else:
|
|
224
|
+
import torch
|
|
225
|
+
|
|
226
|
+
if self.model is None:
|
|
227
|
+
raise ValueError(
|
|
228
|
+
"Model has not been initialized or failed to initialize."
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
with torch.inference_mode():
|
|
232
|
+
image_pairs = [[query, doc] for doc in documents]
|
|
233
|
+
scores = self.model.compute_score(
|
|
234
|
+
image_pairs, max_length=max_length, doc_type="image"
|
|
235
|
+
)
|
|
148
236
|
|
|
149
237
|
return self._sort_documents(documents, scores)
|
|
150
238
|
|
|
@@ -153,7 +241,7 @@ class JinaRerankerToolkit(BaseToolkit):
|
|
|
153
241
|
image_query: str,
|
|
154
242
|
documents: List[str],
|
|
155
243
|
max_length: int = 2048,
|
|
156
|
-
) -> List[
|
|
244
|
+
) -> List[Dict[str, object]]:
|
|
157
245
|
r"""Reranks text documents based on their relevance to an image query.
|
|
158
246
|
|
|
159
247
|
Args:
|
|
@@ -163,30 +251,45 @@ class JinaRerankerToolkit(BaseToolkit):
|
|
|
163
251
|
(default: :obj:`2048`)
|
|
164
252
|
|
|
165
253
|
Returns:
|
|
166
|
-
List[
|
|
254
|
+
List[Dict[str, object]]: A list of dictionary containing
|
|
167
255
|
the reranked documents and their relevance scores.
|
|
168
256
|
"""
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
257
|
+
data = {
|
|
258
|
+
'model': self.model_name,
|
|
259
|
+
'query': image_query,
|
|
260
|
+
'top_n': len(documents),
|
|
261
|
+
'documents': documents,
|
|
262
|
+
'return_documents': True,
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if self.use_api:
|
|
266
|
+
return self._call_jina_api(data)
|
|
267
|
+
|
|
268
|
+
else:
|
|
269
|
+
import torch
|
|
270
|
+
|
|
271
|
+
if self.model is None:
|
|
272
|
+
raise ValueError(
|
|
273
|
+
"Model has not been initialized or failed to initialize."
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
with torch.inference_mode():
|
|
277
|
+
image_pairs = [[image_query, doc] for doc in documents]
|
|
278
|
+
scores = self.model.compute_score(
|
|
279
|
+
image_pairs,
|
|
280
|
+
max_length=max_length,
|
|
281
|
+
query_type="image",
|
|
282
|
+
doc_type="text",
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
return self._sort_documents(documents, scores)
|
|
183
286
|
|
|
184
287
|
def image_query_image_documents(
|
|
185
288
|
self,
|
|
186
289
|
image_query: str,
|
|
187
290
|
documents: List[str],
|
|
188
291
|
max_length: int = 2048,
|
|
189
|
-
) -> List[
|
|
292
|
+
) -> List[Dict[str, object]]:
|
|
190
293
|
r"""Reranks image documents based on their relevance to an image query.
|
|
191
294
|
|
|
192
295
|
Args:
|
|
@@ -196,24 +299,38 @@ class JinaRerankerToolkit(BaseToolkit):
|
|
|
196
299
|
(default: :obj:`2048`)
|
|
197
300
|
|
|
198
301
|
Returns:
|
|
199
|
-
List[
|
|
302
|
+
List[Dict[str, object]]: A list of dictionary containing
|
|
200
303
|
the reranked image URLs/paths and their relevance scores.
|
|
201
304
|
"""
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
305
|
+
data = {
|
|
306
|
+
'model': self.model_name,
|
|
307
|
+
'query': image_query,
|
|
308
|
+
'top_n': len(documents),
|
|
309
|
+
'documents': documents,
|
|
310
|
+
'return_documents': True,
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if self.use_api:
|
|
314
|
+
return self._call_jina_api(data)
|
|
315
|
+
|
|
316
|
+
else:
|
|
317
|
+
import torch
|
|
318
|
+
|
|
319
|
+
if self.model is None:
|
|
320
|
+
raise ValueError(
|
|
321
|
+
"Model has not been initialized or failed to initialize."
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
with torch.inference_mode():
|
|
325
|
+
image_pairs = [[image_query, doc] for doc in documents]
|
|
326
|
+
scores = self.model.compute_score(
|
|
327
|
+
image_pairs,
|
|
328
|
+
max_length=max_length,
|
|
329
|
+
query_type="image",
|
|
330
|
+
doc_type="image",
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
return self._sort_documents(documents, scores)
|
|
217
334
|
|
|
218
335
|
def get_tools(self) -> List[FunctionTool]:
|
|
219
336
|
r"""Returns a list of FunctionTool objects representing the
|
camel/toolkits/mcp_toolkit.py
CHANGED
|
@@ -34,6 +34,7 @@ from urllib.parse import urlparse
|
|
|
34
34
|
if TYPE_CHECKING:
|
|
35
35
|
from mcp import ClientSession, ListToolsResult, Tool
|
|
36
36
|
|
|
37
|
+
|
|
37
38
|
from camel.logger import get_logger
|
|
38
39
|
from camel.toolkits import BaseToolkit, FunctionTool
|
|
39
40
|
|
|
@@ -84,7 +85,6 @@ class MCPClient(BaseToolkit):
|
|
|
84
85
|
await client.disconnect()
|
|
85
86
|
```
|
|
86
87
|
|
|
87
|
-
|
|
88
88
|
Attributes:
|
|
89
89
|
command_or_url (str): URL for SSE mode or command executable for stdio
|
|
90
90
|
mode. (default: :obj:`None`)
|
|
@@ -96,6 +96,10 @@ class MCPClient(BaseToolkit):
|
|
|
96
96
|
(default: :obj:`None`)
|
|
97
97
|
headers (Dict[str, str]): Headers for the HTTP request.
|
|
98
98
|
(default: :obj:`None`)
|
|
99
|
+
mode (Optional[str]): Connection mode. Can be "sse" for Server-Sent
|
|
100
|
+
Events, "streamable-http" for streaming HTTP,
|
|
101
|
+
or None for stdio mode.
|
|
102
|
+
(default: :obj:`None`)
|
|
99
103
|
strict (Optional[bool]): Whether to enforce strict mode for the
|
|
100
104
|
function call. (default: :obj:`False`)
|
|
101
105
|
"""
|
|
@@ -107,6 +111,7 @@ class MCPClient(BaseToolkit):
|
|
|
107
111
|
env: Optional[Dict[str, str]] = None,
|
|
108
112
|
timeout: Optional[float] = None,
|
|
109
113
|
headers: Optional[Dict[str, str]] = None,
|
|
114
|
+
mode: Optional[str] = None,
|
|
110
115
|
strict: Optional[bool] = False,
|
|
111
116
|
):
|
|
112
117
|
from mcp import Tool
|
|
@@ -118,6 +123,7 @@ class MCPClient(BaseToolkit):
|
|
|
118
123
|
self.env = env or {}
|
|
119
124
|
self.headers = headers or {}
|
|
120
125
|
self.strict = strict
|
|
126
|
+
self.mode = mode
|
|
121
127
|
|
|
122
128
|
self._mcp_tools: List[Tool] = []
|
|
123
129
|
self._session: Optional['ClientSession'] = None
|
|
@@ -133,6 +139,7 @@ class MCPClient(BaseToolkit):
|
|
|
133
139
|
from mcp.client.session import ClientSession
|
|
134
140
|
from mcp.client.sse import sse_client
|
|
135
141
|
from mcp.client.stdio import StdioServerParameters, stdio_client
|
|
142
|
+
from mcp.client.streamable_http import streamablehttp_client
|
|
136
143
|
|
|
137
144
|
if self._is_connected:
|
|
138
145
|
logger.warning("Server is already connected")
|
|
@@ -140,16 +147,37 @@ class MCPClient(BaseToolkit):
|
|
|
140
147
|
|
|
141
148
|
try:
|
|
142
149
|
if urlparse(self.command_or_url).scheme in ("http", "https"):
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
150
|
+
if self.mode == "sse" or self.mode is None:
|
|
151
|
+
(
|
|
152
|
+
read_stream,
|
|
153
|
+
write_stream,
|
|
154
|
+
) = await self._exit_stack.enter_async_context(
|
|
155
|
+
sse_client(
|
|
156
|
+
self.command_or_url,
|
|
157
|
+
headers=self.headers,
|
|
158
|
+
timeout=self.timeout,
|
|
159
|
+
)
|
|
160
|
+
)
|
|
161
|
+
elif self.mode == "streamable-http":
|
|
162
|
+
try:
|
|
163
|
+
(
|
|
164
|
+
read_stream,
|
|
165
|
+
write_stream,
|
|
166
|
+
_,
|
|
167
|
+
) = await self._exit_stack.enter_async_context(
|
|
168
|
+
streamablehttp_client(
|
|
169
|
+
self.command_or_url,
|
|
170
|
+
headers=self.headers,
|
|
171
|
+
timeout=timedelta(seconds=self.timeout),
|
|
172
|
+
)
|
|
173
|
+
)
|
|
174
|
+
except Exception as e:
|
|
175
|
+
# Handle anyio task group errors
|
|
176
|
+
logger.error(f"Streamable HTTP client error: {e}")
|
|
177
|
+
else:
|
|
178
|
+
raise ValueError(
|
|
179
|
+
f"Invalid mode '{self.mode}' for HTTP URL"
|
|
151
180
|
)
|
|
152
|
-
)
|
|
153
181
|
else:
|
|
154
182
|
command = self.command_or_url
|
|
155
183
|
arguments = self.args
|
|
@@ -194,9 +222,18 @@ class MCPClient(BaseToolkit):
|
|
|
194
222
|
|
|
195
223
|
async def disconnect(self):
|
|
196
224
|
r"""Explicitly disconnect from the MCP server."""
|
|
225
|
+
# If the server is not connected, do nothing
|
|
226
|
+
if not self._is_connected:
|
|
227
|
+
return
|
|
197
228
|
self._is_connected = False
|
|
198
|
-
|
|
199
|
-
|
|
229
|
+
|
|
230
|
+
try:
|
|
231
|
+
await self._exit_stack.aclose()
|
|
232
|
+
except Exception as e:
|
|
233
|
+
logger.warning(f"{e}")
|
|
234
|
+
finally:
|
|
235
|
+
self._exit_stack = AsyncExitStack()
|
|
236
|
+
self._session = None
|
|
200
237
|
|
|
201
238
|
@asynccontextmanager
|
|
202
239
|
async def connection(self):
|
|
@@ -212,7 +249,10 @@ class MCPClient(BaseToolkit):
|
|
|
212
249
|
await self.connect()
|
|
213
250
|
yield self
|
|
214
251
|
finally:
|
|
215
|
-
|
|
252
|
+
try:
|
|
253
|
+
await self.disconnect()
|
|
254
|
+
except Exception as e:
|
|
255
|
+
logger.warning(f"Error: {e}")
|
|
216
256
|
|
|
217
257
|
async def list_mcp_tools(self) -> Union[str, "ListToolsResult"]:
|
|
218
258
|
r"""Retrieves the list of available tools from the connected MCP
|
|
@@ -435,6 +475,7 @@ class MCPClient(BaseToolkit):
|
|
|
435
475
|
env: Optional[Dict[str, str]] = None,
|
|
436
476
|
timeout: Optional[float] = None,
|
|
437
477
|
headers: Optional[Dict[str, str]] = None,
|
|
478
|
+
mode: Optional[str] = None,
|
|
438
479
|
) -> "MCPClient":
|
|
439
480
|
r"""Factory method that creates and connects to the MCP server.
|
|
440
481
|
|
|
@@ -452,6 +493,10 @@ class MCPClient(BaseToolkit):
|
|
|
452
493
|
(default: :obj:`None`)
|
|
453
494
|
headers (Optional[Dict[str, str]]): Headers for the HTTP request.
|
|
454
495
|
(default: :obj:`None`)
|
|
496
|
+
mode (Optional[str]): Connection mode. Can be "sse" for
|
|
497
|
+
Server-Sent Events, "streamable-http" for
|
|
498
|
+
streaming HTTP, or None for stdio mode.
|
|
499
|
+
(default: :obj:`None`)
|
|
455
500
|
|
|
456
501
|
Returns:
|
|
457
502
|
MCPClient: A fully initialized and connected MCPClient instance.
|
|
@@ -465,6 +510,7 @@ class MCPClient(BaseToolkit):
|
|
|
465
510
|
env=env,
|
|
466
511
|
timeout=timeout,
|
|
467
512
|
headers=headers,
|
|
513
|
+
mode=mode,
|
|
468
514
|
)
|
|
469
515
|
try:
|
|
470
516
|
await client.connect()
|
|
@@ -599,7 +645,6 @@ class MCPToolkit(BaseToolkit):
|
|
|
599
645
|
if config_dict:
|
|
600
646
|
self.servers.extend(self._load_servers_from_dict(config_dict))
|
|
601
647
|
|
|
602
|
-
self._exit_stack = AsyncExitStack()
|
|
603
648
|
self._connected = False
|
|
604
649
|
|
|
605
650
|
def _load_servers_from_config(
|
|
@@ -675,6 +720,7 @@ class MCPToolkit(BaseToolkit):
|
|
|
675
720
|
env={**os.environ, **cfg.get("env", {})},
|
|
676
721
|
timeout=cfg.get("timeout", None),
|
|
677
722
|
headers=headers,
|
|
723
|
+
mode=cfg.get("mode", None),
|
|
678
724
|
strict=strict,
|
|
679
725
|
)
|
|
680
726
|
all_servers.append(server)
|
|
@@ -691,7 +737,6 @@ class MCPToolkit(BaseToolkit):
|
|
|
691
737
|
logger.warning("MCPToolkit is already connected")
|
|
692
738
|
return self
|
|
693
739
|
|
|
694
|
-
self._exit_stack = AsyncExitStack()
|
|
695
740
|
try:
|
|
696
741
|
# Sequentially connect to each server
|
|
697
742
|
for server in self.servers:
|
|
@@ -712,7 +757,6 @@ class MCPToolkit(BaseToolkit):
|
|
|
712
757
|
for server in self.servers:
|
|
713
758
|
await server.disconnect()
|
|
714
759
|
self._connected = False
|
|
715
|
-
await self._exit_stack.aclose()
|
|
716
760
|
|
|
717
761
|
@asynccontextmanager
|
|
718
762
|
async def connection(self) -> AsyncGenerator["MCPToolkit", None]:
|
camel/toolkits/page_script.js
CHANGED
|
@@ -191,35 +191,47 @@ var MultimodalWebSurfer = MultimodalWebSurfer || (function() {
|
|
|
191
191
|
labelElements(getInteractiveElements());
|
|
192
192
|
let elements = document.querySelectorAll("[__elementId]");
|
|
193
193
|
let results = {};
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
194
|
+
let scale = window.devicePixelRatio || 1;
|
|
195
|
+
|
|
196
|
+
for (let i = 0; i < elements.length; i++) {
|
|
197
|
+
let key = elements[i].getAttribute("__elementId");
|
|
198
|
+
let rects = elements[i].getClientRects();
|
|
199
|
+
let ariaRole = getApproximateAriaRole(elements[i]);
|
|
200
|
+
let ariaName = getApproximateAriaName(elements[i]);
|
|
201
|
+
let vScrollable = elements[i].scrollHeight - elements[i].clientHeight >= 1;
|
|
202
|
+
|
|
203
|
+
let record = {
|
|
204
|
+
"tag_name": ariaRole[1],
|
|
205
|
+
"role": ariaRole[0],
|
|
206
|
+
"aria-name": ariaName,
|
|
207
|
+
"v-scrollable": vScrollable,
|
|
208
|
+
"rects": []
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
for (const rect of rects) {
|
|
212
|
+
let x = rect.left + rect.width / 2;
|
|
213
|
+
let y = rect.top + rect.height / 2;
|
|
214
|
+
if (isTopmost(elements[i], x, y)) {
|
|
215
|
+
record["rects"].push({
|
|
216
|
+
x: rect.x * scale,
|
|
217
|
+
y: rect.y * scale,
|
|
218
|
+
width: rect.width * scale,
|
|
219
|
+
height: rect.height * scale,
|
|
220
|
+
top: rect.top * scale,
|
|
221
|
+
left: rect.left * scale,
|
|
222
|
+
right: rect.right * scale,
|
|
223
|
+
bottom: rect.bottom * scale
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (record["rects"].length > 0) {
|
|
229
|
+
results[key] = record;
|
|
230
|
+
}
|
|
220
231
|
}
|
|
232
|
+
|
|
221
233
|
return results;
|
|
222
|
-
};
|
|
234
|
+
};
|
|
223
235
|
|
|
224
236
|
let getVisualViewport = function() {
|
|
225
237
|
let vv = window.visualViewport;
|
|
@@ -373,4 +385,4 @@ var MultimodalWebSurfer = MultimodalWebSurfer || (function() {
|
|
|
373
385
|
getFocusedElementId: getFocusedElementId,
|
|
374
386
|
getPageMetadata: getPageMetadata,
|
|
375
387
|
};
|
|
376
|
-
})();
|
|
388
|
+
})();
|
|
@@ -18,7 +18,6 @@ from http.client import responses
|
|
|
18
18
|
from typing import Any, Dict, List, Optional, Union
|
|
19
19
|
|
|
20
20
|
import requests
|
|
21
|
-
from requests_oauthlib import OAuth1
|
|
22
21
|
|
|
23
22
|
from camel.logger import get_logger
|
|
24
23
|
from camel.toolkits import FunctionTool
|
|
@@ -76,6 +75,8 @@ def create_tweet(
|
|
|
76
75
|
Reference:
|
|
77
76
|
https://developer.x.com/en/docs/x-api/tweets/manage-tweets/api-reference/post-tweets
|
|
78
77
|
"""
|
|
78
|
+
from requests_oauthlib import OAuth1
|
|
79
|
+
|
|
79
80
|
auth = OAuth1(
|
|
80
81
|
os.getenv("TWITTER_CONSUMER_KEY"),
|
|
81
82
|
os.getenv("TWITTER_CONSUMER_SECRET"),
|
|
@@ -160,6 +161,8 @@ def delete_tweet(tweet_id: str) -> str:
|
|
|
160
161
|
Reference:
|
|
161
162
|
https://developer.x.com/en/docs/x-api/tweets/manage-tweets/api-reference/delete-tweets-id
|
|
162
163
|
"""
|
|
164
|
+
from requests_oauthlib import OAuth1
|
|
165
|
+
|
|
163
166
|
auth = OAuth1(
|
|
164
167
|
os.getenv("TWITTER_CONSUMER_KEY"),
|
|
165
168
|
os.getenv("TWITTER_CONSUMER_SECRET"),
|
|
@@ -263,6 +266,8 @@ def _get_user_info(username: Optional[str] = None) -> str:
|
|
|
263
266
|
Returns:
|
|
264
267
|
str: A formatted report of the user's Twitter profile information.
|
|
265
268
|
"""
|
|
269
|
+
from requests_oauthlib import OAuth1
|
|
270
|
+
|
|
266
271
|
oauth = OAuth1(
|
|
267
272
|
os.getenv("TWITTER_CONSUMER_KEY"),
|
|
268
273
|
os.getenv("TWITTER_CONSUMER_SECRET"),
|