beswarm 0.2.35__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.
- beswarm/agents/planact.py +339 -0
- beswarm/aient/setup.py +1 -1
- beswarm/aient/src/aient/core/request.py +21 -4
- beswarm/aient/src/aient/core/response.py +6 -5
- beswarm/aient/src/aient/models/chatgpt.py +4 -3
- beswarm/broker.py +235 -0
- beswarm/core.py +11 -0
- beswarm/{tools/taskmanager.py → taskmanager.py} +3 -81
- beswarm/tools/__init__.py +31 -33
- beswarm/tools/click.py +1 -0
- beswarm/tools/search_web.py +1 -3
- beswarm/tools/subtasks.py +77 -0
- beswarm/tools/worker.py +14 -440
- {beswarm-0.2.35.dist-info → beswarm-0.2.37.dist-info}/METADATA +1 -1
- {beswarm-0.2.35.dist-info → beswarm-0.2.37.dist-info}/RECORD +17 -13
- {beswarm-0.2.35.dist-info → beswarm-0.2.37.dist-info}/WHEEL +0 -0
- {beswarm-0.2.35.dist-info → beswarm-0.2.37.dist-info}/top_level.txt +0 -0
beswarm/broker.py
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""
|
|
2
|
+
使用 Reaktiv 模拟消息队列 (发布/订阅)
|
|
3
|
+
|
|
4
|
+
本模块提供了一个 MessageBroker 类,它利用 Reaktiv 的核心原语(Signal, Computed, Effect)
|
|
5
|
+
来构建一个功能类似消息队列的、内存中的发布/订阅系统。
|
|
6
|
+
"""
|
|
7
|
+
import asyncio
|
|
8
|
+
from typing import Callable, Any, List, Union, Tuple
|
|
9
|
+
|
|
10
|
+
from reaktiv import Signal, Effect, Computed, untracked, to_async_iter
|
|
11
|
+
|
|
12
|
+
class Subscription:
|
|
13
|
+
"""封装一个或多个 Effect,提供统一的暂停、恢复和取消订阅的接口。"""
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
broker: "MessageBroker",
|
|
18
|
+
callback: Callable[[Any], None],
|
|
19
|
+
effects_with_topics: List[Tuple[Effect, str]],
|
|
20
|
+
):
|
|
21
|
+
self._broker = broker
|
|
22
|
+
self._callback = callback
|
|
23
|
+
self._effects_with_topics = effects_with_topics
|
|
24
|
+
self._effects = [e for e, t in effects_with_topics]
|
|
25
|
+
self.is_paused = Signal(False)
|
|
26
|
+
|
|
27
|
+
def pause(self):
|
|
28
|
+
"""暂停订阅,将不再处理新消息。"""
|
|
29
|
+
self.is_paused.set(True)
|
|
30
|
+
if self._broker.debug:
|
|
31
|
+
print(f"Subscription paused.")
|
|
32
|
+
|
|
33
|
+
def resume(self):
|
|
34
|
+
"""恢复订阅,将继续处理新消息。"""
|
|
35
|
+
self.is_paused.set(False)
|
|
36
|
+
if self._broker.debug:
|
|
37
|
+
print(f"Subscription resumed.")
|
|
38
|
+
|
|
39
|
+
def dispose(self):
|
|
40
|
+
"""永久取消订阅并清理资源。"""
|
|
41
|
+
for effect, topic in self._effects_with_topics:
|
|
42
|
+
effect.dispose()
|
|
43
|
+
# 从代理的注册表中移除
|
|
44
|
+
if (
|
|
45
|
+
topic in self._broker._effects_registry
|
|
46
|
+
and self._callback in self._broker._effects_registry[topic]
|
|
47
|
+
):
|
|
48
|
+
del self._broker._effects_registry[topic][self._callback]
|
|
49
|
+
if not self._broker._effects_registry[topic]:
|
|
50
|
+
del self._broker._effects_registry[topic]
|
|
51
|
+
if self._broker.debug:
|
|
52
|
+
print(f"Subscription disposed.")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class MessageBroker:
|
|
56
|
+
"""一个简单的消息代理,使用 Reaktiv Signal 和 Computed 模拟消息队列和派生主题。"""
|
|
57
|
+
|
|
58
|
+
def __init__(self, debug: bool = False):
|
|
59
|
+
# 现在 _topics 可以存储 Signal (原始主题) 或 Computed (派生主题)。
|
|
60
|
+
self._topics: dict[str, Union[Signal[List[Any]], Computed[List[Any]]]] = {}
|
|
61
|
+
# 新增: 注册表来跟踪 (主题, 回调) -> Effect 的映射
|
|
62
|
+
self._effects_registry: dict[str, dict[Callable, Effect]] = {}
|
|
63
|
+
self.debug = debug
|
|
64
|
+
self._channel_counters: dict[str, int] = {}
|
|
65
|
+
# print("消息代理已启动。")
|
|
66
|
+
|
|
67
|
+
def request_channel(self, prefix: str = "channel") -> str:
|
|
68
|
+
"""
|
|
69
|
+
申请一个新的、唯一的频道名称。
|
|
70
|
+
|
|
71
|
+
此方法为每个前缀维护一个独立的计数器。
|
|
72
|
+
返回一个基于前缀和该前缀当前计数值的唯一字符串,例如 'channel0', 'worker_0', 'channel1'。
|
|
73
|
+
它不直接创建主题或任何关联的 Signal;这将在首次发布或订阅到返回的主题名称时发生。
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
prefix: 频道名称的前缀。默认为 'channel'。
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
一个基于前缀的唯一主题/频道名称字符串。
|
|
80
|
+
"""
|
|
81
|
+
if prefix not in self._channel_counters:
|
|
82
|
+
self._channel_counters[prefix] = 0
|
|
83
|
+
|
|
84
|
+
channel_name = f"{prefix}{self._channel_counters[prefix]}"
|
|
85
|
+
self._channel_counters[prefix] += 1
|
|
86
|
+
return channel_name
|
|
87
|
+
|
|
88
|
+
def publish(self, message: Any, topic: Union[str, List[str]] = "default"):
|
|
89
|
+
"""
|
|
90
|
+
向一个或多个主题发布一条新消息。
|
|
91
|
+
"""
|
|
92
|
+
topics_to_publish = [topic] if isinstance(topic, str) else topic
|
|
93
|
+
|
|
94
|
+
for t in topics_to_publish:
|
|
95
|
+
# 只能向原始主题发布
|
|
96
|
+
topic_signal = self._topics.get(t)
|
|
97
|
+
if not isinstance(topic_signal, Signal):
|
|
98
|
+
print(f"警告:主题 '{t}' 不存在或不是一个可发布的原始主题。正在创建...")
|
|
99
|
+
topic_signal = Signal([])
|
|
100
|
+
self._topics[t] = topic_signal
|
|
101
|
+
|
|
102
|
+
# 通过 update 方法追加新消息来触发更新。
|
|
103
|
+
# 必须创建一个新列表才能让 Reaktiv 检测到变化。
|
|
104
|
+
topic_signal.update(lambda messages: messages + [message])
|
|
105
|
+
if self.debug:
|
|
106
|
+
print(f"新消息发布到 '{t}': \"{message}\"")
|
|
107
|
+
|
|
108
|
+
def subscribe(self, callback: Callable[[Any], None], topic: Union[str, List[str]] = "default") -> Subscription:
|
|
109
|
+
"""
|
|
110
|
+
订阅一个或多个主题。每当有新消息发布时,回调函数将被调用。
|
|
111
|
+
此方法是幂等的:重复订阅同一个回调到同一个主题不会产生副作用。
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
callback: 处理消息的回调函数。
|
|
115
|
+
topic: 要订阅的主题,可以是单个字符串或字符串列表。
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
一个 Subscription 实例,用于管理订阅的生命周期(暂停、恢复、取消)。
|
|
119
|
+
"""
|
|
120
|
+
topics_to_subscribe = [topic] if isinstance(topic, str) else topic
|
|
121
|
+
created_effects_with_topics = []
|
|
122
|
+
|
|
123
|
+
# 创建一个 Subscription 实例来管理所有相关的 effects
|
|
124
|
+
# 它需要提前创建,以便 effect_factory 可以访问它的 is_paused 信号
|
|
125
|
+
subscription = Subscription(self, callback, created_effects_with_topics)
|
|
126
|
+
|
|
127
|
+
for t in topics_to_subscribe:
|
|
128
|
+
# 检查此回调是否已订阅该主题
|
|
129
|
+
if t in self._effects_registry and callback in self._effects_registry.get(t, {}):
|
|
130
|
+
print(f"警告:订阅者 '{callback.__name__}' 已经订阅了 '{t}' 主题。跳过。")
|
|
131
|
+
continue
|
|
132
|
+
|
|
133
|
+
if t not in self._topics:
|
|
134
|
+
# 如果订阅一个不存在的主题,也为它创建一个 Signal
|
|
135
|
+
self._topics[t] = Signal([])
|
|
136
|
+
|
|
137
|
+
# 使用一个工厂函数来为每个主题创建独立的闭包,
|
|
138
|
+
# 确保每个订阅都有自己的 'last_processed_index'。
|
|
139
|
+
def effect_factory(current_topic: str):
|
|
140
|
+
last_processed_index = 0
|
|
141
|
+
|
|
142
|
+
def process_new_messages():
|
|
143
|
+
nonlocal last_processed_index
|
|
144
|
+
all_messages = self._topics[current_topic]()
|
|
145
|
+
|
|
146
|
+
# 如果暂停了,只更新索引以跳过消息,不进行处理
|
|
147
|
+
if untracked(subscription.is_paused):
|
|
148
|
+
last_processed_index = len(all_messages)
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
new_messages = all_messages[last_processed_index:]
|
|
152
|
+
|
|
153
|
+
if new_messages:
|
|
154
|
+
if self.debug:
|
|
155
|
+
print(f" -> 订阅者 '{callback.__name__}' 在 '{current_topic}' 主题上收到 {len(new_messages)} 条新消息。")
|
|
156
|
+
for msg in new_messages:
|
|
157
|
+
try:
|
|
158
|
+
if asyncio.iscoroutinefunction(callback):
|
|
159
|
+
asyncio.create_task(callback(msg))
|
|
160
|
+
else:
|
|
161
|
+
callback(msg)
|
|
162
|
+
except Exception as e:
|
|
163
|
+
print(f" !! 在订阅者 '{callback.__name__}' 中发生错误: {e}")
|
|
164
|
+
|
|
165
|
+
last_processed_index = len(all_messages)
|
|
166
|
+
return process_new_messages
|
|
167
|
+
|
|
168
|
+
if self.debug:
|
|
169
|
+
print(f"订阅者 '{callback.__name__}' 已订阅 '{t}' 主题。")
|
|
170
|
+
effect = Effect(effect_factory(t))
|
|
171
|
+
|
|
172
|
+
# 注册新的 effect
|
|
173
|
+
if t not in self._effects_registry:
|
|
174
|
+
self._effects_registry[t] = {}
|
|
175
|
+
self._effects_registry[t][callback] = effect
|
|
176
|
+
|
|
177
|
+
created_effects_with_topics.append((effect, t))
|
|
178
|
+
|
|
179
|
+
return subscription
|
|
180
|
+
|
|
181
|
+
def create_derived_topic(self, new_topic_name: str, source_topic: str, transform_fn: Callable[[List[Any]], List[Any]]):
|
|
182
|
+
"""
|
|
183
|
+
创建一个派生主题。
|
|
184
|
+
|
|
185
|
+
这个新主题的内容是一个 Computed 信号,它会根据源主题的内容和转换函数自动更新。
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
new_topic_name: 派生主题的名称。
|
|
189
|
+
source_topic: 源主题的名称。
|
|
190
|
+
transform_fn: 一个函数,接收源主题的消息列表并返回新的消息列表。
|
|
191
|
+
"""
|
|
192
|
+
if new_topic_name in self._topics:
|
|
193
|
+
print(f"警告:主题 '{new_topic_name}' 已存在。")
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
source_signal = self._topics.get(source_topic)
|
|
197
|
+
if not isinstance(source_signal, (Signal, Computed)):
|
|
198
|
+
print(f"错误:源主题 '{source_topic}' 不存在。")
|
|
199
|
+
return
|
|
200
|
+
|
|
201
|
+
# 创建一个 Computed 信号作为派生主题
|
|
202
|
+
derived_signal = Computed(
|
|
203
|
+
lambda: transform_fn(source_signal())
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
self._topics[new_topic_name] = derived_signal
|
|
207
|
+
if self.debug:
|
|
208
|
+
print(f"已从 '{source_topic}' 创建派生主题 '{new_topic_name}'。")
|
|
209
|
+
|
|
210
|
+
async def iter_topic(self, topic: str):
|
|
211
|
+
"""
|
|
212
|
+
返回一个异步迭代器,用于通过 async for 循环消费主题消息。
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
topic: 要订阅的主题名称。
|
|
216
|
+
|
|
217
|
+
Yields:
|
|
218
|
+
主题中的新消息。
|
|
219
|
+
"""
|
|
220
|
+
if topic not in self._topics:
|
|
221
|
+
# 如果主题不存在,创建一个,以防万一
|
|
222
|
+
self._topics[topic] = Signal([])
|
|
223
|
+
|
|
224
|
+
topic_signal = self._topics[topic]
|
|
225
|
+
last_yielded_index = 0
|
|
226
|
+
|
|
227
|
+
# to_async_iter 会在每次 topic_signal 更新时产生一个新的消息列表
|
|
228
|
+
async for all_messages in to_async_iter(topic_signal):
|
|
229
|
+
new_messages = all_messages[last_yielded_index:]
|
|
230
|
+
for msg in new_messages:
|
|
231
|
+
# 过滤掉内部的 'init' 消息
|
|
232
|
+
if msg != "init":
|
|
233
|
+
yield msg
|
|
234
|
+
|
|
235
|
+
last_yielded_index = len(all_messages)
|
beswarm/core.py
ADDED
|
@@ -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
|
|
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 =
|
|
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 .
|
|
3
|
-
from .
|
|
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 .
|
|
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
|
-
|
|
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
|
-
"
|
|
36
|
-
|
|
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
|
-
"
|
|
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
|
-
"
|
|
59
|
-
"
|
|
55
|
+
"find_and_click_element",
|
|
56
|
+
"download_read_arxiv_pdf",
|
|
57
|
+
"save_screenshot_to_file",
|
|
60
58
|
]
|
beswarm/tools/click.py
CHANGED
|
@@ -223,6 +223,7 @@ Returns:
|
|
|
223
223
|
|
|
224
224
|
# 工作agent初始化
|
|
225
225
|
click_agent = chatgpt(**click_agent_config)
|
|
226
|
+
# https://developers.googleblog.com/en/conversational-image-segmentation-gemini-2-5/
|
|
226
227
|
prompt = f"Give the segmentation masks for the {target_element}. Output a JSON list of segmentation masks where each entry contains the 2D bounding box in \"box_2d\" (format: ymin, xmin, ymax, xmax) and the mask in \"mask\". Only output the one that meets the criteria the most."
|
|
227
228
|
|
|
228
229
|
print("正在截取当前屏幕...")
|
beswarm/tools/search_web.py
CHANGED
|
@@ -2,9 +2,7 @@ import re
|
|
|
2
2
|
import os
|
|
3
3
|
import json
|
|
4
4
|
import httpx
|
|
5
|
-
from urllib.parse import quote_plus
|
|
6
5
|
import threading
|
|
7
|
-
import time
|
|
8
6
|
|
|
9
7
|
from ..aient.src.aient.plugins import register_tool, get_url_content # Assuming a similar plugin structure
|
|
10
8
|
|
|
@@ -364,7 +362,7 @@ if __name__ == '__main__':
|
|
|
364
362
|
# search_query = "美国"
|
|
365
363
|
# search_query = "machine learning models for higher heating value prediction using proximate vs ultimate analysis"
|
|
366
364
|
# search_query = "patent driver cognitive load monitoring micro-expression thermal imaging fusion"
|
|
367
|
-
search_query = "
|
|
365
|
+
search_query = "deep learning models for siRNA activity"
|
|
368
366
|
print(f"Performing web search for: '{search_query}'")
|
|
369
367
|
results = await search_web(search_query) # results is a list of URLs
|
|
370
368
|
|
|
@@ -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
|