aient 1.1.89__tar.gz → 1.1.90__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.1.89 → aient-1.1.90}/PKG-INFO +1 -1
  2. aient-1.1.90/aient/architext/architext/__init__.py +1 -0
  3. aient-1.1.90/aient/architext/architext/core.py +165 -0
  4. aient-1.1.90/aient/architext/test.py +92 -0
  5. {aient-1.1.89 → aient-1.1.90}/aient.egg-info/PKG-INFO +1 -1
  6. {aient-1.1.89 → aient-1.1.90}/aient.egg-info/SOURCES.txt +3 -0
  7. {aient-1.1.89 → aient-1.1.90}/pyproject.toml +1 -1
  8. {aient-1.1.89 → aient-1.1.90}/LICENSE +0 -0
  9. {aient-1.1.89 → aient-1.1.90}/README.md +0 -0
  10. {aient-1.1.89 → aient-1.1.90}/aient/__init__.py +0 -0
  11. {aient-1.1.89 → aient-1.1.90}/aient/core/__init__.py +0 -0
  12. {aient-1.1.89 → aient-1.1.90}/aient/core/log_config.py +0 -0
  13. {aient-1.1.89 → aient-1.1.90}/aient/core/models.py +0 -0
  14. {aient-1.1.89 → aient-1.1.90}/aient/core/request.py +0 -0
  15. {aient-1.1.89 → aient-1.1.90}/aient/core/response.py +0 -0
  16. {aient-1.1.89 → aient-1.1.90}/aient/core/test/test_base_api.py +0 -0
  17. {aient-1.1.89 → aient-1.1.90}/aient/core/test/test_geminimask.py +0 -0
  18. {aient-1.1.89 → aient-1.1.90}/aient/core/test/test_image.py +0 -0
  19. {aient-1.1.89 → aient-1.1.90}/aient/core/test/test_payload.py +0 -0
  20. {aient-1.1.89 → aient-1.1.90}/aient/core/utils.py +0 -0
  21. {aient-1.1.89 → aient-1.1.90}/aient/models/__init__.py +0 -0
  22. {aient-1.1.89 → aient-1.1.90}/aient/models/audio.py +0 -0
  23. {aient-1.1.89 → aient-1.1.90}/aient/models/base.py +0 -0
  24. {aient-1.1.89 → aient-1.1.90}/aient/models/chatgpt.py +0 -0
  25. {aient-1.1.89 → aient-1.1.90}/aient/plugins/__init__.py +0 -0
  26. {aient-1.1.89 → aient-1.1.90}/aient/plugins/arXiv.py +0 -0
  27. {aient-1.1.89 → aient-1.1.90}/aient/plugins/config.py +0 -0
  28. {aient-1.1.89 → aient-1.1.90}/aient/plugins/excute_command.py +0 -0
  29. {aient-1.1.89 → aient-1.1.90}/aient/plugins/get_time.py +0 -0
  30. {aient-1.1.89 → aient-1.1.90}/aient/plugins/image.py +0 -0
  31. {aient-1.1.89 → aient-1.1.90}/aient/plugins/list_directory.py +0 -0
  32. {aient-1.1.89 → aient-1.1.90}/aient/plugins/read_file.py +0 -0
  33. {aient-1.1.89 → aient-1.1.90}/aient/plugins/read_image.py +0 -0
  34. {aient-1.1.89 → aient-1.1.90}/aient/plugins/readonly.py +0 -0
  35. {aient-1.1.89 → aient-1.1.90}/aient/plugins/registry.py +0 -0
  36. {aient-1.1.89 → aient-1.1.90}/aient/plugins/run_python.py +0 -0
  37. {aient-1.1.89 → aient-1.1.90}/aient/plugins/websearch.py +0 -0
  38. {aient-1.1.89 → aient-1.1.90}/aient/plugins/write_file.py +0 -0
  39. {aient-1.1.89 → aient-1.1.90}/aient/utils/__init__.py +0 -0
  40. {aient-1.1.89 → aient-1.1.90}/aient/utils/prompt.py +0 -0
  41. {aient-1.1.89 → aient-1.1.90}/aient/utils/scripts.py +0 -0
  42. {aient-1.1.89 → aient-1.1.90}/aient.egg-info/dependency_links.txt +0 -0
  43. {aient-1.1.89 → aient-1.1.90}/aient.egg-info/requires.txt +0 -0
  44. {aient-1.1.89 → aient-1.1.90}/aient.egg-info/top_level.txt +0 -0
  45. {aient-1.1.89 → aient-1.1.90}/setup.cfg +0 -0
  46. {aient-1.1.89 → aient-1.1.90}/test/test_Web_crawler.py +0 -0
  47. {aient-1.1.89 → aient-1.1.90}/test/test_ddg_search.py +0 -0
  48. {aient-1.1.89 → aient-1.1.90}/test/test_google_search.py +0 -0
  49. {aient-1.1.89 → aient-1.1.90}/test/test_ollama.py +0 -0
  50. {aient-1.1.89 → aient-1.1.90}/test/test_plugin.py +0 -0
  51. {aient-1.1.89 → aient-1.1.90}/test/test_search.py +0 -0
  52. {aient-1.1.89 → aient-1.1.90}/test/test_url.py +0 -0
  53. {aient-1.1.89 → aient-1.1.90}/test/test_whisper.py +0 -0
  54. {aient-1.1.89 → aient-1.1.90}/test/test_yjh.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aient
