aient 1.2.40__tar.gz → 1.2.41__tar.gz

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.
Files changed (54) hide show
  1. {aient-1.2.40 → aient-1.2.41}/PKG-INFO +1 -1
  2. {aient-1.2.40 → aient-1.2.41}/aient/architext/architext/core.py +37 -1
  3. {aient-1.2.40 → aient-1.2.41}/aient/architext/test/test.py +74 -1
  4. {aient-1.2.40 → aient-1.2.41}/aient/models/chatgpt.py +8 -3
  5. {aient-1.2.40 → aient-1.2.41}/aient.egg-info/PKG-INFO +1 -1
  6. {aient-1.2.40 → aient-1.2.41}/aient.egg-info/SOURCES.txt +0 -2
  7. {aient-1.2.40 → aient-1.2.41}/pyproject.toml +1 -1
  8. aient-1.2.40/aient/plugins/read_file.py +0 -198
  9. aient-1.2.40/aient/plugins/write_file.py +0 -90
  10. {aient-1.2.40 → aient-1.2.41}/LICENSE +0 -0
  11. {aient-1.2.40 → aient-1.2.41}/README.md +0 -0
  12. {aient-1.2.40 → aient-1.2.41}/aient/__init__.py +0 -0
  13. {aient-1.2.40 → aient-1.2.41}/aient/architext/architext/__init__.py +0 -0
  14. {aient-1.2.40 → aient-1.2.41}/aient/architext/test/openai_client.py +0 -0
  15. {aient-1.2.40 → aient-1.2.41}/aient/architext/test/test_save_load.py +0 -0
  16. {aient-1.2.40 → aient-1.2.41}/aient/core/__init__.py +0 -0
  17. {aient-1.2.40 → aient-1.2.41}/aient/core/log_config.py +0 -0
  18. {aient-1.2.40 → aient-1.2.41}/aient/core/models.py +0 -0
  19. {aient-1.2.40 → aient-1.2.41}/aient/core/request.py +0 -0
  20. {aient-1.2.40 → aient-1.2.41}/aient/core/response.py +0 -0
  21. {aient-1.2.40 → aient-1.2.41}/aient/core/test/test_base_api.py +0 -0
  22. {aient-1.2.40 → aient-1.2.41}/aient/core/test/test_geminimask.py +0 -0
  23. {aient-1.2.40 → aient-1.2.41}/aient/core/test/test_image.py +0 -0
  24. {aient-1.2.40 → aient-1.2.41}/aient/core/test/test_payload.py +0 -0
  25. {aient-1.2.40 → aient-1.2.41}/aient/core/utils.py +0 -0
  26. {aient-1.2.40 → aient-1.2.41}/aient/models/__init__.py +0 -0
  27. {aient-1.2.40 → aient-1.2.41}/aient/models/audio.py +0 -0
  28. {aient-1.2.40 → aient-1.2.41}/aient/models/base.py +0 -0
  29. {aient-1.2.40 → aient-1.2.41}/aient/plugins/__init__.py +0 -0
  30. {aient-1.2.40 → aient-1.2.41}/aient/plugins/arXiv.py +0 -0
  31. {aient-1.2.40 → aient-1.2.41}/aient/plugins/config.py +0 -0
  32. {aient-1.2.40 → aient-1.2.41}/aient/plugins/excute_command.py +0 -0
  33. {aient-1.2.40 → aient-1.2.41}/aient/plugins/get_time.py +0 -0
  34. {aient-1.2.40 → aient-1.2.41}/aient/plugins/image.py +0 -0
  35. {aient-1.2.40 → aient-1.2.41}/aient/plugins/list_directory.py +0 -0
  36. {aient-1.2.40 → aient-1.2.41}/aient/plugins/read_image.py +0 -0
  37. {aient-1.2.40 → aient-1.2.41}/aient/plugins/readonly.py +0 -0
  38. {aient-1.2.40 → aient-1.2.41}/aient/plugins/registry.py +0 -0
  39. {aient-1.2.40 → aient-1.2.41}/aient/plugins/run_python.py +0 -0
  40. {aient-1.2.40 → aient-1.2.41}/aient/plugins/websearch.py +0 -0
  41. {aient-1.2.40 → aient-1.2.41}/aient/utils/__init__.py +0 -0
  42. {aient-1.2.40 → aient-1.2.41}/aient/utils/prompt.py +0 -0
  43. {aient-1.2.40 → aient-1.2.41}/aient/utils/scripts.py +0 -0
  44. {aient-1.2.40 → aient-1.2.41}/aient.egg-info/dependency_links.txt +0 -0
  45. {aient-1.2.40 → aient-1.2.41}/aient.egg-info/requires.txt +0 -0
  46. {aient-1.2.40 → aient-1.2.41}/aient.egg-info/top_level.txt +0 -0
  47. {aient-1.2.40 → aient-1.2.41}/setup.cfg +0 -0
  48. {aient-1.2.40 → aient-1.2.41}/test/test_Web_crawler.py +0 -0
  49. {aient-1.2.40 → aient-1.2.41}/test/test_ddg_search.py +0 -0
  50. {aient-1.2.40 → aient-1.2.41}/test/test_google_search.py +0 -0
  51. {aient-1.2.40 → aient-1.2.41}/test/test_ollama.py +0 -0
  52. {aient-1.2.40 → aient-1.2.41}/test/test_plugin.py +0 -0
  53. {aient-1.2.40 → aient-1.2.41}/test/test_url.py +0 -0
  54. {aient-1.2.40 → aient-1.2.41}/test/test_whisper.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aient
