aient 1.1.90__py3-none-any.whl → 1.1.91__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.
@@ -1,31 +1,30 @@
1
+ import base64
1
2
  import asyncio
3
+ import logging
4
+ import mimetypes
5
+ from dataclasses import dataclass
2
6
  from abc import ABC, abstractmethod
3
- from typing import List, Dict, Any, Optional, Union
7
+ from typing import List, Dict, Any, Optional
4
8
 
5
9
  # 1. 核心数据结构: ContentBlock
10
+ @dataclass
6
11
  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}')"
12
+ name: str
13
+ content: str
10
14
 
11
15
  # 2. 上下文提供者 (带缓存)
12
16
  class ContextProvider(ABC):
13
17
  def __init__(self, name: str):
14
18
  self.name = name; self._cached_content: Optional[str] = None; self._is_stale: bool = True
15
19
  def mark_stale(self): self._is_stale = True
16
- async def _refresh(self):
20
+ async def refresh(self):
17
21
  if self._is_stale:
18
- # 注意:我们将在这个方法上使用 mock,所以实际的 print 不再重要
19
- # print(f"信息: 正在为上下文提供者 '{self.name}' 刷新内容...")
20
22
  self._cached_content = await self._fetch_content()
21
23
  self._is_stale = False
22
- # else:
23
- # print(f"调试: 上下文提供者 '{self.name}' 正在使用缓存内容。")
24
24
  @abstractmethod
25
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)
26
+ def get_content_block(self) -> Optional[ContentBlock]:
27
+ if self._cached_content is not None: return ContentBlock(self.name, self._cached_content)
29
28
  return None
30
29
 
31
30
  class Texts(ContextProvider):
@@ -41,78 +40,136 @@ class Files(ContextProvider):
41
40
  def update(self, path: str, content: str): self._files[path] = content; self.mark_stale()
42
41
  async def _fetch_content(self) -> str:
43
42
  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)
43
+ return "<files>\n" + "\n".join([f"<file path='{p}'>{c}...</file>" for p, c in self._files.items()]) + "\n</files>"
44
+
45
+ class Images(ContextProvider):
46
+ def __init__(self, image_path: str, name: Optional[str] = None):
47
+ super().__init__(name or image_path)
48
+ self.image_path = image_path
49
+ async def _fetch_content(self) -> Optional[str]:
50
+ try:
51
+ with open(self.image_path, "rb") as image_file:
52
+ encoded_string = base64.b64encode(image_file.read()).decode('utf-8')
53
+ mime_type, _ = mimetypes.guess_type(self.image_path)
54
+ if not mime_type: mime_type = "application/octet-stream" # Fallback
55
+ return f"data:{mime_type};base64,{encoded_string}"
56
+ except FileNotFoundError:
57
+ logging.warning(f"Image file not found: {self.image_path}. Skipping.")
58
+ return None # Or handle error appropriately
59
+
60
+ # 3. 消息类 (已合并 MessageContent)
61
+ class Message(ABC):
62
+ def __init__(self, role: str, *initial_items: ContextProvider):
63
+ self.role = role
64
+ self._items: List[ContextProvider] = list(initial_items)
65
+ self._parent_messages: Optional['Messages'] = None
66
+
67
+ def _render_content(self) -> str:
68
+ blocks = [item.get_content_block() for item in self._items]
63
69
  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
70
 
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}
71
+ def pop(self, name: str) -> Optional[ContextProvider]:
72
+ popped_item = None
73
+ for i, item in enumerate(self._items):
74
+ if hasattr(item, 'name') and item.name == name:
75
+ popped_item = self._items.pop(i)
76
+ break
77
+ if popped_item and self._parent_messages:
78
+ self._parent_messages._notify_provider_removed(popped_item)
79
+ return popped_item
80
+
81
+ def insert(self, index: int, item: ContextProvider):
82
+ self._items.insert(index, item)
83
+ if self._parent_messages:
84
+ self._parent_messages._notify_provider_added(item, self)
85
+
86
+ def append(self, item: ContextProvider):
87
+ self._items.append(item)
88
+ if self._parent_messages:
89
+ self._parent_messages._notify_provider_added(item, self)
90
+
91
+ def providers(self) -> List[ContextProvider]: return self._items
92
+ def __repr__(self): return f"Message(role='{self.role}', items={[i.name for i in self._items]})"
93
+ def to_dict(self) -> Optional[Dict[str, Any]]:
94
+ is_multimodal = any(isinstance(p, Images) for p in self._items)
95
+
96
+ if not is_multimodal:
97
+ rendered_content = self._render_content()
98
+ if not rendered_content: return None
99
+ return {"role": self.role, "content": rendered_content}
100
+ else:
101
+ content_list = []
102
+ for item in self._items:
103
+ block = item.get_content_block()
104
+ if not block or not block.content: continue
105
+ if isinstance(item, Images):
106
+ content_list.append({"type": "image_url", "image_url": {"url": block.content}})
107
+ else:
108
+ content_list.append({"type": "text", "text": block.content})
109
+ if not content_list: return None
110
+ return {"role": self.role, "content": content_list}
79
111
 
