beswarm 0.2.36__py3-none-any.whl → 0.2.37__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 beswarm might be problematic. Click here for more details.

@@ -0,0 +1,339 @@
1
+ import os
2
+ import re
3
+ import sys
4
+ import copy
5
+ import json
6
+ import difflib
7
+ import asyncio
8
+ import platform
9
+ from pathlib import Path
10
+ from datetime import datetime
11
+ from typing import List, Dict, Union
12
+
13
+ from ..broker import MessageBroker
14
+ from ..aient.src.aient.models import chatgpt
15
+ from ..aient.src.aient.plugins import get_function_call_list, registry
16
+ from ..prompt import worker_system_prompt, instruction_system_prompt
17
+ from ..utils import extract_xml_content, get_current_screen_image_message, replace_xml_content, register_mcp_tools
18
+
19
+ class BaseAgent:
20
+ """Base class for agents, handling common initialization and disposal."""
21
+ def __init__(self, goal: str, tools_json: List, agent_config: Dict, work_dir: str, cache_messages: Union[bool, List[Dict]], broker: MessageBroker, listen_topic: str, publish_topic: str, status_topic: str):
22
+ self.goal = goal
23
+ self.tools_json = tools_json
24
+ self.work_dir = work_dir
25
+ self.cache_file = Path(work_dir) / ".beswarm" / "work_agent_conversation_history.json"
26
+ self.config = agent_config
27
+ self.cache_messages = cache_messages
28
+ if cache_messages and isinstance(cache_messages, bool) and cache_messages == True:
29
+ self.cache_messages = json.loads(self.cache_file.read_text(encoding="utf-8"))
30
+ self.broker = broker
31
+ self.listen_topic = listen_topic
32
+ self.error_topic = listen_topic + ".error"
33
+ self.publish_topic = publish_topic
34
+ self.status_topic = status_topic
35
+ self._subscription = self.broker.subscribe(self.handle_message, [self.listen_topic, self.error_topic])
36
+
37
+ async def handle_message(self, message: Dict):
38
+ """Process incoming messages. Must be implemented by subclasses."""
39
+ raise NotImplementedError
40
+
41
+ def dispose(self):
42
+ """Cancels the subscription and cleans up resources."""
43
+ if self._subscription:
44
+ self._subscription.dispose()
45
+
46
+
47
+ class InstructionAgent(BaseAgent):
48
+ """Generates instructions and publishes them to a message broker."""
49
+ def __init__(self, goal: str, tools_json: List, agent_config: Dict, work_dir: str, cache_messages: Union[bool, List[Dict]], broker: MessageBroker, listen_topic: str, publish_topic: str, status_topic: str):
50
+ super().__init__(goal, tools_json, agent_config, work_dir, cache_messages, broker, listen_topic, publish_topic, status_topic)
51
+
52
+ self.last_instruction = None
53
+ self.agent = chatgpt(**self.config)
54
+
55
+ self.goal_diff = None
56
+
57
+ if self.cache_messages and isinstance(self.cache_messages, list) and len(self.cache_messages) > 1:
58
+ old_goal = extract_xml_content(self.cache_messages[1]["content"], "goal")
59
+ if old_goal.strip() != goal.strip():
60
+ diff_generator = difflib.ndiff(old_goal.splitlines(), goal.splitlines())
61
+ changed_lines = []
62
+ for line in diff_generator:
63
+ if (line.startswith('+ ') or line.startswith('- ')) and line[2:].strip():
64
+ changed_lines.append(line)
65
+ self.goal_diff = '\n'.join(changed_lines).strip()
66
+
67
+ def get_conversation_history(self, conversation_history: List[Dict]):
68
+ conversation_history = copy.deepcopy(conversation_history)
69
+
70
+ self.cache_file.write_text(json.dumps(conversation_history, ensure_ascii=False, indent=4), encoding="utf-8")
71
+
72
+ work_agent_system_prompt = conversation_history.pop(0)
73
+ if conversation_history:
74
+ original_content = work_agent_system_prompt["content"]
75
+ regex = r"<latest_file_content>(.*?)</latest_file_content>"
76
+ match = re.search(regex, original_content, re.DOTALL)
77
+ if match:
78
+ extracted_content = f"<latest_file_content>{match.group(1)}</latest_file_content>\n\n"
79
+ else:
80
+ extracted_content = ""
81
+ if isinstance(conversation_history[0]["content"], str):
82
+ conversation_history[0]["content"] = extracted_content + conversation_history[0]["content"]
83
+ elif isinstance(conversation_history[0]["content"], list) and extracted_content:
84
+ conversation_history[0]["content"].append({"type": "text", "text": extracted_content})
85
+
86
+ return conversation_history
87
+
88
+ async def handle_message(self, message: Dict):
89
+ """Receives a worker response, generates the next instruction, and publishes it."""
90
+
91
+ if len(message["conversation"]) > 1 and message["conversation"][-2]["role"] == "user" \
92
+ and "<task_complete_message>" in message["conversation"][-2]["content"]:
93
+ task_complete_message = extract_xml_content(message["conversation"][-2]["content"], "task_complete_message")
94
+ self.broker.publish({"status": "finished", "result": task_complete_message}, self.status_topic)
95
+ return
96
+
97
+ instruction_prompt = "".join([
98
+ "</work_agent_conversation_end>\n\n",
99
+ f"任务目标: {self.goal}\n\n",
100
+ f"任务目标新变化:\n{self.goal_diff}\n\n" if self.goal_diff else "",
101
+ "在 tag <work_agent_conversation_start>...</work_agent_conversation_end> 之前的对话历史都是工作智能体的对话历史。\n\n",
102
+ "根据以上对话历史和目标,请生成下一步指令。如果任务已完成,指示工作智能体调用task_complete工具。\n\n",
103
+ ])
104
+ if self.last_instruction and 'fetch_gpt_response_stream HTTP Error' not in self.last_instruction:
105
+ instruction_prompt = (
106
+ f"{instruction_prompt}\n\n"
107
+ "你生成的指令格式错误,必须把给assistant的指令放在<instructions>...</instructions>标签内。请重新生成格式正确的指令。"
108
+ f"这是你上次给assistant的错误格式的指令:\n{self.last_instruction}"
109
+ )
110
+
111
+ self.agent.conversation["default"][1:] = self.get_conversation_history(message["conversation"])
112
+
113
+ if "find_and_click_element" in json.dumps(self.tools_json):
114
+ instruction_prompt = await get_current_screen_image_message(instruction_prompt)
115
+
116
+ raw_response = await self.agent.ask_async(instruction_prompt)
117
+
118
+ if "fetch_gpt_response_stream HTTP Error', 'status_code': 404" in raw_response:
119
+ raise Exception(f"Model: {self.config['engine']} not found!")
120
+ if "'status_code': 413" in raw_response or \
121
+ "'status_code': 400" in raw_response:
122
+ self.broker.publish({"status": "error", "result": raw_response}, self.status_topic)
123
+ return
124
+
125
+ self.broker.publish({"status": "new_message", "result": "\n🤖 指令智能体:\n" + raw_response}, self.status_topic)
126
+
127
+ self.last_instruction = raw_response
128
+ instruction = extract_xml_content(raw_response, "instructions")
129
+ if instruction:
130
+ if len(message["conversation"]) == 1:
131
+ instruction = (
132
+ "任务描述:\n"
133
+ f"<goal>{self.goal}</goal>\n\n"
134
+ "你作为指令的**执行者**,而非任务的**规划师**,你必须严格遵循以下单步工作流程:\n"
135
+ "**执行指令**\n"
136
+ " - **严格遵从:** 只执行我当前下达的明确指令。在我明确给出下一步指令前,绝不擅自行动或推测、执行任何未明确要求的后续步骤。\n"
137
+ " - **严禁越权:** 禁止执行任何我未指定的步骤。`<goal>` 标签中的内容仅为背景信息,不得据此进行任务规划或推测。\n"
138
+ "**汇报结果**\n"
139
+ " - **聚焦单步:** 指令完成后,仅汇报该步骤的执行结果与产出。\n"
140
+ "**暂停等待**\n"
141
+ " - **原地待命:** 汇报后,任务暂停。在收到我新的指令前,严禁发起任何新的工具调用或操作。\n"
142
+ " - **请求指令:** 回复的最后必须明确请求我提供下一步指令。\n"
143
+ "**注意:** 禁止完成超出下面我未规定的步骤,`<goal>` 标签中的内容仅为背景信息。"
144
+ "现在开始执行第一步:\n"
145
+ f"{instruction}"
146
+ )
147
+ self.broker.publish({"instruction": instruction, "conversation": message["conversation"]}, self.publish_topic)
148
+ else:
149
+ print("\n❌ 指令智能体生成的指令不符合要求,正在重新生成。")
150
+ self.broker.publish(message, self.error_topic)
151
+
152
+
153
+ class WorkerAgent(BaseAgent):
154
+ """Executes instructions and publishes results to a message broker."""
155
+ def __init__(self, goal: str, tools_json: List, agent_config: Dict, work_dir: str, cache_messages: Union[bool, List[Dict]], broker: MessageBroker, listen_topic: str, publish_topic: str, status_topic: str):
156
+ super().__init__(goal, tools_json, agent_config, work_dir, cache_messages, broker, listen_topic, publish_topic, status_topic)
157
+
158
+ if self.cache_messages and isinstance(self.cache_messages, list) and len(self.cache_messages) > 1:
159
+ first_user_message = replace_xml_content(self.cache_messages[1]["content"], "goal", goal)
160
+ self.config["cache_messages"] = self.cache_messages[0:1] + [{"role": "user", "content": first_user_message}] + self.cache_messages[2:]
161
+
162
+ self.agent = chatgpt(**self.config)
163
+
164
+ async def handle_message(self, message: Dict):
165
+ """Receives an instruction, executes it, and publishes the response."""
166
+
167
+ if message.get("instruction") == "Initial kickoff":
168
+ self.broker.publish({
169
+ "conversation": self.agent.conversation["default"]
170
+ }, self.publish_topic)
171
+ return
172
+
173
+ instruction = message["instruction"]
174
+ if "find_and_click_element" in json.dumps(self.tools_json):
175
+ instruction = await get_current_screen_image_message(instruction)
176
+ response = await self.agent.ask_async(instruction)
177
+
178
+ if response.strip() == '':
179
+ print("\n❌ 工作智能体回复为空,请重新生成指令。")
180
+ self.broker.publish(message, self.error_topic)
181
+ else:
182
+ self.broker.publish({"status": "new_message", "result": "\n✅ 工作智能体:\n" + response}, self.status_topic)
183
+ self.broker.publish({
184
+ "conversation": self.agent.conversation["default"]
185
+ }, self.publish_topic)
186
+
187
+ class Tee:
188
+ def __init__(self, *files):
189
+ self.files = files
190
+
191
+ def write(self, obj):
192
+ for f in self.files:
193
+ f.write(obj)
194
+ f.flush()
195
+
196
+ def flush(self):
197
+ for f in self.files:
198
+ f.flush()
199
+
200
+ class BrokerWorker:
201
+ """The 'glue' class that orchestrates agents via a MessageBroker."""
202
+ def __init__(self, goal: str, tools: List[Union[str, Dict]], work_dir: str, cache_messages: Union[bool, List[Dict]] = None, broker = None, mcp_manager = None, task_manager = None):
203
+ self.goal = goal
204
+ self.tools = tools
205
+ self.work_dir = Path(work_dir)
206
+ self.cache_messages = cache_messages
207
+
208
+ self.broker = broker
209
+ self.mcp_manager = mcp_manager
210
+ self.task_manager = task_manager
211
+ self.task_completion_event = asyncio.Event()
212
+ self.final_result = None
213
+ self._status_subscription = None
214
+ self.setup()
215
+
216
+ self.channel = self.broker.request_channel()
217
+ self.INSTRUCTION_TOPIC = self.channel + ".instructions"
218
+ self.WORKER_RESPONSE_TOPIC = self.channel + ".worker_responses"
219
+ self.TASK_STATUS_TOPIC =self.channel + ".task_status"
220
+
221
+ def setup(self):
222
+ cache_dir = self.work_dir / ".beswarm"
223
+ cache_dir.mkdir(parents=True, exist_ok=True)
224
+ self.task_manager.set_root_path(self.work_dir)
225
+ self.cache_file = cache_dir / "work_agent_conversation_history.json"
226
+ if not self.cache_file.exists():
227
+ self.cache_file.write_text("[]", encoding="utf-8")
228
+
229
+ DEBUG = os.getenv("DEBUG", "false").lower() in ("true", "1", "t", "yes")
230
+ if DEBUG:
231
+ log_file = open(cache_dir / "history.log", "a", encoding="utf-8")
232
+ log_file.write(f"========== {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} ==========\n")
233
+ original_stdout = sys.stdout
234
+ original_stderr = sys.stderr
235
+ sys.stdout = Tee(original_stdout, log_file)
236
+ sys.stderr = Tee(original_stderr, log_file)
237
+
238
+ async def _configure_tools(self):
239
+ mcp_list = [item for item in self.tools if isinstance(item, dict)]
240
+ if mcp_list:
241
+ for mcp_item in mcp_list:
242
+ mcp_name, mcp_config = list(mcp_item.items())[0]
243
+ await self.mcp_manager.add_server(mcp_name, mcp_config)
244
+ client = self.mcp_manager.clients.get(mcp_name)
245
+ await register_mcp_tools(client, registry)
246
+ all_mcp_tools = await self.mcp_manager.get_all_tools()
247
+ self.tools.extend([tool.name for tool in sum(all_mcp_tools.values(), [])])
248
+ self.tools = [item for item in self.tools if not isinstance(item, dict)]
249
+ if "task_complete" not in self.tools: self.tools.append("task_complete")
250
+ self.tools_json = [value for _, value in get_function_call_list(self.tools).items()]
251
+
252
+ def _task_status_subscriber(self, message: Dict):
253
+ """Subscriber for task status changes."""
254
+ if message.get("status") == "finished":
255
+ self.final_result = message.get("result")
256
+ self.task_completion_event.set()
257
+
258
+ if message.get("status") == "error":
259
+ raise Exception(message.get("result"))
260
+
261
+ if message.get("status") == "new_message":
262
+ print(message.get("result"))
263
+
264
+ def _setup_agents(self):
265
+ instruction_agent_config = {
266
+ "api_key": os.getenv("API_KEY"), "api_url": os.getenv("BASE_URL"),
267
+ "engine": os.getenv("MODEL"),
268
+ "system_prompt": instruction_system_prompt.format(
269
+ os_version=platform.platform(), tools_list=self.tools_json,
270
+ workspace_path=self.work_dir, current_time=datetime.now().strftime("%Y-%m-%d %H:%M:%S")
271
+ ),
272
+ "print_log": os.getenv("DEBUG", "false").lower() in ("true", "1", "t", "yes"),
273
+ "temperature": 0.7, "use_plugins": False
274
+ }
275
+
276
+ worker_agent_config = {
277
+ "api_key": os.getenv("API_KEY"), "api_url": os.getenv("BASE_URL"),
278
+ "engine": os.getenv("FAST_MODEL") or os.getenv("MODEL"),
279
+ "system_prompt": worker_system_prompt.format(
280
+ os_version=platform.platform(), workspace_path=self.work_dir,
281
+ shell=os.getenv('SHELL', 'Unknown'), current_time=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
282
+ tools_list=self.tools_json
283
+ ),
284
+ "print_log": True, "temperature": 0.5, "function_call_max_loop": 100
285
+ }
286
+
287
+ instruction_agent = InstructionAgent(
288
+ goal=self.goal, tools_json=self.tools_json, agent_config=instruction_agent_config, work_dir=self.work_dir, cache_messages=self.cache_messages,
289
+ broker=self.broker, listen_topic=self.WORKER_RESPONSE_TOPIC,
290
+ publish_topic=self.INSTRUCTION_TOPIC, status_topic=self.TASK_STATUS_TOPIC
291
+ )
292
+
293
+ worker_agent = WorkerAgent(
294
+ goal=self.goal, tools_json=self.tools_json, agent_config=worker_agent_config, work_dir=self.work_dir, cache_messages=self.cache_messages,
295
+ broker=self.broker, listen_topic=self.INSTRUCTION_TOPIC,
296
+ publish_topic=self.WORKER_RESPONSE_TOPIC, status_topic=self.TASK_STATUS_TOPIC
297
+ )
298
+ return instruction_agent, worker_agent
299
+
300
+ async def run(self):
301
+ """Sets up subscriptions and starts the workflow."""
302
+ os.chdir(self.work_dir.absolute())
303
+ await self._configure_tools()
304
+
305
+ instruction_agent, worker_agent = self._setup_agents()
306
+
307
+ self.broker.publish({"instruction": "Initial kickoff"}, self.INSTRUCTION_TOPIC)
308
+
309
+ self._status_subscription = self.broker.subscribe(self._task_status_subscriber, self.TASK_STATUS_TOPIC)
310
+ await self.task_completion_event.wait()
311
+
312
+ instruction_agent.dispose()
313
+ worker_agent.dispose()
314
+ self._status_subscription.dispose()
315
+ await self.mcp_manager.cleanup()
316
+ return self.final_result
317
+
318
+ async def stream_run(self):
319
+ """Runs the workflow and yields status messages."""
320
+ os.chdir(self.work_dir.absolute())
321
+ await self._configure_tools()
322
+
323
+ instruction_agent, worker_agent = self._setup_agents()
324
+
325
+ self.broker.publish({"instruction": "Initial kickoff"}, self.INSTRUCTION_TOPIC)
326
+
327
+ try:
328
+ async for message in self.broker.iter_topic(self.TASK_STATUS_TOPIC):
329
+ if message.get("status") == "new_message":
330
+ yield message.get("result")
331
+ elif message.get("status") == "finished":
332
+ yield message.get("result")
333
+ break
334
+ elif message.get("status") == "error":
335
+ raise Exception(message.get("result"))
336
+ finally:
337
+ instruction_agent.dispose()
338
+ worker_agent.dispose()
339
+ await self.mcp_manager.cleanup()
beswarm/core.py ADDED
@@ -0,0 +1,11 @@
1
+ from .taskmanager import TaskManager
2
+ from .broker import MessageBroker
3
+ from .bemcp.bemcp import MCPManager
4
+
5
+ """
6
+ 全局共享实例
7
+ """
8
+
9
+ task_manager = TaskManager()
10
+ broker = MessageBroker()
11
+ mcp_manager = MCPManager()
@@ -1,11 +1,10 @@
1
- import ast
2
1
  import json
