aient 1.2.1__tar.gz → 1.2.3__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.
- {aient-1.2.1 → aient-1.2.3}/PKG-INFO +1 -1
- {aient-1.2.1 → aient-1.2.3}/aient/architext/architext/core.py +67 -21
- {aient-1.2.1 → aient-1.2.3}/aient/architext/test/test.py +56 -0
- {aient-1.2.1 → aient-1.2.3}/aient.egg-info/PKG-INFO +1 -1
- {aient-1.2.1 → aient-1.2.3}/pyproject.toml +1 -1
- {aient-1.2.1 → aient-1.2.3}/LICENSE +0 -0
- {aient-1.2.1 → aient-1.2.3}/README.md +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient/__init__.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient/architext/architext/__init__.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient/architext/test/openai_client.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient/architext/test/test_save_load.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient/core/__init__.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient/core/log_config.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient/core/models.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient/core/request.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient/core/response.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient/core/test/test_base_api.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient/core/test/test_geminimask.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient/core/test/test_image.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient/core/test/test_payload.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient/core/utils.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient/models/__init__.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient/models/audio.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient/models/base.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient/models/chatgpt.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient/plugins/__init__.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient/plugins/arXiv.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient/plugins/config.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient/plugins/excute_command.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient/plugins/get_time.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient/plugins/image.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient/plugins/list_directory.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient/plugins/read_file.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient/plugins/read_image.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient/plugins/readonly.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient/plugins/registry.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient/plugins/run_python.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient/plugins/websearch.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient/plugins/write_file.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient/utils/__init__.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient/utils/prompt.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient/utils/scripts.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient.egg-info/SOURCES.txt +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient.egg-info/dependency_links.txt +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient.egg-info/requires.txt +0 -0
- {aient-1.2.1 → aient-1.2.3}/aient.egg-info/top_level.txt +0 -0
- {aient-1.2.1 → aient-1.2.3}/setup.cfg +0 -0
- {aient-1.2.1 → aient-1.2.3}/test/test_Web_crawler.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/test/test_ddg_search.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/test/test_google_search.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/test/test_ollama.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/test/test_plugin.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/test/test_search.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/test/test_url.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/test/test_whisper.py +0 -0
- {aient-1.2.1 → aient-1.2.3}/test/test_yjh.py +0 -0
@@ -142,6 +142,15 @@ class Texts(ContextProvider):
|
|
142
142
|
async def render(self) -> Optional[str]:
|
143
143
|
return self.content
|
144
144
|
|
145
|
+
def __eq__(self, other):
|
146
|
+
if not isinstance(other, Texts):
|
147
|
+
return NotImplemented
|
148
|
+
# If either object is dynamic, they are only equal if they are the exact same object.
|
149
|
+
if self._is_dynamic or (hasattr(other, '_is_dynamic') and other._is_dynamic):
|
150
|
+
return self is other
|
151
|
+
# For static content, compare the actual content.
|
152
|
+
return self.content == other.content
|
153
|
+
|
145
154
|
class Tools(ContextProvider):
|
146
155
|
def __init__(self, tools_json: Optional[List[Dict]] = None, name: str = "tools"):
|
147
156
|
super().__init__(name)
|
@@ -154,6 +163,11 @@ class Tools(ContextProvider):
|
|
154
163
|
return None
|
155
164
|
return f"<tools>{str(self._tools_json)}</tools>"
|
156
165
|
|
166
|
+
def __eq__(self, other):
|
167
|
+
if not isinstance(other, Tools):
|
168
|
+
return NotImplemented
|
169
|
+
return self._tools_json == other._tools_json
|
170
|
+
|
157
171
|
class Files(ContextProvider):
|
158
172
|
def __init__(self, *paths: Union[str, List[str]], name: str = "files"):
|
159
173
|
super().__init__(name)
|
@@ -230,6 +244,11 @@ class Files(ContextProvider):
|
|
230
244
|
if not self._files: return None
|
231
245
|
return "<latest_file_content>" + "\n".join([f"<file><file_path>{p}</file_path><file_content>{c}</file_content></file>" for p, c in self._files.items()]) + "\n</latest_file_content>"
|
232
246
|
|
247
|
+
def __eq__(self, other):
|
248
|
+
if not isinstance(other, Files):
|
249
|
+
return NotImplemented
|
250
|
+
return self._files == other._files
|
251
|
+
|
233
252
|
class Images(ContextProvider):
|
234
253
|
def __init__(self, url: str, name: Optional[str] = None):
|
235
254
|
super().__init__(name or url)
|
@@ -250,6 +269,11 @@ class Images(ContextProvider):
|
|
250
269
|
logging.warning(f"Image file not found: {self.url}. Skipping.")
|
251
270
|
return None # Or handle error appropriately
|
252
271
|
|
272
|
+
def __eq__(self, other):
|
273
|
+
if not isinstance(other, Images):
|
274
|
+
return NotImplemented
|
275
|
+
return self.url == other.url
|
276
|
+
|
253
277
|
# 3. 消息类 (已合并 MessageContent)
|
254
278
|
class Message(ABC):
|
255
279
|
def __init__(self, role: str, *initial_items: Union[ContextProvider, str, list]):
|
@@ -297,6 +321,16 @@ class Message(ABC):
|
|
297
321
|
self._items: List[ContextProvider] = processed_items
|
298
322
|
self._parent_messages: Optional['Messages'] = None
|
299
323
|
|
324
|
+
@property
|
325
|
+
def content(self) -> Optional[Union[str, List[Dict[str, Any]]]]:
|
326
|
+
"""
|
327
|
+
Renders the message content.
|
328
|
+
For simple text messages, returns a string.
|
329
|
+
For multimodal messages, returns a list of content blocks.
|
330
|
+
"""
|
331
|
+
rendered_dict = self.to_dict()
|
332
|
+
return rendered_dict.get('content') if rendered_dict else None
|
333
|
+
|
300
334
|
def _render_content(self) -> str:
|
301
335
|
final_parts = []
|
302
336
|
for item in self._items:
|
@@ -346,26 +380,36 @@ class Message(ABC):
|
|
346
380
|
return type(self)(*new_items)
|
347
381
|
return NotImplemented
|
348
382
|
|
349
|
-
def __getitem__(self, key: str) -> Any:
|
383
|
+
def __getitem__(self, key: Union[str, int]) -> Any:
|
350
384
|
"""
|
351
|
-
使得 Message 对象支持字典风格的访问 (e.g., message['content'])
|
385
|
+
使得 Message 对象支持字典风格的访问 (e.g., message['content'])
|
386
|
+
和列表风格的索引访问 (e.g., message[-1])。
|
352
387
|
"""
|
353
|
-
if key
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
388
|
+
if isinstance(key, str):
|
389
|
+
if key == 'role':
|
390
|
+
return self.role
|
391
|
+
elif key == 'content':
|
392
|
+
# 直接调用 to_dict 并提取 'content',确保逻辑一致
|
393
|
+
rendered_dict = self.to_dict()
|
394
|
+
return rendered_dict.get('content') if rendered_dict else None
|
395
|
+
# 对于 tool_calls 等特殊属性,也通过 to_dict 获取
|
396
|
+
elif hasattr(self, key):
|
397
|
+
rendered_dict = self.to_dict()
|
398
|
+
if rendered_dict and key in rendered_dict:
|
399
|
+
return rendered_dict[key]
|
400
|
+
|
401
|
+
# 如果在对象本身或其 to_dict() 中都找不到,则引发 KeyError
|
402
|
+
if hasattr(self, key):
|
403
|
+
return getattr(self, key)
|
404
|
+
raise KeyError(f"'{key}'")
|
405
|
+
elif isinstance(key, int):
|
406
|
+
return self._items[key]
|
407
|
+
else:
|
408
|
+
raise TypeError(f"Message indices must be integers or strings, not {type(key).__name__}")
|
409
|
+
|
410
|
+
def __len__(self) -> int:
|
411
|
+
"""返回消息中 provider 的数量。"""
|
412
|
+
return len(self._items)
|
369
413
|
|
370
414
|
def __repr__(self): return f"Message(role='{self.role}', items={[i.name for i in self._items]})"
|
371
415
|
def __bool__(self) -> bool:
|
@@ -444,15 +488,17 @@ class ToolCalls(Message):
|
|
444
488
|
class ToolResults(Message):
|
445
489
|
"""Represents a tool message with the result of a single tool call."""
|
446
490
|
def __init__(self, tool_call_id: str, content: str):
|
447
|
-
|
491
|
+
# We pass a Texts provider to the parent so it can be rendered,
|
492
|
+
# but the primary way to access content for ToolResults is via its dict representation.
|
493
|
+
super().__init__("tool", Texts(text=content))
|
448
494
|
self.tool_call_id = tool_call_id
|
449
|
-
self.
|
495
|
+
self._content = content
|
450
496
|
|
451
497
|
def to_dict(self) -> Dict[str, Any]:
|
452
498
|
return {
|
453
499
|
"role": self.role,
|
454
500
|
"tool_call_id": self.tool_call_id,
|
455
|
-
"content": self.
|
501
|
+
"content": self._content
|
456
502
|
}
|
457
503
|
|
458
504
|
# 4. 顶层容器: Messages
|
@@ -1102,6 +1102,62 @@ Current time: {Texts(lambda: datetime.now().strftime("%Y-%m-%d %H:%M:%S"))}
|
|
1102
1102
|
self.assertIn("Third explanation.", rendered_final[0]['content'])
|
1103
1103
|
self.assertIn("Some other text.", rendered_final[0]['content'])
|
1104
1104
|
|
1105
|
+
async def test_z9_rolemessage_content_access(self):
|
1106
|
+
"""测试是否支持 RoleMessage.content 来访问渲染好的内容"""
|
1107
|
+
# 1. 创建一个简单的 UserMessage
|
1108
|
+
user_message = UserMessage("你好, Architext!")
|
1109
|
+
# 对于简单的 Texts, refresh 不是必须的, 但这是个好习惯
|
1110
|
+
# Message 类本身没有 refresh, 调用其 providers 的 refresh
|
1111
|
+
for p in user_message.providers():
|
1112
|
+
await p.refresh()
|
1113
|
+
|
1114
|
+
# 2. 直接访问 .content 属性
|
1115
|
+
# 在实现该功能前,这行代码会因 AttributeError 而失败
|
1116
|
+
self.assertEqual(user_message.content, "你好, Architext!")
|
1117
|
+
|
1118
|
+
# 3. 创建一个多模态消息
|
1119
|
+
multimodal_message = AssistantMessage(
|
1120
|
+
"这是一张图片:",
|
1121
|
+
Images(url="data:image/png;base64,FAKE_IMG_DATA")
|
1122
|
+
)
|
1123
|
+
for p in multimodal_message.providers():
|
1124
|
+
await p.refresh()
|
1125
|
+
|
1126
|
+
# 4. 访问多模态消息的 .content 属性,期望返回一个列表
|
1127
|
+
content_list = multimodal_message.content
|
1128
|
+
self.assertIsInstance(content_list, list)
|
1129
|
+
self.assertEqual(len(content_list), 2)
|
1130
|
+
self.assertEqual(content_list[0]['type'], 'text')
|
1131
|
+
self.assertEqual(content_list[1]['type'], 'image_url')
|
1132
|
+
|
1133
|
+
# 5. 测试通过 RoleMessage 工厂创建的消息
|
1134
|
+
role_message = RoleMessage('user', "通过工厂创建的内容")
|
1135
|
+
for p in role_message.providers():
|
1136
|
+
await p.refresh()
|
1137
|
+
self.assertEqual(role_message.content, "通过工厂创建的内容")
|
1138
|
+
|
1139
|
+
async def test_za_message_indexing_and_length(self):
|
1140
|
+
"""测试 Message 对象是否支持通过索引访问 provider 以及获取长度"""
|
1141
|
+
# 1. 创建一个 UserMessage
|
1142
|
+
mess = UserMessage(
|
1143
|
+
Texts("some instruction"),
|
1144
|
+
Texts("hi", name="done")
|
1145
|
+
)
|
1146
|
+
|
1147
|
+
# 2. 测试获取长度
|
1148
|
+
# 这在实现 __len__ 之前会失败
|
1149
|
+
self.assertEqual(len(mess), 2)
|
1150
|
+
|
1151
|
+
# 3. 测试通过索引访问
|
1152
|
+
# 这在修改 __getitem__ 之前会失败
|
1153
|
+
self.assertEqual(mess[-1].name, "done")
|
1154
|
+
self.assertEqual(mess[0].name, Texts("some instruction").name)
|
1155
|
+
self.assertEqual(mess[0], Texts("some instruction"))
|
1156
|
+
|
1157
|
+
# 4. 测试索引越界
|
1158
|
+
with self.assertRaises(IndexError):
|
1159
|
+
_ = mess[2]
|
1160
|
+
|
1105
1161
|
# ==============================================================================
|
1106
1162
|
# 6. 演示
|
1107
1163
|
# ==============================================================================
|
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
|
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
|