80
112
  class SystemMessage(Message):
81
113
  def __init__(self, *items): super().__init__("system", *items)
82
114
  class UserMessage(Message):
83
115
  def __init__(self, *items): super().__init__("user", *items)
84
116
 
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
117
+ # 4. 顶层容器: Messages
91
118
  class Messages:
92
119
  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)
120
+ from typing import Tuple
121
+ self._messages: List[Message] = []
122
+ self._providers_index: Dict[str, Tuple[ContextProvider, Message]] = {}
123
+ if initial_messages:
124
+ for msg in initial_messages:
125
+ self.append(msg)
126
+
127
+ def _notify_provider_added(self, provider: ContextProvider, message: Message):
128
+ if provider.name not in self._providers_index:
129
+ self._providers_index[provider.name] = (provider, message)
130
+
131
+ def _notify_provider_removed(self, provider: ContextProvider):
132
+ if provider.name in self._providers_index:
133
+ del self._providers_index[provider.name]
134
+
135
+ def provider(self, name: str) -> Optional[ContextProvider]:
136
+ indexed = self._providers_index.get(name)
137
+ return indexed[0] if indexed else None
138
+
139
+ def pop(self, name: str) -> Optional[ContextProvider]:
140
+ indexed = self._providers_index.get(name)
141
+ if not indexed:
142
+ return None
143
+ _provider, parent_message = indexed
144
+ return parent_message.pop(name)
145
+
146
+ async def refresh(self):
147
+ tasks = [provider.refresh() for provider, _ in self._providers_index.values()]
148
+ await asyncio.gather(*tasks)
149
+
150
+ def render(self) -> List[Dict[str, Any]]:
151
+ results = [msg.to_dict() for msg in self._messages]
106
152
  return [res for res in results if res]
153
+
154
+ async def render_latest(self) -> List[Dict[str, Any]]:
155
+ await self.refresh()
156
+ return self.render()
157
+
107
158
  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
159
+ if self._messages and self._messages[-1].role == message.role:
160
+ last_message = self._messages[-1]
161
+ for provider in message.providers():
162
+ last_message.append(provider)
163
+ else:
164
+ message._parent_messages = self
165
+ self._messages.append(message)
166
+ for p in message.providers():
167
+ self._notify_provider_added(p, message)
168
+
111
169
  def __getitem__(self, index: int) -> Message: return self._messages[index]
112
170
  def __len__(self) -> int: return len(self._messages)
113
171
  def __iter__(self): return iter(self._messages)
114
172
 
115
-
116
173
  # ==============================================================================
117
174
  # 6. 演示
118
175
  # ==============================================================================
@@ -126,40 +183,55 @@ async def run_demo():
126
183
  print("\n>>> 场景 A: 使用新的、优雅的构造函数直接初始化 Messages")
127
184
  messages = Messages(
128
185
  SystemMessage(system_prompt_provider, tools_provider),
129
- UserMessage(files_provider, Texts("user_input", "这是我的初始问题。"))
186
+ UserMessage(files_provider, Texts("user_input", "这是我的初始问题。")),
187
+ UserMessage(Texts("user_input2", "这是我的初始问题2。"))
130
188
  )
131
189
 
132
190
  print("\n--- 渲染后的初始 Messages (首次渲染,全部刷新) ---")
133
- for msg_dict in await messages.render(): print(msg_dict)
191
+ for msg_dict in await messages.render_latest(): print(msg_dict)
134
192
  print("-" * 40)