3
2
  import uuid
4
3
  import asyncio
5
4
  from enum import Enum
6
5
  from pathlib import Path
7
6
 
8
- from ..aient.src.aient.plugins import register_tool, registry
7
+ from .aient.src.aient.plugins import registry
9
8
 
10
9
  class TaskStatus(Enum):
11
10
  """任务状态枚举"""
@@ -91,7 +90,7 @@ class TaskManager:
91
90
  running_task_id_list = [task_id for task_id, task in self.tasks_cache.items() if task_id != "root_path" and task.get("status") == "RUNNING"]
92
91
  for task_id in running_task_id_list:
93
92
  tasks_params = self.tasks_cache[task_id]["args"]
94
- task_id = task_manager.resume_task(task_id, worker_fun, tasks_params)
93
+ task_id = self.resume_task(task_id, registry.tools["worker"], tasks_params)
95
94
 
96
95
  def resume_task(self, task_id, task_coro, args):
97
96
  """
@@ -223,85 +222,8 @@ class TaskManager:
223
222
  # 如果任务ID不存在,则返回-1
224
223
  return -1
225
224
 
226
-
227
- task_manager = TaskManager()
228
-
229
- worker_fun = registry.tools["worker"]
230
-
231
- @register_tool()
232
- def create_task(goal, tools, work_dir):
233
- """
234
- 启动一个子任务来自动完成指定的任务目标 (`goal`)。
235
-
236
- 这个子任务接收一个清晰的任务描述、一组可供调用的工具 (`tools`),以及一个工作目录 (`work_dir`)。
237
- 它会结合可用的工具,自主规划并逐步执行必要的操作,直到最终完成指定的任务目标。
238
- 核心功能是根据输入的目标,驱动整个任务执行流程。
239
- 子任务下上文为空,因此需要细致的背景信息。
240
-
241
- Args:
242
- goal (str): 需要完成的具体任务目标描述。子任务将围绕此目标进行工作。必须清晰、具体。必须包含背景信息,完成指标等。写清楚什么时候算任务完成,同时交代清楚任务的背景信息,这个背景信息可以是需要读取的文件等一切有助于完成任务的信息。
243
- tools (list[str]): 一个包含可用工具函数对象的列表。子任务在执行任务时可能会调用这些工具来与环境交互(例如读写文件、执行命令等)。
244
- work_dir (str): 工作目录的绝对路径。子任务将在此目录上下文中执行操作。子任务的工作目录位置在主任务的工作目录的子目录。子任务工作目录**禁止**设置为主任务目录本身。
245
-
246
- Returns:
247
- str: 当任务成功完成时,返回字符串 "任务已完成"。
248
- """
249
- tasks_params = [
250
- {"goal": goal, "tools": ast.literal_eval(tools), "work_dir": work_dir, "cache_messages": True}
251
- ]
252
- task_ids = task_manager.create_tasks(worker_fun, tasks_params)
253
- return task_ids
254
-
255
- @register_tool()
256
- def resume_task(task_id, goal):
257
- """
258
- 恢复一个子任务。
259
- """
260
- if task_id not in task_manager.tasks_cache:
261
- return f"任务 {task_id} 不存在"
262
- tasks_params = task_manager.tasks_cache[task_id]["args"]
263
- tasks_params["goal"] = goal
264
- tasks_params["cache_messages"] = True
265
- task_id = task_manager.resume_task(task_id, worker_fun, tasks_params)
266
- return f"任务 {task_id} 已恢复"
267
-
268
- @register_tool()
269
- def get_all_tasks_status():
270
- """
271
- 获取所有任务的状态。
272
- 子任务状态会持久化到磁盘,因此即使历史记录为空,之前的子任务仍然存在。
273
-
274
- Returns:
275
- str: 所有任务的状态。每个任务的id,状态,结果。
276
- """
277
- return task_manager.tasks_cache
278
-
279
- @register_tool()
280
- async def get_task_result():
281
- """
282
- 等待并获取子任务的执行结果。如果需要等待子任务完成,请使用这个工具。一旦有任务完成,会自动获取结果。如果调用时没有任务完成,会等待直到有任务完成。
283
-
284
- Returns:
285
- str: 子任务的执行结果。
286
- """
287
- running_tasks_num = len([task_id for task_id, task in task_manager.tasks_cache.items() if task_id != "root_path" and task.get("status") == "RUNNING"])
288
- if running_tasks_num == 0:
289
- return "All tasks are finished."
290
- task_id, status, result = await task_manager.get_next_result()
291
-
292
- unfinished_tasks = [task_id for task_id, task in task_manager.tasks_cache.items() if task_id != "root_path" and task.get("status") != "DONE"]
293
- text = "".join([
294
- f"Task ID: {task_id}\n",
295
- f"Status: {status.value}\n",
296
- f"Result: {result}\n\n",
297
- f"There are {len(unfinished_tasks)} unfinished tasks, unfinished task ids: {unfinished_tasks}" if unfinished_tasks else "All tasks are finished.",
298
- ])
299
-
300
- return text
301
-
302
225
  async def main():
303
226
  manager = TaskManager()
304
- from worker import worker
305
227
 
306
228
  # --- 任务提交阶段 ---
307
229
  print("--- 任务提交 ---")
@@ -313,7 +235,7 @@ async def main():
313
235
  {"goal": 2},
314
236
  {"goal": 4},
315
237
  ]
316
- task_ids = manager.create_tasks(worker, tasks_to_run)
238
+ task_ids = manager.create_tasks(registry.tools["worker"], tasks_to_run)
317
239
  print(f"\n主程序: {len(task_ids)} 个任务已提交,现在开始等待结果...\n")
318
240
 
319
241
  # --- 结果处理阶段 ---
beswarm/tools/__init__.py CHANGED
@@ -1,60 +1,58 @@
1
1
  from .edit_file import edit_file
2
- from .worker import worker, worker_gen
3
- from .screenshot import save_screenshot_to_file
4
- from .request_input import request_admin_input
5
-
2
+ from .search_web import search_web
3
+ from .completion import task_complete
6
4
  from .search_arxiv import search_arxiv
7
5
  from .repomap import get_code_repo_map
6
+ from .worker import worker, worker_gen
7
+ from .request_input import request_admin_input
8
+ from .screenshot import save_screenshot_to_file
8
9
  from .click import find_and_click_element, scroll_screen
9
- from .search_web import search_web
10
- from .taskmanager import create_task, resume_task, get_all_tasks_status, get_task_result
11
- from .completion import task_complete
10
+ from .subtasks import create_task, resume_task, get_all_tasks_status, get_task_result
12
11
 
13
12
  #显式导入 aient.plugins 中的所需内容
14
13
  from ..aient.src.aient.plugins import (
15
- excute_command,
16
14
  get_time,
15
+ read_file,
16
+ read_image,
17
+ register_tool,
18
+ write_to_file,
19
+ excute_command,
17
20
  generate_image,
18
21
  list_directory,
19
- read_file,
22
+ get_url_content,
20
23
  run_python_script,
24
+ set_readonly_path,
21
25
  get_search_results,
22
- write_to_file,
23
26
  download_read_arxiv_pdf,
24
- get_url_content,
25
- read_image,
26
- set_readonly_path,
27
- register_tool,
28
27
  )
29
28
 
30
29
  __all__ = [
31
- "edit_file",
32
30
  "worker",
31
+ "get_time",
32
+ "edit_file",
33
+ "read_file",
33
34
  "worker_gen",
35
+ "read_image",
36
+ "search_web",
37
+ "create_task",
38
+ "resume_task",
34
39
  "search_arxiv",
35
- "get_code_repo_map",
36
- # aient.plugins
40
+ "write_to_file",
41
+ "scroll_screen",
42
+ "register_tool",
43
+ "task_complete",
37
44
  "excute_command",
38
- "read_image",
39
- "get_time",
40
45
  "generate_image",
41
46
  "list_directory",
42
- "read_file",
43
- "run_python_script",
44
- "get_search_results",
45
- "write_to_file",
46
- "download_read_arxiv_pdf",
47
+ "get_task_result",
47
48
  "get_url_content",
48
- "find_and_click_element",
49
- "scroll_screen",
50
- "register_tool",
51
- "search_web",
52
- "save_screenshot_to_file",
53
49
  "set_readonly_path",
50
+ "get_code_repo_map",
51
+ "run_python_script",
52
+ "get_search_results",
54
53
  "request_admin_input",
55
- "create_task",
56
- "resume_task",
57
54
  "get_all_tasks_status",
58
- "get_task_result",
59
- "task_complete",
55
+ "find_and_click_element",
56
+ "download_read_arxiv_pdf",
57
+ "save_screenshot_to_file",
60
58
  ]
@@ -0,0 +1,77 @@
1
+ import ast
2
+
3
+ from ..core import task_manager
4
+ from ..aient.src.aient.plugins import register_tool, registry
5
+
6
+ worker_fun = registry.tools["worker"]
7
+
8
+ @register_tool()
9
+ def create_task(goal, tools, work_dir):
10
+ """
11
+ 启动一个子任务来自动完成指定的任务目标 (`goal`)。
12
+
13
+ 这个子任务接收一个清晰的任务描述、一组可供调用的工具 (`tools`),以及一个工作目录 (`work_dir`)。
14
+ 它会结合可用的工具,自主规划并逐步执行必要的操作,直到最终完成指定的任务目标。
15
+ 核心功能是根据输入的目标,驱动整个任务执行流程。
16
+ 子任务下上文为空,因此需要细致的背景信息。
17
+
18
+ Args:
19
+ goal (str): 需要完成的具体任务目标描述。子任务将围绕此目标进行工作。必须清晰、具体。必须包含背景信息,完成指标等。写清楚什么时候算任务完成,同时交代清楚任务的背景信息,这个背景信息可以是需要读取的文件等一切有助于完成任务的信息。
20
+ tools (list[str]): 一个包含可用工具函数对象的列表。子任务在执行任务时可能会调用这些工具来与环境交互(例如读写文件、执行命令等)。
21
+ work_dir (str): 工作目录的绝对路径。子任务将在此目录上下文中执行操作。子任务的工作目录位置在主任务的工作目录的子目录。子任务工作目录**禁止**设置为主任务目录本身。
22
+
23
+ Returns:
24
+ str: 当任务成功完成时,返回字符串 "任务已完成"。
25
+ """
26
+ tasks_params = [
27
+ {"goal": goal, "tools": ast.literal_eval(tools), "work_dir": work_dir, "cache_messages": True}
28
+ ]
29
+ task_ids = task_manager.create_tasks(worker_fun, tasks_params)
30
+ return task_ids
31
+
32
+ @register_tool()
33
+ def resume_task(task_id, goal):
34
+ """
35
+ 恢复一个子任务。
36
+ """
37
+ if task_id not in task_manager.tasks_cache:
38
+ return f"任务 {task_id} 不存在"
39
+ tasks_params = task_manager.tasks_cache[task_id]["args"]
40
+ tasks_params["goal"] = goal
41
+ tasks_params["cache_messages"] = True
42
+ task_id = task_manager.resume_task(task_id, worker_fun, tasks_params)
43
+ return f"任务 {task_id} 已恢复"
44
+
45
+ @register_tool()
46
+ def get_all_tasks_status():
47
+ """
48
+ 获取所有任务的状态。
49
+ 子任务状态会持久化到磁盘,因此即使历史记录为空,之前的子任务仍然存在。
50
+
51
+ Returns:
52
+ str: 所有任务的状态。每个任务的id,状态,结果。
53
+ """
54
+ return task_manager.tasks_cache
55
+
56
+ @register_tool()
57
+ async def get_task_result():
58
+ """
59
+ 等待并获取子任务的执行结果。如果需要等待子任务完成,请使用这个工具。一旦有任务完成,会自动获取结果。如果调用时没有任务完成,会等待直到有任务完成。
60
+
61
+ Returns:
62
+ str: 子任务的执行结果。
63
+ """
64
+ running_tasks_num = len([task_id for task_id, task in task_manager.tasks_cache.items() if task_id != "root_path" and task.get("status") == "RUNNING"])
65
+ if running_tasks_num == 0:
66
+ return "All tasks are finished."
67
+ task_id, status, result = await task_manager.get_next_result()
68
+
69
+ unfinished_tasks = [task_id for task_id, task in task_manager.tasks_cache.items() if task_id != "root_path" and task.get("status") != "DONE"]
70
+ text = "".join([
71
+ f"Task ID: {task_id}\n",
72
+ f"Status: {status.value}\n",
73
+ f"Result: {result}\n\n",
74
+ f"There are {len(unfinished_tasks)} unfinished tasks, unfinished task ids: {unfinished_tasks}" if unfinished_tasks else "All tasks are finished.",
75
+ ])
76
+
77
+ return text
beswarm/tools/worker.py CHANGED
@@ -1,351 +1,15 @@
1
- import os
2
- import re
3
- import sys
4
- import copy
5
- import json
6
- import difflib
7
- import asyncio
8
- import platform
9
- from pathlib import Path
10
1
  from datetime import datetime
11
2
  from typing import List, Dict, Union
12
3
 
13
- from ..broker import MessageBroker
14
- from ..aient.src.aient.models import chatgpt
15
- from ..aient.src.aient.plugins import register_tool, get_function_call_list, registry
16
- from ..prompt import worker_system_prompt, instruction_system_prompt
17
- from ..utils import extract_xml_content, get_current_screen_image_message, replace_xml_content, register_mcp_tools
18
- from ..bemcp.bemcp import MCPManager
19
-
20
- class BaseAgent:
21
- """Base class for agents, handling common initialization and disposal."""
22
- def __init__(self, goal: str, tools_json: List, agent_config: Dict, work_dir: str, cache_messages: Union[bool, List[Dict]], broker: MessageBroker, listen_topic: str, publish_topic: str, status_topic: str):
23
- self.goal = goal
24
- self.tools_json = tools_json
25
- self.work_dir = work_dir
26
- self.cache_file = Path(work_dir) / ".beswarm" / "work_agent_conversation_history.json"
27
- self.config = agent_config
28
- self.cache_messages = cache_messages
29
- if cache_messages and isinstance(cache_messages, bool) and cache_messages == True:
30
- self.cache_messages = json.loads(self.cache_file.read_text(encoding="utf-8"))
31
- self.broker = broker
32
- self.listen_topic = listen_topic
33
- self.error_topic = listen_topic + ".error"
34
- self.publish_topic = publish_topic
35
- self.status_topic = status_topic
36
- self._subscription = self.broker.subscribe(self.handle_message, [self.listen_topic, self.error_topic])
37
-
38
- async def handle_message(self, message: Dict):
39
- """Process incoming messages. Must be implemented by subclasses."""
40
- raise NotImplementedError
41
-
42
- def dispose(self):
43
- """Cancels the subscription and cleans up resources."""
44
- if self._subscription:
45
- self._subscription.dispose()
46
-
47
-
48
- class InstructionAgent(BaseAgent):
49
- """Generates instructions and publishes them to a message broker."""
50
- def __init__(self, goal: str, tools_json: List, agent_config: Dict, work_dir: str, cache_messages: Union[bool, List[Dict]], broker: MessageBroker, listen_topic: str, publish_topic: str, status_topic: str):
51
- super().__init__(goal, tools_json, agent_config, work_dir, cache_messages, broker, listen_topic, publish_topic, status_topic)
52
-
53
- self.last_instruction = None
54
- self.agent = chatgpt(**self.config)
55
-
56
- self.goal_diff = None
57
-
58
- if self.cache_messages and isinstance(self.cache_messages, list) and len(self.cache_messages) > 1:
59
- old_goal = extract_xml_content(self.cache_messages[1]["content"], "goal")
60
- if old_goal.strip() != goal.strip():
61
- diff_generator = difflib.ndiff(old_goal.splitlines(), goal.splitlines())
62
- changed_lines = []
63
- for line in diff_generator:
64
- if (line.startswith('+ ') or line.startswith('- ')) and line[2:].strip():
65
- changed_lines.append(line)
66
- self.goal_diff = '\n'.join(changed_lines).strip()
67
-
68
- def get_conversation_history(self, conversation_history: List[Dict]):
69
- conversation_history = copy.deepcopy(conversation_history)
70
-
71
- self.cache_file.write_text(json.dumps(conversation_history, ensure_ascii=False, indent=4), encoding="utf-8")
72
-
73
- work_agent_system_prompt = conversation_history.pop(0)
74
- if conversation_history:
75
- original_content = work_agent_system_prompt["content"]
76
- regex = r"<latest_file_content>(.*?)</latest_file_content>"
77
- match = re.search(regex, original_content, re.DOTALL)
78
- if match:
79
- extracted_content = f"<latest_file_content>{match.group(1)}</latest_file_content>\n\n"
80
- else:
81
- extracted_content = ""
82
- if isinstance(conversation_history[0]["content"], str):
83
- conversation_history[0]["content"] = extracted_content + conversation_history[0]["content"]
84
- elif isinstance(conversation_history[0]["content"], list) and extracted_content:
85
- conversation_history[0]["content"].append({"type": "text", "text": extracted_content})
86
-
87
- return conversation_history
88
-
89
- async def handle_message(self, message: Dict):
90
- """Receives a worker response, generates the next instruction, and publishes it."""
91
-
92
- if len(message["conversation"]) > 1 and message["conversation"][-2]["role"] == "user" \
93
- and "<task_complete_message>" in message["conversation"][-2]["content"]:
94
- task_complete_message = extract_xml_content(message["conversation"][-2]["content"], "task_complete_message")
95
- self.broker.publish({"status": "finished", "result": task_complete_message}, self.status_topic)
96
- return
97
-
98
- instruction_prompt = "".join([
99
- "</work_agent_conversation_end>\n\n",
100
- f"任务目标: {self.goal}\n\n",
101
- f"任务目标新变化:\n{self.goal_diff}\n\n" if self.goal_diff else "",
102
- "在 tag <work_agent_conversation_start>...</work_agent_conversation_end> 之前的对话历史都是工作智能体的对话历史。\n\n",
103
- "根据以上对话历史和目标,请生成下一步指令。如果任务已完成,指示工作智能体调用task_complete工具。\n\n",
104
- ])
105
- if self.last_instruction and 'fetch_gpt_response_stream HTTP Error' not in self.last_instruction:
106
- instruction_prompt = (
107
- f"{instruction_prompt}\n\n"
108
- "你生成的指令格式错误,必须把给assistant的指令放在<instructions>...</instructions>标签内。请重新生成格式正确的指令。"
109
- f"这是你上次给assistant的错误格式的指令:\n{self.last_instruction}"
110
- )
111
-
112
- self.agent.conversation["default"][1:] = self.get_conversation_history(message["conversation"])
113
-
114
- if "find_and_click_element" in json.dumps(self.tools_json):
115
- instruction_prompt = await get_current_screen_image_message(instruction_prompt)
116
-
117
- raw_response = await self.agent.ask_async(instruction_prompt)
118
-
119
- if "fetch_gpt_response_stream HTTP Error', 'status_code': 404" in raw_response:
120
- raise Exception(f"Model: {self.config['engine']} not found!")
121
- if "'status_code': 413" in raw_response or \
122
- "'status_code': 400" in raw_response:
123
- self.broker.publish({"status": "error", "result": raw_response}, self.status_topic)
124
- return
125
-
126
- self.broker.publish({"status": "new_message", "result": "\n🤖 指令智能体:\n" + raw_response}, self.status_topic)
127
-
128
- self.last_instruction = raw_response
129
- instruction = extract_xml_content(raw_response, "instructions")
130
- if instruction:
131
- if len(message["conversation"]) == 1:
132
- instruction = (
133
- "任务描述:\n"
134
- f"<goal>{self.goal}</goal>\n\n"
135
- "你作为指令的**执行者**,而非任务的**规划师**,你必须严格遵循以下单步工作流程:\n"
136
- "**执行指令**\n"
137
- " - **严格遵从:** 只执行我当前下达的明确指令。在我明确给出下一步指令前,绝不擅自行动或推测、执行任何未明确要求的后续步骤。\n"
138
- " - **严禁越权:** 禁止执行任何我未指定的步骤。`<goal>` 标签中的内容仅为背景信息,不得据此进行任务规划或推测。\n"
139
- "**汇报结果**\n"
140
- " - **聚焦单步:** 指令完成后,仅汇报该步骤的执行结果与产出。\n"
141
- "**暂停等待**\n"
142
- " - **原地待命:** 汇报后,任务暂停。在收到我新的指令前,严禁发起任何新的工具调用或操作。\n"
143
- " - **请求指令:** 回复的最后必须明确请求我提供下一步指令。\n"
144
- "**注意:** 禁止完成超出下面我未规定的步骤,`<goal>` 标签中的内容仅为背景信息。"
145
- "现在开始执行第一步:\n"
146
- f"{instruction}"
147
- )
148
- self.broker.publish({"instruction": instruction, "conversation": message["conversation"]}, self.publish_topic)
149
- else:
150
- print("\n❌ 指令智能体生成的指令不符合要求,正在重新生成。")
151
- self.broker.publish(message, self.error_topic)
152
-
153
-
154
- class WorkerAgent(BaseAgent):
155
- """Executes instructions and publishes results to a message broker."""
156
- def __init__(self, goal: str, tools_json: List, agent_config: Dict, work_dir: str, cache_messages: Union[bool, List[Dict]], broker: MessageBroker, listen_topic: str, publish_topic: str, status_topic: str):
157
- super().__init__(goal, tools_json, agent_config, work_dir, cache_messages, broker, listen_topic, publish_topic, status_topic)
158
-
159
- if self.cache_messages and isinstance(self.cache_messages, list) and len(self.cache_messages) > 1:
160
- first_user_message = replace_xml_content(self.cache_messages[1]["content"], "goal", goal)
161
- self.config["cache_messages"] = self.cache_messages[0:1] + [{"role": "user", "content": first_user_message}] + self.cache_messages[2:]
162
-
163
- self.agent = chatgpt(**self.config)
164
-
165
- async def handle_message(self, message: Dict):
166
- """Receives an instruction, executes it, and publishes the response."""
167
-
168
- if message.get("instruction") == "Initial kickoff":
169
- self.broker.publish({
170
- "conversation": self.agent.conversation["default"]
171
- }, self.publish_topic)
172
- return
173
-
174
- instruction = message["instruction"]
175
- if "find_and_click_element" in json.dumps(self.tools_json):
176
- instruction = await get_current_screen_image_message(instruction)
177
- response = await self.agent.ask_async(instruction)
178
-
179
- if response.strip() == '':
180
- print("\n❌ 工作智能体回复为空,请重新生成指令。")
181
- self.broker.publish(message, self.error_topic)
182
- else:
183
- self.broker.publish({"status": "new_message", "result": "\n✅ 工作智能体:\n" + response}, self.status_topic)
184
- self.broker.publish({
185
- "conversation": self.agent.conversation["default"]
186
- }, self.publish_topic)
187
-
188
- class Tee:
189
- def __init__(self, *files):
190
- self.files = files
191
-
192
- def write(self, obj):
193
- for f in self.files:
194
- f.write(obj)
195
- f.flush()
196
-
197
- def flush(self):
198
- for f in self.files:
199
- f.flush()
200
-
201
- broker = MessageBroker()
202
- mcp_manager = MCPManager()
203
-
204
- class BrokerWorker:
205
- """The 'glue' class that orchestrates agents via a MessageBroker."""
206
- def __init__(self, goal: str, tools: List[Union[str, Dict]], work_dir: str, cache_messages: Union[bool, List[Dict]] = None, broker: MessageBroker = None, mcp_manager: MCPManager = None):
207
- self.goal = goal
208
- self.tools = tools
209
- self.work_dir = Path(work_dir)
210
- self.cache_messages = cache_messages
211
-
212
- self.broker = broker
213
- self.mcp_manager = mcp_manager
214
- self.task_completion_event = asyncio.Event()
215
- self.final_result = None
216
- self._status_subscription = None
217
- self.setup()
218
-
219
- self.channel = self.broker.request_channel()
220
- self.INSTRUCTION_TOPIC = self.channel + ".instructions"
221
- self.WORKER_RESPONSE_TOPIC = self.channel + ".worker_responses"
222
- self.TASK_STATUS_TOPIC =self.channel + ".task_status"
223
-
224
- def setup(self):
225
- cache_dir = self.work_dir / ".beswarm"
226
- cache_dir.mkdir(parents=True, exist_ok=True)
227
- task_manager.set_root_path(self.work_dir)
228
- self.cache_file = cache_dir / "work_agent_conversation_history.json"
229
- if not self.cache_file.exists():
230
- self.cache_file.write_text("[]", encoding="utf-8")
231
-
232
- DEBUG = os.getenv("DEBUG", "false").lower() in ("true", "1", "t", "yes")
233
- if DEBUG:
234
- log_file = open(cache_dir / "history.log", "a", encoding="utf-8")
235
- log_file.write(f"========== {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} ==========\n")
236
- original_stdout = sys.stdout
237
- original_stderr = sys.stderr
238
- sys.stdout = Tee(original_stdout, log_file)
239
- sys.stderr = Tee(original_stderr, log_file)
240
-
241
- async def _configure_tools(self):
242
- mcp_list = [item for item in self.tools if isinstance(item, dict)]
243
- if mcp_list:
244
- for mcp_item in mcp_list:
245
- mcp_name, mcp_config = list(mcp_item.items())[0]
246
- await self.mcp_manager.add_server(mcp_name, mcp_config)
247
- client = self.mcp_manager.clients.get(mcp_name)
248
- await register_mcp_tools(client, registry)
249
- all_mcp_tools = await self.mcp_manager.get_all_tools()
250
- self.tools.extend([tool.name for tool in sum(all_mcp_tools.values(), [])])
251
- self.tools = [item for item in self.tools if not isinstance(item, dict)]
252
- if "task_complete" not in self.tools: self.tools.append("task_complete")
253
- self.tools_json = [value for _, value in get_function_call_list(self.tools).items()]
254
-
255
- def _task_status_subscriber(self, message: Dict):
256
- """Subscriber for task status changes."""
257
- if message.get("status") == "finished":
258
- self.final_result = message.get("result")
259
- self.task_completion_event.set()
260
-
261
- if message.get("status") == "error":
262
- raise Exception(message.get("result"))
263
-
264
- if message.get("status") == "new_message":
265
- print(message.get("result"))
266
-
267
- def _setup_agents(self):
268
- instruction_agent_config = {
269
- "api_key": os.getenv("API_KEY"), "api_url": os.getenv("BASE_URL"),
270
- "engine": os.getenv("MODEL"),
271
- "system_prompt": instruction_system_prompt.format(
272
- os_version=platform.platform(), tools_list=self.tools_json,
273
- workspace_path=self.work_dir, current_time=datetime.now().strftime("%Y-%m-%d %H:%M:%S")
274
- ),
275
- "print_log": os.getenv("DEBUG", "false").lower() in ("true", "1", "t", "yes"),
276
- "temperature": 0.7, "use_plugins": False
277
- }
278
-
279
- worker_agent_config = {
280
- "api_key": os.getenv("API_KEY"), "api_url": os.getenv("BASE_URL"),
281
- "engine": os.getenv("FAST_MODEL") or os.getenv("MODEL"),
282
- "system_prompt": worker_system_prompt.format(
283
- os_version=platform.platform(), workspace_path=self.work_dir,
284
- shell=os.getenv('SHELL', 'Unknown'), current_time=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
285
- tools_list=self.tools_json
286
- ),
287
- "print_log": True, "temperature": 0.5, "function_call_max_loop": 100
288
- }
289
-
290
- instruction_agent = InstructionAgent(
291
- goal=self.goal, tools_json=self.tools_json, agent_config=instruction_agent_config, work_dir=self.work_dir, cache_messages=self.cache_messages,
292
- broker=self.broker, listen_topic=self.WORKER_RESPONSE_TOPIC,
293
- publish_topic=self.INSTRUCTION_TOPIC, status_topic=self.TASK_STATUS_TOPIC
294
- )
295
-
296
- worker_agent = WorkerAgent(
297
- goal=self.goal, tools_json=self.tools_json, agent_config=worker_agent_config, work_dir=self.work_dir, cache_messages=self.cache_messages,
298
- broker=self.broker, listen_topic=self.INSTRUCTION_TOPIC,
299
- publish_topic=self.WORKER_RESPONSE_TOPIC, status_topic=self.TASK_STATUS_TOPIC
300
- )
301
- return instruction_agent, worker_agent
302
-
303
- async def run(self):
304
- """Sets up subscriptions and starts the workflow."""
305
- os.chdir(self.work_dir.absolute())
306
- await self._configure_tools()
307
-
308
- instruction_agent, worker_agent = self._setup_agents()
309
-
310
- self.broker.publish({"instruction": "Initial kickoff"}, self.INSTRUCTION_TOPIC)
311
-
312
- self._status_subscription = self.broker.subscribe(self._task_status_subscriber, self.TASK_STATUS_TOPIC)
313
- await self.task_completion_event.wait()
314
-
315
- instruction_agent.dispose()
316
- worker_agent.dispose()
317
- self._status_subscription.dispose()
318
- await self.mcp_manager.cleanup()
319
- return self.final_result
320
-
321
- async def stream_run(self):
322
- """Runs the workflow and yields status messages."""
323
- os.chdir(self.work_dir.absolute())
324
- await self._configure_tools()
325
-
326
- instruction_agent, worker_agent = self._setup_agents()
327
-
328
- self.broker.publish({"instruction": "Initial kickoff"}, self.INSTRUCTION_TOPIC)
329
-
330
- try:
331
- async for message in self.broker.iter_topic(self.TASK_STATUS_TOPIC):
332
- if message.get("status") == "new_message":
333
- yield message.get("result")
334
- elif message.get("status") == "finished":
335
- yield message.get("result")
336
- break
337
- elif message.get("status") == "error":
338
- raise Exception(message.get("result"))
339
- finally:
340
- instruction_agent.dispose()
341
- worker_agent.dispose()
342
- await self.mcp_manager.cleanup()
4
+ from ..core import mcp_manager, broker, task_manager
5
+ from ..agents.planact import BrokerWorker
6
+ from ..aient.src.aient.plugins import register_tool
343
7
 
344
8
 
345
9
  @register_tool()
346
10
  async def worker(goal: str, tools: List[Union[str, Dict]], work_dir: str, cache_messages: Union[bool, List[Dict]] = None):
347
11
  start_time = datetime.now()
348
- worker_instance = BrokerWorker(goal, tools, work_dir, cache_messages, broker, mcp_manager)
12
+ worker_instance = BrokerWorker(goal, tools, work_dir, cache_messages, broker, mcp_manager, task_manager)
349
13
  result = await worker_instance.run()
350
14
  end_time = datetime.now()
351
15
  print(f"\n任务开始时间: {start_time.strftime('%Y-%m-%d %H:%M:%S')}")
@@ -356,12 +20,10 @@ async def worker(goal: str, tools: List[Union[str, Dict]], work_dir: str, cache_
356
20
  @register_tool()
357
21
  async def worker_gen(goal: str, tools: List[Union[str, Dict]], work_dir: str, cache_messages: Union[bool, List[Dict]] = None):
358
22
  start_time = datetime.now()
359
- worker_instance = BrokerWorker(goal, tools, work_dir, cache_messages, broker, mcp_manager)
23
+ worker_instance = BrokerWorker(goal, tools, work_dir, cache_messages, broker, mcp_manager, task_manager)
360
24
  async for result in worker_instance.stream_run():
361
25
  yield result
362
26
  end_time = datetime.now()
363
27
  print(f"\n任务开始时间: {start_time.strftime('%Y-%m-%d %H:%M:%S')}")
364
28
  print(f"任务结束时间: {end_time.strftime('%Y-%m-%d %H:%M:%S')}")
365
- print(f"总用时: {end_time - start_time}")
366
-
367
- from .taskmanager import task_manager
29
+ print(f"总用时: {end_time - start_time}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: beswarm
3
- Version: 0.2.36
3
+ Version: 0.2.37
4
4
  Summary: MAS
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -1,7 +1,10 @@
1
1
  beswarm/__init__.py,sha256=HZjUOJtZR5QhMuDbq-wukQQn1VrBusNWai_ysGo-VVI,20
2
2
  beswarm/broker.py,sha256=RtnQZVbhf25acUHahNBiaS5FGxcrj0rhBhkon9gFY_M,9873
3
+ beswarm/core.py,sha256=_J_LPc0HZFQTLGHy021OMEh2WbsjL_hc6jxd2engpyY,216
3
4
  beswarm/prompt.py,sha256=5JMfOuXWHscsaeDzwBn223mj9N85eAQdOHXQZk7zeWE,32238
5
+ beswarm/taskmanager.py,sha256=oyvmxZmiguUXx1vYDQIuHS_GKDHEoDBhQ2Bjo4s128Q,9329
4
6
  beswarm/utils.py,sha256=xxbNifOPlfcVkKmF_qFzuEnZgF3MQg3mnOfz1EF0Qss,6697
7
+ beswarm/agents/planact.py,sha256=-wS9bVUJRWfUePBh_F6crCSeFIq6rl4RewlLls4d4oE,17322
5
8
  beswarm/aient/main.py,sha256=SiYAIgQlLJqYusnTVEJOx1WNkSJKMImhgn5aWjfroxg,3814
6
9
  beswarm/aient/setup.py,sha256=lSEY6pYNdIdUB8F3gkj0XsEos1FMkd_c6PfIt-hscII,487
7
10
  beswarm/aient/src/aient/__init__.py,sha256=SRfF7oDVlOOAi6nGKiJIUK6B_arqYLO9iSMp-2IZZps,21
@@ -126,7 +129,7 @@ beswarm/queries/tree-sitter-languages/ruby-tags.scm,sha256=vIidsCeE2A0vdFN18yXKq
126
129
  beswarm/queries/tree-sitter-languages/rust-tags.scm,sha256=9ljM1nzhfPs_ZTRw7cr2P9ToOyhGcKkCoN4_HPXSWi4,1451
127
130
  beswarm/queries/tree-sitter-languages/scala-tags.scm,sha256=UxQjz80JIrrJ7Pm56uUnQyThfmQNvwk7aQzPNypB-Ao,1761
128
131
  beswarm/queries/tree-sitter-languages/typescript-tags.scm,sha256=OMdCeedPiA24ky82DpgTMKXK_l2ySTuF2zrQ2fJAi9E,1253
129
- beswarm/tools/__init__.py,sha256=Q24EPaPvYzuwxmshvvCQR0-bXRllzxmMFiya8ZL5YEI,1472
132
+ beswarm/tools/__init__.py,sha256=WUPfDbg7m6GIDL-vmWtSPtA_VNRATHNZETVh6y4y4tk,1448
130
133
  beswarm/tools/click.py,sha256=7g6x1X7ffTInGWp7112KS-MAQ5-8wa1Ze2sIipUIbjc,20884
131
134
  beswarm/tools/completion.py,sha256=BHMMZeDCNEnaoOuwOoJjkuU_idwDB43mD1bT63p_waU,590
132
135
  beswarm/tools/edit_file.py,sha256=iwWl7a8sTVq4vj0e1ny3H6UGcHfYnxALRGcLuk5hZS8,9155
@@ -136,9 +139,9 @@ beswarm/tools/request_input.py,sha256=gXNAJPOJektMqxJVyzNTFOeMQ7xUkO-wWMYH-r2Rdw
136
139
  beswarm/tools/screenshot.py,sha256=u6t8FCgW5YHJ_Oc4coo8e0F3wTusWE_-H8dFh1rBq9Q,1011
137
140
  beswarm/tools/search_arxiv.py,sha256=caVIUOzMhFu-r_gVgJZrH2EO9xI5iV_qLAg0b3Ie9Xg,8095
138
141
  beswarm/tools/search_web.py,sha256=LhgXOSHL9fwxg5T2AAOV4TTJkcJVLogsfRJW2faPvDE,16147
139
- beswarm/tools/taskmanager.py,sha256=n7G6cH96Tcz57MfiOffISMMAfUtr49_uikkeoCDCeRg,12940
140
- beswarm/tools/worker.py,sha256=4zQfVzUmXXnTTvyBPcnyVNM7TV6jzqw6P377ZmORIq0,18567
141
- beswarm-0.2.36.dist-info/METADATA,sha256=NoyWwKf45i-ic0OI2YnCCdGi-q-07Uh3QfUkMLszfYQ,3878
142
- beswarm-0.2.36.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
143
- beswarm-0.2.36.dist-info/top_level.txt,sha256=pJw4O87wvt5882smuSO6DfByJz7FJ8SxxT8h9fHCmpo,8
144
- beswarm-0.2.36.dist-info/RECORD,,
142
+ beswarm/tools/subtasks.py,sha256=isc0bo24vVe3jCWW1wdP7hD5OQymilyka-QR7VSxx78,3652
143
+ beswarm/tools/worker.py,sha256=OVQeY3_I2Ornrv40BzIbd-kWfPd91GWLOziVNLl0NsQ,1383
144
+ beswarm-0.2.37.dist-info/METADATA,sha256=Ts-1AWrJJkDa6_BfwhILt3prvZjrkfmIBGdBid7lY7U,3878
145
+ beswarm-0.2.37.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
146
+ beswarm-0.2.37.dist-info/top_level.txt,sha256=pJw4O87wvt5882smuSO6DfByJz7FJ8SxxT8h9fHCmpo,8
147
+ beswarm-0.2.37.dist-info/RECORD,,