beswarm 0.3.6__py3-none-any.whl → 0.3.8__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.
- beswarm/__init__.py +2 -1
- beswarm/agents/planact.py +6 -4
- beswarm/aient/aient/architext/architext/core.py +37 -1
- beswarm/aient/aient/architext/test/test.py +74 -1
- beswarm/aient/aient/models/chatgpt.py +27 -4
- beswarm/aient/aient/utils/scripts.py +55 -0
- beswarm/tools/__init__.py +4 -2
- beswarm/tools/deep_search.py +12 -12
- beswarm/{aient/aient/plugins → tools}/read_file.py +22 -1
- beswarm/tools/subtasks.py +8 -1
- beswarm/{aient/aient/plugins → tools}/write_file.py +27 -9
- {beswarm-0.3.6.dist-info → beswarm-0.3.8.dist-info}/METADATA +1 -1
- {beswarm-0.3.6.dist-info → beswarm-0.3.8.dist-info}/RECORD +16 -16
- {beswarm-0.3.6.dist-info → beswarm-0.3.8.dist-info}/WHEEL +0 -0
- {beswarm-0.3.6.dist-info → beswarm-0.3.8.dist-info}/entry_points.txt +0 -0
- {beswarm-0.3.6.dist-info → beswarm-0.3.8.dist-info}/top_level.txt +0 -0
beswarm/__init__.py
CHANGED
@@ -1 +1,2 @@
|
|
1
|
-
from .tools import *
|
1
|
+
from .tools import *
|
2
|
+
from .aient.aient.architext.architext import Messages, SystemMessage, UserMessage, AssistantMessage, ToolCalls, ToolResults, Texts, RoleMessage, Images, Files
|
beswarm/agents/planact.py
CHANGED
@@ -94,10 +94,11 @@ class InstructionAgent(BaseAgent):
|
|
94
94
|
|
95
95
|
async def handle_message(self, message: Dict):
|
96
96
|
"""Receives a worker response, generates the next instruction, and publishes it."""
|
97
|
+
goal = await message["conversation"].provider("goal").render() if message["conversation"].provider("goal") else Goal(self.goal)
|
97
98
|
|
98
99
|
instruction_prompt = "".join([
|
99
100
|
"</work_agent_conversation_end>\n\n",
|
100
|
-
f"任务目标: {
|
101
|
+
f"任务目标: {goal}\n\n",
|
101
102
|
f"任务目标新变化:\n{self.goal_diff}\n\n" if self.goal_diff else "",
|
102
103
|
"在 tag <work_agent_conversation_start>...</work_agent_conversation_end> 之前的对话历史都是工作智能体的对话历史。\n\n",
|
103
104
|
"根据以上对话历史和目标,请生成下一步指令。如果任务已完成,指示工作智能体调用task_complete工具。\n\n",
|
@@ -137,7 +138,7 @@ class InstructionAgent(BaseAgent):
|
|
137
138
|
instruction = re.sub(r'^<task_complete>([\D\d\s]+)<\/task_complete>$', '', instruction, flags=re.MULTILINE)
|
138
139
|
instruction = (
|
139
140
|
"任务描述:\n"
|
140
|
-
f"
|
141
|
+
f"{goal}\n\n"
|
141
142
|
"你作为指令的**执行者**,而非任务的**规划师**,你必须严格遵循以下单步工作流程:\n"
|
142
143
|
"**执行指令**\n"
|
143
144
|
" - **严格遵从:** 只执行我当前下达的明确指令。在我明确给出下一步指令前,绝不擅自行动或推测、执行任何未明确要求的后续步骤。\n"
|
@@ -207,7 +208,8 @@ class WorkerAgent(BaseAgent):
|
|
207
208
|
self.broker.publish(message, self.error_topic)
|
208
209
|
else:
|
209
210
|
self.broker.publish({"status": "new_message", "result": "\n✅ 工作智能体:\n" + response}, self.status_topic)
|
210
|
-
self.agent.conversation["default"][-1].
|
211
|
+
if self.agent.conversation["default"][-1].role == "user":
|
212
|
+
self.agent.conversation["default"][-1].rstrip(Texts)
|
211
213
|
self.broker.publish({
|
212
214
|
"conversation": self.agent.conversation["default"]
|
213
215
|
}, self.publish_topic)
|
@@ -244,7 +246,7 @@ class BrokerWorker:
|
|
244
246
|
|
245
247
|
# 创建一个文件处理器,将日志写入任务自己的目录
|
246
248
|
log_file_path = cache_dir / "agent.log"
|
247
|
-
self.logger = setup_logger(
|
249
|
+
self.logger = setup_logger(str(log_file_path.parent.parent.absolute()), log_file_path)
|
248
250
|
self.logger.info(f"beswarm version: {version}")
|
249
251
|
self.logger.info(f"Logger for task '{self.goal}' initialized. Log file: {log_file_path}")
|
250
252
|
|
@@ -192,6 +192,22 @@ class Texts(ContextProvider):
|
|
192
192
|
# For static content, compare the actual content.
|
193
193
|
return self.content == other.content
|
194
194
|
|
195
|
+
def __iadd__(self, other):
|
196
|
+
if isinstance(other, str):
|
197
|
+
new_text = self.content + other
|
198
|
+
self.update(new_text)
|
199
|
+
return self
|
200
|
+
return NotImplemented
|
201
|
+
|
202
|
+
def __add__(self, other):
|
203
|
+
if isinstance(other, str):
|
204
|
+
# Create a new instance of the same class with the combined content
|
205
|
+
return type(self)(text=self.content + other, name=self.name, visible=self.visible, newline=self.newline)
|
206
|
+
elif isinstance(other, Message):
|
207
|
+
new_items = [self] + other.provider()
|
208
|
+
return type(other)(*new_items)
|
209
|
+
return NotImplemented
|
210
|
+
|
195
211
|
class Tools(ContextProvider):
|
196
212
|
def __init__(self, tools_json: Optional[List[Dict]] = None, name: str = "tools", visible: bool = True):
|
197
213
|
super().__init__(name, visible=visible)
|
@@ -711,7 +727,27 @@ class Messages:
|
|
711
727
|
|
712
728
|
def render(self) -> List[Dict[str, Any]]:
|
713
729
|
results = [msg.to_dict() for msg in self._messages]
|
714
|
-
|
730
|
+
non_empty_results = [res for res in results if res]
|
731
|
+
|
732
|
+
if not non_empty_results:
|
733
|
+
return []
|
734
|
+
|
735
|
+
merged_results = [non_empty_results[0]]
|
736
|
+
for i in range(1, len(non_empty_results)):
|
737
|
+
current_msg = non_empty_results[i]
|
738
|
+
last_merged_msg = merged_results[-1]
|
739
|
+
|
740
|
+
# Merge if roles match, no tool_calls, and content is string
|
741
|
+
if (current_msg.get('role') == last_merged_msg.get('role') and
|
742
|
+
'tool_calls' not in current_msg and
|
743
|
+
'tool_calls' not in last_merged_msg and
|
744
|
+
isinstance(current_msg.get('content'), str) and
|
745
|
+
isinstance(last_merged_msg.get('content'), str)):
|
746
|
+
last_merged_msg['content'] += current_msg.get('content', '')
|
747
|
+
else:
|
748
|
+
merged_results.append(current_msg)
|
749
|
+
|
750
|
+
return merged_results
|
715
751
|
|
716
752
|
async def render_latest(self) -> List[Dict[str, Any]]:
|
717
753
|
await self.refresh()
|
@@ -9,7 +9,7 @@ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')
|
|
9
9
|
|
10
10
|
|
11
11
|
from architext import *
|
12
|
-
|
12
|
+
from typing import Optional, Union, Callable
|
13
13
|
# ==============================================================================
|
14
14
|
# 单元测试部分
|
15
15
|
# ==============================================================================
|
@@ -1709,6 +1709,79 @@ Files: {Files(visible=True, name="files")}
|
|
1709
1709
|
rendered_tool_2 = await tool_results_msg.render()
|
1710
1710
|
self.assertEqual(rendered_tool_1, rendered_tool_2)
|
1711
1711
|
|
1712
|
+
async def test_zag_iadd_on_provider(self):
|
1713
|
+
"""测试对 provider 使用 += 操作符来追加文本"""
|
1714
|
+
class Goal(Texts):
|
1715
|
+
def __init__(self, text: Optional[Union[str, Callable[[], str]]] = None, name: str = "goal"):
|
1716
|
+
super().__init__(text=text, name=name)
|
1717
|
+
|
1718
|
+
async def render(self) -> Optional[str]:
|
1719
|
+
content = await super().render()
|
1720
|
+
if content is None:
|
1721
|
+
return None
|
1722
|
+
return f"<goal>{content}</goal>"
|
1723
|
+
|
1724
|
+
messages = Messages(UserMessage(Goal("hi")))
|
1725
|
+
|
1726
|
+
# This is the new syntax we want to test
|
1727
|
+
goal_provider = messages.provider("goal")
|
1728
|
+
goal_provider += "test"
|
1729
|
+
|
1730
|
+
rendered = await messages.render_latest()
|
1731
|
+
|
1732
|
+
self.assertEqual(len(rendered), 1)
|
1733
|
+
self.assertEqual(rendered[0]['content'], "<goal>hitest</goal>")
|
1734
|
+
|
1735
|
+
async def test_zz_user_message_auto_merging(self):
|
1736
|
+
"""测试连续的UserMessage是否能自动合并"""
|
1737
|
+
# 场景1: 初始化时合并
|
1738
|
+
messages_init = Messages(UserMessage("hi"), UserMessage("hi2"))
|
1739
|
+
self.assertEqual(len(messages_init), 1, "初始化时,两个连续的UserMessage应该合并为一个")
|
1740
|
+
self.assertEqual(len(messages_init[0]), 2, "合并后的UserMessage应该包含两个Texts provider")
|
1741
|
+
|
1742
|
+
rendered_init = await messages_init.render_latest()
|
1743
|
+
self.assertEqual(rendered_init[0]['content'], "hihi2", "合并后渲染的内容不正确")
|
1744
|
+
|
1745
|
+
# 场景2: 追加时合并
|
1746
|
+
messages_append = Messages(UserMessage("hi"))
|
1747
|
+
messages_append.append(UserMessage("hi2"))
|
1748
|
+
self.assertEqual(len(messages_append), 1, "追加时,两个连续的UserMessage应该合并为一个")
|
1749
|
+
self.assertEqual(len(messages_append[0]), 2, "追加合并后的UserMessage应该包含两个Texts provider")
|
1750
|
+
|
1751
|
+
rendered_append = await messages_append.render_latest()
|
1752
|
+
self.assertEqual(rendered_append[0]['content'], "hihi2", "追加合并后渲染的内容不正确")
|
1753
|
+
|
1754
|
+
# 场景3: 追加RoleMessage时合并
|
1755
|
+
messages_append.append(RoleMessage("user", "hi3"))
|
1756
|
+
self.assertEqual(len(messages_append), 1, "追加RoleMessage时,连续的UserMessage应该合并为一个")
|
1757
|
+
self.assertEqual(len(messages_append[0]), 3, "追加RoleMessage合并后的UserMessage应该包含三个Texts provider")
|
1758
|
+
|
1759
|
+
rendered_append_role = await messages_append.render_latest()
|
1760
|
+
self.assertEqual(rendered_append_role[0]['content'], "hihi2hi3", "追加RoleMessage合并后渲染的内容不正确")
|
1761
|
+
|
1762
|
+
# 场景4: 追加包含ContextProvider和字符串组合的RoleMessage时合并
|
1763
|
+
class Goal(Texts):
|
1764
|
+
def __init__(self, text: Optional[Union[str, Callable[[], str]]] = None, name: str = "goal", visible: bool = True, newline: bool = False):
|
1765
|
+
super().__init__(text=text, name=name, visible=visible, newline=newline)
|
1766
|
+
|
1767
|
+
async def render(self) -> Optional[str]:
|
1768
|
+
content = await super().render()
|
1769
|
+
if content is None:
|
1770
|
+
return None
|
1771
|
+
return f"<goal>{content}</goal>"
|
1772
|
+
|
1773
|
+
messages_append.append(RoleMessage("user", Goal("goal") + "hi4"))
|
1774
|
+
self.assertEqual(len(messages_append), 1, "追加(ContextProvider + str)的RoleMessage时,未能正确合并")
|
1775
|
+
self.assertEqual(len(messages_append[0]), 4, "追加(ContextProvider + str)的RoleMessage合并后的provider数量不正确")
|
1776
|
+
|
1777
|
+
rendered_append_combo = await messages_append.render_latest()
|
1778
|
+
self.assertEqual(rendered_append_combo[0]['content'], "hihi2hi3<goal>goalhi4</goal>", "追加(ContextProvider + str)合并后渲染的内容不正确")
|
1779
|
+
|
1780
|
+
# 场景5: 被空消息隔开的同角色消息在渲染时合并
|
1781
|
+
messages_separated = Messages(UserMessage("hi"), AssistantMessage(""), UserMessage("hi2"))
|
1782
|
+
rendered_separated = await messages_separated.render_latest()
|
1783
|
+
self.assertEqual(len(rendered_separated), 1, "被空消息隔开的同角色消息在渲染时应该合并")
|
1784
|
+
self.assertEqual(rendered_separated[0]['content'], "hihi2", "被空消息隔开的同角色消息合并后内容不正确")
|
1712
1785
|
|
1713
1786
|
# ==============================================================================
|
1714
1787
|
# 6. 演示
|
@@ -12,7 +12,7 @@ from typing import Union, Optional, Callable
|
|
12
12
|
from .base import BaseLLM
|
13
13
|
from ..plugins.registry import registry
|
14
14
|
from ..plugins import PLUGINS, get_tools_result_async, function_call_list, update_tools_config
|
15
|
-
from ..utils.scripts import safe_get, async_generator_to_sync, parse_function_xml, parse_continuous_json, convert_functions_to_xml, remove_xml_tags_and_content
|
15
|
+
from ..utils.scripts import safe_get, async_generator_to_sync, parse_function_xml, parse_continuous_json, convert_functions_to_xml, remove_xml_tags_and_content, find_most_frequent_phrase
|
16
16
|
from ..core.request import prepare_request_payload
|
17
17
|
from ..core.response import fetch_response_stream, fetch_response
|
18
18
|
from ..architext.architext import Messages, SystemMessage, UserMessage, AssistantMessage, ToolCalls, ToolResults, Texts, RoleMessage, Images, Files
|
@@ -81,6 +81,14 @@ class TaskComplete(Exception):
|
|
81
81
|
super().__init__(f"Task completed with message: {message}")
|
82
82
|
|
83
83
|
|
84
|
+
class RepetitiveResponseError(Exception):
|
85
|
+
"""Custom exception for detecting repetitive and meaningless generated strings."""
|
86
|
+
def __init__(self, message, phrase, count):
|
87
|
+
super().__init__(message)
|
88
|
+
self.phrase = phrase
|
89
|
+
self.count = count
|
90
|
+
|
91
|
+
|
84
92
|
class chatgpt(BaseLLM):
|
85
93
|
"""
|
86
94
|
Official ChatGPT API
|
@@ -172,7 +180,7 @@ class chatgpt(BaseLLM):
|
|
172
180
|
"""
|
173
181
|
Add a message to the conversation
|
174
182
|
"""
|
175
|
-
# self.logger.info(f"role: {role}, function_name: {function_name}, message: {message}")
|
183
|
+
# self.logger.info(f"role: {role}, function_name: {function_name}, message: {message}, function_arguments: {function_arguments}")
|
176
184
|
if convo_id not in self.conversation:
|
177
185
|
self.reset(convo_id=convo_id)
|
178
186
|
if function_name == "" and message:
|
@@ -276,9 +284,9 @@ class chatgpt(BaseLLM):
|
|
276
284
|
}
|
277
285
|
|
278
286
|
done_message = self.conversation[convo_id].provider("done")
|
279
|
-
if
|
287
|
+
if done_message:
|
280
288
|
done_message.visible = False
|
281
|
-
if self.conversation[convo_id][-1][-1].name == "done":
|
289
|
+
if self.check_done and self.conversation[convo_id][-1][-1].name == "done":
|
282
290
|
self.conversation[convo_id][-1][-1].visible = True
|
283
291
|
|
284
292
|
# 构造请求数据
|
@@ -438,6 +446,13 @@ class chatgpt(BaseLLM):
|
|
438
446
|
|
439
447
|
if not full_response.strip() and not need_function_call:
|
440
448
|
raise EmptyResponseError("Response is empty")
|
449
|
+
most_frequent_phrase, most_frequent_phrase_count = find_most_frequent_phrase(full_response)
|
450
|
+
if most_frequent_phrase_count > 100:
|
451
|
+
raise RepetitiveResponseError(
|
452
|
+
f"Detected repetitive and meaningless content. The phrase '{most_frequent_phrase}' appeared {most_frequent_phrase_count} times.",
|
453
|
+
most_frequent_phrase,
|
454
|
+
most_frequent_phrase_count
|
455
|
+
)
|
441
456
|
|
442
457
|
if self.print_log:
|
443
458
|
self.logger.info(f"total_tokens: {total_tokens}")
|
@@ -612,6 +627,11 @@ class chatgpt(BaseLLM):
|
|
612
627
|
elif tool_name == "get_knowledge_graph_tree":
|
613
628
|
self.conversation[convo_id].provider("knowledge_graph").visible = True
|
614
629
|
final_tool_response = "Get knowledge graph tree successfully! The knowledge graph tree has been updated in the tag <knowledge_graph_tree>."
|
630
|
+
elif tool_name.endswith("goal"):
|
631
|
+
goal_provider = self.conversation[convo_id].provider("goal")
|
632
|
+
if goal_provider:
|
633
|
+
goal_provider += tool_response
|
634
|
+
final_tool_response = "Get goal successfully! The goal has been updated in the tag <goal>."
|
615
635
|
elif tool_name == "write_to_file":
|
616
636
|
tool_args = None
|
617
637
|
elif tool_name == "read_image":
|
@@ -800,6 +820,9 @@ class chatgpt(BaseLLM):
|
|
800
820
|
except EmptyResponseError as e:
|
801
821
|
self.logger.warning(f"{e}, retrying...")
|
802
822
|
continue
|
823
|
+
except RepetitiveResponseError as e:
|
824
|
+
self.logger.warning(f"{e}, retrying...")
|
825
|
+
continue
|
803
826
|
except TaskComplete as e:
|
804
827
|
raise
|
805
828
|
except ModelNotFoundError as e:
|
@@ -3,10 +3,65 @@ import re
|
|
3
3
|
import json
|
4
4
|
import fnmatch
|
5
5
|
import requests
|
6
|
+
import collections
|
6
7
|
import urllib.parse
|
7
8
|
|
8
9
|
from ..core.utils import get_image_message
|
9
10
|
|
11
|
+
def find_most_frequent_phrase(s, min_len=4, max_phrase_len=20):
|
12
|
+
"""
|
13
|
+
查找字符串中出现次数最多的短语(单词序列)。
|
14
|
+
此版本经过性能优化,并增加了最大短语长度限制。
|
15
|
+
|
16
|
+
Args:
|
17
|
+
s: 输入字符串。
|
18
|
+
min_len: 短语的最小字符长度。
|
19
|
+
max_phrase_len: 要搜索的最大短语长度(以单词为单位)。
|
20
|
+
|
21
|
+
Returns:
|
22
|
+
一个元组 (most_frequent_phrase, count),其中
|
23
|
+
most_frequent_phrase 是出现次数最多的短语,
|
24
|
+
count 是它的出现次数。
|
25
|
+
如果没有找到符合条件的重复短语,则返回 ("", 0)。
|
26
|
+
"""
|
27
|
+
# start_time = time.time()
|
28
|
+
if not s or len(s) < min_len:
|
29
|
+
return "", 0
|
30
|
+
|
31
|
+
words = [word for word in re.split(r'[\s\n]+', s) if word]
|
32
|
+
if not words:
|
33
|
+
return "", 0
|
34
|
+
n = len(words)
|
35
|
+
|
36
|
+
phrase_counts = collections.defaultdict(int)
|
37
|
+
|
38
|
+
# 确定要检查的实际最大长度
|
39
|
+
effective_max_len = min(n // 2, max_phrase_len)
|
40
|
+
|
41
|
+
# 优化的核心:直接在单词列表上生成并统计所有可能的短语(n-grams)
|
42
|
+
for length in range(1, effective_max_len + 1):
|
43
|
+
for i in range(n - length + 1):
|
44
|
+
phrase_tuple = tuple(words[i:i + length])
|
45
|
+
phrase_counts[phrase_tuple] += 1
|
46
|
+
# 筛选出重复次数大于1且满足最小长度要求的短语
|
47
|
+
best_phrase = ""
|
48
|
+
max_count = 0
|
49
|
+
|
50
|
+
for phrase_tuple, count in phrase_counts.items():
|
51
|
+
if count > 1:
|
52
|
+
phrase = " ".join(phrase_tuple)
|
53
|
+
if len(phrase) >= min_len:
|
54
|
+
if count > max_count:
|
55
|
+
max_count = count
|
56
|
+
best_phrase = phrase
|
57
|
+
elif count == max_count and len(phrase) > len(best_phrase):
|
58
|
+
best_phrase = phrase
|
59
|
+
|
60
|
+
if max_count > 0:
|
61
|
+
return best_phrase, max_count
|
62
|
+
else:
|
63
|
+
return "", 0
|
64
|
+
|
10
65
|
def get_doc_from_url(url):
|
11
66
|
filename = urllib.parse.unquote(url.split("/")[-1])
|
12
67
|
response = requests.get(url, stream=True)
|
beswarm/tools/__init__.py
CHANGED
@@ -20,14 +20,16 @@ from .worker import worker, worker_gen, chatgroup
|
|
20
20
|
from .click import find_and_click_element, scroll_screen
|
21
21
|
from .subtasks import create_task, resume_task, get_all_tasks_status, get_task_result, create_tasks_from_csv
|
22
22
|
from .deep_search import deepsearch
|
23
|
+
from .write_file import write_to_file
|
24
|
+
from .read_file import read_file
|
23
25
|
|
24
26
|
#显式导入 aient.plugins 中的所需内容
|
25
27
|
from ..aient.aient.plugins import (
|
26
28
|
get_time,
|
27
|
-
read_file,
|
29
|
+
# read_file,
|
28
30
|
read_image,
|
29
31
|
register_tool,
|
30
|
-
write_to_file,
|
32
|
+
# write_to_file,
|
31
33
|
excute_command,
|
32
34
|
generate_image,
|
33
35
|
list_directory,
|
beswarm/tools/deep_search.py
CHANGED
@@ -4,7 +4,7 @@ from ..aient.aient.plugins import (
|
|
4
4
|
# read_file,
|
5
5
|
# read_image,
|
6
6
|
register_tool,
|
7
|
-
write_to_file,
|
7
|
+
# write_to_file,
|
8
8
|
excute_command,
|
9
9
|
# list_directory,
|
10
10
|
# get_url_content,
|
@@ -14,6 +14,9 @@ from ..aient.aient.plugins import (
|
|
14
14
|
# download_read_arxiv_pdf,
|
15
15
|
)
|
16
16
|
|
17
|
+
from ..aient.aient.architext.architext import Messages, SystemMessage, UserMessage, AssistantMessage, ToolCalls, ToolResults, Texts, RoleMessage, Images, Files
|
18
|
+
|
19
|
+
from .write_file import write_to_file
|
17
20
|
|
18
21
|
from beswarm.tools import (
|
19
22
|
# worker,
|
@@ -44,13 +47,9 @@ from ..agents.planact import BrokerWorker
|
|
44
47
|
@register_tool()
|
45
48
|
def get_task1_goal(query: str):
|
46
49
|
"""
|
47
|
-
|
48
|
-
|
49
|
-
这个函数为第一个子任务创建一个详细的提示。该子任务的目标是
|
50
|
-
使用 `search_web` 工具搜索与查询相关的信息,然后使用
|
51
|
-
`create_tasks_from_csv` 工具根据搜索结果创建更多的子任务。
|
50
|
+
根据用户查询获取任务1的目标。
|
52
51
|
|
53
|
-
|
52
|
+
返回的字符串将用作初始任务的“目标”。
|
54
53
|
它包含了指导AI代理(扮演“首席情报官”)从网络搜索结果构建知识图谱的说明。
|
55
54
|
|
56
55
|
参数:
|
@@ -64,7 +63,7 @@ def get_task1_goal(query: str):
|
|
64
63
|
"""
|
65
64
|
调用 search_web 工具搜索,该工具会把搜索结果保存到 csv 文件中。
|
66
65
|
|
67
|
-
|
66
|
+
**禁止**使用任何方式读取任何 csv 文件。搜索结果的csv文件是给create_tasks_from_csv工具用的,**禁止**直接读取。
|
68
67
|
|
69
68
|
然后使用 create_tasks_from_csv 工具,从 csv 文件中创建多个子任务。
|
70
69
|
|
@@ -136,7 +135,6 @@ csv 的 title 是 query, url和content,content是url对应的网页内容。qu
|
|
136
135
|
5. **【第5步:完成任务 (Finalize)】**
|
137
136
|
* 只有当你确认知识图谱(情报简报)在**结构、逻辑、深度和洞察力**上都达到了你作为**首席情报官**的最高标准时,才能调用 `task_complete` 结束你的任务。
|
138
137
|
```
|
139
|
-
|
140
138
|
观察知识图谱,如果当前信息不足以回答用户的查询,则继续从不同角度搜索,直到信息足够为止。继续循环上一个子任务,直到信息足够为止。
|
141
139
|
"""
|
142
140
|
)
|
@@ -203,7 +201,7 @@ def get_task2_goal(query: str):
|
|
203
201
|
* 当你对这篇由团队协作完成、并由你亲自统稿的报告感到满意时,编写执行摘要和战略展望章节,编写脚本通过 excute_command 使用 cat 和输出重定向进行文件内容拼接将所有章节合并到同一个md文件保存到本地,最后调用 `task_complete`,并将**最终的、完整的Markdown报告全文的文件路径**作为`message`参数返回。
|
204
202
|
|
205
203
|
---
|
206
|
-
### **【分析师专用】子任务`goal`** (注意:这是子任务的`goal
|
204
|
+
### **【分析师专用】子任务`goal`** (注意:这是子任务的`goal`,填入必要信息后,将下面的goal原样完整传给子-任务执行,不要修改)
|
207
205
|
|
208
206
|
**任务:** 你是一位专业的领域分析师。你的总编辑已经为你规划好了整篇报告的结构,并为你分配了具体的章节撰写任务。
|
209
207
|
|
@@ -277,13 +275,15 @@ async def deepsearch(query: str, work_dir: str):
|
|
277
275
|
|
278
276
|
goal = f"""
|
279
277
|
**禁止**执行 get_task1_goal,get_task2_goal。
|
278
|
+
第一个子任务的 tool 必须有 get_task1_goal,第二个子任务的 tool 必须有 get_task2_goal。
|
280
279
|
|
281
280
|
使用 create_task 启动第一个子任务,下面是 goal 原文,请将```内字符串原样传入,不要修改:
|
282
281
|
|
283
282
|
```
|
284
283
|
用户查询:{query}
|
285
284
|
|
286
|
-
|
285
|
+
首先调用get_task1_goal获取任务安排。
|
286
|
+
等待所有子任务全部完成。
|
287
287
|
```
|
288
288
|
|
289
289
|
使用 create_task 第一个子任务结束后,启动第二个子任务,第二个子任务需要 excute_command 工具,下面是 goal 原文,请将```内字符串原样传入,不要修改:
|
@@ -292,10 +292,10 @@ async def deepsearch(query: str, work_dir: str):
|
|
292
292
|
用户查询:{query}
|
293
293
|
|
294
294
|
使用get_task2_goal获取任务安排。
|
295
|
+
```
|
295
296
|
|
296
297
|
务必先执行第一个子任务,等第一个子任务完成后,再执行第二个子任务。
|
297
298
|
等待两个子任务都完成后,才调用 task_complete 结束任务。
|
298
|
-
```
|
299
299
|
"""
|
300
300
|
|
301
301
|
worker_instance = BrokerWorker(goal, tools, work_dir, True, broker, mcp_manager, task_manager, kgm)
|
@@ -3,7 +3,9 @@ import json
|
|
3
3
|
import chardet
|
4
4
|
from pdfminer.high_level import extract_text
|
5
5
|
|
6
|
-
from .
|
6
|
+
from ..aient.aient.plugins import register_tool
|
7
|
+
from ..core import current_work_dir
|
8
|
+
|
7
9
|
|
8
10
|
# 读取文件内容
|
9
11
|
@register_tool()
|
@@ -49,6 +51,25 @@ Examples:
|
|
49
51
|
<file_path>README.md</file_path>
|
50
52
|
</read_file>
|
51
53
|
"""
|
54
|
+
|
55
|
+
work_dir = current_work_dir.get()
|
56
|
+
if not work_dir:
|
57
|
+
return f"<tool_error>关键上下文 'current_work_dir' 未设置,无法确定安全的工作目录。</tool_error>"
|
58
|
+
|
59
|
+
# Determine the final, absolute path for the file operation.
|
60
|
+
if os.path.isabs(file_path):
|
61
|
+
final_path = file_path
|
62
|
+
else:
|
63
|
+
final_path = os.path.join(work_dir, file_path)
|
64
|
+
|
65
|
+
# Security check: Ensure the final path is within the designated work directory.
|
66
|
+
# abs_work_dir = os.path.abspath(work_dir)
|
67
|
+
abs_final_path = os.path.abspath(final_path)
|
68
|
+
# if not abs_final_path.startswith(abs_work_dir):
|
69
|
+
# return f"<tool_error>路径遍历攻击被阻止。尝试写入的路径 '{file_path}' 解析后超出了允许的工作目录范围 '{abs_work_dir}'。</tool_error>"
|
70
|
+
|
71
|
+
file_path = abs_final_path
|
72
|
+
|
52
73
|
try:
|
53
74
|
# 检查文件是否存在
|
54
75
|
if not os.path.exists(file_path):
|
beswarm/tools/subtasks.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import ast
|
2
2
|
from pathlib import Path
|
3
|
-
from ..core import current_task_manager
|
3
|
+
from ..core import current_task_manager, current_work_dir
|
4
4
|
from ..aient.aient.plugins import register_tool, registry
|
5
5
|
|
6
6
|
worker_fun = registry.tools["worker"]
|
@@ -24,6 +24,13 @@ def create_task(goal, tools, work_dir):
|
|
24
24
|
str: 当任务成功完成时,返回字符串 "任务已完成"。
|
25
25
|
"""
|
26
26
|
task_manager = current_task_manager.get()
|
27
|
+
main_work_dir = current_work_dir.get()
|
28
|
+
|
29
|
+
if os.path.isabs(work_dir):
|
30
|
+
final_path = work_dir
|
31
|
+
else:
|
32
|
+
final_path = os.path.join(main_work_dir, work_dir)
|
33
|
+
work_dir = os.path.abspath(final_path)
|
27
34
|
|
28
35
|
# 检查工作目录是否与现有任务重复
|
29
36
|
for task_id, task_info in task_manager.tasks_cache.items():
|
@@ -1,5 +1,6 @@
|
|
1
|
-
from .
|
2
|
-
from ..utils.scripts import unescape_html
|
1
|
+
from ..aient.aient.plugins import register_tool
|
2
|
+
from ..aient.aient.utils.scripts import unescape_html
|
3
|
+
from ..core import current_work_dir
|
3
4
|
|
4
5
|
import os
|
5
6
|
|
@@ -44,13 +45,31 @@ Example: Requesting to write to frontend-config.json
|
|
44
45
|
</content>
|
45
46
|
</write_to_file>
|
46
47
|
"""
|
48
|
+
work_dir = current_work_dir.get()
|
49
|
+
if not work_dir:
|
50
|
+
return f"<tool_error>关键上下文 'current_work_dir' 未设置,无法确定安全的工作目录。</tool_error>"
|
51
|
+
|
52
|
+
# Determine the final, absolute path for the file operation.
|
53
|
+
if os.path.isabs(path):
|
54
|
+
final_path = path
|
55
|
+
else:
|
56
|
+
final_path = os.path.join(work_dir, path)
|
57
|
+
|
58
|
+
# Security check: Ensure the final path is within the designated work directory.
|
59
|
+
# abs_work_dir = os.path.abspath(work_dir)
|
60
|
+
abs_final_path = os.path.abspath(final_path)
|
61
|
+
# if not abs_final_path.startswith(abs_work_dir):
|
62
|
+
# return f"<tool_error>路径遍历攻击被阻止。尝试写入的路径 '{path}' 解析后超出了允许的工作目录范围 '{abs_work_dir}'。</tool_error>"
|
63
|
+
|
47
64
|
# 确保目录存在
|
48
|
-
os.
|
65
|
+
dir_to_create = os.path.dirname(abs_final_path)
|
66
|
+
if dir_to_create:
|
67
|
+
os.makedirs(dir_to_create, exist_ok=True)
|
49
68
|
|
50
|
-
if content.startswith("##") and (
|
69
|
+
if content.startswith("##") and (abs_final_path.endswith(".md") or abs_final_path.endswith(".txt")):
|
51
70
|
content = "\n\n" + content
|
52
71
|
|
53
|
-
if content.startswith("---\n") and (
|
72
|
+
if content.startswith("---\n") and (abs_final_path.endswith(".md") or abs_final_path.endswith(".txt")):
|
54
73
|
content = "\n" + content
|
55
74
|
|
56
75
|
if newline:
|
@@ -58,13 +77,12 @@ Example: Requesting to write to frontend-config.json
|
|
58
77
|
|
59
78
|
# 写入文件
|
60
79
|
try:
|
61
|
-
with open(
|
80
|
+
with open(abs_final_path, mode, encoding='utf-8') as file:
|
62
81
|
file.write(unescape_html(content))
|
63
82
|
except PermissionError as e:
|
64
|
-
return f"<tool_error
|
65
|
-
|
66
|
-
return f"已成功写入文件:{path}"
|
83
|
+
return f"<tool_error>写入文件 '{abs_final_path}' 失败: {e}</tool_error>"
|
67
84
|
|
85
|
+
return f"已成功写入文件:{abs_final_path}"
|
68
86
|
|
69
87
|
if __name__ == "__main__":
|
70
88
|
text = """
|
@@ -1,4 +1,4 @@
|
|
1
|
-
beswarm/__init__.py,sha256=
|
1
|
+
beswarm/__init__.py,sha256=3VJ9epvLgqS4PoelWivMTK8mwmNdIT0KBCdVCeQMy3M,179
|
2
2
|
beswarm/broker.py,sha256=64Y-djrKYaZfBQ8obwHOmr921QgZeu9BtScZWaYLfDo,9887
|
3
3
|
beswarm/cli.py,sha256=m8fcEYznUq--iLULB6nGNecUGeT-wuuSRqiooINYDpY,2472
|
4
4
|
beswarm/core.py,sha256=jKStpTTOu6Ojond_i-okTZLrvSAJ4yUoTZwtDfFTiRs,553
|
@@ -7,12 +7,12 @@ beswarm/prompt.py,sha256=45onnyoY9plKM86KQefbPw5z9QJMn-mVnjlFQZcrjz0,34373
|
|
7
7
|
beswarm/taskmanager.py,sha256=2Uz_cthW9rWkQMJdzgsXAMlfN8Ni2Qj_DOq_L-p6XZc,12662
|
8
8
|
beswarm/utils.py,sha256=0J-b38P5QGT-A_38co7FjzaUNJykaskI7mbbcQ4w_68,8215
|
9
9
|
beswarm/agents/chatgroup.py,sha256=PzrmRcDKAbB7cxL16nMod_CzPosDV6bfTmXxQVuv-AQ,12012
|
10
|
-
beswarm/agents/planact.py,sha256=
|
10
|
+
beswarm/agents/planact.py,sha256=dhPkMUaZLc1dO0TrI1Orr_i60iLzJHQIYBI_QjjSJwI,18723
|
11
11
|
beswarm/aient/aient/__init__.py,sha256=SRfF7oDVlOOAi6nGKiJIUK6B_arqYLO9iSMp-2IZZps,21
|
12
12
|
beswarm/aient/aient/architext/architext/__init__.py,sha256=79Ih1151rfcqZdr7F8HSZSTs_iT2SKd1xCkehMsXeXs,19
|
13
|
-
beswarm/aient/aient/architext/architext/core.py,sha256=
|
13
|
+
beswarm/aient/aient/architext/architext/core.py,sha256=Sid3_Bf1a988xOWIfXOrojeCSn6ElJ4Hqxy6rmDnqFY,35591
|
14
14
|
beswarm/aient/aient/architext/test/openai_client.py,sha256=Dqtbmubv6vwF8uBqcayG0kbsiO65of7sgU2-DRBi-UM,4590
|
15
|
-
beswarm/aient/aient/architext/test/test.py,sha256=
|
15
|
+
beswarm/aient/aient/architext/test/test.py,sha256=c3ev6as7k9eDpV-le1xvj--x_hUN2xXH-oIzyQ5hsjA,83798
|
16
16
|
beswarm/aient/aient/architext/test/test_save_load.py,sha256=o8DqH6gDYZkFkQy-a7blqLtJTRj5e4a-Lil48pJ0V3g,3260
|
17
17
|
beswarm/aient/aient/core/__init__.py,sha256=NxjebTlku35S4Dzr16rdSqSTWUvvwEeACe8KvHJnjPg,34
|
18
18
|
beswarm/aient/aient/core/log_config.py,sha256=kz2_yJv1p-o3lUQOwA3qh-LSc3wMHv13iCQclw44W9c,274
|
@@ -27,7 +27,7 @@ beswarm/aient/aient/core/test/test_payload.py,sha256=8jBiJY1uidm1jzL-EiK0s6UGmW9
|
|
27
27
|
beswarm/aient/aient/models/__init__.py,sha256=ZTiZgbfBPTjIPSKURE7t6hlFBVLRS9lluGbmqc1WjxQ,43
|
28
28
|
beswarm/aient/aient/models/audio.py,sha256=FNW4lxG1IhxOU7L8mvcbaeC1nXk_lpUZQlg9ijQ0h_Q,1937
|
29
29
|
beswarm/aient/aient/models/base.py,sha256=HWIGfa2A7OTccvHK0wG1-UlHB-yaWRC7hbi4oR1Mu1Y,7228
|
30
|
-
beswarm/aient/aient/models/chatgpt.py,sha256=
|
30
|
+
beswarm/aient/aient/models/chatgpt.py,sha256=o9C85UV8uMLIHjs2_t4_4ViG2Psu0u16Pn1rQtaLZbU,44208
|
31
31
|
beswarm/aient/aient/plugins/__init__.py,sha256=p3KO6Aa3Lupos4i2SjzLQw1hzQTigOAfEHngsldrsyk,986
|
32
32
|
beswarm/aient/aient/plugins/arXiv.py,sha256=yHjb6PS3GUWazpOYRMKMzghKJlxnZ5TX8z9F6UtUVow,1461
|
33
33
|
beswarm/aient/aient/plugins/config.py,sha256=TGgZ5SnNKZ8MmdznrZ-TEq7s2ulhAAwTSKH89bci3dA,7079
|
@@ -35,16 +35,14 @@ beswarm/aient/aient/plugins/excute_command.py,sha256=b-rxsyFN6_HnZJAhUi9Qsp8iJ6X
|
|
35
35
|
beswarm/aient/aient/plugins/get_time.py,sha256=Ih5XIW5SDAIhrZ9W4Qe5Hs1k4ieKPUc_LAd6ySNyqZk,654
|
36
36
|
beswarm/aient/aient/plugins/image.py,sha256=JR4iJ--uUk1abICwQjd9tVIk0-Vs8qMxn6z2lJwuQ4U,2075
|
37
37
|
beswarm/aient/aient/plugins/list_directory.py,sha256=V_uKkLx_fQDL5z__bSDC-PqAP-o32KmQW6Pdhx0Fx0s,1433
|
38
|
-
beswarm/aient/aient/plugins/read_file.py,sha256=qHAhdesOr1VMOCDkeHNvI8UV2ZI98HmJl6GhN4Aq9dU,9183
|
39
38
|
beswarm/aient/aient/plugins/read_image.py,sha256=4FbIiMNVFUQpNyiH5ApGSRvOD9ujcXGyuqlGTJMd7ac,4017
|
40
39
|
beswarm/aient/aient/plugins/readonly.py,sha256=qK5-kBM3NDH1b-otFxFHpAjV5BXEY_e7cTWBcpP7G5k,710
|
41
40
|
beswarm/aient/aient/plugins/registry.py,sha256=YknzhieU_8nQ3oKlUSSWDB4X7t2Jx0JnqT2Jd9Xsvfk,3574
|
42
41
|
beswarm/aient/aient/plugins/run_python.py,sha256=MohvdtZUTDLrHBDtJ9L2_Qu1pWAGrkbzsGmmn5tMN20,4614
|
43
42
|
beswarm/aient/aient/plugins/websearch.py,sha256=aPsBjUQ3zQ4gzNrbVq7BMh28ENj9h_fSAeJFF2h9TNk,15334
|
44
|
-
beswarm/aient/aient/plugins/write_file.py,sha256=Jt8fOEwqhYiSWpCbwfAr1xoi_BmFnx3076GMhuL06uI,3949
|
45
43
|
beswarm/aient/aient/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
46
44
|
beswarm/aient/aient/utils/prompt.py,sha256=ZvGAt_ImJ_CGbDnWgpsWskfSV5fCkpFKRpNQjYL7M7s,11100
|
47
|
-
beswarm/aient/aient/utils/scripts.py,sha256=
|
45
|
+
beswarm/aient/aient/utils/scripts.py,sha256=D_-BCLHV_PS9r6SLXsdEAyey4bVWte-jMMJJKSx0Pcg,42530
|
48
46
|
beswarm/aient/test/test_Web_crawler.py,sha256=l-DY0xwVPBfeEitUASkcIf19b4XwGrN-Ql_p7Dsbg_A,11410
|
49
47
|
beswarm/aient/test/test_ddg_search.py,sha256=HnM72mwi4Yp87BymMmQ0eRd0-OJtyWEIUvJvN9QBhdg,1498
|
50
48
|
beswarm/aient/test/test_google_search.py,sha256=rPaKqD_N3ogHYE5DrMfRmKumcVAHKC7LcYw5euR_zGM,1035
|
@@ -106,23 +104,25 @@ beswarm/queries/tree-sitter-languages/ruby-tags.scm,sha256=vIidsCeE2A0vdFN18yXKq
|
|
106
104
|
beswarm/queries/tree-sitter-languages/rust-tags.scm,sha256=9ljM1nzhfPs_ZTRw7cr2P9ToOyhGcKkCoN4_HPXSWi4,1451
|
107
105
|
beswarm/queries/tree-sitter-languages/scala-tags.scm,sha256=UxQjz80JIrrJ7Pm56uUnQyThfmQNvwk7aQzPNypB-Ao,1761
|
108
106
|
beswarm/queries/tree-sitter-languages/typescript-tags.scm,sha256=OMdCeedPiA24ky82DpgTMKXK_l2ySTuF2zrQ2fJAi9E,1253
|
109
|
-
beswarm/tools/__init__.py,sha256=
|
107
|
+
beswarm/tools/__init__.py,sha256=O2RaDY3rfEng_HRt0e1Tl6mYeq7VnwjHtKa7V-hnKng,2206
|
110
108
|
beswarm/tools/click.py,sha256=wu-Ov5U2ZZLcU0gennDVh_2w_Td7F4dbVJcmu_dfHV4,20872
|
111
109
|
beswarm/tools/completion.py,sha256=AIHtEHfSp6fs4Xa_nTcw9fLgXtYHtYGRDTSy7_x_Fak,544
|
112
|
-
beswarm/tools/deep_search.py,sha256=
|
110
|
+
beswarm/tools/deep_search.py,sha256=O5n9L1foEdfNiwVpjnbM6_1X-MT4kQ2lSWKRvYWhbEg,20552
|
113
111
|
beswarm/tools/edit_file.py,sha256=ZTJvbpsfRlp2t98kTn9XQ5qZBTdsWJVWv9t0lvK4RfU,9147
|
114
112
|
beswarm/tools/graph.py,sha256=Ni51LCB65bLDDLC3ZyEJj_BeHAzhimhFgH9Ekr56Ll8,5871
|
115
113
|
beswarm/tools/planner.py,sha256=vsHd7rE8RQHJrZ7BQ0ZXhbt4Fjh3DeyxU4piA5R-VPM,1253
|
114
|
+
beswarm/tools/read_file.py,sha256=_MX2y-EtBH3wxcCBUtkAaiU4GZX34z2q3VKXjSs9gLs,10060
|
116
115
|
beswarm/tools/repomap.py,sha256=w98aHmjNjtvcUVc5maWORqzKqDy2KVGLooOe__uJVCU,45235
|
117
116
|
beswarm/tools/request_input.py,sha256=3n2UW8m8Q7dxGhd7L7hzSJ1kk4ekMbtdtNZZT3dJf20,938
|
118
117
|
beswarm/tools/screenshot.py,sha256=hyL6F8_k9Y03Nb_X18cY-klCpWWdkqyC-iGXfKX-7jc,1007
|
119
118
|
beswarm/tools/search_arxiv.py,sha256=NLiJV1B7Um6EuZXLxnL950d837_he2LGG7qaGACSgwg,10750
|
120
119
|
beswarm/tools/search_web.py,sha256=0fTeczXiOX_LJQGaLEGbuJtIPzofeuquGWEt3yDMtVw,17498
|
121
|
-
beswarm/tools/subtasks.py,sha256=
|
120
|
+
beswarm/tools/subtasks.py,sha256=Fsf_542CkECcwFwxD-F38_IUsmk06xe1P7e6pBPTy4Y,12894
|
122
121
|
beswarm/tools/worker.py,sha256=mQ1qdrQ8MgL99byAbTvxfEByFFGN9mty3UHqHjARMQ8,2331
|
123
122
|
beswarm/tools/write_csv.py,sha256=u0Hq18Ksfheb52MVtyLNCnSDHibITpsYBPs2ub7USYA,1466
|
124
|
-
beswarm
|
125
|
-
beswarm-0.3.
|
126
|
-
beswarm-0.3.
|
127
|
-
beswarm-0.3.
|
128
|
-
beswarm-0.3.
|
123
|
+
beswarm/tools/write_file.py,sha256=L2coBqz-aRFxPBvJBrbWbUJhu7p3oKAAGb9R144bFtk,4926
|
124
|
+
beswarm-0.3.8.dist-info/METADATA,sha256=nC1uDFNlOVQ44fZUf9NP2UlvGTCkr9kLd3uNVKS0v3U,3877
|
125
|
+
beswarm-0.3.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
126
|
+
beswarm-0.3.8.dist-info/entry_points.txt,sha256=URK7Y4PDzBgxIecQnxsWTu4O-eaFa1CoAcNTWh5R7LM,45
|
127
|
+
beswarm-0.3.8.dist-info/top_level.txt,sha256=pJw4O87wvt5882smuSO6DfByJz7FJ8SxxT8h9fHCmpo,8
|
128
|
+
beswarm-0.3.8.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|