135
193
 
136
194
  # --- 3. 演示穿透更新 ---
137
195
  print("\n>>> 场景 B: 穿透更新 File Provider,渲染时自动刷新")
138
-
139
- # 直接通过 messages 对象穿透访问并更新 files provider
140
196
  files_provider_instance = messages.provider("files")
141
197
  if isinstance(files_provider_instance, Files):
142
198
  files_provider_instance.update("file1.py", "这是新的文件内容!")
143
199
 
144
200
  print("\n--- 再次渲染 Messages (只有文件提供者会刷新) ---")
145
- for msg_dict in await messages.render(): print(msg_dict)
201
+ for msg_dict in await messages.render_latest(): print(msg_dict)
146
202
  print("-" * 40)
147
203
 
148
204
  # --- 4. 演示全局 Pop 和通过索引 Insert ---
149
205
  print("\n>>> 场景 C: 全局 Pop 工具提供者,并 Insert 到 UserMessage 中")
150
-
151
- # a. 全局弹出 'tools' Provider
152
206
  popped_tools_provider = messages.pop("tools")
153
-
154
- # b. 将弹出的 Provider 插入到第一个 UserMessage (索引为1) 的开头
155
207
  if popped_tools_provider:
156
- # 通过索引精确定位
157
- messages[1].content.insert(0, popped_tools_provider)
208
+ messages[1].insert(0, popped_tools_provider)
158
209
  print(f"\n已成功将 '{popped_tools_provider.name}' 提供者移动到用户消息。")
159
210
 
160
211
  print("\n--- Pop 和 Insert 后渲染的 Messages (验证移动效果) ---")
161
- for msg_dict in await messages.render(): print(msg_dict)
212
+ for msg_dict in messages.render(): print(msg_dict)
162
213
  print("-" * 40)
163
214
 
215
+ # --- 5. 演示多模态渲染 ---
216
+ print("\n>>> 场景 D: 演示多模态 (文本+图片) 渲染")
217
+ with open("dummy_image.png", "w") as f:
218
+ f.write("This is a dummy image file.")
219
+
220
+ multimodal_message = Messages(
221
+ UserMessage(
222
+ Texts("prompt", "What do you see in this image?"),
223
+ Images("dummy_image.png")
224
+ )
225
+ )
226
+ print("\n--- 渲染后的多模态 Message ---")
227
+ for msg_dict in await multimodal_message.render_latest():
228
+ if isinstance(msg_dict['content'], list):
229
+ for item in msg_dict['content']:
230
+ if item['type'] == 'image_url':
231
+ item['image_url']['url'] = item['image_url']['url'][:80] + "..."
232
+ print(msg_dict)
233
+ print("-" * 40)
234
+
235
+
164
236
  if __name__ == "__main__":
165
- asyncio.run(run_demo())
237
+ asyncio.run(run_demo())
aient/architext/test.py CHANGED
@@ -21,7 +21,7 @@ class TestContextManagement(unittest.IsolatedAsyncioTestCase):
21
21
  )
22
22
 
23
23
  self.assertEqual(len(messages), 2)
24
- rendered = await messages.render()
24
+ rendered = await messages.render_latest()
25
25
 
26
26
  self.assertEqual(len(rendered), 2)
27
27
  self.assertIn("<tools>", rendered[0]['content'])
@@ -29,29 +29,30 @@ class TestContextManagement(unittest.IsolatedAsyncioTestCase):
29
29
 
30
30
  async def test_b_provider_passthrough_and_refresh(self):
31
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)
32
+ # 我们真正关心的是 _fetch_content 是否被不必要地调用
33
+ # 所以我们 mock 底层的它,而不是 refresh 方法
34
+ original_fetch_content = self.files_provider._fetch_content
35
+ self.files_provider._fetch_content = AsyncMock(side_effect=original_fetch_content)
36
36
 
37
37
  messages = Messages(UserMessage(self.files_provider))
38
38
 
39
- # 1. 初始文件内容为空,渲染一次
39
+ # 1. 首次刷新
40
40
  self.files_provider.update("path1", "content1")
41
- await messages.render()
41
+ await messages.refresh()
42
42
  # _fetch_content 应该被调用了 1 次
43
43
  self.assertEqual(self.files_provider._fetch_content.call_count, 1)
44
44
 
