camel-ai 0.2.72a10__py3-none-any.whl → 0.2.73__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 (52) hide show
  1. camel/__init__.py +1 -1
  2. camel/agents/chat_agent.py +140 -345
  3. camel/memories/agent_memories.py +18 -17
  4. camel/societies/__init__.py +2 -0
  5. camel/societies/workforce/prompts.py +36 -10
  6. camel/societies/workforce/single_agent_worker.py +7 -5
  7. camel/societies/workforce/workforce.py +6 -4
  8. camel/storages/key_value_storages/mem0_cloud.py +48 -47
  9. camel/storages/vectordb_storages/__init__.py +1 -0
  10. camel/storages/vectordb_storages/surreal.py +100 -150
  11. camel/toolkits/__init__.py +6 -1
  12. camel/toolkits/base.py +60 -2
  13. camel/toolkits/excel_toolkit.py +153 -64
  14. camel/toolkits/file_write_toolkit.py +67 -0
  15. camel/toolkits/hybrid_browser_toolkit/config_loader.py +136 -413
  16. camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit.py +131 -1966
  17. camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit_ts.py +1177 -0
  18. camel/toolkits/hybrid_browser_toolkit/ts/package-lock.json +4356 -0
  19. camel/toolkits/hybrid_browser_toolkit/ts/package.json +33 -0
  20. camel/toolkits/hybrid_browser_toolkit/ts/src/browser-scripts.js +125 -0
  21. camel/toolkits/hybrid_browser_toolkit/ts/src/browser-session.ts +945 -0
  22. camel/toolkits/hybrid_browser_toolkit/ts/src/config-loader.ts +226 -0
  23. camel/toolkits/hybrid_browser_toolkit/ts/src/hybrid-browser-toolkit.ts +522 -0
  24. camel/toolkits/hybrid_browser_toolkit/ts/src/index.ts +7 -0
  25. camel/toolkits/hybrid_browser_toolkit/ts/src/types.ts +110 -0
  26. camel/toolkits/hybrid_browser_toolkit/ts/tsconfig.json +26 -0
  27. camel/toolkits/hybrid_browser_toolkit/ts/websocket-server.js +254 -0
  28. camel/toolkits/hybrid_browser_toolkit/ws_wrapper.py +582 -0
  29. camel/toolkits/hybrid_browser_toolkit_py/__init__.py +17 -0
  30. camel/toolkits/hybrid_browser_toolkit_py/config_loader.py +447 -0
  31. camel/toolkits/hybrid_browser_toolkit_py/hybrid_browser_toolkit.py +2077 -0
  32. camel/toolkits/mcp_toolkit.py +341 -46
  33. camel/toolkits/message_integration.py +719 -0
  34. camel/toolkits/notion_mcp_toolkit.py +234 -0
  35. camel/toolkits/screenshot_toolkit.py +116 -31
  36. camel/toolkits/search_toolkit.py +20 -2
  37. camel/toolkits/slack_toolkit.py +43 -48
  38. camel/toolkits/terminal_toolkit.py +288 -46
  39. camel/toolkits/video_analysis_toolkit.py +13 -13
  40. camel/toolkits/video_download_toolkit.py +11 -11
  41. camel/toolkits/web_deploy_toolkit.py +207 -12
  42. camel/types/enums.py +6 -0
  43. {camel_ai-0.2.72a10.dist-info → camel_ai-0.2.73.dist-info}/METADATA +49 -9
  44. {camel_ai-0.2.72a10.dist-info → camel_ai-0.2.73.dist-info}/RECORD +52 -35
  45. /camel/toolkits/{hybrid_browser_toolkit → hybrid_browser_toolkit_py}/actions.py +0 -0
  46. /camel/toolkits/{hybrid_browser_toolkit → hybrid_browser_toolkit_py}/agent.py +0 -0
  47. /camel/toolkits/{hybrid_browser_toolkit → hybrid_browser_toolkit_py}/browser_session.py +0 -0
  48. /camel/toolkits/{hybrid_browser_toolkit → hybrid_browser_toolkit_py}/snapshot.py +0 -0
  49. /camel/toolkits/{hybrid_browser_toolkit → hybrid_browser_toolkit_py}/stealth_script.js +0 -0
  50. /camel/toolkits/{hybrid_browser_toolkit → hybrid_browser_toolkit_py}/unified_analyzer.js +0 -0
  51. {camel_ai-0.2.72a10.dist-info → camel_ai-0.2.73.dist-info}/WHEEL +0 -0
  52. {camel_ai-0.2.72a10.dist-info → camel_ai-0.2.73.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,234 @@
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
+ from typing import Any, ClassVar, Dict, List, Optional, Set
16
+
17
+ from camel.toolkits import BaseToolkit, FunctionTool
18
+
19
+ from .mcp_toolkit import MCPToolkit
20
+
21
+
22
+ class NotionMCPToolkit(BaseToolkit):
23
+ r"""NotionMCPToolkit provides an interface for interacting with Notion
24
+ through the Model Context Protocol (MCP).
25
+
26
+ Attributes:
27
+ timeout (Optional[float]): Connection timeout in seconds.
28
+ (default: :obj:`None`)
29
+
30
+ Note:
31
+ Currently only supports asynchronous operation mode.
32
+ """
33
+
34
+ # TODO: Create unified method to validate and fix the schema
35
+ SCHEMA_KEYWORDS: ClassVar[Set[str]] = {
36
+ "type",
37
+ "properties",
38
+ "items",
39
+ "required",
40
+ "additionalProperties",
41
+ "description",
42
+ "title",
43
+ "default",
44
+ "enum",
45
+ "const",
46
+ "examples",
47
+ "$ref",
48
+ "$defs",
49
+ "definitions",
50
+ "allOf",
51
+ "oneOf",
52
+ "anyOf",
53
+ "not",
54
+ "if",
55
+ "then",
56
+ "else",
57
+ "format",
58
+ "pattern",
59
+ "minimum",
60
+ "maximum",
61
+ "minLength",
62
+ "maxLength",
63
+ "minItems",
64
+ "maxItems",
65
+ "uniqueItems",
66
+ }
67
+
68
+ def __init__(
69
+ self,
70
+ timeout: Optional[float] = None,
71
+ ) -> None:
72
+ r"""Initializes the NotionMCPToolkit.
73
+
74
+ Args:
75
+ timeout (Optional[float]): Connection timeout in seconds.
76
+ (default: :obj:`None`)
77
+ """
78
+ super().__init__(timeout=timeout)
79
+
80
+ self._mcp_toolkit = MCPToolkit(
81
+ config_dict={
82
+ "mcpServers": {
83
+ "notionMCP": {
84
+ "command": "npx",
85
+ "args": [
86
+ "-y",
87
+ "mcp-remote",
88
+ "https://mcp.notion.com/mcp",
89
+ ],
90
+ }
91
+ }
92
+ },
93
+ timeout=timeout,
94
+ )
95
+
96
+ async def connect(self):
97
+ r"""Explicitly connect to the Notion MCP server."""
98
+ await self._mcp_toolkit.connect()
99
+
100
+ async def disconnect(self):
101
+ r"""Explicitly disconnect from the Notion MCP server."""
102
+ await self._mcp_toolkit.disconnect()
103
+
104
+ def get_tools(self) -> List[FunctionTool]:
105
+ r"""Returns a list of tools provided by the NotionMCPToolkit.
106
+
107
+ Returns:
108
+ List[FunctionTool]: List of available tools.
109
+ """
110
+ all_tools = []
111
+ for client in self._mcp_toolkit.clients:
112
+ try:
113
+ original_build_schema = client._build_tool_schema
114
+
115
+ def create_wrapper(orig_func):
116
+ def wrapper(mcp_tool):
117
+ return self._build_notion_tool_schema(
118
+ mcp_tool, orig_func
119
+ )
120
+
121
+ return wrapper
122
+
123
+ client._build_tool_schema = create_wrapper( # type: ignore[method-assign]
124
+ original_build_schema
125
+ )
126
+
127
+ client_tools = client.get_tools()
128
+ all_tools.extend(client_tools)
129
+
130
+ client._build_tool_schema = original_build_schema # type: ignore[method-assign]
131
+
132
+ except Exception as e:
133
+ from camel.logger import get_logger
134
+
135
+ logger = get_logger(__name__)
136
+ logger.error(f"Failed to get tools from client: {e}")
137
+
138
+ return all_tools
139
+
140
+ def _build_notion_tool_schema(self, mcp_tool, original_build_schema):
141
+ r"""Build tool schema with Notion-specific fixes."""
142
+ schema = original_build_schema(mcp_tool)
143
+ self._fix_notion_schema_recursively(schema)
144
+ return schema
145
+
146
+ def _fix_notion_schema_recursively(self, obj: Any) -> None:
147
+ r"""Recursively fix Notion MCP schema issues."""
148
+ if isinstance(obj, dict):
149
+ self._fix_dict_schema(obj)
150
+ self._process_nested_structures(obj)
151
+ elif isinstance(obj, list):
152
+ for item in obj:
153
+ self._fix_notion_schema_recursively(item)
154
+
155
+ def _fix_dict_schema(self, obj: Dict[str, Any]) -> None:
156
+ r"""Fix dictionary schema issues."""
157
+ if "properties" in obj and "type" not in obj:
158
+ self._fix_missing_type_with_properties(obj)
159
+ elif obj.get("type") == "object" and "properties" in obj:
160
+ self._fix_object_with_properties(obj)
161
+
162
+ def _fix_missing_type_with_properties(self, obj: Dict[str, Any]) -> None:
163
+ r"""Fix objects with properties but missing type field."""
164
+ properties = obj.get("properties", {})
165
+ if properties and isinstance(properties, dict):
166
+ obj["type"] = "object"
167
+ obj["additionalProperties"] = False
168
+
169
+ required_properties = self._get_required_properties(
170
+ properties, conservative=True
171
+ )
172
+ if required_properties:
173
+ obj["required"] = required_properties
174
+
175
+ def _fix_object_with_properties(self, obj: Dict[str, Any]) -> None:
176
+ r"""Fix objects with type="object" and properties."""
177
+ properties = obj.get("properties", {})
178
+ if properties and isinstance(properties, dict):
179
+ existing_required = obj.get("required", [])
180
+
181
+ for prop_name, prop_schema in properties.items():
182
+ if (
183
+ prop_name not in existing_required
184
+ and prop_name not in self.SCHEMA_KEYWORDS
185
+ and self._is_property_required(prop_schema)
186
+ ):
187
+ existing_required.append(prop_name)
188
+
189
+ if existing_required:
190
+ obj["required"] = existing_required
191
+
192
+ if "additionalProperties" not in obj:
193
+ obj["additionalProperties"] = False
194
+
195
+ def _get_required_properties(
196
+ self, properties: Dict[str, Any], conservative: bool = False
197
+ ) -> List[str]:
198
+ r"""Get list of required properties from a properties dict."""
199
+ required = []
200
+ for prop_name, prop_schema in properties.items():
201
+ if (
202
+ prop_name not in self.SCHEMA_KEYWORDS
203
+ and isinstance(prop_schema, dict)
204
+ and self._is_property_required(prop_schema)
205
+ ):
206
+ required.append(prop_name)
207
+ return required
208
+
209
+ def _is_property_required(self, prop_schema: Dict[str, Any]) -> bool:
210
+ r"""Check if a property should be marked as required."""
211
+ prop_type = prop_schema.get("type")
212
+ return (
213
+ prop_type is not None
214
+ and prop_type != "null"
215
+ and "default" not in prop_schema
216
+ and not (isinstance(prop_type, list) and "null" in prop_type)
217
+ )
218
+
219
+ def _process_nested_structures(self, obj: Dict[str, Any]) -> None:
220
+ r"""Process all nested structures in a schema object."""
221
+ for key, value in obj.items():
222
+ if key in ["anyOf", "oneOf", "allOf"] and isinstance(value, list):
223
+ for item in value:
224
+ self._fix_notion_schema_recursively(item)
225
+ elif key == "items" and isinstance(value, dict):
226
+ self._fix_notion_schema_recursively(value)
227
+ elif key == "properties" and isinstance(value, dict):
228
+ for prop_value in value.values():
229
+ self._fix_notion_schema_recursively(prop_value)
230
+ elif key == "$defs" and isinstance(value, dict):
231
+ for def_value in value.values():
232
+ self._fix_notion_schema_recursively(def_value)
233
+ elif isinstance(value, (dict, list)):
234
+ self._fix_notion_schema_recursively(value)
@@ -12,22 +12,22 @@
12
12
  # limitations under the License.
13
13
  # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
14
 
15
- import base64
16
- import io
17
15
  import os
18
- import time
19
16
  from pathlib import Path
20
17
  from typing import List, Optional
21
18
 
19
+ from PIL import Image
20
+
22
21
  from camel.logger import get_logger
22
+ from camel.messages import BaseMessage
23
23
  from camel.toolkits import BaseToolkit, FunctionTool
24
+ from camel.toolkits.base import RegisteredAgentToolkit
24
25
  from camel.utils import dependencies_required
25
- from camel.utils.tool_result import ToolResult
26
26
 
27
27
  logger = get_logger(__name__)
28
28
 
29
29
 
30
- class ScreenshotToolkit(BaseToolkit):
30
+ class ScreenshotToolkit(BaseToolkit, RegisteredAgentToolkit):
31
31
  r"""A toolkit for taking screenshots."""
32
32
 
33
33
  @dependencies_required('PIL')
@@ -50,6 +50,7 @@ class ScreenshotToolkit(BaseToolkit):
50
50
  from PIL import ImageGrab
51
51
 
52
52
  super().__init__(timeout=timeout)
53
+ RegisteredAgentToolkit.__init__(self)
53
54
 
54
55
  camel_workdir = os.environ.get("CAMEL_WORKDIR")
55
56
  if working_directory:
@@ -60,25 +61,101 @@ class ScreenshotToolkit(BaseToolkit):
60
61
  path = Path("camel_working_dir")
61
62
 
62
63
  self.ImageGrab = ImageGrab
63
- self.screenshots_dir = path / "screenshots"
64
+ self.screenshots_dir = path
64
65
  self.screenshots_dir.mkdir(parents=True, exist_ok=True)
65
66
 
66
- def take_screenshot(
67
+ def read_image(
68
+ self,
69
+ image_path: str,
70
+ instruction: str = "",
71
+ ) -> str:
72
+ r"""Analyzes an image from a local file path.
73
+
74
+ This function enables you to "see" and interpret an image from a
75
+ file. It's useful for tasks where you need to understand visual
76
+ information, such as reading a screenshot of a webpage or a diagram.
77
+
78
+ Args:
79
+ image_path (str): The local file path to the image.
80
+ For example: 'screenshots/login_page.png'.
81
+ instruction (str, optional): Specific instructions for what to look
82
+ for or what to do with the image. For example: "What is the
83
+ main headline on this page?" or "Find the 'Submit' button.".
84
+
85
+ Returns:
86
+ str: The response after analyzing the image, which could be a
87
+ description, an answer, or a confirmation of an action.
88
+ """
89
+ if self.agent is None:
90
+ logger.error(
91
+ "Cannot record screenshot in memory: No agent registered. "
92
+ "Please pass this toolkit to ChatAgent via "
93
+ "toolkits_to_register_agent parameter."
94
+ )
95
+ return (
96
+ "Error: No agent registered. Please pass this toolkit to "
97
+ "ChatAgent via toolkits_to_register_agent parameter."
98
+ )
99
+
100
+ try:
101
+ image_path = str(Path(image_path).absolute())
102
+
103
+ # Check if file exists before trying to open
104
+ if not os.path.exists(image_path):
105
+ error_msg = f"Screenshot file not found: {image_path}"
106
+ logger.error(error_msg)
107
+ return f"Error: {error_msg}"
108
+
109
+ # Load the image from the path
110
+ img = Image.open(image_path)
111
+
112
+ # Create a message with the screenshot image
113
+ message = BaseMessage.make_user_message(
114
+ role_name="User",
115
+ content=instruction,
116
+ image_list=[img],
117
+ )
118
+
119
+ # Record the message in agent's memory
120
+ response = self.agent.step(message)
121
+ return response.msgs[0].content
122
+
123
+ except Exception as e:
124
+ logger.error(f"Error reading screenshot: {e}")
125
+ return f"Error reading screenshot: {e}"
126
+
127
+ def take_screenshot_and_read_image(
67
128
  self,
129
+ filename: str,
68
130
  save_to_file: bool = True,
69
- ) -> ToolResult:
70
- r"""Take a screenshot of the entire screen and return it as a
71
- base64-encoded image.
131
+ read_image: bool = True,
132
+ instruction: Optional[str] = None,
133
+ ) -> str:
134
+ r"""Captures a screenshot of the entire screen.
135
+
136
+ This function can save the screenshot to a file and optionally analyze
137
+ it. It's useful for capturing the current state of the UI for
138
+ documentation, analysis, or to guide subsequent actions.
72
139
 
73
140
  Args:
74
- save_to_file (bool): Whether to save the screenshot to a file.
141
+ filename (str): The name for the screenshot file (e.g.,
142
+ "homepage.png"). The file is saved in a `screenshots`
143
+ subdirectory within the working directory. Must end with
144
+ `.png`. (default: :obj:`None`)
145
+ save_to_file (bool, optional): If `True`, saves the screenshot to
146
+ a file. (default: :obj:`True`)
147
+ read_image (bool, optional): If `True`, the agent will analyze
148
+ the screenshot. `save_to_file` must also be `True`.
75
149
  (default: :obj:`True`)
150
+ instruction (Optional[str], optional): A specific question or
151
+ command for the agent regarding the screenshot, used only if
152
+ `read_image` is `True`. For example: "Confirm that the
153
+ user is logged in.".
76
154
 
77
155
  Returns:
78
- ToolResult: An object containing:
79
- - text (str): A description of the screenshot.
80
- - images (List[str]): A list containing one base64-encoded
81
- PNG image data URL.
156
+ str: A confirmation message indicating success or failure,
157
+ including the file path if saved, and the agent's response
158
+ if `read_image` is `True`.
82
159
  """
83
160
  try:
84
161
  # Take screenshot of entire screen
@@ -90,32 +167,39 @@ class ScreenshotToolkit(BaseToolkit):
90
167
  # Create directory if it doesn't exist
91
168
  os.makedirs(self.screenshots_dir, exist_ok=True)
92
169
 
93
- # Generate filename with timestamp
94
- timestamp = int(time.time())
95
- filename = f"screenshot_{timestamp}.png"
96
- file_path = os.path.join(self.screenshots_dir, filename)
170
+ # Create unique filename if file already exists
171
+ base_path = os.path.join(self.screenshots_dir, filename)
172
+ file_path = base_path
173
+ counter = 1
174
+ while os.path.exists(file_path):
175
+ name, ext = os.path.splitext(filename)
176
+ unique_filename = f"{name}_{counter}{ext}"
177
+ file_path = os.path.join(
178
+ self.screenshots_dir, unique_filename
179
+ )
180
+ counter += 1
181
+
97
182
  screenshot.save(file_path)
98
183
  logger.info(f"Screenshot saved to {file_path}")
99
184
 
100
- # Convert to base64
101
- img_buffer = io.BytesIO()
102
- screenshot.save(img_buffer, format="PNG")
103
- img_buffer.seek(0)
104
- img_base64 = base64.b64encode(img_buffer.getvalue()).decode(
105
- 'utf-8'
106
- )
107
- img_data_url = f"data:image/png;base64,{img_base64}"
108
-
109
185
  # Create result text
110
186
  result_text = "Screenshot captured successfully"
111
187
  if file_path:
112
188
  result_text += f" and saved to {file_path}"
113
189
 
114
- return ToolResult(text=result_text, images=[img_data_url])
190
+ # Record in agent memory if requested
191
+ if read_image and file_path is not None:
192
+ inst = instruction if instruction is not None else ""
193
+ response = self.read_image(
194
+ str(Path(file_path).absolute()), inst
195
+ )
196
+ result_text += f". Agent response: {response}"
197
+
198
+ return result_text
115
199
 
116
200
  except Exception as e:
117
201
  logger.error(f"Error taking screenshot: {e}")
118
- return ToolResult(text=f"Error taking screenshot: {e}", images=[])
202
+ return f"Error taking screenshot: {e}"
119
203
 
120
204
  def get_tools(self) -> List[FunctionTool]:
121
205
  r"""Returns a list of FunctionTool objects for screenshot operations.
@@ -124,5 +208,6 @@ class ScreenshotToolkit(BaseToolkit):
124
208
  List[FunctionTool]: List of screenshot functions.
125
209
  """
126
210
  return [
127
- FunctionTool(self.take_screenshot),
211
+ FunctionTool(self.take_screenshot_and_read_image),
212
+ FunctionTool(self.read_image),
128
213
  ]
@@ -36,6 +36,7 @@ class SearchToolkit(BaseToolkit):
36
36
  self,
37
37
  timeout: Optional[float] = None,
38
38
  number_of_result_pages: int = 10,
39
+ exclude_domains: Optional[List[str]] = None,
39
40
  ):
40
41
  r"""Initializes the RedditToolkit with the specified number of retries
41
42
  and delay.
@@ -45,9 +46,14 @@ class SearchToolkit(BaseToolkit):
45
46
  (default: :obj:`None`)
46
47
  number_of_result_pages (int): The number of result pages to
47
48
  retrieve. (default: :obj:`10`)
49
+ exclude_domains (Optional[List[str]]): List of domains to
50
+ exclude from search results. Currently only supported
51
+ by the `search_google` function.
52
+ (default: :obj:`None`)
48
53
  """
49
54
  super().__init__(timeout=timeout)
50
55
  self.number_of_result_pages = number_of_result_pages
56
+ self.exclude_domains = exclude_domains
51
57
 
52
58
  @dependencies_required("wikipedia")
53
59
  def search_wiki(self, entity: str) -> str:
@@ -435,7 +441,9 @@ class SearchToolkit(BaseToolkit):
435
441
  ]
436
442
  )
437
443
  def search_google(
438
- self, query: str, search_type: str = "web"
444
+ self,
445
+ query: str,
446
+ search_type: str = "web",
439
447
  ) -> List[Dict[str, Any]]:
440
448
  r"""Use Google search engine to search information for the given query.
441
449
 
@@ -499,11 +507,21 @@ class SearchToolkit(BaseToolkit):
499
507
  start_page_idx = 1
500
508
  # Different language may get different result
501
509
  search_language = "en"
510
+
511
+ modified_query = query
512
+ if self.exclude_domains:
513
+ # Use Google's -site: operator to exclude domains
514
+ exclusion_terms = " ".join(
515
+ [f"-site:{domain}" for domain in self.exclude_domains]
516
+ )
517
+ modified_query = f"{query} {exclusion_terms}"
518
+ logger.debug(f"Excluded domains, modified query: {modified_query}")
519
+
502
520
  # Constructing the URL
503
521
  # Doc: https://developers.google.com/custom-search/v1/using_rest
504
522
  base_url = (
505
523
  f"https://www.googleapis.com/customsearch/v1?"
506
- f"key={GOOGLE_API_KEY}&cx={SEARCH_ENGINE_ID}&q={query}&start="
524
+ f"key={GOOGLE_API_KEY}&cx={SEARCH_ENGINE_ID}&q={modified_query}&start="
507
525
  f"{start_page_idx}&lr={search_language}&num={self.number_of_result_pages}"
508
526
  )
509
527
 
@@ -128,18 +128,15 @@ class SlackToolkit(BaseToolkit):
128
128
  return f"Error creating conversation: {e.response['error']}"
129
129
 
130
130
  def join_slack_channel(self, channel_id: str) -> str:
131
- r"""Joins an existing Slack channel.
131
+ r"""Joins an existing Slack channel. When use this function you must
132
+ call `get_slack_channel_information` function first to get the
133
+ `channel id`.
132
134
 
133
135
  Args:
134
136
  channel_id (str): The ID of the Slack channel to join.
135
137
 
136
138
  Returns:
137
- str: A confirmation message indicating whether join successfully
138
- or an error message.
139
-
140
- Raises:
141
- SlackApiError: If there is an error during get slack channel
142
- information.
139
+ str: A string containing the API response from Slack.
143
140
  """
144
141
  from slack_sdk.errors import SlackApiError
145
142
 
@@ -148,21 +145,18 @@ class SlackToolkit(BaseToolkit):
148
145
  response = slack_client.conversations_join(channel=channel_id)
149
146
  return str(response)
150
147
  except SlackApiError as e:
151
- return f"Error creating conversation: {e.response['error']}"
148
+ return f"Error joining channel: {e.response['error']}"
152
149
 
153
150
  def leave_slack_channel(self, channel_id: str) -> str:
154
- r"""Leaves an existing Slack channel.
151
+ r"""Leaves an existing Slack channel. When use this function you must
152
+ call `get_slack_channel_information` function first to get the
153
+ `channel id`.
155
154
 
156
155
  Args:
157
156
  channel_id (str): The ID of the Slack channel to leave.
158
157
 
159
158
  Returns:
160
- str: A confirmation message indicating whether leave successfully
161
- or an error message.
162
-
163
- Raises:
164
- SlackApiError: If there is an error during get slack channel
165
- information.
159
+ str: A string containing the API response from Slack.
166
160
  """
167
161
  from slack_sdk.errors import SlackApiError
168
162
 
@@ -171,18 +165,18 @@ class SlackToolkit(BaseToolkit):
171
165
  response = slack_client.conversations_leave(channel=channel_id)
172
166
  return str(response)
173
167
  except SlackApiError as e:
174
- return f"Error creating conversation: {e.response['error']}"
168
+ return f"Error leaving channel: {e.response['error']}"
175
169
 
176
170
  def get_slack_channel_information(self) -> str:
177
- r"""Retrieve Slack channels and return relevant information in JSON
178
- format.
171
+ r"""Retrieve a list of all public channels in the Slack workspace.
179
172
 
180
- Returns:
181
- str: JSON string containing information about Slack channels.
173
+ This function is crucial for discovering available channels and their
174
+ `channel_id`s, which are required by many other functions.
182
175
 
183
- Raises:
184
- SlackApiError: If there is an error during get slack channel
185
- information.
176
+ Returns:
177
+ str: A JSON string representing a list of channels. Each channel
178
+ object in the list contains 'id', 'name', 'created', and
179
+ 'num_members'. Returns an error message string on failure.
186
180
  """
187
181
  from slack_sdk.errors import SlackApiError
188
182
 
@@ -204,21 +198,20 @@ class SlackToolkit(BaseToolkit):
204
198
  ]
205
199
  return json.dumps(filtered_result, ensure_ascii=False)
206
200
  except SlackApiError as e:
207
- return f"Error creating conversation: {e.response['error']}"
201
+ return f"Error retrieving channel list: {e.response['error']}"
208
202
 
209
203
  def get_slack_channel_message(self, channel_id: str) -> str:
210
- r"""Retrieve messages from a Slack channel.
204
+ r"""Retrieve messages from a Slack channel. When use this function you
205
+ must call `get_slack_channel_information` function first to get the
206
+ `channel id`.
211
207
 
212
208
  Args:
213
209
  channel_id (str): The ID of the Slack channel to retrieve messages
214
210
  from.
215
211
 
216
212
  Returns:
217
- str: JSON string containing filtered message data.
218
-
219
- Raises:
220
- SlackApiError: If there is an error during get
221
- slack channel message.
213
+ str: A JSON string representing a list of messages. Each message
214
+ object contains 'user', 'text', and 'ts' (timestamp).
222
215
  """
223
216
  from slack_sdk.errors import SlackApiError
224
217
 
@@ -242,19 +235,21 @@ class SlackToolkit(BaseToolkit):
242
235
  file_path: Optional[str] = None,
243
236
  user: Optional[str] = None,
244
237
  ) -> str:
245
- r"""Send a message to a Slack channel.
238
+ r"""Send a message to a Slack channel. When use this function you must
239
+ call `get_slack_channel_information` function first to get the
240
+ `channel id`.
246
241
 
247
242
  Args:
248
243
  message (str): The message to send.
249
- channel_id (str): The ID of the Slack channel to send message.
250
- file_path (Optional[str]): The path of the file to send.
251
- Defaults to `None`.
252
- user (Optional[str]): The user ID of the recipient.
253
- Defaults to `None`.
244
+ channel_id (str): The ID of the channel to send the message to.
245
+ file_path (Optional[str]): The local path of a file to upload
246
+ with the message.
247
+ user (Optional[str]): The ID of a user to send an ephemeral
248
+ message to (visible only to that user).
254
249
 
255
250
  Returns:
256
- str: A confirmation message indicating whether the message was sent
257
- successfully or an error message.
251
+ str: A confirmation message indicating success or an error
252
+ message.
258
253
  """
259
254
  from slack_sdk.errors import SlackApiError
260
255
 
@@ -280,25 +275,25 @@ class SlackToolkit(BaseToolkit):
280
275
  f"got response: {response}"
281
276
  )
282
277
  except SlackApiError as e:
283
- return f"Error creating conversation: {e.response['error']}"
278
+ return f"Error sending message: {e.response['error']}"
284
279
 
285
280
  def delete_slack_message(
286
281
  self,
287
282
  time_stamp: str,
288
283
  channel_id: str,
289
284
  ) -> str:
290
- r"""Delete a message to a Slack channel.
285
+ r"""Delete a message from a Slack channel. When use this function you
286
+ must call `get_slack_channel_information` function first to get the
287
+ `channel id`.
291
288
 
292
289
  Args:
293
- time_stamp (str): Timestamp of the message to be deleted.
294
- channel_id (str): The ID of the Slack channel to delete message.
290
+ time_stamp (str): The 'ts' value of the message to be deleted.
291
+ You can get this from the `get_slack_channel_message` function.
292
+ channel_id (str): The ID of the channel where the message is. Use
293
+ `get_slack_channel_information` to find the `channel_id`.
295
294
 
296
295
  Returns:
297
- str: A confirmation message indicating whether the message
298
- was delete successfully or an error message.
299
-
300
- Raises:
301
- SlackApiError: If an error occurs while sending the message.
296
+ str: A string containing the API response from Slack.
302
297
  """
303
298
  from slack_sdk.errors import SlackApiError
304
299
 
@@ -309,7 +304,7 @@ class SlackToolkit(BaseToolkit):
309
304
  )
310
305
  return str(response)
311
306
  except SlackApiError as e:
312
- return f"Error creating conversation: {e.response['error']}"
307
+ return f"Error deleting message: {e.response['error']}"
313
308
 
314
309
  def get_tools(self) -> List[FunctionTool]:
315
310
  r"""Returns a list of FunctionTool objects representing the