camel-ai 0.2.74a5__py3-none-any.whl → 0.2.75__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 +149 -95
- camel/configs/__init__.py +3 -0
- camel/configs/nebius_config.py +103 -0
- camel/interpreters/e2b_interpreter.py +34 -1
- camel/models/__init__.py +2 -0
- camel/models/aiml_model.py +1 -16
- camel/models/anthropic_model.py +6 -22
- camel/models/aws_bedrock_model.py +1 -16
- camel/models/azure_openai_model.py +1 -16
- camel/models/base_model.py +0 -12
- camel/models/cohere_model.py +1 -16
- camel/models/crynux_model.py +1 -16
- camel/models/deepseek_model.py +1 -16
- camel/models/gemini_model.py +1 -16
- camel/models/groq_model.py +1 -17
- camel/models/internlm_model.py +1 -16
- camel/models/litellm_model.py +1 -16
- camel/models/lmstudio_model.py +1 -17
- camel/models/mistral_model.py +1 -16
- camel/models/model_factory.py +2 -0
- camel/models/modelscope_model.py +1 -16
- camel/models/moonshot_model.py +6 -22
- camel/models/nebius_model.py +83 -0
- camel/models/nemotron_model.py +0 -5
- camel/models/netmind_model.py +1 -16
- camel/models/novita_model.py +1 -16
- camel/models/nvidia_model.py +1 -16
- camel/models/ollama_model.py +4 -19
- camel/models/openai_compatible_model.py +0 -3
- camel/models/openai_model.py +1 -22
- camel/models/openrouter_model.py +1 -17
- camel/models/ppio_model.py +1 -16
- camel/models/qianfan_model.py +1 -16
- camel/models/qwen_model.py +1 -16
- camel/models/reka_model.py +1 -16
- camel/models/samba_model.py +0 -32
- camel/models/sglang_model.py +1 -16
- camel/models/siliconflow_model.py +1 -16
- camel/models/stub_model.py +0 -4
- camel/models/togetherai_model.py +1 -16
- camel/models/vllm_model.py +1 -16
- camel/models/volcano_model.py +0 -17
- camel/models/watsonx_model.py +1 -16
- camel/models/yi_model.py +1 -16
- camel/models/zhipuai_model.py +1 -16
- camel/societies/workforce/prompts.py +1 -8
- camel/societies/workforce/task_channel.py +120 -27
- camel/societies/workforce/workforce.py +35 -3
- camel/toolkits/__init__.py +0 -2
- camel/toolkits/github_toolkit.py +104 -17
- camel/toolkits/hybrid_browser_toolkit/config_loader.py +3 -0
- camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit_ts.py +260 -5
- camel/toolkits/hybrid_browser_toolkit/ts/src/browser-session.ts +288 -37
- camel/toolkits/hybrid_browser_toolkit/ts/src/config-loader.ts +3 -1
- camel/toolkits/hybrid_browser_toolkit/ts/src/hybrid-browser-toolkit.ts +209 -41
- camel/toolkits/hybrid_browser_toolkit/ts/src/types.ts +22 -3
- camel/toolkits/hybrid_browser_toolkit/ts/websocket-server.js +28 -1
- camel/toolkits/hybrid_browser_toolkit/ws_wrapper.py +101 -0
- camel/toolkits/hybrid_browser_toolkit_py/actions.py +158 -0
- camel/toolkits/hybrid_browser_toolkit_py/browser_session.py +55 -8
- camel/toolkits/hybrid_browser_toolkit_py/config_loader.py +43 -0
- camel/toolkits/hybrid_browser_toolkit_py/hybrid_browser_toolkit.py +312 -3
- camel/toolkits/hybrid_browser_toolkit_py/snapshot.py +10 -4
- camel/toolkits/hybrid_browser_toolkit_py/unified_analyzer.js +45 -4
- camel/toolkits/math_toolkit.py +64 -10
- camel/toolkits/mcp_toolkit.py +39 -14
- camel/toolkits/openai_image_toolkit.py +55 -24
- camel/toolkits/search_toolkit.py +153 -29
- camel/types/__init__.py +2 -2
- camel/types/enums.py +54 -10
- camel/types/openai_types.py +2 -2
- camel/types/unified_model_type.py +5 -0
- camel/utils/mcp.py +2 -2
- camel/utils/token_counting.py +18 -3
- {camel_ai-0.2.74a5.dist-info → camel_ai-0.2.75.dist-info}/METADATA +9 -15
- {camel_ai-0.2.74a5.dist-info → camel_ai-0.2.75.dist-info}/RECORD +79 -78
- camel/toolkits/openai_agent_toolkit.py +0 -135
- {camel_ai-0.2.74a5.dist-info → camel_ai-0.2.75.dist-info}/WHEEL +0 -0
- {camel_ai-0.2.74a5.dist-info → camel_ai-0.2.75.dist-info}/licenses/LICENSE +0 -0
camel/toolkits/mcp_toolkit.py
CHANGED
|
@@ -220,26 +220,34 @@ class MCPToolkit(BaseToolkit):
|
|
|
220
220
|
self._exit_stack = AsyncExitStack()
|
|
221
221
|
|
|
222
222
|
try:
|
|
223
|
-
#
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
except Exception as e:
|
|
231
|
-
logger.error(f"Failed to connect to client {i+1}: {e}")
|
|
232
|
-
# AsyncExitStack will handle cleanup of already connected
|
|
233
|
-
await self._exit_stack.aclose()
|
|
234
|
-
self._exit_stack = None
|
|
235
|
-
error_msg = f"Failed to connect to client {i+1}: {e}"
|
|
236
|
-
raise MCPConnectionError(error_msg) from e
|
|
223
|
+
# Apply timeout to the entire connection process
|
|
224
|
+
import asyncio
|
|
225
|
+
|
|
226
|
+
timeout_seconds = self.timeout or 30.0
|
|
227
|
+
await asyncio.wait_for(
|
|
228
|
+
self._connect_all_clients(), timeout=timeout_seconds
|
|
229
|
+
)
|
|
237
230
|
|
|
238
231
|
self._is_connected = True
|
|
239
232
|
msg = f"Successfully connected to {len(self.clients)} MCP servers"
|
|
240
233
|
logger.info(msg)
|
|
241
234
|
return self
|
|
242
235
|
|
|
236
|
+
except (asyncio.TimeoutError, asyncio.CancelledError):
|
|
237
|
+
self._is_connected = False
|
|
238
|
+
if self._exit_stack:
|
|
239
|
+
await self._exit_stack.aclose()
|
|
240
|
+
self._exit_stack = None
|
|
241
|
+
|
|
242
|
+
timeout_seconds = self.timeout or 30.0
|
|
243
|
+
error_msg = (
|
|
244
|
+
f"Connection timeout after {timeout_seconds}s. "
|
|
245
|
+
f"One or more MCP servers are not responding. "
|
|
246
|
+
f"Please check if the servers are running and accessible."
|
|
247
|
+
)
|
|
248
|
+
logger.error(error_msg)
|
|
249
|
+
raise MCPConnectionError(error_msg)
|
|
250
|
+
|
|
243
251
|
except Exception:
|
|
244
252
|
self._is_connected = False
|
|
245
253
|
if self._exit_stack:
|
|
@@ -247,6 +255,23 @@ class MCPToolkit(BaseToolkit):
|
|
|
247
255
|
self._exit_stack = None
|
|
248
256
|
raise
|
|
249
257
|
|
|
258
|
+
async def _connect_all_clients(self):
|
|
259
|
+
r"""Connect to all clients sequentially."""
|
|
260
|
+
# Connect to all clients using AsyncExitStack
|
|
261
|
+
for i, client in enumerate(self.clients):
|
|
262
|
+
try:
|
|
263
|
+
# Use MCPClient directly as async context manager
|
|
264
|
+
await self._exit_stack.enter_async_context(client)
|
|
265
|
+
msg = f"Connected to client {i+1}/{len(self.clients)}"
|
|
266
|
+
logger.debug(msg)
|
|
267
|
+
except Exception as e:
|
|
268
|
+
logger.error(f"Failed to connect to client {i+1}: {e}")
|
|
269
|
+
# AsyncExitStack will cleanup already connected clients
|
|
270
|
+
await self._exit_stack.aclose()
|
|
271
|
+
self._exit_stack = None
|
|
272
|
+
error_msg = f"Failed to connect to client {i+1}: {e}"
|
|
273
|
+
raise MCPConnectionError(error_msg) from e
|
|
274
|
+
|
|
250
275
|
async def disconnect(self):
|
|
251
276
|
r"""Disconnect from all MCP servers."""
|
|
252
277
|
if not self._is_connected:
|
|
@@ -14,9 +14,8 @@
|
|
|
14
14
|
|
|
15
15
|
import base64
|
|
16
16
|
import os
|
|
17
|
-
import uuid
|
|
18
17
|
from io import BytesIO
|
|
19
|
-
from typing import List, Literal, Optional
|
|
18
|
+
from typing import List, Literal, Optional, Union
|
|
20
19
|
|
|
21
20
|
from openai import OpenAI
|
|
22
21
|
from PIL import Image
|
|
@@ -64,13 +63,15 @@ class OpenAIImageToolkit(BaseToolkit):
|
|
|
64
63
|
Literal["auto", "low", "medium", "high", "standard", "hd"]
|
|
65
64
|
] = "standard",
|
|
66
65
|
response_format: Optional[Literal["url", "b64_json"]] = "b64_json",
|
|
67
|
-
n: Optional[int] = 1,
|
|
68
66
|
background: Optional[
|
|
69
67
|
Literal["transparent", "opaque", "auto"]
|
|
70
68
|
] = "auto",
|
|
71
69
|
style: Optional[Literal["vivid", "natural"]] = None,
|
|
72
70
|
working_directory: Optional[str] = "image_save",
|
|
73
71
|
):
|
|
72
|
+
# NOTE: Some arguments are set in the constructor to prevent the agent
|
|
73
|
+
# from making invalid API calls with model-specific parameters. For
|
|
74
|
+
# example, the 'style' argument is only supported by 'dall-e-3'.
|
|
74
75
|
r"""Initializes a new instance of the OpenAIImageToolkit class.
|
|
75
76
|
|
|
76
77
|
Args:
|
|
@@ -94,8 +95,6 @@ class OpenAIImageToolkit(BaseToolkit):
|
|
|
94
95
|
(default: :obj:`"standard"`)
|
|
95
96
|
response_format (Optional[Literal["url", "b64_json"]]):
|
|
96
97
|
The format of the response.(default: :obj:`"b64_json"`)
|
|
97
|
-
n (Optional[int]): The number of images to generate.
|
|
98
|
-
(default: :obj:`1`)
|
|
99
98
|
background (Optional[Literal["transparent", "opaque", "auto"]]):
|
|
100
99
|
The background of the image.(default: :obj:`"auto"`)
|
|
101
100
|
style (Optional[Literal["vivid", "natural"]]): The style of the
|
|
@@ -111,7 +110,6 @@ class OpenAIImageToolkit(BaseToolkit):
|
|
|
111
110
|
self.size = size
|
|
112
111
|
self.quality = quality
|
|
113
112
|
self.response_format = response_format
|
|
114
|
-
self.n = n
|
|
115
113
|
self.background = background
|
|
116
114
|
self.style = style
|
|
117
115
|
self.working_directory: str = working_directory or "image_save"
|
|
@@ -140,11 +138,12 @@ class OpenAIImageToolkit(BaseToolkit):
|
|
|
140
138
|
)
|
|
141
139
|
return None
|
|
142
140
|
|
|
143
|
-
def _build_base_params(self, prompt: str) -> dict:
|
|
141
|
+
def _build_base_params(self, prompt: str, n: Optional[int] = None) -> dict:
|
|
144
142
|
r"""Build base parameters dict for OpenAI API calls.
|
|
145
143
|
|
|
146
144
|
Args:
|
|
147
145
|
prompt (str): The text prompt for the image operation.
|
|
146
|
+
n (Optional[int]): The number of images to generate.
|
|
148
147
|
|
|
149
148
|
Returns:
|
|
150
149
|
dict: Parameters dictionary with non-None values.
|
|
@@ -152,8 +151,8 @@ class OpenAIImageToolkit(BaseToolkit):
|
|
|
152
151
|
params = {"prompt": prompt, "model": self.model}
|
|
153
152
|
|
|
154
153
|
# basic parameters supported by all models
|
|
155
|
-
if
|
|
156
|
-
params["n"] =
|
|
154
|
+
if n is not None:
|
|
155
|
+
params["n"] = n # type: ignore[assignment]
|
|
157
156
|
if self.size is not None:
|
|
158
157
|
params["size"] = self.size
|
|
159
158
|
|
|
@@ -184,13 +183,16 @@ class OpenAIImageToolkit(BaseToolkit):
|
|
|
184
183
|
return params
|
|
185
184
|
|
|
186
185
|
def _handle_api_response(
|
|
187
|
-
self, response, image_name: str, operation: str
|
|
186
|
+
self, response, image_name: Union[str, List[str]], operation: str
|
|
188
187
|
) -> str:
|
|
189
188
|
r"""Handle API response from OpenAI image operations.
|
|
190
189
|
|
|
191
190
|
Args:
|
|
192
191
|
response: The response object from OpenAI API.
|
|
193
|
-
image_name (str): Name for the saved image
|
|
192
|
+
image_name (Union[str, List[str]]): Name(s) for the saved image
|
|
193
|
+
file(s). If str, the same name is used for all images (will
|
|
194
|
+
cause error for multiple images). If list, must have exactly
|
|
195
|
+
the same length as the number of images generated.
|
|
194
196
|
operation (str): Operation type for success message ("generated").
|
|
195
197
|
|
|
196
198
|
Returns:
|
|
@@ -201,6 +203,21 @@ class OpenAIImageToolkit(BaseToolkit):
|
|
|
201
203
|
logger.error(error_msg)
|
|
202
204
|
return error_msg
|
|
203
205
|
|
|
206
|
+
# Validate image_name parameter
|
|
207
|
+
if isinstance(image_name, list):
|
|
208
|
+
if len(image_name) != len(response.data):
|
|
209
|
+
error_msg = (
|
|
210
|
+
f"Error: Number of image names"
|
|
211
|
+
f" ({len(image_name)}) does not match number of "
|
|
212
|
+
f"images generated({len(response.data)})"
|
|
213
|
+
)
|
|
214
|
+
logger.error(error_msg)
|
|
215
|
+
return error_msg
|
|
216
|
+
image_names = image_name
|
|
217
|
+
else:
|
|
218
|
+
# If string, use same name for all images
|
|
219
|
+
image_names = [image_name] * len(response.data)
|
|
220
|
+
|
|
204
221
|
results = []
|
|
205
222
|
|
|
206
223
|
for i, image_data in enumerate(response.data):
|
|
@@ -215,18 +232,27 @@ class OpenAIImageToolkit(BaseToolkit):
|
|
|
215
232
|
image_bytes = base64.b64decode(image_b64)
|
|
216
233
|
os.makedirs(self.working_directory, exist_ok=True)
|
|
217
234
|
|
|
218
|
-
|
|
219
|
-
if len(response.data) > 1:
|
|
220
|
-
filename = f"{image_name}_{i+1}_{uuid.uuid4().hex}.png"
|
|
221
|
-
else:
|
|
222
|
-
filename = f"{image_name}_{uuid.uuid4().hex}.png"
|
|
235
|
+
filename = f"{image_names[i]}"
|
|
223
236
|
|
|
224
237
|
image_path = os.path.join(self.working_directory, filename)
|
|
225
238
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
239
|
+
# Check if file already exists
|
|
240
|
+
if os.path.exists(image_path):
|
|
241
|
+
error_msg = (
|
|
242
|
+
f"Error: File '{image_path}' already exists. "
|
|
243
|
+
"Please use a different image_name."
|
|
244
|
+
)
|
|
245
|
+
logger.error(error_msg)
|
|
246
|
+
return error_msg
|
|
247
|
+
|
|
248
|
+
try:
|
|
249
|
+
with open(image_path, "wb") as f:
|
|
250
|
+
f.write(image_bytes)
|
|
251
|
+
results.append(f"Image saved to {image_path}")
|
|
252
|
+
except Exception as e:
|
|
253
|
+
error_msg = f"Error saving image to '{image_path}': {e!s}"
|
|
254
|
+
logger.error(error_msg)
|
|
255
|
+
return error_msg
|
|
230
256
|
else:
|
|
231
257
|
error_msg = (
|
|
232
258
|
f"No valid image data (URL or base64) found in image {i+1}"
|
|
@@ -254,7 +280,8 @@ class OpenAIImageToolkit(BaseToolkit):
|
|
|
254
280
|
def generate_image(
|
|
255
281
|
self,
|
|
256
282
|
prompt: str,
|
|
257
|
-
image_name: str = "image",
|
|
283
|
+
image_name: Union[str, List[str]] = "image.png",
|
|
284
|
+
n: int = 1,
|
|
258
285
|
) -> str:
|
|
259
286
|
r"""Generate an image using OpenAI's Image Generation models.
|
|
260
287
|
The generated image will be saved locally (for ``b64_json`` response
|
|
@@ -263,14 +290,18 @@ class OpenAIImageToolkit(BaseToolkit):
|
|
|
263
290
|
|
|
264
291
|
Args:
|
|
265
292
|
prompt (str): The text prompt to generate the image.
|
|
266
|
-
image_name (str): The name of the image to
|
|
267
|
-
|
|
293
|
+
image_name (Union[str, List[str]]): The name(s) of the image(s) to
|
|
294
|
+
save. The image name must end with `.png`. If str: same name
|
|
295
|
+
used for all images (causes error if n > 1). If list: must
|
|
296
|
+
match the number of images being generated (n parameter).
|
|
297
|
+
(default: :obj:`"image.png"`)
|
|
298
|
+
n (int): The number of images to generate. (default: :obj:`1`)
|
|
268
299
|
|
|
269
300
|
Returns:
|
|
270
301
|
str: the content of the model response or format of the response.
|
|
271
302
|
"""
|
|
272
303
|
try:
|
|
273
|
-
params = self._build_base_params(prompt)
|
|
304
|
+
params = self._build_base_params(prompt, n)
|
|
274
305
|
response = self.client.images.generate(**params)
|
|
275
306
|
return self._handle_api_response(response, image_name, "generated")
|
|
276
307
|
except Exception as e:
|
camel/toolkits/search_toolkit.py
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
# limitations under the License.
|
|
13
13
|
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
|
|
14
14
|
import os
|
|
15
|
+
import warnings
|
|
15
16
|
from typing import Any, Dict, List, Literal, Optional, TypeAlias, Union, cast
|
|
16
17
|
|
|
17
18
|
import requests
|
|
@@ -458,17 +459,21 @@ class SearchToolkit(BaseToolkit):
|
|
|
458
459
|
|
|
459
460
|
Args:
|
|
460
461
|
query (str): The query to be searched.
|
|
461
|
-
search_type (str): The type of search to perform.
|
|
462
|
-
web pages or "image" for image search.
|
|
462
|
+
search_type (str): The type of search to perform. Must be either
|
|
463
|
+
"web" for web pages or "image" for image search. Any other
|
|
464
|
+
value will raise a ValueError. (default: "web")
|
|
463
465
|
number_of_result_pages (int): The number of result pages to
|
|
464
|
-
retrieve.
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
(
|
|
466
|
+
retrieve. Must be a positive integer between 1 and 10.
|
|
467
|
+
Google Custom Search API limits results to 10 per request.
|
|
468
|
+
If a value greater than 10 is provided, it will be capped
|
|
469
|
+
at 10 with a warning. Adjust this based on your task - use
|
|
470
|
+
fewer results for focused searches and more for comprehensive
|
|
471
|
+
searches. (default: :obj:`10`)
|
|
472
|
+
start_page (int): The result page to start from. Must be a
|
|
473
|
+
positive integer (>= 1). Use this for pagination - e.g.,
|
|
474
|
+
start_page=1 for results 1-10, start_page=11 for results
|
|
475
|
+
11-20, etc. This allows agents to check initial results
|
|
476
|
+
and continue searching if needed. (default: :obj:`1`)
|
|
472
477
|
|
|
473
478
|
Returns:
|
|
474
479
|
List[Dict[str, Any]]: A list of dictionaries where each dictionary
|
|
@@ -514,8 +519,33 @@ class SearchToolkit(BaseToolkit):
|
|
|
514
519
|
'height': 600
|
|
515
520
|
}
|
|
516
521
|
"""
|
|
522
|
+
from urllib.parse import quote
|
|
523
|
+
|
|
517
524
|
import requests
|
|
518
525
|
|
|
526
|
+
# Validate input parameters
|
|
527
|
+
if not isinstance(start_page, int) or start_page < 1:
|
|
528
|
+
raise ValueError("start_page must be a positive integer")
|
|
529
|
+
|
|
530
|
+
if (
|
|
531
|
+
not isinstance(number_of_result_pages, int)
|
|
532
|
+
or number_of_result_pages < 1
|
|
533
|
+
):
|
|
534
|
+
raise ValueError(
|
|
535
|
+
"number_of_result_pages must be a positive integer"
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
# Google Custom Search API has a limit of 10 results per request
|
|
539
|
+
if number_of_result_pages > 10:
|
|
540
|
+
logger.warning(
|
|
541
|
+
f"Google API limits results to 10 per request. "
|
|
542
|
+
f"Requested {number_of_result_pages}, using 10 instead."
|
|
543
|
+
)
|
|
544
|
+
number_of_result_pages = 10
|
|
545
|
+
|
|
546
|
+
if search_type not in ["web", "image"]:
|
|
547
|
+
raise ValueError("search_type must be either 'web' or 'image'")
|
|
548
|
+
|
|
519
549
|
# https://developers.google.com/custom-search/v1/overview
|
|
520
550
|
GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
|
|
521
551
|
# https://cse.google.com/cse/all
|
|
@@ -535,11 +565,13 @@ class SearchToolkit(BaseToolkit):
|
|
|
535
565
|
modified_query = f"{query} {exclusion_terms}"
|
|
536
566
|
logger.debug(f"Excluded domains, modified query: {modified_query}")
|
|
537
567
|
|
|
568
|
+
encoded_query = quote(modified_query)
|
|
569
|
+
|
|
538
570
|
# Constructing the URL
|
|
539
571
|
# Doc: https://developers.google.com/custom-search/v1/using_rest
|
|
540
572
|
base_url = (
|
|
541
573
|
f"https://www.googleapis.com/customsearch/v1?"
|
|
542
|
-
f"key={GOOGLE_API_KEY}&cx={SEARCH_ENGINE_ID}&q={
|
|
574
|
+
f"key={GOOGLE_API_KEY}&cx={SEARCH_ENGINE_ID}&q={encoded_query}&start="
|
|
543
575
|
f"{start_page_idx}&lr={search_language}&num={number_of_result_pages}"
|
|
544
576
|
)
|
|
545
577
|
|
|
@@ -584,7 +616,6 @@ class SearchToolkit(BaseToolkit):
|
|
|
584
616
|
"context_url": context_url,
|
|
585
617
|
}
|
|
586
618
|
|
|
587
|
-
# Add dimensions if available
|
|
588
619
|
if width:
|
|
589
620
|
response["width"] = int(width)
|
|
590
621
|
if height:
|
|
@@ -592,8 +623,6 @@ class SearchToolkit(BaseToolkit):
|
|
|
592
623
|
|
|
593
624
|
responses.append(response)
|
|
594
625
|
else:
|
|
595
|
-
# Process web search results (existing logic)
|
|
596
|
-
# Check metatags are present
|
|
597
626
|
if "pagemap" not in search_item:
|
|
598
627
|
continue
|
|
599
628
|
if "metatags" not in search_item["pagemap"]:
|
|
@@ -607,12 +636,9 @@ class SearchToolkit(BaseToolkit):
|
|
|
607
636
|
][0]["og:description"]
|
|
608
637
|
else:
|
|
609
638
|
long_description = "N/A"
|
|
610
|
-
# Get the page title
|
|
611
639
|
title = search_item.get("title")
|
|
612
|
-
# Page snippet
|
|
613
640
|
snippet = search_item.get("snippet")
|
|
614
641
|
|
|
615
|
-
# Extract the page url
|
|
616
642
|
link = search_item.get("link")
|
|
617
643
|
response = {
|
|
618
644
|
"result_id": i,
|
|
@@ -623,22 +649,42 @@ class SearchToolkit(BaseToolkit):
|
|
|
623
649
|
}
|
|
624
650
|
responses.append(response)
|
|
625
651
|
else:
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
652
|
+
if "error" in data:
|
|
653
|
+
error_info = data.get("error", {})
|
|
654
|
+
logger.error(
|
|
655
|
+
f"Google search failed - API response: {error_info}"
|
|
656
|
+
)
|
|
657
|
+
responses.append(
|
|
658
|
+
{
|
|
659
|
+
"error": f"Google search failed - "
|
|
660
|
+
f"API response: {error_info}"
|
|
661
|
+
}
|
|
662
|
+
)
|
|
663
|
+
elif "searchInformation" in data:
|
|
664
|
+
search_info = data.get("searchInformation", {})
|
|
665
|
+
total_results = search_info.get("totalResults", "0")
|
|
666
|
+
if total_results == "0":
|
|
667
|
+
logger.info(f"No results found for query: {query}")
|
|
668
|
+
# Return empty list to indicate no results (not an error)
|
|
669
|
+
responses = []
|
|
670
|
+
else:
|
|
671
|
+
logger.warning(
|
|
672
|
+
f"Google search returned no items but claims {total_results} results"
|
|
673
|
+
)
|
|
674
|
+
responses = []
|
|
675
|
+
else:
|
|
676
|
+
logger.error(
|
|
677
|
+
f"Unexpected Google API response format: {data}"
|
|
678
|
+
)
|
|
679
|
+
responses.append(
|
|
680
|
+
{"error": "Unexpected response format from Google API"}
|
|
681
|
+
)
|
|
636
682
|
|
|
637
683
|
except Exception as e:
|
|
638
684
|
responses.append({"error": f"google search failed: {e!s}"})
|
|
639
685
|
return responses
|
|
640
686
|
|
|
641
|
-
def
|
|
687
|
+
def search_tavily(
|
|
642
688
|
self, query: str, number_of_result_pages: int = 10, **kwargs
|
|
643
689
|
) -> List[Dict[str, Any]]:
|
|
644
690
|
r"""Use Tavily Search API to search information for the given query.
|
|
@@ -1233,6 +1279,73 @@ class SearchToolkit(BaseToolkit):
|
|
|
1233
1279
|
f"search: {e!s}"
|
|
1234
1280
|
}
|
|
1235
1281
|
|
|
1282
|
+
@api_keys_required([(None, 'METASO_API_KEY')])
|
|
1283
|
+
def search_metaso(
|
|
1284
|
+
self,
|
|
1285
|
+
query: str,
|
|
1286
|
+
page: int = 1,
|
|
1287
|
+
include_summary: bool = False,
|
|
1288
|
+
include_raw_content: bool = False,
|
|
1289
|
+
concise_snippet: bool = False,
|
|
1290
|
+
scope: Literal[
|
|
1291
|
+
"webpage", "document", "scholar", "image", "video", "podcast"
|
|
1292
|
+
] = "webpage",
|
|
1293
|
+
) -> Dict[str, Any]:
|
|
1294
|
+
r"""Perform a web search using the metaso.cn API.
|
|
1295
|
+
|
|
1296
|
+
Args:
|
|
1297
|
+
query (str): The search query string.
|
|
1298
|
+
page (int): Page number. (default: :obj:`1`)
|
|
1299
|
+
include_summary (bool): Whether to include summary in the result.
|
|
1300
|
+
(default: :obj:`False`)
|
|
1301
|
+
include_raw_content (bool): Whether to include raw content in the
|
|
1302
|
+
result. (default: :obj:`False`)
|
|
1303
|
+
concise_snippet (bool): Whether to return concise snippet.
|
|
1304
|
+
(default: :obj:`False`)
|
|
1305
|
+
scope (Literal["webpage", "document", "scholar", "image", "video",
|
|
1306
|
+
"podcast"]): Search scope. (default: :obj:`"webpage"`)
|
|
1307
|
+
|
|
1308
|
+
Returns:
|
|
1309
|
+
Dict[str, Any]: Search results or error information.
|
|
1310
|
+
"""
|
|
1311
|
+
import http.client
|
|
1312
|
+
import json
|
|
1313
|
+
|
|
1314
|
+
# It is recommended to put the token in environment variable for
|
|
1315
|
+
# security
|
|
1316
|
+
|
|
1317
|
+
METASO_API_KEY = os.getenv("METASO_API_KEY")
|
|
1318
|
+
|
|
1319
|
+
conn = http.client.HTTPSConnection("metaso.cn")
|
|
1320
|
+
payload = json.dumps(
|
|
1321
|
+
{
|
|
1322
|
+
"q": query,
|
|
1323
|
+
"scope": scope,
|
|
1324
|
+
"includeSummary": include_summary,
|
|
1325
|
+
"page": str(page),
|
|
1326
|
+
"includeRawContent": include_raw_content,
|
|
1327
|
+
"conciseSnippet": concise_snippet,
|
|
1328
|
+
}
|
|
1329
|
+
)
|
|
1330
|
+
headers = {
|
|
1331
|
+
'Authorization': f'Bearer {METASO_API_KEY}',
|
|
1332
|
+
'Accept': 'application/json',
|
|
1333
|
+
'Content-Type': 'application/json',
|
|
1334
|
+
}
|
|
1335
|
+
try:
|
|
1336
|
+
conn.request("POST", "/api/v1/search", payload, headers)
|
|
1337
|
+
res = conn.getresponse()
|
|
1338
|
+
data = res.read()
|
|
1339
|
+
result = data.decode("utf-8")
|
|
1340
|
+
try:
|
|
1341
|
+
return json.loads(result)
|
|
1342
|
+
except Exception:
|
|
1343
|
+
return {
|
|
1344
|
+
"error": f"Metaso returned content could not be parsed: {result}"
|
|
1345
|
+
}
|
|
1346
|
+
except Exception as e:
|
|
1347
|
+
return {"error": f"Metaso search failed: {e}"}
|
|
1348
|
+
|
|
1236
1349
|
def get_tools(self) -> List[FunctionTool]:
|
|
1237
1350
|
r"""Returns a list of FunctionTool objects representing the
|
|
1238
1351
|
functions in the toolkit.
|
|
@@ -1246,11 +1359,22 @@ class SearchToolkit(BaseToolkit):
|
|
|
1246
1359
|
FunctionTool(self.search_linkup),
|
|
1247
1360
|
FunctionTool(self.search_google),
|
|
1248
1361
|
FunctionTool(self.search_duckduckgo),
|
|
1249
|
-
FunctionTool(self.
|
|
1362
|
+
FunctionTool(self.search_tavily),
|
|
1250
1363
|
FunctionTool(self.search_brave),
|
|
1251
1364
|
FunctionTool(self.search_bocha),
|
|
1252
1365
|
FunctionTool(self.search_baidu),
|
|
1253
1366
|
FunctionTool(self.search_bing),
|
|
1254
1367
|
FunctionTool(self.search_exa),
|
|
1255
1368
|
FunctionTool(self.search_alibaba_tongxiao),
|
|
1369
|
+
FunctionTool(self.search_metaso),
|
|
1256
1370
|
]
|
|
1371
|
+
|
|
1372
|
+
# Deprecated method alias for backward compatibility
|
|
1373
|
+
def tavily_search(self, *args, **kwargs):
|
|
1374
|
+
r"""Deprecated: Use search_tavily instead for consistency with other search methods."""
|
|
1375
|
+
warnings.warn(
|
|
1376
|
+
"tavily_search is deprecated. Use search_tavily instead for consistency.",
|
|
1377
|
+
DeprecationWarning,
|
|
1378
|
+
stacklevel=2,
|
|
1379
|
+
)
|
|
1380
|
+
return self.search_tavily(*args, **kwargs)
|
camel/types/__init__.py
CHANGED
|
@@ -41,8 +41,8 @@ from .openai_types import (
|
|
|
41
41
|
ChatCompletionAssistantMessageParam,
|
|
42
42
|
ChatCompletionChunk,
|
|
43
43
|
ChatCompletionMessage,
|
|
44
|
+
ChatCompletionMessageFunctionToolCall,
|
|
44
45
|
ChatCompletionMessageParam,
|
|
45
|
-
ChatCompletionMessageToolCall,
|
|
46
46
|
ChatCompletionSystemMessageParam,
|
|
47
47
|
ChatCompletionToolMessageParam,
|
|
48
48
|
ChatCompletionUserMessageParam,
|
|
@@ -71,7 +71,7 @@ __all__ = [
|
|
|
71
71
|
'ChatCompletionUserMessageParam',
|
|
72
72
|
'ChatCompletionAssistantMessageParam',
|
|
73
73
|
'ChatCompletionToolMessageParam',
|
|
74
|
-
'
|
|
74
|
+
'ChatCompletionMessageFunctionToolCall',
|
|
75
75
|
'CompletionUsage',
|
|
76
76
|
'OpenAIImageType',
|
|
77
77
|
'OpenAIVisionDetailType',
|