aient 1.2.44__tar.gz → 1.2.46__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 (52) hide show
  1. {aient-1.2.44 → aient-1.2.46}/PKG-INFO +1 -1
  2. {aient-1.2.44 → aient-1.2.46}/aient/architext/architext/core.py +83 -40
  3. {aient-1.2.44 → aient-1.2.46}/aient/architext/test/test.py +101 -0
  4. {aient-1.2.44 → aient-1.2.46}/aient/core/request.py +3 -0
  5. {aient-1.2.44 → aient-1.2.46}/aient/core/utils.py +1 -1
  6. {aient-1.2.44 → aient-1.2.46}/aient/models/chatgpt.py +1 -1
  7. {aient-1.2.44 → aient-1.2.46}/aient.egg-info/PKG-INFO +1 -1
  8. {aient-1.2.44 → aient-1.2.46}/pyproject.toml +1 -1
  9. {aient-1.2.44 → aient-1.2.46}/LICENSE +0 -0
  10. {aient-1.2.44 → aient-1.2.46}/README.md +0 -0
  11. {aient-1.2.44 → aient-1.2.46}/aient/__init__.py +0 -0
  12. {aient-1.2.44 → aient-1.2.46}/aient/architext/architext/__init__.py +0 -0
  13. {aient-1.2.44 → aient-1.2.46}/aient/architext/test/openai_client.py +0 -0
  14. {aient-1.2.44 → aient-1.2.46}/aient/architext/test/test_save_load.py +0 -0
  15. {aient-1.2.44 → aient-1.2.46}/aient/core/__init__.py +0 -0
  16. {aient-1.2.44 → aient-1.2.46}/aient/core/log_config.py +0 -0
  17. {aient-1.2.44 → aient-1.2.46}/aient/core/models.py +0 -0
  18. {aient-1.2.44 → aient-1.2.46}/aient/core/response.py +0 -0
  19. {aient-1.2.44 → aient-1.2.46}/aient/core/test/test_base_api.py +0 -0
  20. {aient-1.2.44 → aient-1.2.46}/aient/core/test/test_geminimask.py +0 -0
  21. {aient-1.2.44 → aient-1.2.46}/aient/core/test/test_image.py +0 -0
  22. {aient-1.2.44 → aient-1.2.46}/aient/core/test/test_payload.py +0 -0
  23. {aient-1.2.44 → aient-1.2.46}/aient/models/__init__.py +0 -0
  24. {aient-1.2.44 → aient-1.2.46}/aient/models/audio.py +0 -0
  25. {aient-1.2.44 → aient-1.2.46}/aient/models/base.py +0 -0
  26. {aient-1.2.44 → aient-1.2.46}/aient/plugins/__init__.py +0 -0
  27. {aient-1.2.44 → aient-1.2.46}/aient/plugins/arXiv.py +0 -0
  28. {aient-1.2.44 → aient-1.2.46}/aient/plugins/config.py +0 -0
  29. {aient-1.2.44 → aient-1.2.46}/aient/plugins/excute_command.py +0 -0
  30. {aient-1.2.44 → aient-1.2.46}/aient/plugins/get_time.py +0 -0
  31. {aient-1.2.44 → aient-1.2.46}/aient/plugins/image.py +0 -0
  32. {aient-1.2.44 → aient-1.2.46}/aient/plugins/list_directory.py +0 -0
  33. {aient-1.2.44 → aient-1.2.46}/aient/plugins/read_image.py +0 -0
  34. {aient-1.2.44 → aient-1.2.46}/aient/plugins/readonly.py +0 -0
  35. {aient-1.2.44 → aient-1.2.46}/aient/plugins/registry.py +0 -0
  36. {aient-1.2.44 → aient-1.2.46}/aient/plugins/run_python.py +0 -0
  37. {aient-1.2.44 → aient-1.2.46}/aient/plugins/websearch.py +0 -0
  38. {aient-1.2.44 → aient-1.2.46}/aient/utils/__init__.py +0 -0
  39. {aient-1.2.44 → aient-1.2.46}/aient/utils/prompt.py +0 -0
  40. {aient-1.2.44 → aient-1.2.46}/aient/utils/scripts.py +0 -0
  41. {aient-1.2.44 → aient-1.2.46}/aient.egg-info/SOURCES.txt +0 -0
  42. {aient-1.2.44 → aient-1.2.46}/aient.egg-info/dependency_links.txt +0 -0
  43. {aient-1.2.44 → aient-1.2.46}/aient.egg-info/requires.txt +0 -0
  44. {aient-1.2.44 → aient-1.2.46}/aient.egg-info/top_level.txt +0 -0
  45. {aient-1.2.44 → aient-1.2.46}/setup.cfg +0 -0
  46. {aient-1.2.44 → aient-1.2.46}/test/test_Web_crawler.py +0 -0
  47. {aient-1.2.44 → aient-1.2.46}/test/test_ddg_search.py +0 -0
  48. {aient-1.2.44 → aient-1.2.46}/test/test_google_search.py +0 -0
  49. {aient-1.2.44 → aient-1.2.46}/test/test_ollama.py +0 -0
  50. {aient-1.2.44 → aient-1.2.46}/test/test_plugin.py +0 -0
  51. {aient-1.2.44 → aient-1.2.46}/test/test_url.py +0 -0
  52. {aient-1.2.44 → aient-1.2.46}/test/test_whisper.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aient
3
- Version: 1.2.44
3
+ Version: 1.2.46
4
4
  Summary: Aient: The Awakening of Agent.
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -233,73 +233,116 @@ class Files(ContextProvider):
233
233
  def __init__(self, *paths: Union[str, List[str]], name: str = "files", visible: bool = True):
234
234
  super().__init__(name, visible=visible)
235
235
  self._files: Dict[str, str] = {}
236
+ self._file_sources: Dict[str, Dict] = {}
236
237
 
237
238
  file_paths: List[str] = []
238
239
  if paths:
239
- # Handle the case where the first argument is a list of paths, e.g., Files(['a', 'b'])
240
240
  if len(paths) == 1 and isinstance(paths[0], list):
241
241
  file_paths.extend(paths[0])
242
- # Handle the case where arguments are individual string paths, e.g., Files('a', 'b')
243
242
  else:
244
243
  file_paths.extend(paths)
245
244
 
246
245
  if file_paths:
247
246
  for path in file_paths:
248
- try:
249
- with open(path, 'r', encoding='utf-8') as f:
250
- self._files[path] = f.read()
251
- except FileNotFoundError:
252
- logging.warning(f"File not found during initialization: {path}. Skipping.")
253
- except Exception as e:
254
- logging.error(f"Error reading file {path} during initialization: {e}")
247
+ self.update(path)
248
+
249
+ def _read_from_disk(self, path: str, head: Optional[int] = None) -> str:
250
+ """Reads content from a file on disk, respecting the head parameter."""
251
+ try:
252
+ with open(path, 'r', encoding='utf-8') as f:
253
+ if head is not None and head > 0:
254
+ lines = []
255
+ for _ in range(head):
256
+ try:
257
+ lines.append(next(f))
258
+ except StopIteration:
259
+ break
260
+ return "".join(lines).rstrip('\n')
261
+ else:
262
+ return f.read()
263
+ except FileNotFoundError:
264
+ raise
265
+ except Exception as e:
266
+ logging.error(f"Error reading file {path}: {e}")
267
+ return f"[Error: Could not read file at path '{path}': {e}]"
255
268
 
256
269
  async def refresh(self):
257
270
  """
258
- Overrides the default refresh behavior. It synchronizes the content of
259
- all tracked files with the file system. If a file is not found, its
260
- content is updated to reflect the error.
271
+ Synchronizes content for files sourced from disk.
272
+ Content set manually is overwritten if the file exists, but preserved if it does not.
261
273
  """
262
274
  is_changed = False
263
- for path in list(self._files.keys()):
264
- try:
265
- with open(path, 'r', encoding='utf-8') as f:
266
- new_content = f.read()
267
- if self._files.get(path) != new_content:
268
- self._files[path] = new_content
269
- is_changed = True
270
- except FileNotFoundError:
271
- error_msg = f"[Error: File not found at path '{path}']"
272
- if self._files.get(path) != error_msg:
273
- self._files[path] = error_msg
274
- is_changed = True
275
- except Exception as e:
276
- error_msg = f"[Error: Could not read file at path '{path}': {e}]"
277
- if self._files.get(path) != error_msg:
278
- self._files[path] = error_msg
279
- is_changed = True
275
+ for path, spec in list(self._file_sources.items()):
276
+ if spec.get('source') == 'disk':
277
+ try:
278
+ head = spec.get('head')
279
+ new_content = self._read_from_disk(path, head)
280
+ if self._files.get(path) != new_content:
281
+ self._files[path] = new_content
282
+ is_changed = True
283
+ except FileNotFoundError:
284
+ error_msg = f"[Error: File not found at path '{path}']"
285
+ if self._files.get(path) != error_msg:
286
+ self._files[path] = error_msg
287
+ is_changed = True
288
+ elif spec.get('source') == 'manual':
289
+ try:
290
+ # File exists, so we must overwrite manual content.
291
+ head = spec.get('head')
292
+ new_content = self._read_from_disk(path, head)
293
+ if self._files.get(path) != new_content:
294
+ self._files[path] = new_content
295
+ is_changed = True
296
+ # If we are here, manual content was overwritten by disk content,
297
+ # so we should update the source.
298
+ self._file_sources[path] = {'source': 'disk'}
299
+
300
+ except FileNotFoundError:
301
+ # File does not exist, so we keep the manual content. No change.
302
+ pass
280
303
 
281
304
  if is_changed:
282
305
  self.mark_stale()
283
-
284
306
  await super().refresh()
285
307
 
286
- def update(self, path: str, content: Optional[str] = None):
308
+ def update(self, path: str, content: Optional[str] = None, head: Optional[Union[int, str]] = None):
287
309
  """
288
- Updates a single file. If content is provided, it updates the file in
289
- memory. If content is None, it reads the file from disk.
310
+ Updates a single file's content and its source specification.
290
311
  """
312
+ if head is not None:
313
+ try:
314
+ head = int(head)
315
+ except (ValueError, TypeError):
316
+ logging.warning(f"Invalid 'head' parameter for file '{path}': {head}. Must be an integer. Ignoring.")
317
+ head = None
318
+
291
319
  if content is not None:
292
- self._files[path] = content
320
+ # New logic: if head is also provided, decide what to do with content.
321
+ if head is not None and head > 0:
322
+ try:
323
+ # If file exists, prioritize reading from disk.
324
+ self._files[path] = self._read_from_disk(path, head)
325
+ self._file_sources[path] = {'source': 'disk', 'head': head}
326
+ except FileNotFoundError:
327
+ # If file does not exist, use the provided content's head.
328
+ lines = content.split('\n')
329
+ self._files[path] = "\n".join(lines[:head])
330
+ self._file_sources[path] = {'source': 'manual', 'head': head}
331
+ else:
332
+ # Original logic for when only content is provided.
333
+ self._files[path] = content
334
+ self._file_sources[path] = {'source': 'manual'}
293
335
  else:
336
+ # Original logic for when only path (and optional head) is provided.
294
337
  try:
295
- with open(path, 'r', encoding='utf-8') as f:
296
- self._files[path] = f.read()
338
+ self._files[path] = self._read_from_disk(path, head)
339
+ spec = {'source': 'disk'}
340
+ if head is not None and head > 0:
341
+ spec['head'] = head
342
+ self._file_sources[path] = spec
297
343
  except FileNotFoundError:
298
- logging.error(f"File not found for update: {path}.")
299
344
  self._files[path] = f"[Error: File not found at path '{path}']"
300
- except Exception as e:
301
- logging.error(f"Error reading file for update {path}: {e}.")
302
- self._files[path] = f"[Error: Could not read file at path '{path}': {e}]"
345
+ self._file_sources[path] = {'source': 'disk'}
303
346
  self.mark_stale()
304
347
  async def render(self) -> str:
305
348
  if not self._files: return None
@@ -1783,6 +1783,107 @@ Files: {Files(visible=True, name="files")}
1783
1783
  self.assertEqual(len(rendered_separated), 1, "被空消息隔开的同角色消息在渲染时应该合并")
1784
1784
  self.assertEqual(rendered_separated[0]['content'], "hihi2", "被空消息隔开的同角色消息合并后内容不正确")
1785
1785
 
1786
+ async def test_zzc_files_update_with_head(self):
1787
+ """测试 Files.update 是否支持 head 参数以及新的 refresh 逻辑"""
1788
+ test_file = "test_file_head.txt"
1789
+ file_content = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5"
1790
+ with open(test_file, "w", encoding='utf-8') as f:
1791
+ f.write(file_content)
1792
+
1793
+ try:
1794
+ messages = Messages(UserMessage(Files(name="files")))
1795
+
1796
+ # 1. Test update with head=3 using the provider from Messages
1797
+ files_provider = messages.provider("files")
1798
+ self.assertIsInstance(files_provider, Files)
1799
+ files_provider.update(path=test_file, head=3)
1800
+
1801
+ rendered_head = await messages.render_latest()
1802
+ expected_head_content = "Line 1\nLine 2\nLine 3"
1803
+ self.assertIn(f"<file_content>{expected_head_content}</file_content>", rendered_head[0]['content'])
1804
+ self.assertNotIn("Line 4", rendered_head[0]['content'])
1805
+
1806
+ # 2. Test update without head (default behavior)
1807
+ files_provider.update(path=test_file)
1808
+ rendered_full = await messages.render_latest()
1809
+ self.assertIn(f"<file_content>{file_content}</file_content>", rendered_full[0]['content'])
1810
+
1811
+ # 3. Test that refresh overwrites manual content when file exists
1812
+ files_provider.update(path=test_file, content="manual content")
1813
+ rendered_overwritten = await messages.render_latest()
1814
+ self.assertIn(f"<file_content>{file_content}</file_content>", rendered_overwritten[0]['content'])
1815
+ self.assertNotIn("manual content", rendered_overwritten[0]['content'])
1816
+
1817
+ # 4. Test that manual content is kept if file does not exist
1818
+ non_existent_file = "non_existent_for_sure.txt"
1819
+ files_provider.update(path=non_existent_file, content="manual content to keep")
1820
+ rendered_kept = await messages.render_latest()
1821
+ self.assertIn("<file_content>manual content to keep</file_content>", rendered_kept[0]['content'])
1822
+
1823
+ finally:
1824
+ if os.path.exists(test_file):
1825
+ os.remove(test_file)
1826
+
1827
+ async def test_zzd_files_update_with_string_head(self):
1828
+ """测试 Files.update 是否能自动将字符串 head 参数转换为整数"""
1829
+ test_file = "test_file_string_head.txt"
1830
+ file_content = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5"
1831
+ with open(test_file, "w", encoding='utf-8') as f:
1832
+ f.write(file_content)
1833
+
1834
+ try:
1835
+ messages = Messages(UserMessage(Files(name="files")))
1836
+
1837
+ # Test update with head="3"
1838
+ files_provider = messages.provider("files")
1839
+ self.assertIsInstance(files_provider, Files)
1840
+ # This is the part that will likely fail before implementation
1841
+ files_provider.update(path=test_file, head="3")
1842
+
1843
+ rendered_head = await messages.render_latest()
1844
+ expected_head_content = "Line 1\nLine 2\nLine 3"
1845
+ self.assertIn(f"<file_content>{expected_head_content}</file_content>", rendered_head[0]['content'])
1846
+ self.assertNotIn("Line 4", rendered_head[0]['content'])
1847
+
1848
+ finally:
1849
+ if os.path.exists(test_file):
1850
+ os.remove(test_file)
1851
+
1852
+ async def test_zzc_files_update_with_head_and_content(self):
1853
+ """测试update同时设置path, content, head时的行为"""
1854
+ existing_file = "test_file_existing.txt"
1855
+ non_existent_file = "test_file_non_existent.txt"
1856
+ existing_content = "Line 1 (from file)\nLine 2 (from file)\nLine 3 (from file)\nLine 4 (from file)"
1857
+ manual_content = "Line 1 (manual)\nLine 2 (manual)\nLine 3 (manual)\nLine 4 (manual)"
1858
+
1859
+ # --- 场景1: 文件存在 ---
1860
+ with open(existing_file, "w", encoding='utf-8') as f:
1861
+ f.write(existing_content)
1862
+ try:
1863
+ messages = Messages(UserMessage(Files(name="files")))
1864
+ files_provider = messages.provider("files")
1865
+ files_provider.update(path=existing_file, content=manual_content, head=2)
1866
+ rendered_existing = await messages.render_latest()
1867
+ expected_existing_head = "Line 1 (from file)\nLine 2 (from file)"
1868
+ self.assertIn(f"<file_content>{expected_existing_head}</file_content>", rendered_existing[0]['content'])
1869
+ self.assertNotIn("manual", rendered_existing[0]['content'])
1870
+ finally:
1871
+ if os.path.exists(existing_file):
1872
+ os.remove(existing_file)
1873
+
1874
+ # --- 场景2: 文件不存在 ---
1875
+ try:
1876
+ messages = Messages(UserMessage(Files(name="files")))
1877
+ files_provider = messages.provider("files")
1878
+ files_provider.update(path=non_existent_file, content=manual_content, head=2)
1879
+ rendered_non_existent = await messages.render_latest()
1880
+ expected_manual_head = "Line 1 (manual)\nLine 2 (manual)"
1881
+ self.assertIn(f"<file_content>{expected_manual_head}</file_content>", rendered_non_existent[0]['content'])
1882
+ self.assertNotIn("from file", rendered_non_existent[0]['content'])
1883
+ finally:
1884
+ if os.path.exists(non_existent_file):
1885
+ os.remove(non_existent_file)
1886
+
1786
1887
  # ==============================================================================
1787
1888
  # 6. 演示
1788
1889
  # ==============================================================================
@@ -1116,6 +1116,9 @@ async def get_gpt_payload(request, engine, provider, api_key=None):
1116
1116
  if "temperature" in payload:
1117
1117
  payload.pop("temperature")
1118
1118
 
1119
+ if "v1/responses" in url:
1120
+ payload.pop("stream_options", None)
1121
+
1119
1122
  # 代码生成/数学解题  0.0
1120
1123
  # 数据抽取/分析 1.0
1121
1124
  # 通用对话 1.3
@@ -52,7 +52,7 @@ class BaseAPI:
52
52
  self.v1_models: str = urlunparse(parsed_url[:2] + (before_v1 + "models",) + ("",) * 3)
53
53
 
54
54
  if "v1/responses" in parsed_url.path:
55
- self.chat_url: str = urlunparse(parsed_url[:2] + ("v1/responses",) + ("",) * 3)
55
+ self.chat_url: str = api_url
56
56
  else:
57
57
  self.chat_url: str = urlunparse(parsed_url[:2] + (before_v1 + "chat/completions",) + ("",) * 3)
58
58
  self.image_url: str = urlunparse(parsed_url[:2] + (before_v1 + "images/generations",) + ("",) * 3)
@@ -615,7 +615,7 @@ class chatgpt(BaseLLM):
615
615
  final_tool_response = tool_response
616
616
  if "<tool_error>" not in tool_response:
617
617
  if tool_name == "read_file":
618
- self.conversation[convo_id].provider("files").update(tool_info['parameter']["file_path"], tool_response)
618
+ self.conversation[convo_id].provider("files").update(tool_info['parameter']["file_path"], tool_response, head=safe_get(tool_info, 'parameter', "head", default=None))
619
619
  final_tool_response = "Read file successfully! The file content has been updated in the tag <latest_file_content>."
620
620
  elif tool_name == "get_knowledge_graph_tree":
621
621
  self.conversation[convo_id].provider("knowledge_graph").visible = True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aient
3
- Version: 1.2.44
3
+ Version: 1.2.46
4
4
  Summary: Aient: The Awakening of Agent.
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "aient"
3
- version = "1.2.44"
3
+ version = "1.2.46"
4
4
  description = "Aient: The Awakening of Agent."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
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