3
- Version: 1.1.89
3
+ Version: 1.1.90
4
4
  Summary: Aient: The Awakening of Agent.
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -0,0 +1 @@
1
+ from .core import *
@@ -0,0 +1,165 @@
1
+ import asyncio
2
+ from abc import ABC, abstractmethod
3
+ from typing import List, Dict, Any, Optional, Union
4
+
5
+ # 1. 核心数据结构: ContentBlock
6
+ class ContentBlock:
7
+ def __init__(self, name: str, content: str, provider: Optional['ContextProvider'] = None):
8
+ self.name = name; self.content = content; self.provider = provider
9
+ def __repr__(self): return f"Block(name='{self.name}')"
10
+
11
+ # 2. 上下文提供者 (带缓存)
12
+ class ContextProvider(ABC):
13
+ def __init__(self, name: str):
14
+ self.name = name; self._cached_content: Optional[str] = None; self._is_stale: bool = True
15
+ def mark_stale(self): self._is_stale = True
16
+ async def _refresh(self):
17
+ if self._is_stale:
18
+ # 注意:我们将在这个方法上使用 mock,所以实际的 print 不再重要
19
+ # print(f"信息: 正在为上下文提供者 '{self.name}' 刷新内容...")
20
+ self._cached_content = await self._fetch_content()
21
+ self._is_stale = False
22
+ # else:
23
+ # print(f"调试: 上下文提供者 '{self.name}' 正在使用缓存内容。")
24
+ @abstractmethod
25
+ async def _fetch_content(self) -> Optional[str]: raise NotImplementedError
26
+ async def render(self) -> Optional[ContentBlock]:
27
+ await self._refresh()
28
+ if self._cached_content is not None: return ContentBlock(self.name, self._cached_content, self)
29
+ return None
30
+
31
+ class Texts(ContextProvider):
32
+ def __init__(self, name: str, text: str): super().__init__(name); self._text = text
33
+ async def _fetch_content(self) -> str: return self._text
34
+
35
+ class Tools(ContextProvider):
36
+ def __init__(self, tools_json: List[Dict]): super().__init__("tools"); self._tools_json = tools_json
37
+ async def _fetch_content(self) -> str: return f"<tools>{str(self._tools_json)}</tools>"
38
+
39
+ class Files(ContextProvider):
40
+ def __init__(self): super().__init__("files"); self._files: Dict[str, str] = {}
41
+ def update(self, path: str, content: str): self._files[path] = content; self.mark_stale()
42
+ async def _fetch_content(self) -> str:
43
+ if not self._files: return None
44
+ return "<files>\n" + "\n".join([f"<file path='{p}'>{c[:50]}...</file>" for p, c in self._files.items()]) + "\n</files>"
45
+
46
+ Item = Union[ContentBlock, ContextProvider]
47
+
48
+ # 3. 消息内容类与消息类
49
+ class MessageContent:
50
+ def __init__(self, items: List[Item]): self._items: List[Item] = items
51
+ async def render(self) -> str:
52
+ tasks = []
53
+ for item in self._items:
54
+ if isinstance(item, ContextProvider):
55
+ tasks.append(item.render())
56
+ elif isinstance(item, ContentBlock):
57
+ # 为了统一处理,将 ContentBlock 也包装成一个已完成的 Future
58
+ future = asyncio.Future()
59
+ future.set_result(item)
60
+ tasks.append(future)
61
+
62
+ blocks = await asyncio.gather(*tasks)
63
+ return "\n\n".join(b.content for b in blocks if b and b.content)
64
+ def pop(self, name: str) -> Optional[Item]:
65
+ for i, item in enumerate(self._items):
66
+ if hasattr(item, 'name') and item.name == name: return self._items.pop(i)
67
+ return None
68
+ def insert(self, index: int, item: Item): self._items.insert(index, item)
69
+ def append(self, item: Item): self._items.append(item)
70
+ def providers(self) -> List[ContextProvider]: return [item for item in self._items if isinstance(item, ContextProvider)]
71
+ def __repr__(self): return f"Content(items={[item.name for item in self._items if hasattr(item, 'name')]})"
72
+
73
+ class Message(ABC):
74
+ def __init__(self, role: str, *initial_items: Item): self.role = role; self.content = MessageContent(list(initial_items))
75
+ async def to_dict(self) -> Optional[Dict[str, Any]]:
76
+ rendered_content = await self.content.render()
77
+ if not rendered_content: return None
78
+ return {"role": self.role, "content": rendered_content}
79
+
80
+ class SystemMessage(Message):
81
+ def __init__(self, *items): super().__init__("system", *items)
82
+ class UserMessage(Message):
83
+ def __init__(self, *items): super().__init__("user", *items)
84
+
85
+ # 4. 上下文构建器 (内部使用)
86
+ class ContextBuilder:
87
+ def __init__(self, providers: List[ContextProvider]): self.providers = {p.name: p for p in providers}
88
+ def get_provider(self, name: str) -> Optional[ContextProvider]: return self.providers.get(name)
89
+
90
+ # 5. 顶层容器: Messages
91
+ class Messages:
92
+ def __init__(self, *initial_messages: Message):
93
+ self._messages: List[Message] = list(initial_messages)
94
+ all_providers = []
95
+ for msg in self._messages: all_providers.extend(msg.content.providers())
96
+ self._context_builder = ContextBuilder(all_providers)
97
+ def provider(self, name: str) -> Optional[ContextProvider]: return self._context_builder.get_provider(name)
98
+ def pop(self, name: str) -> Optional[Item]:
99
+ for message in self._messages:
100
+ popped_item = message.content.pop(name)
101
+ if popped_item: return popped_item
102
+ return None
103
+ async def render(self) -> List[Dict[str, Any]]:
104
+ tasks = [msg.to_dict() for msg in self._messages]
105
+ results = await asyncio.gather(*tasks)
106
+ return [res for res in results if res]
107
+ def append(self, message: Message):
108
+ self._messages.append(message)
109
+ for p in message.content.providers():
110
+ if p.name not in self._context_builder.providers: self._context_builder.providers[p.name] = p
111
+ def __getitem__(self, index: int) -> Message: return self._messages[index]
112
+ def __len__(self) -> int: return len(self._messages)
113
+ def __iter__(self): return iter(self._messages)
114
+
115
+
116
+ # ==============================================================================
117
+ # 6. 演示
118
+ # ==============================================================================
119
+ async def run_demo():
120
+ # --- 1. 初始化提供者 ---
121
+ system_prompt_provider = Texts("system_prompt", "你是一个AI助手。")
122
+ tools_provider = Tools(tools_json=[{"name": "read_file"}])
123
+ files_provider = Files()
124
+
125
+ # --- 2. 演示新功能:优雅地构建 Messages ---
126
+ print("\n>>> 场景 A: 使用新的、优雅的构造函数直接初始化 Messages")
127
+ messages = Messages(
128
+ SystemMessage(system_prompt_provider, tools_provider),
129
+ UserMessage(files_provider, Texts("user_input", "这是我的初始问题。"))
130
+ )
131
+
132
+ print("\n--- 渲染后的初始 Messages (首次渲染,全部刷新) ---")
133
+ for msg_dict in await messages.render(): print(msg_dict)
134
+ print("-" * 40)
135
+
136
+ # --- 3. 演示穿透更新 ---
137
+ print("\n>>> 场景 B: 穿透更新 File Provider,渲染时自动刷新")
138
+
139
+ # 直接通过 messages 对象穿透访问并更新 files provider
140
+ files_provider_instance = messages.provider("files")
141
+ if isinstance(files_provider_instance, Files):
142
+ files_provider_instance.update("file1.py", "这是新的文件内容!")
143
+
144
+ print("\n--- 再次渲染 Messages (只有文件提供者会刷新) ---")
145
+ for msg_dict in await messages.render(): print(msg_dict)
146
+ print("-" * 40)
147
+
148
+ # --- 4. 演示全局 Pop 和通过索引 Insert ---
149
+ print("\n>>> 场景 C: 全局 Pop 工具提供者,并 Insert 到 UserMessage 中")
150
+
151
+ # a. 全局弹出 'tools' Provider
152
+ popped_tools_provider = messages.pop("tools")
153
+
154
+ # b. 将弹出的 Provider 插入到第一个 UserMessage (索引为1) 的开头
155
+ if popped_tools_provider:
156
+ # 通过索引精确定位
157
+ messages[1].content.insert(0, popped_tools_provider)
158
+ print(f"\n已成功将 '{popped_tools_provider.name}' 提供者移动到用户消息。")
159
+
160
+ print("\n--- Pop 和 Insert 后渲染的 Messages (验证移动效果) ---")
161
+ for msg_dict in await messages.render(): print(msg_dict)
162
+ print("-" * 40)
163
+
164
+ if __name__ == "__main__":
165
+ asyncio.run(run_demo())
@@ -0,0 +1,92 @@
1
+ import unittest
2
+ from unittest.mock import AsyncMock
3
+ from architext import *
4
+
5
+ # ==============================================================================
6
+ # 单元测试部分
7
+ # ==============================================================================
8
+ class TestContextManagement(unittest.IsolatedAsyncioTestCase):
9
+
10
+ def setUp(self):
11
+ """在每个测试前设置环境"""
12
+ self.system_prompt_provider = Texts("system_prompt", "你是一个AI助手。")
13
+ self.tools_provider = Tools(tools_json=[{"name": "read_file"}])
14
+ self.files_provider = Files()
15
+
16
+ async def test_a_initial_construction_and_render(self):
17
+ """测试优雅的初始化和首次渲染"""
18
+ messages = Messages(
19
+ SystemMessage(self.system_prompt_provider, self.tools_provider),
20
+ UserMessage(self.files_provider, Texts("user_input", "这是我的初始问题。"))
21
+ )
22
+
23
+ self.assertEqual(len(messages), 2)
24
+ rendered = await messages.render()
25
+
26
+ self.assertEqual(len(rendered), 2)
27
+ self.assertIn("<tools>", rendered[0]['content'])
28
+ self.assertNotIn("<files>", rendered[1]['content'])
29
+
30
+ async def test_b_provider_passthrough_and_refresh(self):
31
+ """测试通过 mock 验证缓存和刷新逻辑"""
32
+ # 为 files_provider 的 _fetch_content 方法创建一个 mock
33
+ # 我们希望它在被调用时仍然返回真实的结果,所以使用 side_effect
34
+ original_fetch = self.files_provider._fetch_content
35
+ self.files_provider._fetch_content = AsyncMock(side_effect=original_fetch)
36
+
37
+ messages = Messages(UserMessage(self.files_provider))
38
+
39
+ # 1. 初始文件内容为空,渲染一次
40
+ self.files_provider.update("path1", "content1")
41
+ await messages.render()
42
+ # _fetch_content 应该被调用了 1 次
43
+ self.assertEqual(self.files_provider._fetch_content.call_count, 1)
44
+
45
+ # 2. 再次渲染,内容未变,不应再次调用 _fetch_content
46
+ await messages.render()
47
+ # 调用次数应该仍然是 1,证明缓存生效
48
+ self.assertEqual(self.files_provider._fetch_content.call_count, 1)
49
+
50
+ # 3. 更新文件内容,这会标记 provider 为 stale
51
+ self.files_provider.update("path2", "content2")
52
+
53
+ # 4. 再次渲染,现在应该会重新调用 _fetch_content
54
+ rendered = await messages.render()
55
+ # 调用次数应该变为 2
56
+ self.assertEqual(self.files_provider._fetch_content.call_count, 2)
57
+ # 并且渲染结果包含了新内容
58
+ self.assertIn("content2", rendered[0]['content'])
59
+
60
+ async def test_c_global_pop_and_indexed_insert(self):
61
+ """测试全局pop和通过索引insert的功能"""
62
+ messages = Messages(
63
+ SystemMessage(self.system_prompt_provider, self.tools_provider),
64
+ UserMessage(self.files_provider)
65
+ )
66
+
67
+ # 验证初始状态
68
+ initial_rendered = await messages.render()
69
+ self.assertTrue(any("<tools>" in msg['content'] for msg in initial_rendered if msg['role'] == 'system'))
70
+
71
+ # 全局弹出 'tools' Provider
72
+ popped_tools_provider = messages.pop("tools")
73
+ self.assertIs(popped_tools_provider, self.tools_provider)
74
+
75
+ # 验证 pop 后的状态
76
+ rendered_after_pop = await messages.render()
77
+ self.assertFalse(any("<tools>" in msg['content'] for msg in rendered_after_pop if msg['role'] == 'system'))
78
+
79
+ # 通过索引将弹出的provider插入到UserMessage的开头
80
+ messages[1].content.insert(0, popped_tools_provider)
81
+
82
+ # 验证 insert 后的状态
83
+ rendered_after_insert = await messages.render()
84
+ user_message_content = next(msg['content'] for msg in rendered_after_insert if msg['role'] == 'user')
85
+ self.assertTrue(user_message_content.startswith("<tools>"))
86
+
87
+ if __name__ == '__main__':
88
+ # 为了在普通脚本环境中运行,添加这两行
89
+ suite = unittest.TestSuite()
90
+ suite.addTest(unittest.makeSuite(TestContextManagement))
91
+ runner = unittest.TextTestRunner()
92
+ runner.run(suite)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aient
3
- Version: 1.1.89
3
+ Version: 1.1.90
4
4
  Summary: Aient: The Awakening of Agent.
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -7,6 +7,9 @@ aient.egg-info/SOURCES.txt
7
7
  aient.egg-info/dependency_links.txt
8
8
  aient.egg-info/requires.txt
9
9
  aient.egg-info/top_level.txt
10
+ aient/architext/test.py
11
+ aient/architext/architext/__init__.py
12
+ aient/architext/architext/core.py
10
13
  aient/core/__init__.py
11
14
  aient/core/log_config.py
12
15
  aient/core/models.py
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "aient"
3
- version = "1.1.89"
3
+ version = "1.1.90"
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