beswarm 0.2.43__py3-none-any.whl → 0.2.45__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/chatgroup.py +3 -1
- beswarm/agents/planact.py +40 -9
- beswarm/core.py +5 -3
- beswarm/knowledge_graph.py +209 -0
- beswarm/taskmanager.py +6 -4
- beswarm/tools/__init__.py +6 -0
- beswarm/tools/graph.py +73 -0
- beswarm/tools/subtasks.py +3 -0
- beswarm/tools/worker.py +4 -4
- {beswarm-0.2.43.dist-info → beswarm-0.2.45.dist-info}/METADATA +1 -1
- {beswarm-0.2.43.dist-info → beswarm-0.2.45.dist-info}/RECORD +13 -11
- {beswarm-0.2.43.dist-info → beswarm-0.2.45.dist-info}/WHEEL +0 -0
- {beswarm-0.2.43.dist-info → beswarm-0.2.45.dist-info}/top_level.txt +0 -0
beswarm/agents/chatgroup.py
CHANGED
|
@@ -100,7 +100,7 @@ class Tee:
|
|
|
100
100
|
|
|
101
101
|
class ChatGroupWorker:
|
|
102
102
|
"""The 'glue' class that orchestrates agents via a MessageBroker."""
|
|
103
|
-
def __init__(self, tools: List[Union[str, Dict]], work_dir: str, cache_messages: Union[bool, List[Dict]] = None, broker: MessageBroker = None, mcp_manager: MCPManager = None, task_manager = None):
|
|
103
|
+
def __init__(self, tools: List[Union[str, Dict]], work_dir: str, cache_messages: Union[bool, List[Dict]] = None, broker: MessageBroker = None, mcp_manager: MCPManager = None, task_manager = None, kgm = None):
|
|
104
104
|
self.tools = tools
|
|
105
105
|
self.work_dir = Path(work_dir)
|
|
106
106
|
self.cache_messages = cache_messages
|
|
@@ -108,6 +108,7 @@ class ChatGroupWorker:
|
|
|
108
108
|
self.broker = broker
|
|
109
109
|
self.mcp_manager = mcp_manager
|
|
110
110
|
self.task_manager = task_manager
|
|
111
|
+
self.kgm = kgm
|
|
111
112
|
self.task_completion_event = asyncio.Event()
|
|
112
113
|
self.final_result = None
|
|
113
114
|
self._status_subscription = None
|
|
@@ -122,6 +123,7 @@ class ChatGroupWorker:
|
|
|
122
123
|
cache_dir = self.work_dir / ".beswarm"
|
|
123
124
|
cache_dir.mkdir(parents=True, exist_ok=True)
|
|
124
125
|
self.task_manager.set_root_path(self.work_dir)
|
|
126
|
+
self.kgm.set_root_path(self.work_dir)
|
|
125
127
|
self.cache_file = cache_dir / "work_agent_conversation_history.json"
|
|
126
128
|
if not self.cache_file.exists():
|
|
127
129
|
self.cache_file.write_text("[]", encoding="utf-8")
|
beswarm/agents/planact.py
CHANGED
|
@@ -17,7 +17,7 @@ from ..utils import extract_xml_content, get_current_screen_image_message, repla
|
|
|
17
17
|
|
|
18
18
|
class BaseAgent:
|
|
19
19
|
"""Base class for agents, handling common initialization and disposal."""
|
|
20
|
-
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):
|
|
20
|
+
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, graph_update_topic: str = None):
|
|
21
21
|
self.goal = goal
|
|
22
22
|
self.tools_json = tools_json
|
|
23
23
|
self.work_dir = work_dir
|
|
@@ -32,12 +32,20 @@ class BaseAgent:
|
|
|
32
32
|
self.error_topic = listen_topic + ".error"
|
|
33
33
|
self.publish_topic = publish_topic
|
|
34
34
|
self.status_topic = status_topic
|
|
35
|
+
self.graph_update_topic = graph_update_topic
|
|
36
|
+
if self.graph_update_topic:
|
|
37
|
+
self.graph_update_subscription = self.broker.subscribe(self.handle_graph_update, self.graph_update_topic)
|
|
38
|
+
|
|
35
39
|
self._subscription = self.broker.subscribe(self.handle_message, [self.listen_topic, self.error_topic])
|
|
36
40
|
|
|
37
41
|
async def handle_message(self, message: Dict):
|
|
38
42
|
"""Process incoming messages. Must be implemented by subclasses."""
|
|
39
43
|
raise NotImplementedError
|
|
40
44
|
|
|
45
|
+
def handle_graph_update(self, message: Dict):
|
|
46
|
+
"""Handle graph update messages."""
|
|
47
|
+
raise NotImplementedError
|
|
48
|
+
|
|
41
49
|
def dispose(self):
|
|
42
50
|
"""Cancels the subscription and cleans up resources."""
|
|
43
51
|
if self._subscription:
|
|
@@ -46,12 +54,13 @@ class BaseAgent:
|
|
|
46
54
|
|
|
47
55
|
class InstructionAgent(BaseAgent):
|
|
48
56
|
"""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)
|
|
57
|
+
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, graph_update_topic: str):
|
|
58
|
+
super().__init__(goal, tools_json, agent_config, work_dir, cache_messages, broker, listen_topic, publish_topic, status_topic, graph_update_topic)
|
|
51
59
|
|
|
52
60
|
self.last_instruction = None
|
|
53
61
|
self.agent = chatgpt(**self.config)
|
|
54
62
|
|
|
63
|
+
self.graph_tree = None
|
|
55
64
|
self.goal_diff = None
|
|
56
65
|
|
|
57
66
|
if self.cache_messages and isinstance(self.cache_messages, list) and len(self.cache_messages) > 1:
|
|
@@ -64,8 +73,13 @@ class InstructionAgent(BaseAgent):
|
|
|
64
73
|
changed_lines.append(line)
|
|
65
74
|
self.goal_diff = '\n'.join(changed_lines).strip()
|
|
66
75
|
|
|
67
|
-
def
|
|
68
|
-
|
|
76
|
+
def handle_graph_update(self, message: Dict):
|
|
77
|
+
"""Handle graph update messages."""
|
|
78
|
+
if message.get("message") == "graph_updated":
|
|
79
|
+
self.graph_tree = message.get("graph")
|
|
80
|
+
|
|
81
|
+
def get_conversation_history(self, raw_conversation_history: List[Dict]):
|
|
82
|
+
conversation_history = copy.deepcopy(raw_conversation_history)
|
|
69
83
|
|
|
70
84
|
self.cache_file.write_text(json.dumps(conversation_history, ensure_ascii=False, indent=4), encoding="utf-8")
|
|
71
85
|
|
|
@@ -83,6 +97,17 @@ class InstructionAgent(BaseAgent):
|
|
|
83
97
|
elif isinstance(conversation_history[0]["content"], list) and extracted_content:
|
|
84
98
|
conversation_history[0]["content"].append({"type": "text", "text": extracted_content})
|
|
85
99
|
|
|
100
|
+
for message in conversation_history:
|
|
101
|
+
if message.get("content") and isinstance(message["content"], str) \
|
|
102
|
+
and "<knowledge_graph_tree>" in message["content"] and self.graph_tree:
|
|
103
|
+
message["content"] = replace_xml_content(message["content"], "knowledge_graph_tree", self.graph_tree)
|
|
104
|
+
|
|
105
|
+
for index, message in enumerate(raw_conversation_history):
|
|
106
|
+
if message.get("content") and isinstance(message["content"], str) \
|
|
107
|
+
and "<knowledge_graph_tree>" in message["content"] and self.graph_tree:
|
|
108
|
+
message["content"] = replace_xml_content(message["content"], "knowledge_graph_tree", self.graph_tree)
|
|
109
|
+
raw_conversation_history[index] = message
|
|
110
|
+
|
|
86
111
|
return conversation_history
|
|
87
112
|
|
|
88
113
|
async def handle_message(self, message: Dict):
|
|
@@ -188,7 +213,7 @@ class WorkerAgent(BaseAgent):
|
|
|
188
213
|
|
|
189
214
|
class BrokerWorker:
|
|
190
215
|
"""The 'glue' class that orchestrates agents via a MessageBroker."""
|
|
191
|
-
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):
|
|
216
|
+
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, kgm = None):
|
|
192
217
|
self.goal = goal
|
|
193
218
|
self.tools = tools
|
|
194
219
|
self.work_dir = Path(work_dir)
|
|
@@ -197,20 +222,25 @@ class BrokerWorker:
|
|
|
197
222
|
self.broker = broker
|
|
198
223
|
self.mcp_manager = mcp_manager
|
|
199
224
|
self.task_manager = task_manager
|
|
225
|
+
self.kgm = kgm
|
|
200
226
|
self.task_completion_event = asyncio.Event()
|
|
201
227
|
self.final_result = None
|
|
202
228
|
self._status_subscription = None
|
|
203
|
-
self.setup()
|
|
204
229
|
|
|
205
230
|
self.channel = self.broker.request_channel()
|
|
206
231
|
self.INSTRUCTION_TOPIC = self.channel + ".instructions"
|
|
207
232
|
self.WORKER_RESPONSE_TOPIC = self.channel + ".worker_responses"
|
|
208
233
|
self.TASK_STATUS_TOPIC =self.channel + ".task_status"
|
|
234
|
+
self.GRAPH_UPDATE_TOPIC = self.channel + ".knowledge_graph"
|
|
235
|
+
|
|
236
|
+
self.setup()
|
|
209
237
|
|
|
210
238
|
def setup(self):
|
|
211
239
|
cache_dir = self.work_dir / ".beswarm"
|
|
212
240
|
cache_dir.mkdir(parents=True, exist_ok=True)
|
|
213
241
|
self.task_manager.set_root_path(self.work_dir)
|
|
242
|
+
self.kgm.set_root_path(self.work_dir)
|
|
243
|
+
self.kgm.set_publish_topic(self.GRAPH_UPDATE_TOPIC)
|
|
214
244
|
self.cache_file = cache_dir / "work_agent_conversation_history.json"
|
|
215
245
|
if not self.cache_file.exists():
|
|
216
246
|
self.cache_file.write_text("[]", encoding="utf-8")
|
|
@@ -274,13 +304,14 @@ class BrokerWorker:
|
|
|
274
304
|
instruction_agent = InstructionAgent(
|
|
275
305
|
goal=self.goal, tools_json=self.tools_json, agent_config=instruction_agent_config, work_dir=self.work_dir, cache_messages=self.cache_messages,
|
|
276
306
|
broker=self.broker, listen_topic=self.WORKER_RESPONSE_TOPIC,
|
|
277
|
-
publish_topic=self.INSTRUCTION_TOPIC, status_topic=self.TASK_STATUS_TOPIC
|
|
307
|
+
publish_topic=self.INSTRUCTION_TOPIC, status_topic=self.TASK_STATUS_TOPIC,
|
|
308
|
+
graph_update_topic=self.GRAPH_UPDATE_TOPIC
|
|
278
309
|
)
|
|
279
310
|
|
|
280
311
|
worker_agent = WorkerAgent(
|
|
281
312
|
goal=self.goal, tools_json=self.tools_json, agent_config=worker_agent_config, work_dir=self.work_dir, cache_messages=self.cache_messages,
|
|
282
313
|
broker=self.broker, listen_topic=self.INSTRUCTION_TOPIC,
|
|
283
|
-
publish_topic=self.WORKER_RESPONSE_TOPIC, status_topic=self.TASK_STATUS_TOPIC
|
|
314
|
+
publish_topic=self.WORKER_RESPONSE_TOPIC, status_topic=self.TASK_STATUS_TOPIC,
|
|
284
315
|
)
|
|
285
316
|
return instruction_agent, worker_agent
|
|
286
317
|
|
beswarm/core.py
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
from .taskmanager import TaskManager
|
|
2
1
|
from .broker import MessageBroker
|
|
3
2
|
from .bemcp.bemcp import MCPManager
|
|
3
|
+
from .taskmanager import TaskManager
|
|
4
|
+
from .knowledge_graph import KnowledgeGraphManager
|
|
4
5
|
|
|
5
6
|
"""
|
|
6
7
|
全局共享实例
|
|
7
8
|
"""
|
|
8
9
|
|
|
9
|
-
task_manager = TaskManager()
|
|
10
10
|
broker = MessageBroker()
|
|
11
|
-
mcp_manager = MCPManager()
|
|
11
|
+
mcp_manager = MCPManager()
|
|
12
|
+
task_manager = TaskManager()
|
|
13
|
+
kgm = KnowledgeGraphManager(broker=broker)
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
import networkx as nx
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
class KnowledgeGraphManager:
|
|
6
|
+
"""
|
|
7
|
+
一个使用 NetworkX 管理知识图谱的管理器。
|
|
8
|
+
- 图数据存储在 networkx.DiGraph 对象中。
|
|
9
|
+
- 数据持久化为 GraphML 文件。
|
|
10
|
+
- 提供添加、删除、重命名、移动节点和渲染树状图的功能。
|
|
11
|
+
"""
|
|
12
|
+
def __init__(self, storage_path="knowledge_graph.graphml", broker=None, publish_topic=None):
|
|
13
|
+
"""
|
|
14
|
+
初始化知识图谱管理器。
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
storage_path (str, optional): GraphML文件的存储路径。
|
|
18
|
+
默认为 'knowledge_graph.graphml'。
|
|
19
|
+
"""
|
|
20
|
+
self.storage_path = Path(storage_path)
|
|
21
|
+
self.graph = nx.DiGraph()
|
|
22
|
+
self.root_path = None
|
|
23
|
+
self.broker = broker
|
|
24
|
+
self.publish_topic = publish_topic
|
|
25
|
+
# self._load_graph()
|
|
26
|
+
|
|
27
|
+
def set_root_path(self, root_path):
|
|
28
|
+
"""设置工作根目录并加载持久化的任务状态。"""
|
|
29
|
+
if self.root_path is not None:
|
|
30
|
+
return
|
|
31
|
+
self.root_path = Path(root_path)
|
|
32
|
+
self.cache_dir = self.root_path / ".beswarm"
|
|
33
|
+
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
|
34
|
+
self.storage_path = self.cache_dir / "knowledge_graph.graphml"
|
|
35
|
+
|
|
36
|
+
self._load_graph()
|
|
37
|
+
|
|
38
|
+
def _load_graph(self):
|
|
39
|
+
"""从文件加载图,如果文件不存在或加载失败,则创建一个新的。"""
|
|
40
|
+
if self.storage_path.exists():
|
|
41
|
+
try:
|
|
42
|
+
self.graph = nx.read_graphml(self.storage_path, node_type=str)
|
|
43
|
+
except Exception:
|
|
44
|
+
self._create_new_graph()
|
|
45
|
+
else:
|
|
46
|
+
self._create_new_graph()
|
|
47
|
+
|
|
48
|
+
def _create_new_graph(self):
|
|
49
|
+
"""创建一个带有根节点的新图谱并保存。"""
|
|
50
|
+
self.graph = nx.DiGraph()
|
|
51
|
+
self.graph.add_node("root", name=".", description="知识图谱根节点")
|
|
52
|
+
self._save_graph()
|
|
53
|
+
|
|
54
|
+
def set_publish_topic(self, publish_topic):
|
|
55
|
+
if not publish_topic:
|
|
56
|
+
return
|
|
57
|
+
self.publish_topic = publish_topic
|
|
58
|
+
|
|
59
|
+
def _save_graph(self):
|
|
60
|
+
"""将当前图的状态保存到文件。"""
|
|
61
|
+
nx.write_graphml(self.graph, self.storage_path)
|
|
62
|
+
|
|
63
|
+
if self.publish_topic and self.broker:
|
|
64
|
+
self.broker.publish({"message": "graph_updated", "graph": self.render_tree()}, self.publish_topic)
|
|
65
|
+
|
|
66
|
+
def _get_node_id_by_path(self, path: str):
|
|
67
|
+
"""通过'/'分隔的路径查找节点的唯一ID,支持前缀匹配。"""
|
|
68
|
+
if path is None or not path.strip() or path.strip() in ['.', '/']:
|
|
69
|
+
return "root"
|
|
70
|
+
|
|
71
|
+
segments = path.strip('/').split('/')
|
|
72
|
+
current_node_id = "root"
|
|
73
|
+
|
|
74
|
+
for segment in segments:
|
|
75
|
+
# 首先尝试完全匹配
|
|
76
|
+
found_child = False
|
|
77
|
+
for child_id in self.graph.successors(current_node_id):
|
|
78
|
+
if self.graph.nodes[child_id].get('name') == segment:
|
|
79
|
+
current_node_id = child_id
|
|
80
|
+
found_child = True
|
|
81
|
+
break
|
|
82
|
+
|
|
83
|
+
if found_child:
|
|
84
|
+
continue
|
|
85
|
+
|
|
86
|
+
# 如果完全匹配失败,尝试前缀匹配
|
|
87
|
+
prefix_matches = []
|
|
88
|
+
for child_id in self.graph.successors(current_node_id):
|
|
89
|
+
child_name = self.graph.nodes[child_id].get('name', '')
|
|
90
|
+
if child_name.startswith(segment):
|
|
91
|
+
prefix_matches.append(child_id)
|
|
92
|
+
|
|
93
|
+
if len(prefix_matches) == 1:
|
|
94
|
+
# 只有一个前缀匹配,使用这个节点
|
|
95
|
+
current_node_id = prefix_matches[0]
|
|
96
|
+
elif len(prefix_matches) > 1:
|
|
97
|
+
# 多个前缀匹配,存在歧义,返回None
|
|
98
|
+
return None
|
|
99
|
+
else:
|
|
100
|
+
# 没有前缀匹配,节点不存在
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
return current_node_id
|
|
104
|
+
|
|
105
|
+
def add_node(self, parent_path: str, node_name: str, description: str = "") -> str:
|
|
106
|
+
"""在指定父节点下添加一个新节点。"""
|
|
107
|
+
if not node_name.strip():
|
|
108
|
+
return "❌ 错误:节点名称不能为空。"
|
|
109
|
+
if '/' in node_name:
|
|
110
|
+
return f"❌ 错误:节点名称 '{node_name}' 不能包含'/'。"
|
|
111
|
+
|
|
112
|
+
parent_id = self._get_node_id_by_path(parent_path)
|
|
113
|
+
if parent_id is None:
|
|
114
|
+
return f"❌ 错误:父路径 '{parent_path}' 不存在。"
|
|
115
|
+
|
|
116
|
+
for child_id in self.graph.successors(parent_id):
|
|
117
|
+
if self.graph.nodes[child_id].get('name') == node_name:
|
|
118
|
+
return f"❌ 错误:在 '{parent_path}' 下已存在名为 '{node_name}' 的节点。"
|
|
119
|
+
|
|
120
|
+
new_node_id = str(uuid.uuid4())
|
|
121
|
+
self.graph.add_node(new_node_id, name=node_name, description=description)
|
|
122
|
+
self.graph.add_edge(parent_id, new_node_id)
|
|
123
|
+
self._save_graph()
|
|
124
|
+
return f"✅ 成功在 '{parent_path}' 下添加节点 '{node_name}'。"
|
|
125
|
+
|
|
126
|
+
def delete_node(self, node_path: str) -> str:
|
|
127
|
+
"""删除一个节点及其所有子孙节点。"""
|
|
128
|
+
if node_path is None or node_path.strip() in ['.', '/']:
|
|
129
|
+
return "❌ 错误:不能删除根节点。"
|
|
130
|
+
|
|
131
|
+
node_id = self._get_node_id_by_path(node_path)
|
|
132
|
+
if node_id is None:
|
|
133
|
+
return f"❌ 错误:路径 '{node_path}' 不存在。"
|
|
134
|
+
if node_id == "root":
|
|
135
|
+
return "❌ 错误:不能删除根节点。"
|
|
136
|
+
|
|
137
|
+
descendants = nx.descendants(self.graph, node_id)
|
|
138
|
+
self.graph.remove_nodes_from(descendants.union({node_id}))
|
|
139
|
+
self._save_graph()
|
|
140
|
+
return f"✅ 成功删除节点 '{node_path}' 及其所有子节点。"
|
|
141
|
+
|
|
142
|
+
def rename_node(self, node_path: str, new_name: str) -> str:
|
|
143
|
+
"""重命名一个节点。"""
|
|
144
|
+
if not new_name.strip():
|
|
145
|
+
return "❌ 错误:新名称不能为空。"
|
|
146
|
+
if '/' in new_name:
|
|
147
|
+
return f"❌ 错误:新名称 '{new_name}' 不能包含'/'。"
|
|
148
|
+
|
|
149
|
+
node_id = self._get_node_id_by_path(node_path)
|
|
150
|
+
if node_id is None:
|
|
151
|
+
return f"❌ 错误:路径 '{node_path}' 不存在。"
|
|
152
|
+
if node_id == "root":
|
|
153
|
+
return "❌ 错误:不能重命名根节点。"
|
|
154
|
+
|
|
155
|
+
parent_id = list(self.graph.predecessors(node_id))[0]
|
|
156
|
+
for sibling_id in self.graph.successors(parent_id):
|
|
157
|
+
if sibling_id != node_id and self.graph.nodes[sibling_id].get('name') == new_name:
|
|
158
|
+
return f"❌ 错误:同级目录下已存在名为 '{new_name}' 的节点。"
|
|
159
|
+
|
|
160
|
+
self.graph.nodes[node_id]['name'] = new_name
|
|
161
|
+
self._save_graph()
|
|
162
|
+
return f"✅ 成功将节点 '{node_path}' 重命名为 '{new_name}'。"
|
|
163
|
+
|
|
164
|
+
def move_node(self, source_path: str, target_parent_path: str) -> str:
|
|
165
|
+
"""将一个节点移动到另一个父节点下。"""
|
|
166
|
+
source_id = self._get_node_id_by_path(source_path)
|
|
167
|
+
if source_id is None:
|
|
168
|
+
return f"❌ 错误:源路径 '{source_path}' 不存在。"
|
|
169
|
+
if source_id == "root":
|
|
170
|
+
return "❌ 错误:不能移动根节点。"
|
|
171
|
+
|
|
172
|
+
target_parent_id = self._get_node_id_by_path(target_parent_path)
|
|
173
|
+
if target_parent_id is None:
|
|
174
|
+
return f"❌ 错误:目标父路径 '{target_parent_path}' 不存在。"
|
|
175
|
+
|
|
176
|
+
if source_id == target_parent_id or target_parent_id in nx.descendants(self.graph, source_id):
|
|
177
|
+
return "❌ 错误:不能将节点移动到其自身或其子孙节点下。"
|
|
178
|
+
|
|
179
|
+
source_name = self.graph.nodes[source_id]['name']
|
|
180
|
+
for child_id in self.graph.successors(target_parent_id):
|
|
181
|
+
if self.graph.nodes[child_id].get('name') == source_name:
|
|
182
|
+
return f"❌ 错误:目标目录 '{target_parent_path}' 下已存在同名节点 '{source_name}'。"
|
|
183
|
+
|
|
184
|
+
old_parent_id = list(self.graph.predecessors(source_id))[0]
|
|
185
|
+
self.graph.remove_edge(old_parent_id, source_id)
|
|
186
|
+
self.graph.add_edge(target_parent_id, source_id)
|
|
187
|
+
self._save_graph()
|
|
188
|
+
return f"✅ 成功将节点 '{source_path}' 移动到 '{target_parent_path}' 下。"
|
|
189
|
+
|
|
190
|
+
def render_tree(self) -> str:
|
|
191
|
+
"""渲染整个知识图谱为树状结构的文本。"""
|
|
192
|
+
if not self.graph or "root" not in self.graph:
|
|
193
|
+
return "图谱为空或未正确初始化。"
|
|
194
|
+
|
|
195
|
+
root_name = self.graph.nodes["root"].get("name", ".")
|
|
196
|
+
tree_lines = [root_name]
|
|
197
|
+
self._build_tree_string_recursive("root", "", tree_lines)
|
|
198
|
+
return "\n".join(tree_lines)
|
|
199
|
+
|
|
200
|
+
def _build_tree_string_recursive(self, parent_id, prefix, tree_lines):
|
|
201
|
+
"""递归辅助函数,用于构建树状图字符串。"""
|
|
202
|
+
children = sorted(list(self.graph.successors(parent_id)), key=lambda n: self.graph.nodes[n].get('name', ''))
|
|
203
|
+
for i, child_id in enumerate(children):
|
|
204
|
+
is_last = (i == len(children) - 1)
|
|
205
|
+
connector = "└── " if is_last else "├── "
|
|
206
|
+
node_name = self.graph.nodes[child_id].get('name', '[Unnamed Node]')
|
|
207
|
+
tree_lines.append(f"{prefix}{connector}{node_name}")
|
|
208
|
+
new_prefix = prefix + " " if is_last else prefix + "│ "
|
|
209
|
+
self._build_tree_string_recursive(child_id, new_prefix, tree_lines)
|
beswarm/taskmanager.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import os
|
|
1
2
|
import json
|
|
2
3
|
import uuid
|
|
3
4
|
import asyncio
|
|
@@ -21,11 +22,12 @@ class TaskManager:
|
|
|
21
22
|
一个带并发控制的异步任务管理器。
|
|
22
23
|
它管理任务的生命周期,并通过一个固定大小的工作者池来控制并发执行的任务数量。
|
|
23
24
|
"""
|
|
24
|
-
def __init__(self, concurrency_limit=
|
|
25
|
-
|
|
25
|
+
def __init__(self, concurrency_limit=None):
|
|
26
|
+
self.concurrency_limit = concurrency_limit or int(os.getenv("BESWARM_CONCURRENCY_LIMIT", "3"))
|
|
27
|
+
|
|
28
|
+
if self.concurrency_limit <= 0:
|
|
26
29
|
raise ValueError("并发限制必须大于0")
|
|
27
30
|
|
|
28
|
-
self.concurrency_limit = concurrency_limit
|
|
29
31
|
self.tasks_cache = {} # 存储所有任务的状态和元数据, key: task_id
|
|
30
32
|
|
|
31
33
|
self._pending_queue = asyncio.Queue() # 内部待办任务队列
|
|
@@ -101,7 +103,7 @@ class TaskManager:
|
|
|
101
103
|
self._pending_queue.task_done()
|
|
102
104
|
|
|
103
105
|
except asyncio.CancelledError:
|
|
104
|
-
print(f"[{worker_name}] 被取消,正在退出...")
|
|
106
|
+
# print(f"[{worker_name}] 被取消,正在退出...")
|
|
105
107
|
break
|
|
106
108
|
except Exception as e:
|
|
107
109
|
print(f"[{worker_name}] 循环中遇到严重错误: {e}")
|
beswarm/tools/__init__.py
CHANGED
|
@@ -4,6 +4,7 @@ from .completion import task_complete
|
|
|
4
4
|
from .search_arxiv import search_arxiv
|
|
5
5
|
from .repomap import get_code_repo_map
|
|
6
6
|
from .write_csv import append_row_to_csv
|
|
7
|
+
from .graph import add_knowledge_node, delete_knowledge_node, rename_knowledge_node, move_knowledge_node, get_knowledge_graph_tree
|
|
7
8
|
from .request_input import request_admin_input
|
|
8
9
|
from .screenshot import save_screenshot_to_file
|
|
9
10
|
from .worker import worker, worker_gen, chatgroup
|
|
@@ -48,6 +49,11 @@ __all__ = [
|
|
|
48
49
|
"list_directory",
|
|
49
50
|
"get_task_result",
|
|
50
51
|
"get_url_content",
|
|
52
|
+
"add_knowledge_node",
|
|
53
|
+
"move_knowledge_node",
|
|
54
|
+
"delete_knowledge_node",
|
|
55
|
+
"rename_knowledge_node",
|
|
56
|
+
"get_knowledge_graph_tree",
|
|
51
57
|
"append_row_to_csv",
|
|
52
58
|
"set_readonly_path",
|
|
53
59
|
"get_code_repo_map",
|
beswarm/tools/graph.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from ..aient.src.aient.plugins import register_tool
|
|
2
|
+
from ..core import kgm
|
|
3
|
+
|
|
4
|
+
@register_tool()
|
|
5
|
+
def add_knowledge_node(parent_path: str, node_name: str, description: str = "") -> str:
|
|
6
|
+
"""
|
|
7
|
+
在知识图谱的指定父路径下添加一个新节点。
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
parent_path (str): 父节点的路径。路径由'/'分隔,例如 'a/b'。根节点路径为 '.' 或 '/'。
|
|
11
|
+
node_name (str): 新节点的名称。名称中不能包含'/'字符。
|
|
12
|
+
description (str, optional): 节点的可选描述信息。默认为空字符串。
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
str: 操作结果的描述信息,例如成功或失败的原因。
|
|
16
|
+
"""
|
|
17
|
+
return kgm.add_node(parent_path, node_name, description)
|
|
18
|
+
|
|
19
|
+
@register_tool()
|
|
20
|
+
def delete_knowledge_node(node_path: str) -> str:
|
|
21
|
+
"""
|
|
22
|
+
从知识图谱中删除一个节点及其所有后代节点。
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
node_path (str): 要删除的节点的完整路径,例如 'a/b/c'。不允许删除根节点。
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
str: 操作结果的描述信息,例如成功或失败的原因。
|
|
29
|
+
"""
|
|
30
|
+
return kgm.delete_node(node_path)
|
|
31
|
+
|
|
32
|
+
@register_tool()
|
|
33
|
+
def rename_knowledge_node(node_path: str, new_name: str) -> str:
|
|
34
|
+
"""
|
|
35
|
+
重命名知识图谱中的一个现有节点。
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
node_path (str): 要重命名的节点的当前完整路径。
|
|
39
|
+
new_name (str): 节点的新名称。名称中不能包含'/'字符。
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
str: 操作结果的描述信息,例如成功或失败的原因。
|
|
43
|
+
"""
|
|
44
|
+
return kgm.rename_node(node_path, new_name)
|
|
45
|
+
|
|
46
|
+
@register_tool()
|
|
47
|
+
def move_knowledge_node(source_path: str, target_parent_path: str) -> str:
|
|
48
|
+
"""
|
|
49
|
+
将一个节点(及其整个子树)移动到知识图谱中的另一个父节点下。
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
source_path (str): 要移动的节点的当前完整路径。
|
|
53
|
+
target_parent_path (str): 目标父节点的完整路径。节点将被移动到这个新父节点之下。
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
str: 操作结果的描述信息,例如成功或失败的原因。
|
|
57
|
+
"""
|
|
58
|
+
return kgm.move_node(source_path, target_parent_path)
|
|
59
|
+
|
|
60
|
+
@register_tool()
|
|
61
|
+
def get_knowledge_graph_tree() -> str:
|
|
62
|
+
"""
|
|
63
|
+
渲染并返回整个知识图谱的文本树状图。
|
|
64
|
+
|
|
65
|
+
此工具不需要任何参数,它会读取当前的图状态并生成一个易于阅读的、
|
|
66
|
+
表示层级结构的字符串。
|
|
67
|
+
|
|
68
|
+
注意:此函数返回的知识图谱状态是实时更新的,永远是最新的。只需要调用一次,不必重复调用。
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
str: 表示整个知识图谱的、格式化的树状结构字符串。
|
|
72
|
+
"""
|
|
73
|
+
return "<knowledge_graph_tree>" + kgm.render_tree() + "</knowledge_graph_tree>"
|
beswarm/tools/subtasks.py
CHANGED
|
@@ -138,6 +138,7 @@ def create_tasks_from_csv(goal_template: str, csv_file_path: str, tools_json_str
|
|
|
138
138
|
"""
|
|
139
139
|
从一个CSV文件批量创建子任务。
|
|
140
140
|
此工具读取CSV文件的每一行,使用行数据填充goal模板,然后为每一行创建一个新的子任务。
|
|
141
|
+
可以使用 get_task_result 工具获取每个子任务的运行结果。
|
|
141
142
|
|
|
142
143
|
Args:
|
|
143
144
|
goal_template (str): 一个包含占位符的字符串模板。占位符的格式应为 `{column_name}`,
|
|
@@ -168,6 +169,8 @@ def create_tasks_from_csv(goal_template: str, csv_file_path: str, tools_json_str
|
|
|
168
169
|
|
|
169
170
|
try:
|
|
170
171
|
# 2. 读取并处理CSV文件
|
|
172
|
+
# 增加CSV字段大小限制以处理大字段
|
|
173
|
+
csv.field_size_limit(min(2**31-1, 2**20)) # 设置为1MB或系统最大值
|
|
171
174
|
with open(csv_file_path, mode='r', encoding='utf-8') as csvfile:
|
|
172
175
|
# 使用 DictReader 可以方便地通过列名访问数据
|
|
173
176
|
reader = csv.DictReader(csvfile)
|
beswarm/tools/worker.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
2
|
from typing import List, Dict, Union
|
|
3
3
|
|
|
4
|
-
from ..core import mcp_manager, broker, task_manager
|
|
4
|
+
from ..core import mcp_manager, broker, task_manager, kgm
|
|
5
5
|
from ..agents.planact import BrokerWorker
|
|
6
6
|
from ..agents.chatgroup import ChatGroupWorker
|
|
7
7
|
from ..aient.src.aient.plugins import register_tool
|
|
@@ -10,7 +10,7 @@ from ..aient.src.aient.plugins import register_tool
|
|
|
10
10
|
@register_tool()
|
|
11
11
|
async def worker(goal: str, tools: List[Union[str, Dict]], work_dir: str, cache_messages: Union[bool, List[Dict]] = None):
|
|
12
12
|
start_time = datetime.now()
|
|
13
|
-
worker_instance = BrokerWorker(goal, tools, work_dir, cache_messages, broker, mcp_manager, task_manager)
|
|
13
|
+
worker_instance = BrokerWorker(goal, tools, work_dir, cache_messages, broker, mcp_manager, task_manager, kgm)
|
|
14
14
|
result = await worker_instance.run()
|
|
15
15
|
end_time = datetime.now()
|
|
16
16
|
print(f"\n任务开始时间: {start_time.strftime('%Y-%m-%d %H:%M:%S')}")
|
|
@@ -21,7 +21,7 @@ async def worker(goal: str, tools: List[Union[str, Dict]], work_dir: str, cache_
|
|
|
21
21
|
@register_tool()
|
|
22
22
|
async def worker_gen(goal: str, tools: List[Union[str, Dict]], work_dir: str, cache_messages: Union[bool, List[Dict]] = None):
|
|
23
23
|
start_time = datetime.now()
|
|
24
|
-
worker_instance = BrokerWorker(goal, tools, work_dir, cache_messages, broker, mcp_manager, task_manager)
|
|
24
|
+
worker_instance = BrokerWorker(goal, tools, work_dir, cache_messages, broker, mcp_manager, task_manager, kgm)
|
|
25
25
|
async for result in worker_instance.stream_run():
|
|
26
26
|
yield result
|
|
27
27
|
end_time = datetime.now()
|
|
@@ -32,7 +32,7 @@ async def worker_gen(goal: str, tools: List[Union[str, Dict]], work_dir: str, ca
|
|
|
32
32
|
@register_tool()
|
|
33
33
|
async def chatgroup(tools: List[Union[str, Dict]], work_dir: str, cache_messages: Union[bool, List[Dict]] = None):
|
|
34
34
|
start_time = datetime.now()
|
|
35
|
-
worker_instance = ChatGroupWorker(tools, work_dir, cache_messages, broker, mcp_manager, task_manager)
|
|
35
|
+
worker_instance = ChatGroupWorker(tools, work_dir, cache_messages, broker, mcp_manager, task_manager, kgm)
|
|
36
36
|
result = await worker_instance.run()
|
|
37
37
|
end_time = datetime.now()
|
|
38
38
|
print(f"\n任务开始时间: {start_time.strftime('%Y-%m-%d %H:%M:%S')}")
|
|
@@ -1,11 +1,12 @@
|
|
|
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=
|
|
3
|
+
beswarm/core.py,sha256=htssaaeIBZ_yOqvX9VtANoVWaZHt_7oWcxyDI1z0paQ,310
|
|
4
|
+
beswarm/knowledge_graph.py,sha256=xbSl-GPPWNP8ZVQC95frn_AoDvRL0eR56mYwUVHXw2g,9174
|
|
4
5
|
beswarm/prompt.py,sha256=n0a1a6NThIxAYSkisg1sEKjvz2w0tozpKL4BIplaAkI,32286
|
|
5
|
-
beswarm/taskmanager.py,sha256=
|
|
6
|
+
beswarm/taskmanager.py,sha256=xczsnehCaOAI3SRE8wUxhzmt6i6G7ynndSWS_bAZn3k,12192
|
|
6
7
|
beswarm/utils.py,sha256=S9jEtht0hTZbjZ2Hk24p4Ip41R69BogOkYS6fgPKY2Y,8219
|
|
7
|
-
beswarm/agents/chatgroup.py,sha256=
|
|
8
|
-
beswarm/agents/planact.py,sha256=
|
|
8
|
+
beswarm/agents/chatgroup.py,sha256=YHofra9kE0x7UrhqZjlP7PbWvinEew1BkjJ0XilzudM,12020
|
|
9
|
+
beswarm/agents/planact.py,sha256=PGj69lhy6r3q07R8w16aMoWY1OjXVXothu7lDG-UC4Y,19140
|
|
9
10
|
beswarm/aient/main.py,sha256=SiYAIgQlLJqYusnTVEJOx1WNkSJKMImhgn5aWjfroxg,3814
|
|
10
11
|
beswarm/aient/setup.py,sha256=Mq1M05mT9_UYBK2jk5mP_sLxKQAolcuh8PYXexfj-XU,487
|
|
11
12
|
beswarm/aient/src/aient/__init__.py,sha256=SRfF7oDVlOOAi6nGKiJIUK6B_arqYLO9iSMp-2IZZps,21
|
|
@@ -130,20 +131,21 @@ beswarm/queries/tree-sitter-languages/ruby-tags.scm,sha256=vIidsCeE2A0vdFN18yXKq
|
|
|
130
131
|
beswarm/queries/tree-sitter-languages/rust-tags.scm,sha256=9ljM1nzhfPs_ZTRw7cr2P9ToOyhGcKkCoN4_HPXSWi4,1451
|
|
131
132
|
beswarm/queries/tree-sitter-languages/scala-tags.scm,sha256=UxQjz80JIrrJ7Pm56uUnQyThfmQNvwk7aQzPNypB-Ao,1761
|
|
132
133
|
beswarm/queries/tree-sitter-languages/typescript-tags.scm,sha256=OMdCeedPiA24ky82DpgTMKXK_l2ySTuF2zrQ2fJAi9E,1253
|
|
133
|
-
beswarm/tools/__init__.py,sha256=
|
|
134
|
+
beswarm/tools/__init__.py,sha256=fhJSEaYcfgMTW_nzUqQiyX5RLBhE-gxs8u4PpFquOdg,1868
|
|
134
135
|
beswarm/tools/click.py,sha256=7g6x1X7ffTInGWp7112KS-MAQ5-8wa1Ze2sIipUIbjc,20884
|
|
135
136
|
beswarm/tools/completion.py,sha256=wHEJrdzjuTKQNQZhelSuPnK2YRsGbeUqZ0P6IgT3c10,605
|
|
136
137
|
beswarm/tools/edit_file.py,sha256=iwWl7a8sTVq4vj0e1ny3H6UGcHfYnxALRGcLuk5hZS8,9155
|
|
138
|
+
beswarm/tools/graph.py,sha256=-Lyhi-AsJNKQ5p9BJk6mzARd1-IDhdLyKmNAVN5PdUE,2703
|
|
137
139
|
beswarm/tools/planner.py,sha256=lguBCS6kpwNPoXQvqH-WySabVubT82iyWOkJnjt6dXw,1265
|
|
138
140
|
beswarm/tools/repomap.py,sha256=YsTPq5MXfn_Ds5begcvHDnY_Xp2d4jH-xmWqNMHnNHY,45239
|
|
139
141
|
beswarm/tools/request_input.py,sha256=gXNAJPOJektMqxJVyzNTFOeMQ7xUkO-wWMYH-r2Rdwk,942
|
|
140
142
|
beswarm/tools/screenshot.py,sha256=u6t8FCgW5YHJ_Oc4coo8e0F3wTusWE_-H8dFh1rBq9Q,1011
|
|
141
143
|
beswarm/tools/search_arxiv.py,sha256=8naYRvmELQryMIcCCBHebIEzNJ8_YivXamM4fzGG3Dk,10754
|
|
142
144
|
beswarm/tools/search_web.py,sha256=LhgXOSHL9fwxg5T2AAOV4TTJkcJVLogsfRJW2faPvDE,16147
|
|
143
|
-
beswarm/tools/subtasks.py,sha256=
|
|
144
|
-
beswarm/tools/worker.py,sha256
|
|
145
|
+
beswarm/tools/subtasks.py,sha256=zZmRCjDocbvrU8MvZvpDWpQCKNntDJ123ByBiS8pRko,9889
|
|
146
|
+
beswarm/tools/worker.py,sha256=-tcLGrdLPZYxc1z66iQIB1VH7RWcR-5CgVWuDIQJBCQ,2013
|
|
145
147
|
beswarm/tools/write_csv.py,sha256=-r5OghcvjCg00hY0YQbp6u31VIJLrgaqDIvczAFoqDE,1470
|
|
146
|
-
beswarm-0.2.
|
|
147
|
-
beswarm-0.2.
|
|
148
|
-
beswarm-0.2.
|
|
149
|
-
beswarm-0.2.
|
|
148
|
+
beswarm-0.2.45.dist-info/METADATA,sha256=4d7hy--9PFuYXlvmyfQlug8yurPyOx9otmIPJBBXd-U,3878
|
|
149
|
+
beswarm-0.2.45.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
150
|
+
beswarm-0.2.45.dist-info/top_level.txt,sha256=pJw4O87wvt5882smuSO6DfByJz7FJ8SxxT8h9fHCmpo,8
|
|
151
|
+
beswarm-0.2.45.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|