camel-ai 0.2.73a4__py3-none-any.whl → 0.2.80a2__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.
Files changed (173) hide show
  1. camel/__init__.py +1 -1
  2. camel/agents/_utils.py +38 -0
  3. camel/agents/chat_agent.py +2217 -519
  4. camel/agents/mcp_agent.py +30 -27
  5. camel/configs/__init__.py +15 -0
  6. camel/configs/aihubmix_config.py +88 -0
  7. camel/configs/amd_config.py +70 -0
  8. camel/configs/cometapi_config.py +104 -0
  9. camel/configs/minimax_config.py +93 -0
  10. camel/configs/nebius_config.py +103 -0
  11. camel/data_collectors/alpaca_collector.py +15 -6
  12. camel/datasets/base_generator.py +39 -10
  13. camel/environments/single_step.py +28 -3
  14. camel/environments/tic_tac_toe.py +1 -1
  15. camel/interpreters/__init__.py +2 -0
  16. camel/interpreters/docker/Dockerfile +3 -12
  17. camel/interpreters/e2b_interpreter.py +34 -1
  18. camel/interpreters/microsandbox_interpreter.py +395 -0
  19. camel/loaders/__init__.py +11 -2
  20. camel/loaders/chunkr_reader.py +9 -0
  21. camel/memories/agent_memories.py +48 -4
  22. camel/memories/base.py +26 -0
  23. camel/memories/blocks/chat_history_block.py +122 -4
  24. camel/memories/context_creators/score_based.py +25 -384
  25. camel/memories/records.py +88 -8
  26. camel/messages/base.py +153 -34
  27. camel/models/__init__.py +10 -0
  28. camel/models/aihubmix_model.py +83 -0
  29. camel/models/aiml_model.py +1 -16
  30. camel/models/amd_model.py +101 -0
  31. camel/models/anthropic_model.py +6 -19
  32. camel/models/aws_bedrock_model.py +2 -33
  33. camel/models/azure_openai_model.py +114 -89
  34. camel/models/base_audio_model.py +3 -1
  35. camel/models/base_model.py +32 -14
  36. camel/models/cohere_model.py +1 -16
  37. camel/models/cometapi_model.py +83 -0
  38. camel/models/crynux_model.py +1 -16
  39. camel/models/deepseek_model.py +1 -16
  40. camel/models/fish_audio_model.py +6 -0
  41. camel/models/gemini_model.py +36 -18
  42. camel/models/groq_model.py +1 -17
  43. camel/models/internlm_model.py +1 -16
  44. camel/models/litellm_model.py +1 -16
  45. camel/models/lmstudio_model.py +1 -17
  46. camel/models/minimax_model.py +83 -0
  47. camel/models/mistral_model.py +1 -16
  48. camel/models/model_factory.py +27 -1
  49. camel/models/modelscope_model.py +1 -16
  50. camel/models/moonshot_model.py +105 -24
  51. camel/models/nebius_model.py +83 -0
  52. camel/models/nemotron_model.py +0 -5
  53. camel/models/netmind_model.py +1 -16
  54. camel/models/novita_model.py +1 -16
  55. camel/models/nvidia_model.py +1 -16
  56. camel/models/ollama_model.py +4 -19
  57. camel/models/openai_compatible_model.py +62 -41
  58. camel/models/openai_model.py +62 -57
  59. camel/models/openrouter_model.py +1 -17
  60. camel/models/ppio_model.py +1 -16
  61. camel/models/qianfan_model.py +1 -16
  62. camel/models/qwen_model.py +1 -16
  63. camel/models/reka_model.py +1 -16
  64. camel/models/samba_model.py +34 -47
  65. camel/models/sglang_model.py +64 -31
  66. camel/models/siliconflow_model.py +1 -16
  67. camel/models/stub_model.py +0 -4
  68. camel/models/togetherai_model.py +1 -16
  69. camel/models/vllm_model.py +1 -16
  70. camel/models/volcano_model.py +0 -17
  71. camel/models/watsonx_model.py +1 -16
  72. camel/models/yi_model.py +1 -16
  73. camel/models/zhipuai_model.py +60 -16
  74. camel/parsers/__init__.py +18 -0
  75. camel/parsers/mcp_tool_call_parser.py +176 -0
  76. camel/retrievers/auto_retriever.py +1 -0
  77. camel/runtimes/daytona_runtime.py +11 -12
  78. camel/societies/__init__.py +2 -0
  79. camel/societies/workforce/__init__.py +2 -0
  80. camel/societies/workforce/events.py +122 -0
  81. camel/societies/workforce/prompts.py +146 -66
  82. camel/societies/workforce/role_playing_worker.py +15 -11
  83. camel/societies/workforce/single_agent_worker.py +302 -65
  84. camel/societies/workforce/structured_output_handler.py +30 -18
  85. camel/societies/workforce/task_channel.py +163 -27
  86. camel/societies/workforce/utils.py +107 -13
  87. camel/societies/workforce/workflow_memory_manager.py +772 -0
  88. camel/societies/workforce/workforce.py +1949 -579
  89. camel/societies/workforce/workforce_callback.py +74 -0
  90. camel/societies/workforce/workforce_logger.py +168 -145
  91. camel/societies/workforce/workforce_metrics.py +33 -0
  92. camel/storages/key_value_storages/json.py +15 -2
  93. camel/storages/key_value_storages/mem0_cloud.py +48 -47
  94. camel/storages/object_storages/google_cloud.py +1 -1
  95. camel/storages/vectordb_storages/oceanbase.py +13 -13
  96. camel/storages/vectordb_storages/qdrant.py +3 -3
  97. camel/storages/vectordb_storages/tidb.py +8 -6
  98. camel/tasks/task.py +4 -3
  99. camel/toolkits/__init__.py +20 -7
  100. camel/toolkits/aci_toolkit.py +45 -0
  101. camel/toolkits/base.py +6 -4
  102. camel/toolkits/code_execution.py +28 -1
  103. camel/toolkits/context_summarizer_toolkit.py +684 -0
  104. camel/toolkits/dappier_toolkit.py +5 -1
  105. camel/toolkits/dingtalk.py +1135 -0
  106. camel/toolkits/edgeone_pages_mcp_toolkit.py +11 -31
  107. camel/toolkits/excel_toolkit.py +1 -1
  108. camel/toolkits/{file_write_toolkit.py → file_toolkit.py} +430 -36
  109. camel/toolkits/function_tool.py +13 -3
  110. camel/toolkits/github_toolkit.py +104 -17
  111. camel/toolkits/gmail_toolkit.py +1839 -0
  112. camel/toolkits/google_calendar_toolkit.py +38 -4
  113. camel/toolkits/google_drive_mcp_toolkit.py +12 -31
  114. camel/toolkits/hybrid_browser_toolkit/config_loader.py +15 -0
  115. camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit.py +77 -8
  116. camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit_ts.py +884 -88
  117. camel/toolkits/hybrid_browser_toolkit/installer.py +203 -0
  118. camel/toolkits/hybrid_browser_toolkit/ts/package-lock.json +5 -612
  119. camel/toolkits/hybrid_browser_toolkit/ts/package.json +0 -1
  120. camel/toolkits/hybrid_browser_toolkit/ts/src/browser-session.ts +959 -89
  121. camel/toolkits/hybrid_browser_toolkit/ts/src/config-loader.ts +9 -2
  122. camel/toolkits/hybrid_browser_toolkit/ts/src/hybrid-browser-toolkit.ts +281 -213
  123. camel/toolkits/hybrid_browser_toolkit/ts/src/parent-child-filter.ts +226 -0
  124. camel/toolkits/hybrid_browser_toolkit/ts/src/snapshot-parser.ts +219 -0
  125. camel/toolkits/hybrid_browser_toolkit/ts/src/som-screenshot-injected.ts +543 -0
  126. camel/toolkits/hybrid_browser_toolkit/ts/src/types.ts +23 -3
  127. camel/toolkits/hybrid_browser_toolkit/ts/websocket-server.js +72 -7
  128. camel/toolkits/hybrid_browser_toolkit/ws_wrapper.py +582 -132
  129. camel/toolkits/hybrid_browser_toolkit_py/actions.py +158 -0
  130. camel/toolkits/hybrid_browser_toolkit_py/browser_session.py +55 -8
  131. camel/toolkits/hybrid_browser_toolkit_py/config_loader.py +43 -0
  132. camel/toolkits/hybrid_browser_toolkit_py/hybrid_browser_toolkit.py +321 -8
  133. camel/toolkits/hybrid_browser_toolkit_py/snapshot.py +10 -4
  134. camel/toolkits/hybrid_browser_toolkit_py/unified_analyzer.js +45 -4
  135. camel/toolkits/{openai_image_toolkit.py → image_generation_toolkit.py} +151 -53
  136. camel/toolkits/klavis_toolkit.py +5 -1
  137. camel/toolkits/markitdown_toolkit.py +27 -1
  138. camel/toolkits/math_toolkit.py +64 -10
  139. camel/toolkits/mcp_toolkit.py +366 -71
  140. camel/toolkits/memory_toolkit.py +5 -1
  141. camel/toolkits/message_integration.py +18 -13
  142. camel/toolkits/minimax_mcp_toolkit.py +195 -0
  143. camel/toolkits/note_taking_toolkit.py +19 -10
  144. camel/toolkits/notion_mcp_toolkit.py +16 -26
  145. camel/toolkits/openbb_toolkit.py +5 -1
  146. camel/toolkits/origene_mcp_toolkit.py +8 -49
  147. camel/toolkits/playwright_mcp_toolkit.py +12 -31
  148. camel/toolkits/resend_toolkit.py +168 -0
  149. camel/toolkits/search_toolkit.py +264 -91
  150. camel/toolkits/slack_toolkit.py +64 -10
  151. camel/toolkits/terminal_toolkit/__init__.py +18 -0
  152. camel/toolkits/terminal_toolkit/terminal_toolkit.py +957 -0
  153. camel/toolkits/terminal_toolkit/utils.py +532 -0
  154. camel/toolkits/vertex_ai_veo_toolkit.py +590 -0
  155. camel/toolkits/video_analysis_toolkit.py +17 -11
  156. camel/toolkits/wechat_official_toolkit.py +483 -0
  157. camel/toolkits/zapier_toolkit.py +5 -1
  158. camel/types/__init__.py +2 -2
  159. camel/types/enums.py +274 -7
  160. camel/types/openai_types.py +2 -2
  161. camel/types/unified_model_type.py +15 -0
  162. camel/utils/commons.py +36 -5
  163. camel/utils/constants.py +3 -0
  164. camel/utils/context_utils.py +1003 -0
  165. camel/utils/mcp.py +138 -4
  166. camel/utils/token_counting.py +43 -20
  167. {camel_ai-0.2.73a4.dist-info → camel_ai-0.2.80a2.dist-info}/METADATA +223 -83
  168. {camel_ai-0.2.73a4.dist-info → camel_ai-0.2.80a2.dist-info}/RECORD +170 -141
  169. camel/loaders/pandas_reader.py +0 -368
  170. camel/toolkits/openai_agent_toolkit.py +0 -135
  171. camel/toolkits/terminal_toolkit.py +0 -1550
  172. {camel_ai-0.2.73a4.dist-info → camel_ai-0.2.80a2.dist-info}/WHEEL +0 -0
  173. {camel_ai-0.2.73a4.dist-info → camel_ai-0.2.80a2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,483 @@
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
+ import time
17
+ from typing import Any, Dict, List, Literal, Optional
18
+
19
+ import requests
20
+
21
+ from camel.logger import get_logger
22
+ from camel.toolkits import FunctionTool
23
+ from camel.toolkits.base import BaseToolkit
24
+ from camel.utils import MCPServer, api_keys_required, retry_on_error
25
+
26
+ logger = get_logger(__name__)
27
+
28
+ # Global variables for caching access token
29
+ _wechat_access_token = None
30
+ _wechat_access_token_expires_at = 0
31
+
32
+
33
+ @retry_on_error()
34
+ def _get_wechat_access_token() -> str:
35
+ r"""Retrieves or refreshes the WeChat Official Account access token.
36
+
37
+ Returns:
38
+ str: The valid access token.
39
+
40
+ Raises:
41
+ ValueError: If credentials are missing or token retrieval fails.
42
+
43
+ References:
44
+ https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html
45
+ """
46
+ global _wechat_access_token, _wechat_access_token_expires_at
47
+
48
+ if _wechat_access_token and _wechat_access_token_expires_at > time.time():
49
+ return _wechat_access_token
50
+
51
+ app_id = os.environ.get("WECHAT_APP_ID", "")
52
+ app_secret = os.environ.get("WECHAT_APP_SECRET", "")
53
+
54
+ url = (
55
+ "https://api.weixin.qq.com/cgi-bin/token?"
56
+ f"grant_type=client_credential&appid={app_id}&secret={app_secret}"
57
+ )
58
+ response = requests.get(url)
59
+ response.raise_for_status()
60
+ data = response.json()
61
+
62
+ if "access_token" in data:
63
+ _wechat_access_token = data["access_token"]
64
+ _wechat_access_token_expires_at = (
65
+ time.time() + data.get("expires_in", 7200) - 60
66
+ )
67
+ logger.info("WeChat access token refreshed.")
68
+ return _wechat_access_token
69
+ else:
70
+ errcode = data.get("errcode")
71
+ errmsg = data.get("errmsg", "Unknown error")
72
+ raise ValueError(f"Failed to get access token {errcode}: {errmsg}")
73
+
74
+
75
+ def _make_wechat_request(
76
+ method: Literal["GET", "POST"], endpoint: str, **kwargs
77
+ ) -> Dict[str, Any]:
78
+ r"""Makes a request to WeChat API with proper error handling.
79
+
80
+ Args:
81
+ method (Literal["GET", "POST"]): HTTP method ('GET' or 'POST').
82
+ endpoint (str): API endpoint path.
83
+ **kwargs: Additional arguments for requests.
84
+
85
+ Returns:
86
+ Dict[str, Any]: API response data.
87
+
88
+ Raises:
89
+ requests.exceptions.RequestException: If request fails.
90
+ ValueError: If API returns an error.
91
+ """
92
+ global _wechat_access_token, _wechat_access_token_expires_at
93
+ access_token = _get_wechat_access_token()
94
+
95
+ # Handle URL parameter concatenation
96
+ separator = "&" if "?" in endpoint else "?"
97
+ url = (
98
+ f"https://api.weixin.qq.com{endpoint}{separator}"
99
+ f"access_token={access_token}"
100
+ )
101
+
102
+ if method.upper() == "GET":
103
+ response = requests.get(url, **kwargs)
104
+ else:
105
+ response = requests.post(url, **kwargs)
106
+
107
+ response.raise_for_status()
108
+ data = response.json()
109
+
110
+ if data.get("errcode") and data.get("errcode") != 0:
111
+ errcode = data.get("errcode")
112
+ errmsg = data.get("errmsg", "Unknown error")
113
+ raise ValueError(f"WeChat API error {errcode}: {errmsg}")
114
+
115
+ return data
116
+
117
+
118
+ @MCPServer()
119
+ class WeChatOfficialToolkit(BaseToolkit):
120
+ r"""A toolkit for WeChat Official Account operations.
121
+
122
+ This toolkit provides methods to interact with the WeChat Official Account
123
+ API, allowing users to send messages, manage users, and handle media files.
124
+
125
+ References:
126
+ - Documentation: https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Overview.html
127
+ - Test Account: https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
128
+
129
+ Notes:
130
+ Set environment variables: WECHAT_APP_ID, WECHAT_APP_SECRET
131
+ """
132
+
133
+ def __init__(self, timeout: Optional[float] = None):
134
+ r"""Initializes the WeChatOfficialToolkit."""
135
+ super().__init__(timeout=timeout)
136
+ self.base_url = "https://api.weixin.qq.com"
137
+
138
+ # Validate credentials
139
+ app_id = os.environ.get("WECHAT_APP_ID", "")
140
+ app_secret = os.environ.get("WECHAT_APP_SECRET", "")
141
+
142
+ if not all([app_id, app_secret]):
143
+ raise ValueError(
144
+ "WeChat credentials missing. Set WECHAT_APP_ID and"
145
+ " WECHAT_APP_SECRET."
146
+ )
147
+
148
+ # Define full logic as class methods; top-level functions delegate here
149
+
150
+ @api_keys_required(
151
+ [
152
+ (None, "WECHAT_APP_ID"),
153
+ (None, "WECHAT_APP_SECRET"),
154
+ ]
155
+ )
156
+ def send_customer_message(
157
+ self,
158
+ openid: str,
159
+ content: str,
160
+ msgtype: Literal[
161
+ "text",
162
+ "image",
163
+ "voice",
164
+ "video",
165
+ ] = "text",
166
+ ) -> str:
167
+ r"""Sends a customer service message to a WeChat user.
168
+
169
+ Args:
170
+ openid (str): The user's OpenID.
171
+ content (str): Message content or media_id for non-text messages.
172
+ msgtype (str): Message type: "text", "image", "voice", "video".
173
+
174
+ Returns:
175
+ str: Success or error message.
176
+
177
+ References:
178
+ https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Service_Center_messages.html
179
+ """
180
+ payload: Dict[str, Any] = {"touser": openid, "msgtype": msgtype}
181
+ if msgtype == "text":
182
+ payload["text"] = {"content": content}
183
+ elif msgtype in ["image", "voice"]:
184
+ payload[msgtype] = {"media_id": content}
185
+ elif msgtype == "video":
186
+ parts = content.split(",", 2)
187
+ payload["video"] = {
188
+ "media_id": parts[0],
189
+ "title": parts[1] if len(parts) > 1 else "",
190
+ "description": parts[2] if len(parts) > 2 else "",
191
+ }
192
+ else:
193
+ return f"Unsupported message type: {msgtype}"
194
+
195
+ _make_wechat_request(
196
+ "POST",
197
+ "/cgi-bin/message/custom/send",
198
+ headers={"Content-Type": "application/json"},
199
+ json=payload,
200
+ )
201
+ return f"Message sent successfully to {openid}."
202
+
203
+ @api_keys_required(
204
+ [
205
+ (None, "WECHAT_APP_ID"),
206
+ (None, "WECHAT_APP_SECRET"),
207
+ ]
208
+ )
209
+ def get_user_info(
210
+ self,
211
+ openid: str,
212
+ lang: str = "zh_CN",
213
+ ) -> Dict[str, Any]:
214
+ r"""Retrieves WeChat user information.
215
+
216
+ Args:
217
+ openid (str): The user's OpenID.
218
+ lang (str): Response language. Common values: "zh_CN", "zh_TW",
219
+ "en". (default: "zh_CN")
220
+
221
+ Returns:
222
+ Dict[str, Any]: User information as dictionary or error
223
+ information.
224
+
225
+ References:
226
+ https://developers.weixin.qq.com/doc/offiaccount/User_Management/
227
+ Getting_user_basic_information.html
228
+ """
229
+ data = _make_wechat_request(
230
+ "GET", f"/cgi-bin/user/info?openid={openid}&lang={lang}"
231
+ )
232
+ return data
233
+
234
+ @api_keys_required(
235
+ [
236
+ (None, "WECHAT_APP_ID"),
237
+ (None, "WECHAT_APP_SECRET"),
238
+ ]
239
+ )
240
+ def get_followers_list(
241
+ self,
242
+ next_openid: str = "",
243
+ ) -> Dict[str, Any]:
244
+ r"""Retrieves list of followers' OpenIDs.
245
+
246
+ Args:
247
+ next_openid (str): Starting OpenID for pagination. (default: "")
248
+
249
+ Returns:
250
+ Dict[str, Any]: Followers list as dictionary or error information.
251
+
252
+ References:
253
+ https://developers.weixin.qq.com/doc/offiaccount/User_Management/
254
+ Getting_a_list_of_followers.html
255
+ """
256
+ endpoint = "/cgi-bin/user/get"
257
+ if next_openid:
258
+ endpoint += f"?next_openid={next_openid}"
259
+ data = _make_wechat_request("GET", endpoint)
260
+ return data
261
+
262
+ @api_keys_required(
263
+ [
264
+ (None, "WECHAT_APP_ID"),
265
+ (None, "WECHAT_APP_SECRET"),
266
+ ]
267
+ )
268
+ def upload_wechat_media(
269
+ self,
270
+ media_type: Literal[
271
+ "image",
272
+ "voice",
273
+ "video",
274
+ "thumb",
275
+ ],
276
+ file_path: str,
277
+ permanent: bool = False,
278
+ description: Optional[str] = None,
279
+ ) -> Dict[str, Any]:
280
+ r"""Uploads media file to WeChat.
281
+
282
+ Args:
283
+ media_type (str): Media type: "image", "voice", "video", "thumb".
284
+ file_path (str): Local file path.
285
+ permanent (bool): Whether to upload as permanent media.
286
+ (default: :obj:`False`)
287
+ description (Optional[str]): Video description in JSON format
288
+ for permanent upload. (default: :obj:`None`)
289
+
290
+ Returns:
291
+ Dict[str, Any]: Upload result with media_id or error information.
292
+
293
+ References:
294
+ - Temporary: https://developers.weixin.qq.com/doc/offiaccount/
295
+ Asset_Management/Adding_Temporary_Assets.html
296
+ - Permanent: https://developers.weixin.qq.com/doc/offiaccount/
297
+ Asset_Management/Adding_Permanent_Assets.html
298
+ """
299
+ if permanent:
300
+ endpoint = f"/cgi-bin/material/add_material?type={media_type}"
301
+ data_payload = {}
302
+ if media_type == "video" and description:
303
+ data_payload["description"] = description
304
+ with open(file_path, "rb") as media_file:
305
+ files: Dict[str, Any] = {"media": media_file}
306
+ if media_type == "video" and description:
307
+ files["description"] = (None, description)
308
+ data = _make_wechat_request(
309
+ "POST", endpoint, files=files, data=data_payload
310
+ )
311
+ else:
312
+ endpoint = f"/cgi-bin/media/upload?type={media_type}"
313
+ with open(file_path, "rb") as f:
314
+ files = {"media": f}
315
+ data = _make_wechat_request("POST", endpoint, files=files)
316
+
317
+ return data
318
+
319
+ @api_keys_required(
320
+ [
321
+ (None, "WECHAT_APP_ID"),
322
+ (None, "WECHAT_APP_SECRET"),
323
+ ]
324
+ )
325
+ def get_media_list(
326
+ self,
327
+ media_type: Literal[
328
+ "image",
329
+ "voice",
330
+ "video",
331
+ "news",
332
+ ],
333
+ offset: int = 0,
334
+ count: int = 20,
335
+ ) -> Dict[str, Any]:
336
+ r"""Gets list of permanent media files.
337
+
338
+ Args:
339
+ media_type (str): Media type: "image", "voice", "video", "news".
340
+ offset (int): Starting position. (default: :obj:`0`)
341
+ count (int): Number of items (1-20). (default: :obj:`20`)
342
+
343
+ Returns:
344
+ Dict[str, Any]: Media list as dictionary or error information.
345
+
346
+ References:
347
+ https://developers.weixin.qq.com/doc/offiaccount/Asset_Management/
348
+ Get_the_list_of_all_materials.html
349
+ """
350
+ payload = {"type": media_type, "offset": offset, "count": count}
351
+ data = _make_wechat_request(
352
+ "POST",
353
+ "/cgi-bin/material/batchget_material",
354
+ headers={"Content-Type": "application/json"},
355
+ json=payload,
356
+ )
357
+ return data
358
+
359
+ @api_keys_required(
360
+ [
361
+ (None, "WECHAT_APP_ID"),
362
+ (None, "WECHAT_APP_SECRET"),
363
+ ]
364
+ )
365
+ def send_mass_message_to_all(
366
+ self,
367
+ content: str,
368
+ msgtype: Literal[
369
+ "text",
370
+ "image",
371
+ "voice",
372
+ "video",
373
+ ] = "text",
374
+ clientmsgid: Optional[str] = None,
375
+ send_ignore_reprint: Optional[int] = 0,
376
+ batch_size: int = 10000,
377
+ ) -> Dict[str, Any]:
378
+ r"""Sends a mass message to all followers (by OpenID list).
379
+
380
+ This method paginates all follower OpenIDs and calls the
381
+ mass-send API in batches.
382
+
383
+ Args:
384
+ content (str): For text, the message content; for non-text,
385
+ the media_id.
386
+ msgtype (Literal["text","image","voice","video"]):
387
+ Message type. For "video", the mass API expects
388
+ "mpvideo" internally.
389
+ clientmsgid (Optional[str]): Idempotency key to avoid
390
+ duplicate mass jobs.
391
+ send_ignore_reprint (Optional[int]): Whether to continue
392
+ when a news article is judged as a reprint (reserved;
393
+ applies to news/mpnews).
394
+ batch_size (int): Max OpenIDs per request (WeChat limit
395
+ is up to 10000 per batch).
396
+
397
+ Returns:
398
+ Dict[str, Any]: Aggregated result including counts and
399
+ each batch response.
400
+
401
+ References:
402
+ - Mass send by OpenID list:
403
+ https://developers.weixin.qq.com/doc/service/api/notify/message/
404
+ api_masssend.html
405
+ """
406
+ # 1) Collect all follower OpenIDs
407
+ all_openids: List[str] = []
408
+ next_openid = ""
409
+ while True:
410
+ endpoint = "/cgi-bin/user/get"
411
+ if next_openid:
412
+ endpoint += f"?next_openid={next_openid}"
413
+ page = _make_wechat_request("GET", endpoint)
414
+ data_block = page.get("data", {}) if isinstance(page, dict) else {}
415
+ openids = (
416
+ data_block.get("openid", [])
417
+ if isinstance(data_block, dict)
418
+ else []
419
+ )
420
+ if openids:
421
+ all_openids.extend(openids)
422
+ next_openid = (
423
+ page.get("next_openid", "") if isinstance(page, dict) else ""
424
+ )
425
+ if not next_openid:
426
+ break
427
+
428
+ # 2) Build and send batches
429
+ results: List[Dict[str, Any]] = []
430
+ if not all_openids:
431
+ return {
432
+ "total_openids": 0,
433
+ "batches": 0,
434
+ "results": results,
435
+ }
436
+
437
+ def build_payload(openid_batch: List[str]) -> Dict[str, Any]:
438
+ payload: Dict[str, Any] = {
439
+ "touser": openid_batch,
440
+ }
441
+ if msgtype == "text":
442
+ payload["msgtype"] = "text"
443
+ payload["text"] = {"content": content}
444
+ elif msgtype in ("image", "voice"):
445
+ payload["msgtype"] = msgtype
446
+ payload[msgtype] = {"media_id": content}
447
+ elif msgtype == "video":
448
+ # Mass API expects mpvideo
449
+ payload["msgtype"] = "mpvideo"
450
+ payload["mpvideo"] = {"media_id": content}
451
+ if clientmsgid:
452
+ payload["clientmsgid"] = clientmsgid
453
+ if send_ignore_reprint is not None:
454
+ payload["send_ignore_reprint"] = send_ignore_reprint
455
+ return payload
456
+
457
+ for i in range(0, len(all_openids), batch_size):
458
+ batch = all_openids[i : i + batch_size]
459
+ payload = build_payload(batch)
460
+ resp = _make_wechat_request(
461
+ "POST",
462
+ "/cgi-bin/message/mass/send",
463
+ headers={"Content-Type": "application/json"},
464
+ json=payload,
465
+ )
466
+ results.append(resp)
467
+
468
+ return {
469
+ "total_openids": len(all_openids),
470
+ "batches": (len(all_openids) + batch_size - 1) // batch_size,
471
+ "results": results,
472
+ }
473
+
474
+ def get_tools(self) -> List[FunctionTool]:
475
+ r"""Returns toolkit functions as tools."""
476
+ return [
477
+ FunctionTool(self.send_customer_message),
478
+ FunctionTool(self.get_user_info),
479
+ FunctionTool(self.get_followers_list),
480
+ FunctionTool(self.upload_wechat_media),
481
+ FunctionTool(self.get_media_list),
482
+ FunctionTool(self.send_mass_message_to_all),
483
+ ]
@@ -19,7 +19,11 @@ import requests
19
19
 
20
20
  from camel.toolkits.base import BaseToolkit
21
21
  from camel.toolkits.function_tool import FunctionTool
22
- from camel.utils import MCPServer, api_keys_required, dependencies_required
22
+ from camel.utils import (
23
+ MCPServer,
24
+ api_keys_required,
25
+ dependencies_required,
26
+ )
23
27
 
24
28
 
25
29
  @MCPServer()
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',