45
- # 2. 再次渲染,内容未变,不应再次调用 _fetch_content
46
- await messages.render()
45
+ # 2. 再次刷新,内容未变,不应再次调用 _fetch_content
46
+ await messages.refresh()
47
47
  # 调用次数应该仍然是 1,证明缓存生效
48
48
  self.assertEqual(self.files_provider._fetch_content.call_count, 1)
49
49
 
50
50
  # 3. 更新文件内容,这会标记 provider 为 stale
51
51
  self.files_provider.update("path2", "content2")
52
52
 
53
- # 4. 再次渲染,现在应该会重新调用 _fetch_content
54
- rendered = await messages.render()
53
+ # 4. 再次刷新,现在应该会重新调用 _fetch_content
54
+ await messages.refresh()
55
+ rendered = messages.render()
55
56
  # 调用次数应该变为 2
56
57
  self.assertEqual(self.files_provider._fetch_content.call_count, 2)
57
58
  # 并且渲染结果包含了新内容
@@ -65,7 +66,7 @@ class TestContextManagement(unittest.IsolatedAsyncioTestCase):
65
66
  )
66
67
 
67
68
  # 验证初始状态
68
- initial_rendered = await messages.render()
69
+ initial_rendered = await messages.render_latest()
69
70
  self.assertTrue(any("<tools>" in msg['content'] for msg in initial_rendered if msg['role'] == 'system'))
70
71
 
71
72
  # 全局弹出 'tools' Provider
@@ -73,20 +74,153 @@ class TestContextManagement(unittest.IsolatedAsyncioTestCase):
73
74
  self.assertIs(popped_tools_provider, self.tools_provider)
74
75
 
75
76
  # 验证 pop 后的状态
76
- rendered_after_pop = await messages.render()
77
+ rendered_after_pop = messages.render()
77
78
  self.assertFalse(any("<tools>" in msg['content'] for msg in rendered_after_pop if msg['role'] == 'system'))
78
79
 
79
80
  # 通过索引将弹出的provider插入到UserMessage的开头
80
- messages[1].content.insert(0, popped_tools_provider)
81
+ messages[1].insert(0, popped_tools_provider)
81
82
 
82
83
  # 验证 insert 后的状态
83
- rendered_after_insert = await messages.render()
84
+ rendered_after_insert = messages.render()
84
85
  user_message_content = next(msg['content'] for msg in rendered_after_insert if msg['role'] == 'user')
85
86
  self.assertTrue(user_message_content.startswith("<tools>"))
86
87
 
