camel-ai 0.2.26__py3-none-any.whl → 0.2.27__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 CHANGED
@@ -14,7 +14,7 @@
14
14
 
15
15
  from camel.logger import disable_logging, enable_logging, set_log_level
16
16
 
17
- __version__ = '0.2.26'
17
+ __version__ = '0.2.27'
18
18
 
19
19
  __all__ = [
20
20
  '__version__',
camel/models/__init__.py CHANGED
@@ -37,9 +37,11 @@ from .qwen_model import QwenModel
37
37
  from .reka_model import RekaModel
38
38
  from .samba_model import SambaModel
39
39
  from .sglang_model import SGLangModel
40
+ from .siliconflow_model import SiliconFlowModel
40
41
  from .stub_model import StubModel
41
42
  from .togetherai_model import TogetherAIModel
42
43
  from .vllm_model import VLLMModel
44
+ from .volcano_model import VolcanoModel
43
45
  from .yi_model import YiModel
44
46
  from .zhipuai_model import ZhipuAIModel
45
47
 
@@ -76,4 +78,6 @@ __all__ = [
76
78
  'MoonshotModel',
77
79
  'AIMLModel',
78
80
  'BaseAudioModel',
81
+ 'SiliconFlowModel',
82
+ 'VolcanoModel',
79
83
  ]
@@ -128,18 +128,23 @@ class AzureOpenAIModel(BaseModelBackend):
128
128
  Args:
129
129
  messages (List[OpenAIMessage]): Message list with the chat history
130
130
  in OpenAI API format.
131
+ response_format (Optional[Type[BaseModel]]): The format of the
132
+ response.
133
+ tools (Optional[List[Dict[str, Any]]]): The schema of the tools to
134
+ use for the request.
131
135
 
132
136
  Returns:
133
137
  Union[ChatCompletion, Stream[ChatCompletionChunk]]:
134
138
  `ChatCompletion` in the non-stream mode, or
135
139
  `Stream[ChatCompletionChunk]` in the stream mode.
136
140
  """
137
- response = self._client.chat.completions.create(
138
- messages=messages,
139
- model=self.azure_deployment_name, # type:ignore[arg-type]
140
- **self.model_config_dict,
141
+ response_format = response_format or self.model_config_dict.get(
142
+ "response_format", None
141
143
  )
142
- return response
144
+ if response_format:
145
+ return self._request_parse(messages, response_format, tools)
146
+ else:
147
+ return self._request_chat_completion(messages, tools)
143
148
 
144
149
  async def _arun(
145
150
  self,
@@ -152,18 +157,93 @@ class AzureOpenAIModel(BaseModelBackend):
152
157
  Args:
153
158
  messages (List[OpenAIMessage]): Message list with the chat history
154
159
  in OpenAI API format.
160
+ response_format (Optional[Type[BaseModel]]): The format of the
161
+ response.
162
+ tools (Optional[List[Dict[str, Any]]]): The schema of the tools to
163
+ use for the request.
155
164
 
156
165
  Returns:
157
166
  Union[ChatCompletion, AsyncStream[ChatCompletionChunk]]:
158
167
  `ChatCompletion` in the non-stream mode, or
159
168
  `AsyncStream[ChatCompletionChunk]` in the stream mode.
160
169
  """
161
- response = await self._async_client.chat.completions.create(
170
+ response_format = response_format or self.model_config_dict.get(
171
+ "response_format", None
172
+ )
173
+ if response_format:
174
+ return await self._arequest_parse(messages, response_format, tools)
175
+ else:
176
+ return await self._arequest_chat_completion(messages, tools)
177
+
178
+ def _request_chat_completion(
179
+ self,
180
+ messages: List[OpenAIMessage],
181
+ tools: Optional[List[Dict[str, Any]]] = None,
182
+ ) -> Union[ChatCompletion, Stream[ChatCompletionChunk]]:
183
+ request_config = self.model_config_dict.copy()
184
+
185
+ if tools:
186
+ request_config["tools"] = tools
187
+
188
+ return self._client.chat.completions.create(
189
+ messages=messages,
190
+ model=self.azure_deployment_name, # type:ignore[arg-type]
191
+ **request_config,
192
+ )
193
+
194
+ async def _arequest_chat_completion(
195
+ self,
196
+ messages: List[OpenAIMessage],
197
+ tools: Optional[List[Dict[str, Any]]] = None,
198
+ ) -> Union[ChatCompletion, AsyncStream[ChatCompletionChunk]]:
199
+ request_config = self.model_config_dict.copy()
200
+
201
+ if tools:
202
+ request_config["tools"] = tools
203
+
204
+ return await self._async_client.chat.completions.create(
205
+ messages=messages,
206
+ model=self.azure_deployment_name, # type:ignore[arg-type]
207
+ **request_config,
208
+ )
209
+
210
+ def _request_parse(
211
+ self,
212
+ messages: List[OpenAIMessage],
213
+ response_format: Type[BaseModel],
214
+ tools: Optional[List[Dict[str, Any]]] = None,
215
+ ) -> ChatCompletion:
216
+ request_config = self.model_config_dict.copy()
217
+
218
+ request_config["response_format"] = response_format
219
+ request_config.pop("stream", None)
220
+ if tools is not None:
221
+ request_config["tools"] = tools
222
+
223
+ return self._client.beta.chat.completions.parse(
224
+ messages=messages,
225
+ model=self.azure_deployment_name, # type:ignore[arg-type]
226
+ **request_config,
227
+ )
228
+
229
+ async def _arequest_parse(
230
+ self,
231
+ messages: List[OpenAIMessage],
232
+ response_format: Type[BaseModel],
233
+ tools: Optional[List[Dict[str, Any]]] = None,
234
+ ) -> ChatCompletion:
235
+ request_config = self.model_config_dict.copy()
236
+
237
+ request_config["response_format"] = response_format
238
+ request_config.pop("stream", None)
239
+ if tools is not None:
240
+ request_config["tools"] = tools
241
+
242
+ return await self._async_client.beta.chat.completions.parse(
162
243
  messages=messages,
163
244
  model=self.azure_deployment_name, # type:ignore[arg-type]
164
- **self.model_config_dict,
245
+ **request_config,
165
246
  )
166
- return response
167
247
 
168
248
  def check_model_config(self):
169
249
  r"""Check whether the model configuration contains any
@@ -37,6 +37,7 @@ from camel.models.siliconflow_model import SiliconFlowModel
37
37
  from camel.models.stub_model import StubModel
38
38
  from camel.models.togetherai_model import TogetherAIModel
39
39
  from camel.models.vllm_model import VLLMModel
40
+ from camel.models.volcano_model import VolcanoModel
40
41
  from camel.models.yi_model import YiModel
41
42
  from camel.models.zhipuai_model import ZhipuAIModel
42
43
  from camel.types import ModelPlatformType, ModelType, UnifiedModelType
@@ -107,6 +108,8 @@ class ModelFactory:
107
108
  model_class = SiliconFlowModel
108
109
  elif model_platform.is_aiml:
109
110
  model_class = AIMLModel
111
+ elif model_platform.is_volcano:
112
+ model_class = VolcanoModel
110
113
 
111
114
  elif model_platform.is_openai and model_type.is_openai:
112
115
  model_class = OpenAIModel
@@ -86,18 +86,23 @@ class OpenAICompatibleModel(BaseModelBackend):
86
86
  Args:
87
87
  messages (List[OpenAIMessage]): Message list with the chat history
88
88
  in OpenAI API format.
89
+ response_format (Optional[Type[BaseModel]]): The format of the
90
+ response.
91
+ tools (Optional[List[Dict[str, Any]]]): The schema of the tools to
92
+ use for the request.
89
93
 
90
94
  Returns:
91
95
  Union[ChatCompletion, Stream[ChatCompletionChunk]]:
92
96
  `ChatCompletion` in the non-stream mode, or
93
97
  `Stream[ChatCompletionChunk]` in the stream mode.
94
98
  """
95
- response = self._client.chat.completions.create(
96
- messages=messages,
97
- model=self.model_type,
98
- **self.model_config_dict,
99
+ response_format = response_format or self.model_config_dict.get(
100
+ "response_format", None
99
101
  )
100
- return response
102
+ if response_format:
103
+ return self._request_parse(messages, response_format, tools)
104
+ else:
105
+ return self._request_chat_completion(messages, tools)
101
106
 
102
107
  async def _arun(
103
108
  self,
@@ -110,18 +115,93 @@ class OpenAICompatibleModel(BaseModelBackend):
110
115
  Args:
111
116
  messages (List[OpenAIMessage]): Message list with the chat history
112
117
  in OpenAI API format.
118
+ response_format (Optional[Type[BaseModel]]): The format of the
119
+ response.
120
+ tools (Optional[List[Dict[str, Any]]]): The schema of the tools to
121
+ use for the request.
113
122
 
114
123
  Returns:
115
124
  Union[ChatCompletion, AsyncStream[ChatCompletionChunk]]:
116
125
  `ChatCompletion` in the non-stream mode, or
117
126
  `AsyncStream[ChatCompletionChunk]` in the stream mode.
118
127
  """
119
- response = await self._async_client.chat.completions.create(
128
+ response_format = response_format or self.model_config_dict.get(
129
+ "response_format", None
130
+ )
131
+ if response_format:
132
+ return await self._arequest_parse(messages, response_format, tools)
133
+ else:
134
+ return await self._arequest_chat_completion(messages, tools)
135
+
136
+ def _request_chat_completion(
137
+ self,
138
+ messages: List[OpenAIMessage],
139
+ tools: Optional[List[Dict[str, Any]]] = None,
140
+ ) -> Union[ChatCompletion, Stream[ChatCompletionChunk]]:
141
+ request_config = self.model_config_dict.copy()
142
+
143
+ if tools:
144
+ request_config["tools"] = tools
145
+
146
+ return self._client.chat.completions.create(
147
+ messages=messages,
148
+ model=self.model_type,
149
+ **request_config,
150
+ )
151
+
152
+ async def _arequest_chat_completion(
153
+ self,
154
+ messages: List[OpenAIMessage],
155
+ tools: Optional[List[Dict[str, Any]]] = None,
156
+ ) -> Union[ChatCompletion, AsyncStream[ChatCompletionChunk]]:
157
+ request_config = self.model_config_dict.copy()
158
+
159
+ if tools:
160
+ request_config["tools"] = tools
161
+
162
+ return await self._async_client.chat.completions.create(
163
+ messages=messages,
164
+ model=self.model_type,
165
+ **request_config,
166
+ )
167
+
168
+ def _request_parse(
169
+ self,
170
+ messages: List[OpenAIMessage],
171
+ response_format: Type[BaseModel],
172
+ tools: Optional[List[Dict[str, Any]]] = None,
173
+ ) -> ChatCompletion:
174
+ request_config = self.model_config_dict.copy()
175
+
176
+ request_config["response_format"] = response_format
177
+ request_config.pop("stream", None)
178
+ if tools is not None:
179
+ request_config["tools"] = tools
180
+
181
+ return self._client.beta.chat.completions.parse(
182
+ messages=messages,
183
+ model=self.model_type,
184
+ **request_config,
185
+ )
186
+
187
+ async def _arequest_parse(
188
+ self,
189
+ messages: List[OpenAIMessage],
190
+ response_format: Type[BaseModel],
191
+ tools: Optional[List[Dict[str, Any]]] = None,
192
+ ) -> ChatCompletion:
193
+ request_config = self.model_config_dict.copy()
194
+
195
+ request_config["response_format"] = response_format
196
+ request_config.pop("stream", None)
197
+ if tools is not None:
198
+ request_config["tools"] = tools
199
+
200
+ return await self._async_client.beta.chat.completions.parse(
120
201
  messages=messages,
121
202
  model=self.model_type,
122
- **self.model_config_dict,
203
+ **request_config,
123
204
  )
124
- return response
125
205
 
126
206
  @property
127
207
  def token_counter(self) -> BaseTokenCounter:
@@ -0,0 +1,100 @@
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, Optional, Union
17
+
18
+ from camel.configs import OPENAI_API_PARAMS
19
+ from camel.models.openai_compatible_model import OpenAICompatibleModel
20
+ from camel.types import ModelType
21
+ from camel.utils import (
22
+ BaseTokenCounter,
23
+ OpenAITokenCounter,
24
+ api_keys_required,
25
+ )
26
+
27
+
28
+ class VolcanoModel(OpenAICompatibleModel):
29
+ r"""Volcano Engine API in a unified BaseModelBackend interface.
30
+
31
+ Args:
32
+ model_type (Union[ModelType, str]): Model for which a backend is
33
+ created.
34
+ model_config_dict (Optional[Dict[str, Any]], optional): A dictionary
35
+ that will be fed into the API call. If
36
+ :obj:`None`, :obj:`{}` will be used. (default: :obj:`None`)
37
+ api_key (Optional[str], optional): The API key for authenticating with
38
+ the Volcano Engine service. (default: :obj:`None`)
39
+ url (Optional[str], optional): The url to the Volcano Engine service.
40
+ (default: :obj:`https://ark.cn-beijing.volces.com/api/v3`)
41
+ token_counter (Optional[BaseTokenCounter], optional): Token counter to
42
+ use for the model. If not provided, :obj:`OpenAITokenCounter`
43
+ will be used. (default: :obj:`None`)
44
+ """
45
+
46
+ @api_keys_required(
47
+ [
48
+ ("api_key", "VOLCANO_API_KEY"),
49
+ ]
50
+ )
51
+ def __init__(
52
+ self,
53
+ model_type: Union[ModelType, str],
54
+ model_config_dict: Optional[Dict[str, Any]] = None,
55
+ api_key: Optional[str] = None,
56
+ url: Optional[str] = None,
57
+ token_counter: Optional[BaseTokenCounter] = None,
58
+ ) -> None:
59
+ if model_config_dict is None:
60
+ model_config_dict = {}
61
+
62
+ api_key = api_key or os.environ.get("VOLCANO_API_KEY")
63
+ url = (
64
+ url
65
+ or os.environ.get("VOLCANO_API_BASE_URL")
66
+ or "https://ark.cn-beijing.volces.com/api/v3"
67
+ )
68
+
69
+ super().__init__(
70
+ model_type, model_config_dict, api_key, url, token_counter
71
+ )
72
+
73
+ @property
74
+ def token_counter(self) -> BaseTokenCounter:
75
+ r"""Initialize the token counter for the model backend.
76
+
77
+ Returns:
78
+ BaseTokenCounter: The token counter following the model's
79
+ tokenization style.
80
+ """
81
+ if not self._token_counter:
82
+ # Use OpenAI token counter as an approximation
83
+ self._token_counter = OpenAITokenCounter(ModelType.GPT_4O_MINI)
84
+ return self._token_counter
85
+
86
+ def check_model_config(self):
87
+ r"""Check whether the model configuration is valid for Volcano
88
+ model backends.
89
+
90
+ Raises:
91
+ ValueError: If the model configuration dictionary contains any
92
+ unexpected arguments to Volcano API.
93
+ """
94
+ # Using OpenAI API params as Volcano Engine API is OpenAI-compatible
95
+ for param in self.model_config_dict:
96
+ if param not in OPENAI_API_PARAMS:
97
+ raise ValueError(
98
+ f"Unexpected argument `{param}` is "
99
+ "input into Volcano model backend."
100
+ )
@@ -55,7 +55,7 @@ from .excel_toolkit import ExcelToolkit
55
55
  from .video_analysis_toolkit import VideoAnalysisToolkit
56
56
  from .image_analysis_toolkit import ImageAnalysisToolkit
57
57
  from .mcp_toolkit import MCPToolkit
58
- from .web_toolkit import WebToolkit
58
+ from .browser_toolkit import BrowserToolkit
59
59
  from .file_write_toolkit import FileWriteToolkit
60
60
  from .terminal_toolkit import TerminalToolkit
61
61
  from .pubmed_toolkit import PubMedToolkit
@@ -102,7 +102,7 @@ __all__ = [
102
102
  'ExcelToolkit',
103
103
  'VideoAnalysisToolkit',
104
104
  'ImageAnalysisToolkit',
105
- 'WebToolkit',
105
+ 'BrowserToolkit',
106
106
  'FileWriteToolkit',
107
107
  'TerminalToolkit',
108
108
  'PubMedToolkit',
@@ -92,7 +92,7 @@ ACTION_WITH_FEEDBACK_LIST = [
92
92
  ]
93
93
 
94
94
 
95
- # codes from magentic-one
95
+ # Code from magentic-one
96
96
  class DOMRectangle(TypedDict):
97
97
  x: Union[int, float]
98
98
  y: Union[int, float]
@@ -127,21 +127,36 @@ class InteractiveRegion(TypedDict):
127
127
 
128
128
 
129
129
  def _get_str(d: Any, k: str) -> str:
130
+ r"""Safely retrieve a string value from a dictionary."""
131
+ if k not in d:
132
+ raise KeyError(f"Missing required key: '{k}'")
130
133
  val = d[k]
131
- assert isinstance(val, str)
132
- return val
134
+ if isinstance(val, str):
135
+ return val
136
+ raise TypeError(
137
+ f"Expected a string for key '{k}', " f"but got {type(val).__name__}"
138
+ )
133
139
 
134
140
 
135
141
  def _get_number(d: Any, k: str) -> Union[int, float]:
142
+ r"""Safely retrieve a number (int or float) from a dictionary"""
136
143
  val = d[k]
137
- assert isinstance(val, int) or isinstance(val, float)
138
- return val
144
+ if isinstance(val, (int, float)):
145
+ return val
146
+ raise TypeError(
147
+ f"Expected a number (int/float) for key "
148
+ f"'{k}', but got {type(val).__name__}"
149
+ )
139
150
 
140
151
 
141
152
  def _get_bool(d: Any, k: str) -> bool:
153
+ r"""Safely retrieve a boolean value from a dictionary."""
142
154
  val = d[k]
143
- assert isinstance(val, bool)
144
- return val
155
+ if isinstance(val, bool):
156
+ return val
157
+ raise TypeError(
158
+ f"Expected a boolean for key '{k}', " f"but got {type(val).__name__}"
159
+ )
145
160
 
146
161
 
147
162
  def _parse_json_output(text: str) -> Dict[str, Any]:
@@ -208,7 +223,8 @@ def _reload_image(image: Image.Image):
208
223
  return Image.open(buffer)
209
224
 
210
225
 
211
- def domrectangle_from_dict(rect: Dict[str, Any]) -> DOMRectangle:
226
+ def dom_rectangle_from_dict(rect: Dict[str, Any]) -> DOMRectangle:
227
+ r"""Create a DOMRectangle object from a dictionary."""
212
228
  return DOMRectangle(
213
229
  x=_get_number(rect, "x"),
214
230
  y=_get_number(rect, "y"),
@@ -221,10 +237,11 @@ def domrectangle_from_dict(rect: Dict[str, Any]) -> DOMRectangle:
221
237
  )
222
238
 
223
239
 
224
- def interactiveregion_from_dict(region: Dict[str, Any]) -> InteractiveRegion:
240
+ def interactive_region_from_dict(region: Dict[str, Any]) -> InteractiveRegion:
241
+ r"""Create an :class:`InteractiveRegion` object from a dictionary."""
225
242
  typed_rects: List[DOMRectangle] = []
226
243
  for rect in region["rects"]:
227
- typed_rects.append(domrectangle_from_dict(rect))
244
+ typed_rects.append(dom_rectangle_from_dict(rect))
228
245
 
229
246
  return InteractiveRegion(
230
247
  tag_name=_get_str(region, "tag_name"),
@@ -235,7 +252,8 @@ def interactiveregion_from_dict(region: Dict[str, Any]) -> InteractiveRegion:
235
252
  )
236
253
 
237
254
 
238
- def visualviewport_from_dict(viewport: Dict[str, Any]) -> VisualViewport:
255
+ def visual_viewport_from_dict(viewport: Dict[str, Any]) -> VisualViewport:
256
+ r"""Create a :class:`VisualViewport` object from a dictionary."""
239
257
  return VisualViewport(
240
258
  height=_get_number(viewport, "height"),
241
259
  width=_get_number(viewport, "width"),
@@ -252,7 +270,7 @@ def visualviewport_from_dict(viewport: Dict[str, Any]) -> VisualViewport:
252
270
 
253
271
 
254
272
  def add_set_of_mark(
255
- screenshot: bytes | Image.Image | io.BufferedIOBase,
273
+ screenshot: Union[bytes, Image.Image, io.BufferedIOBase],
256
274
  ROIs: Dict[str, InteractiveRegion],
257
275
  ) -> Tuple[Image.Image, List[str], List[str], List[str]]:
258
276
  if isinstance(screenshot, Image.Image):
@@ -272,6 +290,18 @@ def add_set_of_mark(
272
290
  def _add_set_of_mark(
273
291
  screenshot: Image.Image, ROIs: Dict[str, InteractiveRegion]
274
292
  ) -> Tuple[Image.Image, List[str], List[str], List[str]]:
293
+ r"""Add a set of marks to the screenshot.
294
+
295
+ Args:
296
+ screenshot (Image.Image): The screenshot to add marks to.
297
+ ROIs (Dict[str, InteractiveRegion]): The regions to add marks to.
298
+
299
+ Returns:
300
+ Tuple[Image.Image, List[str], List[str], List[str]]: A tuple
301
+ containing the screenshot with marked ROIs, ROIs fully within the
302
+ images, ROIs located above the visible area, and ROIs located below
303
+ the visible area.
304
+ """
275
305
  visible_rects: List[str] = list()
276
306
  rects_above: List[str] = list() # Scroll up to see
277
307
  rects_below: List[str] = list() # Scroll down to see
@@ -284,22 +314,22 @@ def _add_set_of_mark(
284
314
  for r in ROIs:
285
315
  for rect in ROIs[r]["rects"]:
286
316
  # Empty rectangles
287
- if not rect:
288
- continue
289
- if rect["width"] * rect["height"] == 0:
317
+ if not rect or rect["width"] == 0 or rect["height"] == 0:
290
318
  continue
291
319
 
292
- mid = (
293
- (rect["right"] + rect["left"]) / 2.0,
294
- (rect["top"] + rect["bottom"]) / 2.0,
295
- )
320
+ # TODO: add scroll left and right?
321
+ horizontal_center = (rect["right"] + rect["left"]) / 2.0
322
+ vertical_center = (rect["top"] + rect["bottom"]) / 2.0
323
+ is_within_horizon = 0 <= horizontal_center < base.size[0]
324
+ is_above_viewport = vertical_center < 0
325
+ is_below_viewport = vertical_center >= base.size[1]
296
326
 
297
- if 0 <= mid[0] and mid[0] < base.size[0]:
298
- if mid[1] < 0:
327
+ if is_within_horizon:
328
+ if is_above_viewport:
299
329
  rects_above.append(r)
300
- elif mid[1] >= base.size[1]:
330
+ elif is_below_viewport:
301
331
  rects_below.append(r)
302
- else:
332
+ else: # Fully visible
303
333
  visible_rects.append(r)
304
334
  _draw_roi(draw, int(r), fnt, rect)
305
335
 
@@ -314,9 +344,16 @@ def _draw_roi(
314
344
  font: ImageFont.FreeTypeFont | ImageFont.ImageFont,
315
345
  rect: DOMRectangle,
316
346
  ) -> None:
317
- color = _color(idx)
318
- luminance = color[0] * 0.3 + color[1] * 0.59 + color[2] * 0.11
319
- text_color = (0, 0, 0, 255) if luminance > 90 else (255, 255, 255, 255)
347
+ r"""Draw a ROI on the image.
348
+
349
+ Args:
350
+ draw (ImageDraw.ImageDraw): The draw object.
351
+ idx (int): The index of the ROI.
352
+ font (ImageFont.FreeTypeFont | ImageFont.ImageFont): The font.
353
+ rect (DOMRectangle): The DOM rectangle.
354
+ """
355
+ color = _get_random_color(idx)
356
+ text_color = _get_text_color(color)
320
357
 
321
358
  roi = ((rect["left"], rect["top"]), (rect["right"], rect["bottom"]))
322
359
 
@@ -351,9 +388,36 @@ def _draw_roi(
351
388
  )
352
389
 
353
390
 
354
- def _color(identifier: int) -> Tuple[int, int, int, int]:
391
+ def _get_text_color(
392
+ bg_color: Tuple[int, int, int, int],
393
+ ) -> Tuple[int, int, int, int]:
394
+ r"""Determine the ideal text color (black or white) for contrast.
395
+
396
+ Args:
397
+ bg_color: The background color (R, G, B, A).
398
+
399
+ Returns:
400
+ A tuple representing black or white color for text.
401
+ """
402
+ luminance = bg_color[0] * 0.3 + bg_color[1] * 0.59 + bg_color[2] * 0.11
403
+ return (0, 0, 0, 255) if luminance > 120 else (255, 255, 255, 255)
404
+
405
+
406
+ def _get_random_color(identifier: int) -> Tuple[int, int, int, int]:
407
+ r"""Generate a consistent random RGBA color based on the identifier.
408
+
409
+ Args:
410
+ identifier: The ID used as a seed to ensure color consistency.
411
+
412
+ Returns:
413
+ A tuple representing (R, G, B, A) values.
414
+ """
355
415
  rnd = random.Random(int(identifier))
356
- color = [rnd.randint(0, 255), rnd.randint(125, 255), rnd.randint(0, 50)]
416
+ r = rnd.randint(0, 255)
417
+ g = rnd.randint(125, 255)
418
+ b = rnd.randint(0, 50)
419
+ color = [r, g, b]
420
+ # TODO: check why shuffle is needed?
357
421
  rnd.shuffle(color)
358
422
  color.append(255)
359
423
  return cast(Tuple[int, int, int, int], tuple(color))
@@ -379,13 +443,11 @@ class BaseBrowser:
379
443
  self.playwright = sync_playwright().start()
380
444
  self.page_history: list = [] # stores the history of visited pages
381
445
 
382
- # set the cache directory
383
- self.cache_dir = "tmp/"
446
+ # Set the cache directory
447
+ self.cache_dir = "tmp/" if cache_dir is None else cache_dir
384
448
  os.makedirs(self.cache_dir, exist_ok=True)
385
- if cache_dir is not None:
386
- self.cache_dir = cache_dir
387
449
 
388
- # load the page script
450
+ # Load the page script
389
451
  abs_dir_path = os.path.dirname(os.path.abspath(__file__))
390
452
  page_script_path = os.path.join(abs_dir_path, "page_script.js")
391
453
 
@@ -398,34 +460,35 @@ class BaseBrowser:
398
460
  f"Page script file not found at path: {page_script_path}"
399
461
  )
400
462
 
401
- def init(self):
463
+ def init(self) -> None:
402
464
  r"""Initialize the browser."""
403
- self.browser = self.playwright.chromium.launch(
404
- headless=self.headless
405
- ) # Launch the browser, if headless is False, the browser will display
406
- self.context = self.browser.new_context(
407
- accept_downloads=True
408
- ) # create a new context
409
- self.page = self.context.new_page() # create a new page
410
-
411
- def clean_cache(self):
412
- r"""delete the cache directory and its contents."""
465
+ # Launch the browser, if headless is False, the browser will display
466
+ self.browser = self.playwright.chromium.launch(headless=self.headless)
467
+ # Create a new context
468
+ self.context = self.browser.new_context(accept_downloads=True)
469
+ # Create a new page
470
+ self.page = self.context.new_page()
471
+
472
+ def clean_cache(self) -> None:
473
+ r"""Delete the cache directory and its contents."""
413
474
  if os.path.exists(self.cache_dir):
414
475
  shutil.rmtree(self.cache_dir)
415
476
 
416
- def _wait_for_load(self, timeout: int = 20):
477
+ def _wait_for_load(self, timeout: int = 20) -> None:
417
478
  r"""Wait for a certain amount of time for the page to load."""
418
479
  timeout_ms = timeout * 1000
419
480
 
420
481
  self.page.wait_for_load_state("load", timeout=timeout_ms)
482
+
483
+ # TODO: check if this is needed
421
484
  time.sleep(2)
422
485
 
423
- def click_blank_area(self):
486
+ def click_blank_area(self) -> None:
424
487
  r"""Click a blank area of the page to unfocus the current element."""
425
488
  self.page.mouse.click(0, 0)
426
489
  self._wait_for_load()
427
490
 
428
- def visit_page(self, url: str):
491
+ def visit_page(self, url: str) -> None:
429
492
  r"""Visit a page with the given URL."""
430
493
 
431
494
  self.page.goto(url)
@@ -433,8 +496,8 @@ class BaseBrowser:
433
496
  self.page_url = url
434
497
 
435
498
  def ask_question_about_video(self, question: str) -> str:
436
- r"""Ask a question about the video on the current page. It is suitable
437
- to process youtube video.
499
+ r"""Ask a question about the video on the current page,
500
+ such as YouTube video.
438
501
 
439
502
  Args:
440
503
  question (str): The question to ask.
@@ -459,8 +522,9 @@ class BaseBrowser:
459
522
  directory.
460
523
 
461
524
  Returns:
462
- Tuple[Image.Image, str]: A tuple containing the screenshot image
463
- and the path to the image file.
525
+ Tuple[Image.Image, str]: A tuple containing the screenshot
526
+ image and the path to the image file if saved, otherwise
527
+ :obj:`None`.
464
528
  """
465
529
 
466
530
  image_data = self.page.screenshot(timeout=60000)
@@ -468,12 +532,13 @@ class BaseBrowser:
468
532
 
469
533
  file_path = None
470
534
  if save_image:
471
- # get url name to form a file name
535
+ # Get url name to form a file name
536
+ # TODO: Use a safer way for the url name
472
537
  url_name = self.page_url.split("/")[-1]
473
538
  for char in ['\\', '/', ':', '*', '?', '"', '<', '>', '|', '.']:
474
539
  url_name = url_name.replace(char, "_")
475
540
 
476
- # get formatted time: mmddhhmmss
541
+ # Get formatted time: mmddhhmmss
477
542
  timestamp = datetime.datetime.now().strftime("%m%d%H%M%S")
478
543
  file_path = os.path.join(
479
544
  self.cache_dir, f"{url_name}_{timestamp}.png"
@@ -492,23 +557,18 @@ class BaseBrowser:
492
557
 
493
558
  Args:
494
559
  scroll_ratio (float): The ratio of viewport height to scroll each
495
- step (default: 0.7).
560
+ step (default: 0.8).
496
561
 
497
562
  Returns:
498
563
  List[str]: A list of paths to the screenshot files.
499
564
  """
500
565
  screenshots = []
501
566
  scroll_height = self.page.evaluate("document.body.scrollHeight")
567
+ assert self.page.viewport_size is not None
502
568
  viewport_height = self.page.viewport_size["height"]
503
569
  current_scroll = 0
504
570
  screenshot_index = 1
505
571
 
506
- url_name = self.page.url.split("/")[-1].replace(".", "_")
507
- timestamp = datetime.datetime.now().strftime("%m%d%H%M%S")
508
- base_file_path = os.path.join(
509
- self.cache_dir, f"{url_name}_{timestamp}"
510
- )
511
-
512
572
  max_height = scroll_height - viewport_height
513
573
  scroll_step = int(viewport_height * scroll_ratio)
514
574
 
@@ -520,14 +580,15 @@ class BaseBrowser:
520
580
  f"{max_height}, step: {scroll_step}"
521
581
  )
522
582
 
523
- file_path = f"{base_file_path}_{screenshot_index}.png"
524
583
  _, file_path = self.get_screenshot(save_image=True)
525
584
  screenshots.append(file_path)
526
585
 
527
586
  self.page.evaluate(f"window.scrollBy(0, {scroll_step})")
587
+ # Allow time for content to load
528
588
  time.sleep(0.5)
529
589
 
530
590
  current_scroll = self.page.evaluate("window.scrollY")
591
+ # Break if there is no significant scroll
531
592
  if abs(current_scroll - last_height) < viewport_height * 0.1:
532
593
  break
533
594
 
@@ -547,12 +608,16 @@ class BaseBrowser:
547
608
  except Exception as e:
548
609
  logger.warning(f"Error evaluating page script: {e}")
549
610
 
550
- return visualviewport_from_dict(
611
+ return visual_viewport_from_dict(
551
612
  self.page.evaluate("MultimodalWebSurfer.getVisualViewport();")
552
613
  )
553
614
 
554
- def get_interactive_elements(self) -> List[Dict[str, Any]]:
555
- # codes from magentic-one
615
+ def get_interactive_elements(self) -> Dict[str, InteractiveRegion]:
616
+ r"""Get the interactive elements of the current page.
617
+
618
+ Returns:
619
+ Dict[str, InteractiveRegion]: A dictionary of interactive elements.
620
+ """
556
621
  try:
557
622
  self.page.evaluate(self.page_script)
558
623
  except Exception as e:
@@ -565,12 +630,13 @@ class BaseBrowser:
565
630
 
566
631
  typed_results: Dict[str, InteractiveRegion] = {}
567
632
  for k in result:
568
- typed_results[k] = interactiveregion_from_dict(result[k])
633
+ typed_results[k] = interactive_region_from_dict(result[k])
569
634
 
570
635
  return typed_results # type: ignore[return-value]
571
636
 
572
637
  def get_som_screenshot(
573
- self, save_image: bool = False
638
+ self,
639
+ save_image: bool = False,
574
640
  ) -> Tuple[Image.Image, Union[str, None]]:
575
641
  r"""Get a screenshot of the current viewport with interactive elements
576
642
  marked.
@@ -608,15 +674,19 @@ class BaseBrowser:
608
674
  return comp, file_path
609
675
 
610
676
  def scroll_up(self) -> None:
677
+ r"""Scroll up the page."""
611
678
  self.page.keyboard.press("PageUp")
612
679
 
613
680
  def scroll_down(self) -> None:
681
+ r"""Scroll down the page."""
614
682
  self.page.keyboard.press("PageDown")
615
683
 
616
684
  def get_url(self) -> str:
685
+ r"""Get the URL of the current page."""
617
686
  return self.page.url
618
687
 
619
- def click_id(self, identifier: Union[str, int]):
688
+ def click_id(self, identifier: Union[str, int]) -> None:
689
+ r"""Click an element with the given identifier."""
620
690
  if isinstance(identifier, int):
621
691
  identifier = str(identifier)
622
692
  target = self.page.locator(f"[__elementId='{identifier}']")
@@ -649,7 +719,7 @@ class BaseBrowser:
649
719
 
650
720
  self._wait_for_load()
651
721
 
652
- def extract_url_content(self):
722
+ def extract_url_content(self) -> str:
653
723
  r"""Extract the content of the current page."""
654
724
  content = self.page.content()
655
725
  return content
@@ -821,7 +891,6 @@ class BaseBrowser:
821
891
 
822
892
  def close(self):
823
893
  self.browser.close()
824
- self.playwright.stop()
825
894
 
826
895
  # ruff: noqa: E501
827
896
  def show_interactive_elements(self):
@@ -846,7 +915,7 @@ class BaseBrowser:
846
915
  return markdown_content
847
916
 
848
917
 
849
- class WebToolkit(BaseToolkit):
918
+ class BrowserToolkit(BaseToolkit):
850
919
  r"""A class for browsing the web and interacting with web pages.
851
920
 
852
921
  This class provides methods for browsing the web and interacting with web
@@ -862,7 +931,7 @@ class WebToolkit(BaseToolkit):
862
931
  planning_agent_model: Optional[BaseModelBackend] = None,
863
932
  output_language: str = "en",
864
933
  ):
865
- r"""Initialize the WebToolkit instance.
934
+ r"""Initialize the BrowserToolkit instance.
866
935
 
867
936
  Args:
868
937
  headless (bool): Whether to run the browser in headless mode.
@@ -1026,9 +1095,7 @@ out the information you need. Sometimes they are extremely useful.
1026
1095
  """
1027
1096
 
1028
1097
  # get current state
1029
- som_screenshot, som_screenshot_path = self.browser.get_som_screenshot(
1030
- save_image=True
1031
- )
1098
+ som_screenshot, _ = self.browser.get_som_screenshot(save_image=True)
1032
1099
  img = _reload_image(som_screenshot)
1033
1100
  message = BaseMessage.make_user_message(
1034
1101
  role_name='user', content=observe_prompt, image_list=[img]
@@ -1222,7 +1289,7 @@ Your output should be in json format, including the following fields:
1222
1289
  return False, replanned_schema
1223
1290
 
1224
1291
  @dependencies_required("playwright")
1225
- def browser_simulation(
1292
+ def browse_url(
1226
1293
  self, task_prompt: str, start_url: str, round_limit: int = 12
1227
1294
  ) -> str:
1228
1295
  r"""A powerful toolkit which can simulate the browser interaction to solve the task which needs multi-step actions.
@@ -1303,4 +1370,4 @@ Your output should be in json format, including the following fields:
1303
1370
  return simulation_result
1304
1371
 
1305
1372
  def get_tools(self) -> List[FunctionTool]:
1306
- return [FunctionTool(self.browser_simulation)]
1373
+ return [FunctionTool(self.browse_url)]
@@ -704,6 +704,68 @@ class SearchToolkit(BaseToolkit):
704
704
  except Exception as e:
705
705
  return [{"error": f"An unexpected error occurred: {e!s}"}]
706
706
 
707
+ @api_keys_required([(None, 'BOCHA_API_KEY')])
708
+ def search_bocha(
709
+ self,
710
+ query: str,
711
+ freshness: str = "noLimit",
712
+ summary: bool = False,
713
+ count: int = 10,
714
+ page: int = 1,
715
+ ) -> Dict[str, Any]:
716
+ r"""Query the Bocha AI search API and return search results.
717
+
718
+ Args:
719
+ query (str): The search query.
720
+ freshness (str): Time frame filter for search results. Default
721
+ is "noLimit". Options include:
722
+ - 'noLimit': no limit (default).
723
+ - 'oneDay': past day.
724
+ - 'oneWeek': past week.
725
+ - 'oneMonth': past month.
726
+ - 'oneYear': past year.
727
+ summary (bool): Whether to include text summaries in results.
728
+ Default is False.
729
+ count (int): Number of results to return (1-50). Default is 10.
730
+ page (int): Page number of results. Default is 1.
731
+
732
+ Returns:
733
+ Dict[str, Any]: A dictionary containing search results, including
734
+ web pages, images, and videos if available. The structure
735
+ follows the Bocha AI search API response format.
736
+ """
737
+ import json
738
+
739
+ BOCHA_API_KEY = os.getenv("BOCHA_API_KEY")
740
+
741
+ url = "https://api.bochaai.com/v1/web-search"
742
+ headers = {
743
+ "Authorization": f"Bearer {BOCHA_API_KEY}",
744
+ "Content-Type": "application/json",
745
+ }
746
+
747
+ payload = json.dumps(
748
+ {
749
+ "query": query,
750
+ "freshness": freshness,
751
+ "summary": summary,
752
+ "count": count,
753
+ "page": page,
754
+ }
755
+ )
756
+ try:
757
+ response = requests.post(url, headers=headers, data=payload)
758
+ if response.status_code != 200:
759
+ return {
760
+ "error": (
761
+ f"Bocha API failed with {response.status_code}: "
762
+ f"{response.text}"
763
+ )
764
+ }
765
+ return response.json()["data"]
766
+ except requests.exceptions.RequestException as e:
767
+ return {"error": f"Bocha AI search failed: {e!s}"}
768
+
707
769
  def get_tools(self) -> List[FunctionTool]:
708
770
  r"""Returns a list of FunctionTool objects representing the
709
771
  functions in the toolkit.
@@ -720,4 +782,5 @@ class SearchToolkit(BaseToolkit):
720
782
  FunctionTool(self.query_wolfram_alpha),
721
783
  FunctionTool(self.tavily_search),
722
784
  FunctionTool(self.search_brave),
785
+ FunctionTool(self.search_bocha),
723
786
  ]
camel/types/enums.py CHANGED
@@ -249,6 +249,7 @@ class ModelType(UnifiedModelType, Enum):
249
249
  self.is_siliconflow,
250
250
  self.is_zhipuai,
251
251
  self.is_aiml,
252
+ self.is_azure_openai,
252
253
  ]
253
254
  )
254
255
 
@@ -889,6 +890,7 @@ class ModelPlatformType(Enum):
889
890
  MOONSHOT = "moonshot"
890
891
  SILICONFLOW = "siliconflow"
891
892
  AIML = "aiml"
893
+ VOLCANO = "volcano"
892
894
 
893
895
  @property
894
896
  def is_openai(self) -> bool:
@@ -1011,6 +1013,11 @@ class ModelPlatformType(Enum):
1011
1013
  r"""Returns whether this platform is AIML."""
1012
1014
  return self is ModelPlatformType.AIML
1013
1015
 
1016
+ @property
1017
+ def is_volcano(self) -> bool:
1018
+ r"""Returns whether this platform is volcano."""
1019
+ return self is ModelPlatformType.VOLCANO
1020
+
1014
1021
 
1015
1022
  class AudioModelType(Enum):
1016
1023
  TTS_1 = "tts-1"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: camel-ai
3
- Version: 0.2.26
3
+ Version: 0.2.27
4
4
  Summary: Communicative Agents for AI Society Study
5
5
  Project-URL: Homepage, https://www.camel-ai.org/
6
6
  Project-URL: Repository, https://github.com/camel-ai/camel
@@ -1,4 +1,4 @@
1
- camel/__init__.py,sha256=vUQIqmv9Y3SKQSw2mOa58Ppfzinc5zLe5psIFxbYhSI,912
1
+ camel/__init__.py,sha256=6XPf3TQFxgCHXxPaRb-Lda_NJ-gHT1IsrHIeYeMzGGA,912
2
2
  camel/generators.py,sha256=JRqj9_m1PF4qT6UtybzTQ-KBT9MJQt18OAAYvQ_fr2o,13844
3
3
  camel/human.py,sha256=9X09UmxI2JqQnhrFfnZ3B9EzFmVfdSWQcjLWTIXKXe0,4962
4
4
  camel/logger.py,sha256=j6mPsLJyKOn16o6Um57882mHsURQ8h-jia6Jd_34wRA,4239
@@ -134,11 +134,11 @@ camel/messages/conversion/sharegpt/__init__.py,sha256=oWUuHV5w85kxqhz_hoElLmCfzL
134
134
  camel/messages/conversion/sharegpt/function_call_formatter.py,sha256=cn7e7CfmxEVFlfOqhjhNuA8nuWvWD6hXYn-3okXNxxQ,1832
135
135
  camel/messages/conversion/sharegpt/hermes/__init__.py,sha256=mxuMSm-neaTgInIjYXuIVdC310E6jKJzM3IdtaJ4qY4,812
136
136
  camel/messages/conversion/sharegpt/hermes/hermes_function_formatter.py,sha256=-9TT8iOQ-ieKSKR_PmJSA5Bi0uBx-qR7WQ6vxuFkorM,4639
137
- camel/models/__init__.py,sha256=30gJOmde3TS-j5I4m0hrxI5BJDd2H3UA1hxQD9TQzGE,2637
137
+ camel/models/__init__.py,sha256=iNZ1LcesJ3YaAbNRxHTA-sUaxfjn6gUQ-EQArll21KQ,2769
138
138
  camel/models/_utils.py,sha256=hob1ehnS5xZitMCdYToHVgaTB55JnaP4_DSWnTEfVsg,2045
139
139
  camel/models/aiml_model.py,sha256=4FW66DxmVMPWAJckh4UjMM6eD1QNyrAPAPtrpmWxzjc,6524
140
140
  camel/models/anthropic_model.py,sha256=8XAj9sVaN1X0hvrL9a-qsmkAFWoGe1Ozj5XZsXYe1UI,5894
141
- camel/models/azure_openai_model.py,sha256=WBxnR-UNnBw49zyLxqF_d-7VKWTjBPnNR_xMvhWrggI,7471
141
+ camel/models/azure_openai_model.py,sha256=AblW2scYp12_odI1GG0ATHI8-Tn7d6SCsxHe7g66rWs,10386
142
142
  camel/models/base_audio_model.py,sha256=QkLqh0v-5kcE_jwFB5xAgvztAqB2Bot4_iG9sZdcl8A,2986
143
143
  camel/models/base_model.py,sha256=RolL8fRwVpfz8g9lpb_71h0mYTNl-U63f8KBy6hc3E0,10679
144
144
  camel/models/cohere_model.py,sha256=RAYHCyppDQxQ7BOR-e314AagB09vRxoScoHc-FtL6Bc,13355
@@ -149,14 +149,14 @@ camel/models/groq_model.py,sha256=FgXOHmIKAxGFASUmmk5tK49bPcXsr7diB3zRFGg9XDA,73
149
149
  camel/models/internlm_model.py,sha256=4nr5LXhxBfOjm-0i65pXyaS0_sT5oAXKXaUfkijAGmQ,5612
150
150
  camel/models/litellm_model.py,sha256=xi4kDd0FKuznKtox8ArsB39u40ueOhcb-CpWv4bcbXw,5544
151
151
  camel/models/mistral_model.py,sha256=OB948fRVnXikVIDO3PqxV0zb_qpwwta0DIW1bbX3SYI,11666
152
- camel/models/model_factory.py,sha256=1PgJdifxYYqQ9VnZRrEER7nKjz2X07uHwPMxg454Alk,6732
152
+ camel/models/model_factory.py,sha256=GdjGCxslqe04oWvpfiOdPl0ZGzuGooeA9M8ppby6hFo,6863
153
153
  camel/models/model_manager.py,sha256=gfpL-WUxuTXgNeCkIVg8Y0zRvxMqRLX8JGt0XEAPQ8Y,9214
154
154
  camel/models/moonshot_model.py,sha256=DNZzDYz0AWU1q6pIvbPALqesejoawwuKzeP0_ZbjDSg,6149
155
155
  camel/models/nemotron_model.py,sha256=jJrW8tpTlEJDT1FjflB9krhgEQhD5KBeLmyUIcZvWPk,3886
156
156
  camel/models/nvidia_model.py,sha256=lqp1iPwVDq6zSQ9B0SyBZ48Z3J5WbXwPshwlhj1ogZ8,6711
157
157
  camel/models/ollama_model.py,sha256=byJ0YbMlilEFRKJZIot-MPUcojwMHLIaBES0a1SURtg,10604
158
158
  camel/models/openai_audio_models.py,sha256=fYpxFvxT8p93KVb5BYODTuI5wdNXV9pu_bvxfARgVYk,13193
159
- camel/models/openai_compatible_model.py,sha256=9fzV3EtcE5YFZ5-rYYMoWVmqjjcGfC7rDml5Ngoz6Fs,5299
159
+ camel/models/openai_compatible_model.py,sha256=Nmps04Fo0ILmynE8wbKVzTrQ0VDyOrDR1ICD1nGrjd0,8142
160
160
  camel/models/openai_model.py,sha256=CbfD9yVtAltyqdFpjnLXncFnmaGPDZq8JhJDaSfG0pc,10186
161
161
  camel/models/qwen_model.py,sha256=_LeeB0yrXRMI-gZOEEOHg0bWNOJpuQHf2G7u40--3r8,7064
162
162
  camel/models/reka_model.py,sha256=15DscZf3lbqsIzm6kzjzDrhblBt1_0xlphT4isuQMu0,10146
@@ -166,6 +166,7 @@ camel/models/siliconflow_model.py,sha256=c5vk4zAhZVf8pDF1uh-iSa_v8d0QoPLuIN27Eem
166
166
  camel/models/stub_model.py,sha256=dygYoxemnWWaxEX21L8QyKe-c75ti2CK9HnTuyHL5vs,5160
167
167
  camel/models/togetherai_model.py,sha256=-YwZV1S1bkrX8jGguQI5dbtIHVuqhv96MoAcl33ptPo,6657
168
168
  camel/models/vllm_model.py,sha256=dzH4rYr2Se7cejk2hobblaW-s483uxPxb8976RQE8x0,6884
169
+ camel/models/volcano_model.py,sha256=inYDiKOfGvq8o3XW4KVQIrXiZOhXQfB4HfCHGCWHPKs,3792
169
170
  camel/models/yi_model.py,sha256=V4sc9n8MAKVfjGO-NU0I8W4lGKdORSCbMV020SHT3R0,6180
170
171
  camel/models/zhipuai_model.py,sha256=o3uoTY30p1yUIklvoRMyr8JX39xZ5mLVKSTtUknW8nE,6517
171
172
  camel/models/reward/__init__.py,sha256=MqPN6wXh7Y1SoeNoFlYaMG6xHzLG0CYsv_3kB2atIQk,984
@@ -255,11 +256,12 @@ camel/terminators/__init__.py,sha256=t8uqrkUnXEOYMXQDgaBkMFJ0EXFKI0kmx4cUimli3Ls
255
256
  camel/terminators/base.py,sha256=xmJzERX7GdSXcxZjAHHODa0rOxRChMSRboDCNHWSscs,1511
256
257
  camel/terminators/response_terminator.py,sha256=n3G5KP6Oj7-7WlRN0yFcrtLpqAJKaKS0bmhrWlFfCgQ,4982
257
258
  camel/terminators/token_limit_terminator.py,sha256=YWv6ZR8R9yI2Qnf_3xES5bEE_O5bb2CxQ0EUXfMh34c,2118
258
- camel/toolkits/__init__.py,sha256=1MKp0Md0TtpI55l5EjX6vQg6nq7nUa996ARMLh2HEHc,3687
259
+ camel/toolkits/__init__.py,sha256=SJ1Agk9YI_qH4gKX-pcvhl-m2t0Nori6R9Fs0gXReDs,3699
259
260
  camel/toolkits/arxiv_toolkit.py,sha256=d0Zn8LQGENhtlZ0BHlDr1pUV8xHOc6TOenAaKgbelu8,6279
260
261
  camel/toolkits/ask_news_toolkit.py,sha256=PAxio8I2eTau9TgOu1jyFC9fsHhvGb-aLIkroWPtwx4,23268
261
262
  camel/toolkits/audio_analysis_toolkit.py,sha256=LC0C6SEIwko8HqkT-C3ub6Ila2PfuIbKLBOEjrrF6BE,8552
262
263
  camel/toolkits/base.py,sha256=7WRovKrz380b25lYdwT-2FCXzS3dkllOjT53hmmCg_I,1999
264
+ camel/toolkits/browser_toolkit.py,sha256=Pbw7CoiHPtbFQagmXvCUiVHihCXwaN6uUbwCAfC-jso,51016
263
265
  camel/toolkits/code_execution.py,sha256=seqTtjulBZXH4qd5m2YAXQaxyL2_n2ekmqsYB-wBxvw,4547
264
266
  camel/toolkits/dalle_toolkit.py,sha256=Usmw3JiJErLQgWSB1qKq_bOACNwbUTQPFc_EsVzTrGo,5115
265
267
  camel/toolkits/dappier_toolkit.py,sha256=_69IAmXE2QSbwGxnSEycaV2XrrkiM5wKI6heM7-4MfU,8175
@@ -285,7 +287,7 @@ camel/toolkits/page_script.js,sha256=gypbuQ_gn_oa3rQDoCN_q-kJ0jND1eSvY-30PufPZmQ
285
287
  camel/toolkits/pubmed_toolkit.py,sha256=vrd5GIhSYt9Z8EHaWkFb0x9i6_TP7pQZc7jlLHSol04,12180
286
288
  camel/toolkits/reddit_toolkit.py,sha256=cTqEq1CRaLq9XxUHkHCmd09tRzb5Mz_bUs2JV58ewrs,8012
287
289
  camel/toolkits/retrieval_toolkit.py,sha256=y_mQtknrSIDDXSyQb-4FY6ahV_mOxkBhDkA2eMIVnz0,3801
288
- camel/toolkits/search_toolkit.py,sha256=4yTYkook3E3Yb-EOaio2O6FBcvzIXlVVC4WOvNSaYcw,29881
290
+ camel/toolkits/search_toolkit.py,sha256=KxJMB8tz37uyqlzHSK9X28hSZZMK793aOwkYT8jBnGc,32131
289
291
  camel/toolkits/semantic_scholar_toolkit.py,sha256=Kp-5rz99rkeUsUmk5ShQdNKJRGVs78hQvCNg-NQMFDk,11547
290
292
  camel/toolkits/slack_toolkit.py,sha256=n8cn3kZIc27B-2KMTRK6Nsdan37SwMqBiBi1PMtuUvQ,10744
291
293
  camel/toolkits/stripe_toolkit.py,sha256=1sCywkpo8mh4E_KwxFKLhAb-G5uZ47NXXQvcddThjqg,9781
@@ -295,7 +297,6 @@ camel/toolkits/twitter_toolkit.py,sha256=a2OLSJSW2wY7pOwOApb1qchZPXzH22Rbgm9Yd7-
295
297
  camel/toolkits/video_analysis_toolkit.py,sha256=lDAY6YP1zXykSxt8Qanf0WZR3l1p8c4akKPkaF5R3wU,15064
296
298
  camel/toolkits/video_download_toolkit.py,sha256=XjZICJTOG4dmKxfkHxYxmBMFESsOX51GvTeQXAQslMU,7104
297
299
  camel/toolkits/weather_toolkit.py,sha256=qHAMD56zqd5GWnEWiaA_0aBDwvgacdx0pAHScinY4GY,6965
298
- camel/toolkits/web_toolkit.py,sha256=DapiYYP95EFMVB1hU38a052nUPs4L5WdE25o_74h1Cw,48407
299
300
  camel/toolkits/whatsapp_toolkit.py,sha256=MBY92WLLptkXxnl4Ky5erGwI415XLFFGu9i2q6_b0oQ,5736
300
301
  camel/toolkits/zapier_toolkit.py,sha256=mgYxRGPw7_VZVj0xU9XwHewqFcV49eUjvuum_IHfuNs,6854
301
302
  camel/toolkits/open_api_specs/security_config.py,sha256=ZVnBa_zEifaE_ao2xsvV5majuJHpn2Tn7feMDOnj-eo,898
@@ -324,7 +325,7 @@ camel/toolkits/open_api_specs/web_scraper/openapi.yaml,sha256=u_WalQ01e8W1D27VnZ
324
325
  camel/toolkits/open_api_specs/web_scraper/paths/__init__.py,sha256=OKCZrQCDwaWtXIN_2rA9FSqEvgpQRieRoHh7Ek6N16A,702
325
326
  camel/toolkits/open_api_specs/web_scraper/paths/scraper.py,sha256=aWy1_ppV4NVVEZfnbN3tu9XA9yAPAC9bRStJ5JuXMRU,1117
326
327
  camel/types/__init__.py,sha256=VLWhAt857IFct3XepY5BNOIhyhDhfmODTezr9jhO_TI,2251
327
- camel/types/enums.py,sha256=h5UFYF6l2o1ITHClfAB79WU9GhbqFwHPTb3Lx3VYAgQ,34126
328
+ camel/types/enums.py,sha256=b3KZqTbenkWSxC7QrgR_rYO1bV8tpcmTczwofOiByJA,34343
328
329
  camel/types/openai_types.py,sha256=8ZFzLe-zGmKNPfuVZFzxlxAX98lGf18gtrPhOgMmzus,2104
329
330
  camel/types/unified_model_type.py,sha256=GP5GYtA3RfvLsqnk1c4UcOaRKMFhjDgZrLr0ln6JFw8,4253
330
331
  camel/types/agents/__init__.py,sha256=cbvVkogPoZgcwZrgxLH6EtpGXk0kavF79nOic0Dc1vg,786
@@ -340,7 +341,7 @@ camel/verifiers/__init__.py,sha256=p6UEyvaOlwUQaFACGB4C07fL1xSnpTouElt19YRuneQ,9
340
341
  camel/verifiers/base.py,sha256=efWZV9g58IHzJ24U4zr109y34CaAi8tV9WZPMCzP3YI,12017
341
342
  camel/verifiers/models.py,sha256=hC6m_YxEX-mqi_tkCNZHZWLBWf04ZTyv5vfKR-BEyU4,2818
342
343
  camel/verifiers/python_verifier.py,sha256=bj-UGxeJTZzxVVa3a8IEQ1lNOpSaaW3JdGnUEoPeQD0,7519
343
- camel_ai-0.2.26.dist-info/METADATA,sha256=md1wlHHOxnVY3b-m7kkN8AbotAoAioTx8SshfLOm5BI,37992
344
- camel_ai-0.2.26.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
345
- camel_ai-0.2.26.dist-info/licenses/LICENSE,sha256=id0nB2my5kG0xXeimIu5zZrbHLS6EQvxvkKkzIHaT2k,11343
346
- camel_ai-0.2.26.dist-info/RECORD,,
344
+ camel_ai-0.2.27.dist-info/METADATA,sha256=B4jItSRHDPybZtfm-i0xxUq3z4HWUQ99OEdaRJ33Mfw,37992
345
+ camel_ai-0.2.27.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
346
+ camel_ai-0.2.27.dist-info/licenses/LICENSE,sha256=id0nB2my5kG0xXeimIu5zZrbHLS6EQvxvkKkzIHaT2k,11343
347
+ camel_ai-0.2.27.dist-info/RECORD,,