aient 1.2.40__py3-none-any.whl → 1.2.42__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.
- aient/architext/architext/core.py +37 -1
- aient/architext/test/test.py +74 -1
- aient/models/chatgpt.py +8 -3
- {aient-1.2.40.dist-info → aient-1.2.42.dist-info}/METADATA +1 -1
- {aient-1.2.40.dist-info → aient-1.2.42.dist-info}/RECORD +8 -10
- aient/plugins/read_file.py +0 -198
- aient/plugins/write_file.py +0 -90
- {aient-1.2.40.dist-info → aient-1.2.42.dist-info}/WHEEL +0 -0
- {aient-1.2.40.dist-info → aient-1.2.42.dist-info}/licenses/LICENSE +0 -0
- {aient-1.2.40.dist-info → aient-1.2.42.dist-info}/top_level.txt +0 -0
@@ -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()
|
aient/architext/test/test.py
CHANGED
@@ -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. 演示
|
aient/models/chatgpt.py
CHANGED
@@ -180,7 +180,7 @@ class chatgpt(BaseLLM):
|
|
180
180
|
"""
|
181
181
|
Add a message to the conversation
|
182
182
|
"""
|
183
|
-
# 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}")
|
184
184
|
if convo_id not in self.conversation:
|
185
185
|
self.reset(convo_id=convo_id)
|
186
186
|
if function_name == "" and message:
|
@@ -284,9 +284,9 @@ class chatgpt(BaseLLM):
|
|
284
284
|
}
|
285
285
|
|
286
286
|
done_message = self.conversation[convo_id].provider("done")
|
287
|
-
if
|
287
|
+
if done_message:
|
288
288
|
done_message.visible = False
|
289
|
-
if self.conversation[convo_id][-1][-1].name == "done":
|
289
|
+
if self.check_done and self.conversation[convo_id][-1][-1].name == "done":
|
290
290
|
self.conversation[convo_id][-1][-1].visible = True
|
291
291
|
|
292
292
|
# 构造请求数据
|
@@ -627,6 +627,11 @@ class chatgpt(BaseLLM):
|
|
627
627
|
elif tool_name == "get_knowledge_graph_tree":
|
628
628
|
self.conversation[convo_id].provider("knowledge_graph").visible = True
|
629
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 += f"\n\n<{tool_name}>{tool_response}</{tool_name}>"
|
634
|
+
final_tool_response = "Get goal successfully! The goal has been updated in the tag <goal>."
|
630
635
|
elif tool_name == "write_to_file":
|
631
636
|
tool_args = None
|
632
637
|
elif tool_name == "read_image":
|
@@ -1,8 +1,8 @@
|
|
1
1
|
aient/__init__.py,sha256=SRfF7oDVlOOAi6nGKiJIUK6B_arqYLO9iSMp-2IZZps,21
|
2
2
|
aient/architext/architext/__init__.py,sha256=79Ih1151rfcqZdr7F8HSZSTs_iT2SKd1xCkehMsXeXs,19
|
3
|
-
aient/architext/architext/core.py,sha256=
|
3
|
+
aient/architext/architext/core.py,sha256=Sid3_Bf1a988xOWIfXOrojeCSn6ElJ4Hqxy6rmDnqFY,35591
|
4
4
|
aient/architext/test/openai_client.py,sha256=Dqtbmubv6vwF8uBqcayG0kbsiO65of7sgU2-DRBi-UM,4590
|
5
|
-
aient/architext/test/test.py,sha256=
|
5
|
+
aient/architext/test/test.py,sha256=c3ev6as7k9eDpV-le1xvj--x_hUN2xXH-oIzyQ5hsjA,83798
|
6
6
|
aient/architext/test/test_save_load.py,sha256=o8DqH6gDYZkFkQy-a7blqLtJTRj5e4a-Lil48pJ0V3g,3260
|
7
7
|
aient/core/__init__.py,sha256=NxjebTlku35S4Dzr16rdSqSTWUvvwEeACe8KvHJnjPg,34
|
8
8
|
aient/core/log_config.py,sha256=kz2_yJv1p-o3lUQOwA3qh-LSc3wMHv13iCQclw44W9c,274
|
@@ -17,7 +17,7 @@ aient/core/test/test_payload.py,sha256=8jBiJY1uidm1jzL-EiK0s6UGmW9XkdsuuKFGrwFhF
|
|
17
17
|
aient/models/__init__.py,sha256=ZTiZgbfBPTjIPSKURE7t6hlFBVLRS9lluGbmqc1WjxQ,43
|
18
18
|
aient/models/audio.py,sha256=FNW4lxG1IhxOU7L8mvcbaeC1nXk_lpUZQlg9ijQ0h_Q,1937
|
19
19
|
aient/models/base.py,sha256=HWIGfa2A7OTccvHK0wG1-UlHB-yaWRC7hbi4oR1Mu1Y,7228
|
20
|
-
aient/models/chatgpt.py,sha256=
|
20
|
+
aient/models/chatgpt.py,sha256=3AA_jXB_efB86VzbtWw7CyTN4HPaAF7_79XoA9EzHlw,44244
|
21
21
|
aient/plugins/__init__.py,sha256=p3KO6Aa3Lupos4i2SjzLQw1hzQTigOAfEHngsldrsyk,986
|
22
22
|
aient/plugins/arXiv.py,sha256=yHjb6PS3GUWazpOYRMKMzghKJlxnZ5TX8z9F6UtUVow,1461
|
23
23
|
aient/plugins/config.py,sha256=TGgZ5SnNKZ8MmdznrZ-TEq7s2ulhAAwTSKH89bci3dA,7079
|
@@ -25,18 +25,16 @@ aient/plugins/excute_command.py,sha256=b-rxsyFN6_HnZJAhUi9Qsp8iJ6XTf-zU-CIUIxeQO
|
|
25
25
|
aient/plugins/get_time.py,sha256=Ih5XIW5SDAIhrZ9W4Qe5Hs1k4ieKPUc_LAd6ySNyqZk,654
|
26
26
|
aient/plugins/image.py,sha256=JR4iJ--uUk1abICwQjd9tVIk0-Vs8qMxn6z2lJwuQ4U,2075
|
27
27
|
aient/plugins/list_directory.py,sha256=V_uKkLx_fQDL5z__bSDC-PqAP-o32KmQW6Pdhx0Fx0s,1433
|
28
|
-
aient/plugins/read_file.py,sha256=qHAhdesOr1VMOCDkeHNvI8UV2ZI98HmJl6GhN4Aq9dU,9183
|
29
28
|
aient/plugins/read_image.py,sha256=4FbIiMNVFUQpNyiH5ApGSRvOD9ujcXGyuqlGTJMd7ac,4017
|
30
29
|
aient/plugins/readonly.py,sha256=qK5-kBM3NDH1b-otFxFHpAjV5BXEY_e7cTWBcpP7G5k,710
|
31
30
|
aient/plugins/registry.py,sha256=YknzhieU_8nQ3oKlUSSWDB4X7t2Jx0JnqT2Jd9Xsvfk,3574
|
32
31
|
aient/plugins/run_python.py,sha256=MohvdtZUTDLrHBDtJ9L2_Qu1pWAGrkbzsGmmn5tMN20,4614
|
33
32
|
aient/plugins/websearch.py,sha256=aPsBjUQ3zQ4gzNrbVq7BMh28ENj9h_fSAeJFF2h9TNk,15334
|
34
|
-
aient/plugins/write_file.py,sha256=Jt8fOEwqhYiSWpCbwfAr1xoi_BmFnx3076GMhuL06uI,3949
|
35
33
|
aient/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
36
34
|
aient/utils/prompt.py,sha256=ZvGAt_ImJ_CGbDnWgpsWskfSV5fCkpFKRpNQjYL7M7s,11100
|
37
35
|
aient/utils/scripts.py,sha256=D_-BCLHV_PS9r6SLXsdEAyey4bVWte-jMMJJKSx0Pcg,42530
|
38
|
-
aient-1.2.
|
39
|
-
aient-1.2.
|
40
|
-
aient-1.2.
|
41
|
-
aient-1.2.
|
42
|
-
aient-1.2.
|
36
|
+
aient-1.2.42.dist-info/licenses/LICENSE,sha256=XNdbcWldt0yaNXXWB_Bakoqnxb3OVhUft4MgMA_71ds,1051
|
37
|
+
aient-1.2.42.dist-info/METADATA,sha256=-26zs7nKpZR08gqULPGguhPBfOut0xUWRKDiLyljpoU,4842
|
38
|
+
aient-1.2.42.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
39
|
+
aient-1.2.42.dist-info/top_level.txt,sha256=3oXzrP5sAVvyyqabpeq8A2_vfMtY554r4bVE-OHBrZk,6
|
40
|
+
aient-1.2.42.dist-info/RECORD,,
|
aient/plugins/read_file.py
DELETED
@@ -1,198 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
import json
|
3
|
-
import chardet
|
4
|
-
from pdfminer.high_level import extract_text
|
5
|
-
|
6
|
-
from .registry import register_tool
|
7
|
-
|
8
|
-
# 读取文件内容
|
9
|
-
@register_tool()
|
10
|
-
def read_file(file_path, head: int = None):
|
11
|
-
"""
|
12
|
-
Description: Request to read the contents of a file at the specified path. Use this when you need to examine the contents of an existing file you do not know the contents of, for example to analyze code, review text files, or extract information from configuration files. Automatically extracts raw text from PDF and DOCX files. May not be suitable for other types of binary files, as it returns the raw content as a string.
|
13
|
-
|
14
|
-
注意:
|
15
|
-
1. pdf 文件 必须使用 read_file 读取,可以使用 read_file 直接读取 PDF。
|
16
|
-
|
17
|
-
参数:
|
18
|
-
file_path: 要读取的文件路径,(required) The path of the file to read (relative to the current working directory)
|
19
|
-
head: (可选) 读取文件的前N行,默认为None,读取整个文件
|
20
|
-
|
21
|
-
返回:
|
22
|
-
文件内容的字符串
|
23
|
-
|
24
|
-
Usage:
|
25
|
-
<read_file>
|
26
|
-
<file_path>File path here</file_path>
|
27
|
-
</read_file>
|
28
|
-
|
29
|
-
Examples:
|
30
|
-
|
31
|
-
1. Reading an entire file:
|
32
|
-
<read_file>
|
33
|
-
<file_path>frontend.pdf</file_path>
|
34
|
-
</read_file>
|
35
|
-
|
36
|
-
2. Reading multiple files:
|
37
|
-
|
38
|
-
<read_file>
|
39
|
-
<file_path>frontend-config.json</file_path>
|
40
|
-
</read_file>
|
41
|
-
|
42
|
-
<read_file>
|
43
|
-
<file_path>backend-config.txt</file_path>
|
44
|
-
</read_file>
|
45
|
-
|
46
|
-
...
|
47
|
-
|
48
|
-
<read_file>
|
49
|
-
<file_path>README.md</file_path>
|
50
|
-
</read_file>
|
51
|
-
"""
|
52
|
-
try:
|
53
|
-
# 检查文件是否存在
|
54
|
-
if not os.path.exists(file_path):
|
55
|
-
return f"<tool_error>文件 '{file_path}' 不存在</tool_error>"
|
56
|
-
|
57
|
-
# 检查是否为文件
|
58
|
-
if not os.path.isfile(file_path):
|
59
|
-
return f"<tool_error>'{file_path}' 不是一个文件</tool_error>"
|
60
|
-
|
61
|
-
# 检查文件扩展名
|
62
|
-
if file_path.lower().endswith('.pdf'):
|
63
|
-
# 提取PDF文本
|
64
|
-
text_content = extract_text(file_path)
|
65
|
-
|
66
|
-
# 如果提取结果为空
|
67
|
-
if not text_content:
|
68
|
-
return f"<tool_error>无法从 '{file_path}' 提取文本内容</tool_error>"
|
69
|
-
elif file_path.lower().endswith('.ipynb'):
|
70
|
-
try:
|
71
|
-
with open(file_path, 'r', encoding='utf-8') as file:
|
72
|
-
notebook_content = json.load(file)
|
73
|
-
|
74
|
-
for cell in notebook_content.get('cells', []):
|
75
|
-
if cell.get('cell_type') == 'code' and 'outputs' in cell:
|
76
|
-
filtered_outputs = []
|
77
|
-
for output in cell.get('outputs', []):
|
78
|
-
new_output = output.copy()
|
79
|
-
if 'data' in new_output:
|
80
|
-
original_data = new_output['data']
|
81
|
-
filtered_data = {}
|
82
|
-
for key, value in original_data.items():
|
83
|
-
if key.startswith('image/'):
|
84
|
-
continue
|
85
|
-
if key == 'text/html':
|
86
|
-
html_content = "".join(value) if isinstance(value, list) else value
|
87
|
-
if isinstance(html_content, str) and '<table class="show_videos"' in html_content:
|
88
|
-
continue
|
89
|
-
filtered_data[key] = value
|
90
|
-
if filtered_data:
|
91
|
-
new_output['data'] = filtered_data
|
92
|
-
filtered_outputs.append(new_output)
|
93
|
-
elif 'output_type' in new_output and new_output['output_type'] in ['stream', 'error']:
|
94
|
-
filtered_outputs.append(new_output)
|
95
|
-
|
96
|
-
cell['outputs'] = filtered_outputs
|
97
|
-
|
98
|
-
text_content = json.dumps(notebook_content, indent=2, ensure_ascii=False)
|
99
|
-
except json.JSONDecodeError:
|
100
|
-
return f"<tool_error>文件 '{file_path}' 不是有效的JSON格式 (IPython Notebook)。</tool_error>"
|
101
|
-
except Exception as e:
|
102
|
-
return f"<tool_error>处理IPython Notebook文件 '{file_path}' 时发生错误: {e}</tool_error>"
|
103
|
-
else:
|
104
|
-
# 更新:修改通用文件读取逻辑以支持多种编码
|
105
|
-
# 这部分替换了原有的 else 块内容
|
106
|
-
try:
|
107
|
-
with open(file_path, 'rb') as file: # 以二进制模式读取
|
108
|
-
raw_data = file.read()
|
109
|
-
|
110
|
-
if not raw_data: # 处理空文件
|
111
|
-
text_content = ""
|
112
|
-
else:
|
113
|
-
detected_info = chardet.detect(raw_data)
|
114
|
-
primary_encoding_to_try = detected_info['encoding']
|
115
|
-
confidence = detected_info['confidence']
|
116
|
-
|
117
|
-
decoded_successfully = False
|
118
|
-
|
119
|
-
# 尝试1: 使用检测到的编码 (如果置信度高且编码有效)
|
120
|
-
if primary_encoding_to_try and confidence > 0.7: # 您可以根据需要调整置信度阈值
|
121
|
-
try:
|
122
|
-
text_content = raw_data.decode(primary_encoding_to_try)
|
123
|
-
decoded_successfully = True
|
124
|
-
except (UnicodeDecodeError, LookupError): # LookupError 用于处理无效的编码名称
|
125
|
-
# 解码失败,将尝试后备编码
|
126
|
-
pass
|
127
|
-
|
128
|
-
# 尝试2: UTF-8 (如果第一次尝试失败或未进行)
|
129
|
-
if not decoded_successfully:
|
130
|
-
try:
|
131
|
-
text_content = raw_data.decode('utf-8')
|
132
|
-
decoded_successfully = True
|
133
|
-
except UnicodeDecodeError:
|
134
|
-
# 解码失败,将尝试下一个后备编码
|
135
|
-
pass
|
136
|
-
|
137
|
-
# 尝试3: UTF-16 (如果之前的尝试都失败)
|
138
|
-
# 'utf-16' 会处理带BOM的LE/BE编码。若无BOM,则假定为本机字节序。
|
139
|
-
# chardet 通常能更准确地检测具体的 utf-16le 或 utf-16be。
|
140
|
-
if not decoded_successfully:
|
141
|
-
try:
|
142
|
-
text_content = raw_data.decode('utf-16')
|
143
|
-
decoded_successfully = True
|
144
|
-
except UnicodeDecodeError:
|
145
|
-
# 所有主要尝试都失败
|
146
|
-
pass
|
147
|
-
|
148
|
-
if not decoded_successfully:
|
149
|
-
# 所有尝试均失败后的错误信息
|
150
|
-
detected_str_part = ""
|
151
|
-
if primary_encoding_to_try and confidence > 0.7: # 如果有高置信度的检测结果
|
152
|
-
detected_str_part = f"检测到的编码 '{primary_encoding_to_try}' (置信度 {confidence:.2f}), "
|
153
|
-
elif primary_encoding_to_try: # 如果有检测结果但置信度低
|
154
|
-
detected_str_part = f"低置信度检测编码 '{primary_encoding_to_try}' (置信度 {confidence:.2f}), "
|
155
|
-
|
156
|
-
return f"<tool_error>文件 '{file_path}' 无法解码。已尝试: {detected_str_part}UTF-8, UTF-16。</tool_error>"
|
157
|
-
|
158
|
-
except FileNotFoundError:
|
159
|
-
# 此处不太可能触发 FileNotFoundError,因为函数开头已有 os.path.exists 检查
|
160
|
-
return f"<tool_error>文件 '{file_path}' 在读取过程中未找到。</tool_error>"
|
161
|
-
except Exception as e:
|
162
|
-
# 捕获在此块中可能发生的其他错误,例如未被早期检查捕获的文件读取问题
|
163
|
-
return f"<tool_error>处理通用文件 '{file_path}' 时发生错误: {e}</tool_error>"
|
164
|
-
|
165
|
-
if head is not None:
|
166
|
-
try:
|
167
|
-
num_lines = int(head)
|
168
|
-
if num_lines > 0:
|
169
|
-
lines = text_content.splitlines(True)
|
170
|
-
return "".join(lines[:num_lines])
|
171
|
-
except (ValueError, TypeError):
|
172
|
-
# Invalid head value, ignore and proceed with normal logic.
|
173
|
-
pass
|
174
|
-
|
175
|
-
# if file_path.lower().endswith('.csv'):
|
176
|
-
# lines = text_content.splitlines(True)
|
177
|
-
# if len(lines) > 500:
|
178
|
-
# top_lines = lines[:250]
|
179
|
-
# bottom_lines = lines[-250:]
|
180
|
-
# omitted_count = len(lines) - 500
|
181
|
-
# text_content = "".join(top_lines) + f"\n... (中间省略了 {omitted_count} 行) ...\n" + "".join(bottom_lines)
|
182
|
-
|
183
|
-
# 返回文件内容
|
184
|
-
return text_content
|
185
|
-
|
186
|
-
except PermissionError:
|
187
|
-
return f"<tool_error>没有权限访问文件 '{file_path}'</tool_error>"
|
188
|
-
except UnicodeDecodeError:
|
189
|
-
# 更新:修改全局 UnicodeDecodeError 错误信息使其更通用
|
190
|
-
return f"<tool_error>文件 '{file_path}' 包含无法解码的字符 (UnicodeDecodeError)。</tool_error>"
|
191
|
-
except Exception as e:
|
192
|
-
return f"<tool_error>读取文件时发生错误: {e}</tool_error>"
|
193
|
-
|
194
|
-
if __name__ == "__main__":
|
195
|
-
# python -m beswarm.aient.aient.plugins.read_file
|
196
|
-
result = read_file("./work/cax/Lenia Notebook.ipynb", head=10)
|
197
|
-
print(result)
|
198
|
-
print(len(result))
|
aient/plugins/write_file.py
DELETED
@@ -1,90 +0,0 @@
|
|
1
|
-
from .registry import register_tool
|
2
|
-
from ..utils.scripts import unescape_html
|
3
|
-
|
4
|
-
import os
|
5
|
-
|
6
|
-
@register_tool()
|
7
|
-
def write_to_file(path, content, mode='w', newline=False):
|
8
|
-
"""
|
9
|
-
## write_to_file
|
10
|
-
Description: Request to write full content to a file at the specified path. If the file exists, it will be overwritten with the provided content. If the file doesn't exist, it will be created. This tool will automatically create any directories needed to write the file.
|
11
|
-
Parameters:
|
12
|
-
- path: (required) The path of the file to write to (relative to the current working directory ${args.cwd})
|
13
|
-
- content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified. Do NOT include the line numbers in the content though, just the actual content of the file.
|
14
|
-
- mode: (optional) The mode to write to the file. Default is 'w'. 'w' for write, 'a' for append.
|
15
|
-
- newline: (optional) Whether to add a newline before the content. Default is False.
|
16
|
-
Usage:
|
17
|
-
<write_to_file>
|
18
|
-
<path>File path here</path>
|
19
|
-
<content>
|
20
|
-
Your file content here
|
21
|
-
</content>
|
22
|
-
<mode>w</mode>
|
23
|
-
<newline>False</newline>
|
24
|
-
</write_to_file>
|
25
|
-
|
26
|
-
Example: Requesting to write to frontend-config.json
|
27
|
-
<write_to_file>
|
28
|
-
<path>frontend-config.json</path>
|
29
|
-
<content>
|
30
|
-
{
|
31
|
-
"apiEndpoint": "https://api.example.com",
|
32
|
-
"theme": {
|
33
|
-
"primaryColor": "#007bff",
|
34
|
-
"secondaryColor": "#6c757d",
|
35
|
-
"fontFamily": "Arial, sans-serif"
|
36
|
-
},
|
37
|
-
"features": {
|
38
|
-
"darkMode": true,
|
39
|
-
"notifications": true,
|
40
|
-
"analytics": false
|
41
|
-
},
|
42
|
-
"version": "1.0.0"
|
43
|
-
}
|
44
|
-
</content>
|
45
|
-
</write_to_file>
|
46
|
-
"""
|
47
|
-
# 确保目录存在
|
48
|
-
os.makedirs(os.path.dirname(path) or '.', exist_ok=True)
|
49
|
-
|
50
|
-
if content.startswith("##") and (path.endswith(".md") or path.endswith(".txt")):
|
51
|
-
content = "\n\n" + content
|
52
|
-
|
53
|
-
if content.startswith("---\n") and (path.endswith(".md") or path.endswith(".txt")):
|
54
|
-
content = "\n" + content
|
55
|
-
|
56
|
-
if newline:
|
57
|
-
content = '\n' + content
|
58
|
-
|
59
|
-
# 写入文件
|
60
|
-
try:
|
61
|
-
with open(path, mode, encoding='utf-8') as file:
|
62
|
-
file.write(unescape_html(content))
|
63
|
-
except PermissionError as e:
|
64
|
-
return f"<tool_error>写入文件失败: {e}</tool_error>"
|
65
|
-
|
66
|
-
return f"已成功写入文件:{path}"
|
67
|
-
|
68
|
-
|
69
|
-
if __name__ == "__main__":
|
70
|
-
text = """
|
71
|
-
<!DOCTYPE html>
|
72
|
-
<html lang="zh-CN">
|
73
|
-
<head>
|
74
|
-
<meta charset="UTF-8">
|
75
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
76
|
-
<title>Continuous Thought Machines (CTM) 原理解读</title>
|
77
|
-
<script>MathJax={chtml:{fontURL:'https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2'}}</script>
|
78
|
-
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js" id="MathJax-script" async></script>
|
79
|
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/viz.js/2.1.2/viz.js" defer></script>
|
80
|
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/viz.js/2.1.2/full.render.js" defer></script>
|
81
|
-
<script src="https://unpkg.com/@panzoom/panzoom@4.5.1/dist/panzoom.min.js" defer></script>
|
82
|
-
<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-okaidia.min.css" rel="stylesheet"/>
|
83
|
-
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Fira+Code:wght@400;500&display=swap" rel="stylesheet">
|
84
|
-
<link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" rel="stylesheet">
|
85
|
-
<style>
|
86
|
-
"""
|
87
|
-
with open("test.txt", "r", encoding="utf-8") as file:
|
88
|
-
content = file.read()
|
89
|
-
print(write_to_file("test.txt", content))
|
90
|
-
# python -m beswarm.aient.aient.plugins.write_file
|
File without changes
|
File without changes
|
File without changes
|