88
+ async def test_d_multimodal_rendering(self):
89
+ """测试多模态(文本+图片)渲染"""
90
+ # Create a dummy image file for the test
91
+ dummy_image_path = "test_dummy_image.png"
92
+ with open(dummy_image_path, "w") as f:
93
+ f.write("dummy content")
94
+
95
+ messages = Messages(
96
+ UserMessage(
97
+ Texts("prompt", "Describe the image."),
98
+ Images(dummy_image_path) # Test with optional name
99
+ )
100
+ )
101
+
102
+ rendered = await messages.render_latest()
103
+ self.assertEqual(len(rendered), 1)
104
+
105
+ content = rendered[0]['content']
106
+ self.assertIsInstance(content, list)
107
+ self.assertEqual(len(content), 2)
108
+
109
+ # Check text part
110
+ self.assertEqual(content[0]['type'], 'text')
111
+ self.assertEqual(content[0]['text'], 'Describe the image.')
112
+
113
+ # Check image part
114
+ self.assertEqual(content[1]['type'], 'image_url')
115
+ self.assertIn('data:image/png;base64,', content[1]['image_url']['url'])
116
+
117
+ # Clean up the dummy file
118
+ import os
119
+ os.remove(dummy_image_path)
120
+
121
+ async def test_e_multimodal_type_switching(self):
122
+ """测试多模态消息在pop图片后是否能正确回退到字符串渲染"""
123
+ dummy_image_path = "test_dummy_image_2.png"
124
+ with open(dummy_image_path, "w") as f:
125
+ f.write("dummy content")
126
+
127
+ messages = Messages(
128
+ UserMessage(
129
+ Texts("prefix", "Look at this:"),
130
+ Images(dummy_image_path, name="image"), # Explicit name for popping
131
+ Texts("suffix", "Any thoughts?")
132
+ )
133
+ )
134
+
135
+ # 1. Initial multimodal render
136
+ rendered_multi = await messages.render_latest()
137
+ content_multi = rendered_multi[0]['content']
138
+ self.assertIsInstance(content_multi, list)
139
+ self.assertEqual(len(content_multi), 3) # prefix, image, suffix
140
+
141
+ # 2. Pop the image
142
+ popped_image = messages.pop("image")
143
+ self.assertIsNotNone(popped_image)
144
+
145
+ # 3. Render again, should fall back to string content
146
+ rendered_str = messages.render() # No refresh needed
147
+ content_str = rendered_str[0]['content']
148
+ self.assertIsInstance(content_str, str)
149
+ self.assertEqual(content_str, "Look at this:\n\nAny thoughts?")
150
+
151
+ # Clean up
152
+ import os
153
+ os.remove(dummy_image_path)
154
+
155
+ def test_f_message_merging(self):
156
+ """测试初始化和追加时自动合并消息的功能"""
157
+ # 1. Test merging during initialization
158
+ messages = Messages(
159
+ UserMessage(Texts("part1", "Hello,")),
160
+ UserMessage(Texts("part2", "world!")),
161
+ SystemMessage(Texts("system", "System prompt.")),
162
+ UserMessage(Texts("part3", "How are you?"))
163
+ )
164
+ # Should be merged into: User, System, User
165
+ self.assertEqual(len(messages), 3)
166
+ self.assertEqual(len(messages[0]._items), 2) # First UserMessage has 2 items
167
+ self.assertEqual(messages[0]._items[1].name, "part2")
168
+ self.assertEqual(messages[1].role, "system")
169
+ self.assertEqual(messages[2].role, "user")
170
+
171
+ # 2. Test merging during append
172
+ messages.append(UserMessage(Texts("part4", "I am fine.")))
173
+ self.assertEqual(len(messages), 3) # Still 3 messages
174
+ self.assertEqual(len(messages[2]._items), 2) # Last UserMessage now has 2 items
175
+ self.assertEqual(messages[2]._items[1].name, "part4")
176
+
177
+ # 3. Test appending a different role
178
+ messages.append(SystemMessage(Texts("system2", "Another prompt.")))
179
+ self.assertEqual(len(messages), 4) # Should not merge
180
+ self.assertEqual(messages[3].role, "system")
181
+
182
+ async def test_g_state_inconsistency_on_direct_message_modification(self):
183
+ """
184
+ 测试当直接在 Message 对象上执行 pop 操作时,
185
+ 顶层 Messages 对象的 _providers_index 是否会产生不一致。
186
+ """
187
+ messages = Messages(
188
+ SystemMessage(self.system_prompt_provider, self.tools_provider),
189
+ UserMessage(self.files_provider)
190
+ )
191
+
192
+ # 0. 先刷新一次,确保所有 provider 的 cache 都已填充
193
+ await messages.refresh()
194
+
195
+ # 1. 初始状态:'tools' 提供者应该在索引中
196
+ self.assertIsNotNone(messages.provider("tools"), "初始状态下 'tools' 提供者应该能被找到")
197
+ self.assertIs(messages.provider("tools"), self.tools_provider)
198
+
199
+ # 2. 直接在子消息对象上执行 pop 操作
200
+ system_message = messages[0]
201
+ popped_provider = system_message.pop("tools")
202
+
203
+ # 验证是否真的从 Message 对象中弹出了
204
+ self.assertIs(popped_provider, self.tools_provider, "应该从 SystemMessage 中成功弹出 provider")
205
+ self.assertNotIn(self.tools_provider, system_message.providers(), "provider 不应再存在于 SystemMessage 的 providers 列表中")
206
+
207
+ # 3. 核心问题:检查顶层 Messages 的索引
208
+ # 在理想情况下,直接修改子消息应该同步更新顶层索引。
209
+ # 因此,我们断言 provider 现在应该是找不到的。这个测试现在应该会失败。
210
+ provider_after_pop = messages.provider("tools")
211
+ self.assertIsNone(provider_after_pop, "BUG: 直接从子消息中 pop 后,顶层索引未同步,仍然可以找到 provider")
212
+
213
+ # 4. 进一步验证:渲染结果和索引内容不一致
214
+ # 渲染结果应该不再包含 tools 内容,因为 Message 对象本身是正确的
215
+ rendered_messages = messages.render()
216
+ self.assertGreater(len(rendered_messages), 0, "渲染后的消息列表不应为空")
217
+ rendered_content = rendered_messages[0]['content']
218
+ self.assertNotIn("<tools>", rendered_content, "渲染结果中不应再包含 'tools' 的内容,证明数据源已更新")
219
+
220
+
87
221
  if __name__ == '__main__':