3
- Version: 1.2.40
3
+ Version: 1.2.41
4
4
  Summary: Aient: The Awakening of Agent.
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -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
- return [res for res in results if res]
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. 演示
@@ -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 self.check_done and done_message:
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 += tool_response
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aient
3
- Version: 1.2.40
3
+ Version: 1.2.41
4
4
  Summary: Aient: The Awakening of Agent.
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -33,13 +33,11 @@ aient/plugins/excute_command.py
33
33
  aient/plugins/get_time.py
34
34
  aient/plugins/image.py
35
35
  aient/plugins/list_directory.py
36
- aient/plugins/read_file.py
37
36
  aient/plugins/read_image.py
38
37
  aient/plugins/readonly.py
39
38
  aient/plugins/registry.py
40
39
  aient/plugins/run_python.py
41
40
  aient/plugins/websearch.py
42
- aient/plugins/write_file.py
43
41
  aient/utils/__init__.py
44
42
  aient/utils/prompt.py
45
43
  aient/utils/scripts.py
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "aient"
3
- version = "1.2.40"
3
+ version = "1.2.41"
4
4
  description = "Aient: The Awakening of Agent."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -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))
@@ -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
- &lt;!DOCTYPE html&gt;
72
- &lt;html lang=&quot;zh-CN&quot;&gt;
73
- &lt;head&gt;
74
- &lt;meta charset=&quot;UTF-8&quot;&gt;
75
- &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
76
- &lt;title&gt;Continuous Thought Machines (CTM) 原理解读&lt;/title&gt;
77
- &lt;script&gt;MathJax={chtml:{fontURL:'https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2'}}&lt;/script&gt;
78
- &lt;script src=&quot;https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js&quot; id=&quot;MathJax-script&quot; async&gt;&lt;/script&gt;
79
- &lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/viz.js/2.1.2/viz.js&quot; defer&gt;&lt;/script&gt;
80
- &lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/viz.js/2.1.2/full.render.js&quot; defer&gt;&lt;/script&gt;
81
- &lt;script src=&quot;https://unpkg.com/@panzoom/panzoom@4.5.1/dist/panzoom.min.js&quot; defer&gt;&lt;/script&gt;
82
- &lt;link href=&quot;https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-okaidia.min.css&quot; rel=&quot;stylesheet&quot;/&gt;
83
- &lt;link href=&quot;https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&amp;family=Fira+Code:wght@400;500&amp;display=swap&quot; rel=&quot;stylesheet&quot;&gt;
84
- &lt;link href=&quot;https://fonts.googleapis.com/icon?family=Material+Icons+Outlined&quot; rel=&quot;stylesheet&quot;&gt;
85
- &lt;style&gt;
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes