aient 1.1.94__tar.gz → 1.1.96__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 (56) hide show
  1. {aient-1.1.94 → aient-1.1.96}/PKG-INFO +1 -1
  2. {aient-1.1.94 → aient-1.1.96}/aient/architext/architext/core.py +59 -7
  3. {aient-1.1.94 → aient-1.1.96}/aient/architext/test/test.py +102 -3
  4. {aient-1.1.94 → aient-1.1.96}/aient.egg-info/PKG-INFO +1 -1
  5. {aient-1.1.94 → aient-1.1.96}/pyproject.toml +1 -1
  6. {aient-1.1.94 → aient-1.1.96}/LICENSE +0 -0
  7. {aient-1.1.94 → aient-1.1.96}/README.md +0 -0
  8. {aient-1.1.94 → aient-1.1.96}/aient/__init__.py +0 -0
  9. {aient-1.1.94 → aient-1.1.96}/aient/architext/architext/__init__.py +0 -0
  10. {aient-1.1.94 → aient-1.1.96}/aient/architext/test/openai_client.py +0 -0
  11. {aient-1.1.94 → aient-1.1.96}/aient/architext/test/test_save_load.py +0 -0
  12. {aient-1.1.94 → aient-1.1.96}/aient/core/__init__.py +0 -0
  13. {aient-1.1.94 → aient-1.1.96}/aient/core/log_config.py +0 -0
  14. {aient-1.1.94 → aient-1.1.96}/aient/core/models.py +0 -0
  15. {aient-1.1.94 → aient-1.1.96}/aient/core/request.py +0 -0
  16. {aient-1.1.94 → aient-1.1.96}/aient/core/response.py +0 -0
  17. {aient-1.1.94 → aient-1.1.96}/aient/core/test/test_base_api.py +0 -0
  18. {aient-1.1.94 → aient-1.1.96}/aient/core/test/test_geminimask.py +0 -0
  19. {aient-1.1.94 → aient-1.1.96}/aient/core/test/test_image.py +0 -0
  20. {aient-1.1.94 → aient-1.1.96}/aient/core/test/test_payload.py +0 -0
  21. {aient-1.1.94 → aient-1.1.96}/aient/core/utils.py +0 -0
  22. {aient-1.1.94 → aient-1.1.96}/aient/models/__init__.py +0 -0
  23. {aient-1.1.94 → aient-1.1.96}/aient/models/audio.py +0 -0
  24. {aient-1.1.94 → aient-1.1.96}/aient/models/base.py +0 -0
  25. {aient-1.1.94 → aient-1.1.96}/aient/models/chatgpt.py +0 -0
  26. {aient-1.1.94 → aient-1.1.96}/aient/plugins/__init__.py +0 -0
  27. {aient-1.1.94 → aient-1.1.96}/aient/plugins/arXiv.py +0 -0
  28. {aient-1.1.94 → aient-1.1.96}/aient/plugins/config.py +0 -0
  29. {aient-1.1.94 → aient-1.1.96}/aient/plugins/excute_command.py +0 -0
  30. {aient-1.1.94 → aient-1.1.96}/aient/plugins/get_time.py +0 -0
  31. {aient-1.1.94 → aient-1.1.96}/aient/plugins/image.py +0 -0
  32. {aient-1.1.94 → aient-1.1.96}/aient/plugins/list_directory.py +0 -0
  33. {aient-1.1.94 → aient-1.1.96}/aient/plugins/read_file.py +0 -0
  34. {aient-1.1.94 → aient-1.1.96}/aient/plugins/read_image.py +0 -0
  35. {aient-1.1.94 → aient-1.1.96}/aient/plugins/readonly.py +0 -0
  36. {aient-1.1.94 → aient-1.1.96}/aient/plugins/registry.py +0 -0
  37. {aient-1.1.94 → aient-1.1.96}/aient/plugins/run_python.py +0 -0
  38. {aient-1.1.94 → aient-1.1.96}/aient/plugins/websearch.py +0 -0
  39. {aient-1.1.94 → aient-1.1.96}/aient/plugins/write_file.py +0 -0
  40. {aient-1.1.94 → aient-1.1.96}/aient/utils/__init__.py +0 -0
  41. {aient-1.1.94 → aient-1.1.96}/aient/utils/prompt.py +0 -0
  42. {aient-1.1.94 → aient-1.1.96}/aient/utils/scripts.py +0 -0
  43. {aient-1.1.94 → aient-1.1.96}/aient.egg-info/SOURCES.txt +0 -0
  44. {aient-1.1.94 → aient-1.1.96}/aient.egg-info/dependency_links.txt +0 -0
  45. {aient-1.1.94 → aient-1.1.96}/aient.egg-info/requires.txt +0 -0
  46. {aient-1.1.94 → aient-1.1.96}/aient.egg-info/top_level.txt +0 -0
  47. {aient-1.1.94 → aient-1.1.96}/setup.cfg +0 -0
  48. {aient-1.1.94 → aient-1.1.96}/test/test_Web_crawler.py +0 -0
  49. {aient-1.1.94 → aient-1.1.96}/test/test_ddg_search.py +0 -0
  50. {aient-1.1.94 → aient-1.1.96}/test/test_google_search.py +0 -0
  51. {aient-1.1.94 → aient-1.1.96}/test/test_ollama.py +0 -0
  52. {aient-1.1.94 → aient-1.1.96}/test/test_plugin.py +0 -0
  53. {aient-1.1.94 → aient-1.1.96}/test/test_search.py +0 -0
  54. {aient-1.1.94 → aient-1.1.96}/test/test_url.py +0 -0
  55. {aient-1.1.94 → aient-1.1.96}/test/test_whisper.py +0 -0
  56. {aient-1.1.94 → aient-1.1.96}/test/test_yjh.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aient
3
- Version: 1.1.94
3
+ Version: 1.1.96
4
4
  Summary: Aient: The Awakening of Agent.
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -4,10 +4,28 @@ import asyncio
4
4
  import logging
5
5
  import hashlib
6
6
  import mimetypes
7
+ import uuid
8
+ import threading
7
9
  from dataclasses import dataclass
8
10
  from abc import ABC, abstractmethod
9
11
  from typing import List, Dict, Any, Optional, Union, Callable
10
12
 
13
+ # Global, thread-safe registry for providers created within f-strings
14
+ _fstring_provider_registry = {}
15
+ _registry_lock = threading.Lock()
16
+
17
+ def _register_provider(provider: 'ContextProvider') -> str:
18
+ """Registers a provider and returns a unique placeholder."""
19
+ with _registry_lock:
20
+ provider_id = f"__provider_placeholder_{uuid.uuid4().hex}__"
21
+ _fstring_provider_registry[provider_id] = provider
22
+ return provider_id
23
+
24
+ def _retrieve_provider(placeholder: str) -> Optional['ContextProvider']:
25
+ """Retrieves a provider from the registry."""
26
+ with _registry_lock:
27
+ return _fstring_provider_registry.pop(placeholder, None)
28
+
11
29
  # 1. 核心数据结构: ContentBlock
12
30
  @dataclass
13
31
  class ContentBlock:
@@ -18,6 +36,11 @@ class ContentBlock:
18
36
  class ContextProvider(ABC):
19
37
  def __init__(self, name: str):
20
38
  self.name = name; self._cached_content: Optional[str] = None; self._is_stale: bool = True
39
+
40
+ def __str__(self):
41
+ # This allows the object to be captured when used inside an f-string.
42
+ return _register_provider(self)
43
+
21
44
  def mark_stale(self): self._is_stale = True
22
45
  async def refresh(self):
23
46
  if self._is_stale:
@@ -63,15 +86,23 @@ class Texts(ContextProvider):
63
86
 
64
87
  async def render(self) -> Optional[str]:
65
88
  if self._is_dynamic:
66
- return self._text()
67
- return self._text
89
+ # Ensure dynamic content returns a string, even if empty
90
+ result = self._text()
91
+ return result if result is not None else ""
92
+ # Ensure static content returns a string, even if empty
93
+ return self._text if self._text is not None else ""
68
94
 
69
95
  class Tools(ContextProvider):
70
- def __init__(self, tools_json: List[Dict]): super().__init__("tools"); self._tools_json = tools_json
96
+ def __init__(self, tools_json: Optional[List[Dict]] = None):
97
+ super().__init__("tools")
98
+ self._tools_json = tools_json or []
71
99
  def update(self, tools_json: List[Dict]):
72
100
  self._tools_json = tools_json
73
101
  self.mark_stale()
74
- async def render(self) -> str: return f"<tools>{str(self._tools_json)}</tools>"
102
+ async def render(self) -> Optional[str]:
103
+ if not self._tools_json:
104
+ return None
105
+ return f"<tools>{str(self._tools_json)}</tools>"
75
106
 
76
107
  class Files(ContextProvider):
77
108
  def __init__(self, *paths: Union[str, List[str]]):
@@ -176,7 +207,23 @@ class Message(ABC):
176
207
  processed_items = []
177
208
  for item in initial_items:
178
209
  if isinstance(item, str):
179
- processed_items.append(Texts(text=item))
210
+ # Check if the string contains placeholders from f-string rendering
211
+ import re
212
+ placeholder_pattern = re.compile(r'(__provider_placeholder_[a-f0-9]{32}__)')
213
+ parts = placeholder_pattern.split(item)
214
+
215
+ if len(parts) > 1: # Placeholders were found
216
+ for part in parts:
217
+ if not part: continue
218
+ if placeholder_pattern.match(part):
219
+ provider = _retrieve_provider(part)
220
+ if provider:
221
+ processed_items.append(provider)
222
+ else:
223
+ processed_items.append(Texts(text=part))
224
+ else: # No placeholders, just a regular string
225
+ processed_items.append(Texts(text=item))
226
+
180
227
  elif isinstance(item, Message):
181
228
  processed_items.extend(item.providers())
182
229
  elif isinstance(item, ContextProvider):
@@ -201,8 +248,13 @@ class Message(ABC):
201
248
  self._parent_messages: Optional['Messages'] = None
202
249
 
203
250
  def _render_content(self) -> str:
204
- blocks = [item.get_content_block() for item in self._items]
205
- return "\n\n".join(b.content for b in blocks if b and b.content)
251
+ final_parts = []
252
+ for item in self._items:
253
+ block = item.get_content_block()
254
+ if block and block.content is not None:
255
+ final_parts.append(block.content)
256
+
257
+ return "".join(final_parts)
206
258
 
207
259
  def pop(self, name: str) -> Optional[ContextProvider]:
208
260
  popped_item = None
@@ -143,11 +143,12 @@ class TestContextManagement(unittest.IsolatedAsyncioTestCase):
143
143
  popped_image = messages.pop("image")
144
144
  self.assertIsNotNone(popped_image)
145
145
 
146
- # 3. Render again, should fall back to string content
146
+ # 3. Render again, should fall back to string content and seamlessly join texts
147
147
  rendered_str = messages.render() # No refresh needed
148
148
  content_str = rendered_str[0]['content']
149
149
  self.assertIsInstance(content_str, str)
150
- self.assertEqual(content_str, "Look at this:\n\nAny thoughts?")
150
+ # With the new render logic, adjacent texts are joined with ""
151
+ self.assertEqual(content_str, "Look at this:Any thoughts?")
151
152
 
152
153
  # Clean up
153
154
  import os
@@ -884,7 +885,11 @@ class TestContextManagement(unittest.IsolatedAsyncioTestCase):
884
885
  # If there's no content, the message itself might not be rendered.
885
886
  # Let's assume an empty provider results in the message not rendering.
886
887
  await deferred_text_provider.refresh()
887
- self.assertIsNone(deferred_text_provider.get_content_block())
888
+ # With the new logic, it should return a block with an empty string
889
+ content_block = deferred_text_provider.get_content_block()
890
+ self.assertIsNotNone(content_block)
891
+ self.assertEqual(content_block.content, "")
892
+
888
893
 
889
894
  rendered_initial = await messages.render_latest()
890
895
  self.assertEqual(len(rendered_initial), 0)
@@ -899,6 +904,100 @@ class TestContextManagement(unittest.IsolatedAsyncioTestCase):
899
904
  self.assertEqual(len(rendered_updated), 1)
900
905
  self.assertEqual(rendered_updated[0]['content'], "This is the new content.")
901
906
 
907
+ async def test_z4_direct_fstring_usage(self):
908
+ """直接使用 f-string 语法,并预期其能够被处理"""
909
+
910
+ # 这个测试将直接使用用户期望的 f-string 语法。
911
+ # 由于 Python 的限制,这行代码会立即对 f-string 求值,
912
+ # 导致 providers 的字符串表示形式(而不是 provider 对象本身)被插入。
913
+ # 因此,这个测试最初会失败。
914
+ f_string_message = f"""<user_info>
915
+ The user's OS version is {Texts(name="os_version")}.
916
+ Tools: {Tools()}
917
+ Files: {Files()}
918
+ Current time: {Texts(name="current_time")}
919
+ </user_info>"""
920
+
921
+ # 借助新的 f-string 处理机制,UserMessage 现在可以直接消费 f-string 的结果。
922
+ messages = Messages(UserMessage(f_string_message))
923
+
924
+ # 初始渲染时,provider 的内容应该为空
925
+ rendered_initial = await messages.render_latest()
926
+
927
+ # With the new simplest rendering logic, the output should match the f-string exactly,
928
+ # with empty strings for the providers and no leading whitespace.
929
+ expected_initial = (
930
+ "<user_info>\n"
931
+ "The user's OS version is .\n"
932
+ "Tools: \n"
933
+ "Files: \n"
934
+ "Current time: \n"
935
+ "</user_info>"
936
+ )
937
+ self.assertEqual(rendered_initial[0]['content'].strip(), expected_initial.strip())
938
+
939
+ # 现在,尝试通过 provider 更新内容。这应该会成功。
940
+ messages.provider("os_version").update("TestOS")
941
+ messages.provider("tools").update([{"name": "test_tool"}])
942
+ messages.provider("current_time").update("2025-12-25")
943
+
944
+ test_file = "fstring_test.txt"
945
+ with open(test_file, "w") as f: f.write("content from f-string test")
946
+
947
+ try:
948
+ messages.provider("files").update(test_file)
949
+
950
+ rendered_final = await messages.render_latest()
951
+ final_content = rendered_final[0]['content']
952
+
953
+ # 断言内容已经被成功更新
954
+ tools_str = "<tools>[{'name': 'test_tool'}]</tools>"
955
+ files_str = f"<latest_file_content><file><file_path>fstring_test.txt</file_path><file_content>content from f-string test</file_content></file>\n</latest_file_content>"
956
+
957
+ expected_final = f"""<user_info>
958
+ The user's OS version is TestOS.
959
+ Tools: {tools_str}
960
+ Files: {files_str}
961
+ Current time: 2025-12-25
962
+ </user_info>"""
963
+ self.assertEqual(final_content.strip(), expected_final.strip())
964
+ finally:
965
+ if os.path.exists(test_file):
966
+ os.remove(test_file)
967
+
968
+ async def test_z5_fstring_with_dynamic_lambda(self):
969
+ """测试 f-string 消息是否支持动态 lambda 函数"""
970
+ from datetime import datetime
971
+ import time
972
+
973
+ # 这个测试将验证 f-string 是否能正确处理包含 lambda 的动态 provider
974
+ f_string_message = f"""<user_info>
975
+ The user's OS version is {Texts(name="os_version")}.
976
+ Current time: {Texts(lambda: datetime.now().strftime("%Y-%m-%d %H:%M:%S"))}
977
+ </user_info>"""
978
+
979
+ messages = Messages(UserMessage(f_string_message))
980
+ messages.provider("os_version").update("TestOS")
981
+
982
+ # 第一次渲染
983
+ rendered1 = await messages.render_latest()
984
+ content1 = rendered1[0]['content']
985
+ self.assertIn("TestOS", content1)
986
+
987
+ time1_str_part = content1.split("Current time:")[1].strip().split("\n")[0]
988
+
989
+
990
+ # 等待一秒
991
+ time.sleep(1)
992
+
993
+ # 第二次渲染
994
+ rendered2 = await messages.render_latest()
995
+ content2 = rendered2[0]['content']
996
+ time2_str_part = content2.split("Current time:")[1].strip().split("\n")[0]
997
+
998
+ # 验证两次渲染的时间戳不同
999
+ self.assertNotEqual(time1_str_part, time2_str_part, "f-string 中的动态 lambda 内容在两次渲染之间应该更新")
1000
+
902
1001
 
903
1002
  # ==============================================================================
904
1003
  # 6. 演示
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aient
3
- Version: 1.1.94
3
+ Version: 1.1.96
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.1.94"
3
+ version = "1.1.96"
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
File without changes
File without changes
File without changes
File without changes
File without changes