88
222
  # 为了在普通脚本环境中运行,添加这两行
89
223
  suite = unittest.TestSuite()
90
224
  suite.addTest(unittest.makeSuite(TestContextManagement))
91
225
  runner = unittest.TextTestRunner()
92
- runner.run(suite)
226
+ runner.run(suite)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aient
3
- Version: 1.1.90
3
+ Version: 1.1.91
4
4
  Summary: Aient: The Awakening of Agent.
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -1,7 +1,7 @@
1
1
  aient/__init__.py,sha256=SRfF7oDVlOOAi6nGKiJIUK6B_arqYLO9iSMp-2IZZps,21
2
- aient/architext/test.py,sha256=nLrF8AkVO1minNf8Us1FS4YkGuXhwMTp8E-VRUvUXh8,4115
2
+ aient/architext/test.py,sha256=n2hUwHOPhOdpHRLCPuqeR-r4hGK43SF1WKaLdivdmC8,10021
3
3
  aient/architext/architext/__init__.py,sha256=79Ih1151rfcqZdr7F8HSZSTs_iT2SKd1xCkehMsXeXs,19
4
- aient/architext/architext/core.py,sha256=l-bjazODG5tkYQ_fVS8df-xg0TFkBK5oVOF8Uz5Wdqg,7843
4
+ aient/architext/architext/core.py,sha256=vww5nxNyoGXmWwmNEfLMGfv7k4GhCDZSiRqKzyqYv_8,10070
5
5
  aient/core/__init__.py,sha256=NxjebTlku35S4Dzr16rdSqSTWUvvwEeACe8KvHJnjPg,34
6
6
  aient/core/log_config.py,sha256=kz2_yJv1p-o3lUQOwA3qh-LSc3wMHv13iCQclw44W9c,274
7
7
  aient/core/models.py,sha256=KMlCRLjtq1wQHZTJGqnbWhPS2cHq6eLdnk7peKDrzR8,7490
@@ -33,8 +33,8 @@ aient/plugins/write_file.py,sha256=Jt8fOEwqhYiSWpCbwfAr1xoi_BmFnx3076GMhuL06uI,3
33
33
  aient/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
34
  aient/utils/prompt.py,sha256=UcSzKkFE4-h_1b6NofI6xgk3GoleqALRKY8VBaXLjmI,11311
35
35
  aient/utils/scripts.py,sha256=VqtK4RFEx7KxkmcqG3lFDS1DxoNlFFGErEjopVcc8IE,40974
36
- aient-1.1.90.dist-info/licenses/LICENSE,sha256=XNdbcWldt0yaNXXWB_Bakoqnxb3OVhUft4MgMA_71ds,1051
37
- aient-1.1.90.dist-info/METADATA,sha256=IHi6EZF4kBr85Ape07JXmYtzBlwha3yNkst8uf9oSEQ,4842
38
- aient-1.1.90.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
39
- aient-1.1.90.dist-info/top_level.txt,sha256=3oXzrP5sAVvyyqabpeq8A2_vfMtY554r4bVE-OHBrZk,6
40
- aient-1.1.90.dist-info/RECORD,,
36
+ aient-1.1.91.dist-info/licenses/LICENSE,sha256=XNdbcWldt0yaNXXWB_Bakoqnxb3OVhUft4MgMA_71ds,1051
37
+ aient-1.1.91.dist-info/METADATA,sha256=SByLoF4YtLoR1QxQ1qmVim3FbMHQIgD-b1OcxliO4bw,4842
38
+ aient-1.1.91.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
39
+ aient-1.1.91.dist-info/top_level.txt,sha256=3oXzrP5sAVvyyqabpeq8A2_vfMtY554r4bVE-OHBrZk,6
40
+ aient-1.1.91.dist-info/RECORD,,
File without changes