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.

Files changed (80) hide show
  1. camel/__init__.py +1 -1
  2. camel/agents/chat_agent.py +149 -95
  3. camel/configs/__init__.py +3 -0
  4. camel/configs/nebius_config.py +103 -0
  5. camel/interpreters/e2b_interpreter.py +34 -1
  6. camel/models/__init__.py +2 -0
  7. camel/models/aiml_model.py +1 -16
  8. camel/models/anthropic_model.py +6 -22
  9. camel/models/aws_bedrock_model.py +1 -16
  10. camel/models/azure_openai_model.py +1 -16
  11. camel/models/base_model.py +0 -12
  12. camel/models/cohere_model.py +1 -16
  13. camel/models/crynux_model.py +1 -16
  14. camel/models/deepseek_model.py +1 -16
  15. camel/models/gemini_model.py +1 -16
  16. camel/models/groq_model.py +1 -17
  17. camel/models/internlm_model.py +1 -16
  18. camel/models/litellm_model.py +1 -16
  19. camel/models/lmstudio_model.py +1 -17
  20. camel/models/mistral_model.py +1 -16
  21. camel/models/model_factory.py +2 -0
  22. camel/models/modelscope_model.py +1 -16
  23. camel/models/moonshot_model.py +6 -22
  24. camel/models/nebius_model.py +83 -0
  25. camel/models/nemotron_model.py +0 -5
  26. camel/models/netmind_model.py +1 -16
  27. camel/models/novita_model.py +1 -16
  28. camel/models/nvidia_model.py +1 -16
  29. camel/models/ollama_model.py +4 -19
  30. camel/models/openai_compatible_model.py +0 -3
  31. camel/models/openai_model.py +1 -22
  32. camel/models/openrouter_model.py +1 -17
  33. camel/models/ppio_model.py +1 -16
  34. camel/models/qianfan_model.py +1 -16
  35. camel/models/qwen_model.py +1 -16
  36. camel/models/reka_model.py +1 -16
  37. camel/models/samba_model.py +0 -32
  38. camel/models/sglang_model.py +1 -16
  39. camel/models/siliconflow_model.py +1 -16
  40. camel/models/stub_model.py +0 -4
  41. camel/models/togetherai_model.py +1 -16
  42. camel/models/vllm_model.py +1 -16
  43. camel/models/volcano_model.py +0 -17
  44. camel/models/watsonx_model.py +1 -16
  45. camel/models/yi_model.py +1 -16
  46. camel/models/zhipuai_model.py +1 -16
  47. camel/societies/workforce/prompts.py +1 -8
  48. camel/societies/workforce/task_channel.py +120 -27
  49. camel/societies/workforce/workforce.py +35 -3
  50. camel/toolkits/__init__.py +0 -2
  51. camel/toolkits/github_toolkit.py +104 -17
  52. camel/toolkits/hybrid_browser_toolkit/config_loader.py +3 -0
  53. camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit_ts.py +260 -5
  54. camel/toolkits/hybrid_browser_toolkit/ts/src/browser-session.ts +288 -37
  55. camel/toolkits/hybrid_browser_toolkit/ts/src/config-loader.ts +3 -1
  56. camel/toolkits/hybrid_browser_toolkit/ts/src/hybrid-browser-toolkit.ts +209 -41
  57. camel/toolkits/hybrid_browser_toolkit/ts/src/types.ts +22 -3
  58. camel/toolkits/hybrid_browser_toolkit/ts/websocket-server.js +28 -1
  59. camel/toolkits/hybrid_browser_toolkit/ws_wrapper.py +101 -0
  60. camel/toolkits/hybrid_browser_toolkit_py/actions.py +158 -0
  61. camel/toolkits/hybrid_browser_toolkit_py/browser_session.py +55 -8
  62. camel/toolkits/hybrid_browser_toolkit_py/config_loader.py +43 -0
  63. camel/toolkits/hybrid_browser_toolkit_py/hybrid_browser_toolkit.py +312 -3
  64. camel/toolkits/hybrid_browser_toolkit_py/snapshot.py +10 -4
  65. camel/toolkits/hybrid_browser_toolkit_py/unified_analyzer.js +45 -4
  66. camel/toolkits/math_toolkit.py +64 -10
  67. camel/toolkits/mcp_toolkit.py +39 -14
  68. camel/toolkits/openai_image_toolkit.py +55 -24
  69. camel/toolkits/search_toolkit.py +153 -29
  70. camel/types/__init__.py +2 -2
  71. camel/types/enums.py +54 -10
  72. camel/types/openai_types.py +2 -2
  73. camel/types/unified_model_type.py +5 -0
  74. camel/utils/mcp.py +2 -2
  75. camel/utils/token_counting.py +18 -3
  76. {camel_ai-0.2.74a5.dist-info → camel_ai-0.2.75.dist-info}/METADATA +9 -15
  77. {camel_ai-0.2.74a5.dist-info → camel_ai-0.2.75.dist-info}/RECORD +79 -78
  78. camel/toolkits/openai_agent_toolkit.py +0 -135
  79. {camel_ai-0.2.74a5.dist-info → camel_ai-0.2.75.dist-info}/WHEEL +0 -0
  80. {camel_ai-0.2.74a5.dist-info → camel_ai-0.2.75.dist-info}/licenses/LICENSE +0 -0
@@ -220,26 +220,34 @@ class MCPToolkit(BaseToolkit):
220
220
  self._exit_stack = AsyncExitStack()
221
221
 
222
222
  try:
223
- # Connect to all clients using AsyncExitStack
224
- for i, client in enumerate(self.clients):
225
- try:
226
- # Use MCPClient directly as async context manager
227
- await self._exit_stack.enter_async_context(client)
228
- msg = f"Connected to client {i+1}/{len(self.clients)}"
229
- logger.debug(msg)
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 self.n is not None:
156
- params["n"] = self.n # type: ignore[assignment]
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 file.
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
- # Add index to filename when multiple images
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
- with open(image_path, "wb") as f:
227
- f.write(image_bytes)
228
-
229
- results.append(f"Image saved to {image_path}")
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 save.
267
- (default: :obj:`"image"`)
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:
@@ -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. Either "web" for
462
- web pages or "image" for image search. (default: "web")
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. Adjust this based on your task - use fewer results
465
- for focused searches and more for comprehensive searches.
466
- (default: :obj:`10`)
467
- start_page (int): The result page to start from. Use this for
468
- pagination - e.g., start_page=1 for results 1-10,
469
- start_page=11 for results 11-20, etc. This allows agents to
470
- check initial results and continue searching if needed.
471
- (default: :obj:`1`)
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={modified_query}&start="
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
- error_info = data.get("error", {})
627
- logger.error(
628
- f"Google search failed - API response: {error_info}"
629
- )
630
- responses.append(
631
- {
632
- "error": f"Google search failed - "
633
- f"API response: {error_info}"
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 tavily_search(
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.tavily_search),
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
- 'ChatCompletionMessageToolCall',
74
+ 'ChatCompletionMessageFunctionToolCall',
75
75
  'CompletionUsage',
76
76
  'OpenAIImageType',
77
77
  'OpenAIVisionDetailType',