aient 1.2.0__tar.gz → 1.2.2__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.2.0 → aient-1.2.2}/PKG-INFO +1 -1
  2. {aient-1.2.0 → aient-1.2.2}/aient/architext/architext/core.py +73 -13
  3. {aient-1.2.0 → aient-1.2.2}/aient/architext/test/test.py +82 -0
  4. {aient-1.2.0 → aient-1.2.2}/aient.egg-info/PKG-INFO +1 -1
  5. {aient-1.2.0 → aient-1.2.2}/pyproject.toml +1 -1
  6. {aient-1.2.0 → aient-1.2.2}/LICENSE +0 -0
  7. {aient-1.2.0 → aient-1.2.2}/README.md +0 -0
  8. {aient-1.2.0 → aient-1.2.2}/aient/__init__.py +0 -0
  9. {aient-1.2.0 → aient-1.2.2}/aient/architext/architext/__init__.py +0 -0
  10. {aient-1.2.0 → aient-1.2.2}/aient/architext/test/openai_client.py +0 -0
  11. {aient-1.2.0 → aient-1.2.2}/aient/architext/test/test_save_load.py +0 -0
  12. {aient-1.2.0 → aient-1.2.2}/aient/core/__init__.py +0 -0
  13. {aient-1.2.0 → aient-1.2.2}/aient/core/log_config.py +0 -0
  14. {aient-1.2.0 → aient-1.2.2}/aient/core/models.py +0 -0
  15. {aient-1.2.0 → aient-1.2.2}/aient/core/request.py +0 -0
  16. {aient-1.2.0 → aient-1.2.2}/aient/core/response.py +0 -0
  17. {aient-1.2.0 → aient-1.2.2}/aient/core/test/test_base_api.py +0 -0
  18. {aient-1.2.0 → aient-1.2.2}/aient/core/test/test_geminimask.py +0 -0
  19. {aient-1.2.0 → aient-1.2.2}/aient/core/test/test_image.py +0 -0
  20. {aient-1.2.0 → aient-1.2.2}/aient/core/test/test_payload.py +0 -0
  21. {aient-1.2.0 → aient-1.2.2}/aient/core/utils.py +0 -0
  22. {aient-1.2.0 → aient-1.2.2}/aient/models/__init__.py +0 -0
  23. {aient-1.2.0 → aient-1.2.2}/aient/models/audio.py +0 -0
  24. {aient-1.2.0 → aient-1.2.2}/aient/models/base.py +0 -0
  25. {aient-1.2.0 → aient-1.2.2}/aient/models/chatgpt.py +0 -0
  26. {aient-1.2.0 → aient-1.2.2}/aient/plugins/__init__.py +0 -0
  27. {aient-1.2.0 → aient-1.2.2}/aient/plugins/arXiv.py +0 -0
  28. {aient-1.2.0 → aient-1.2.2}/aient/plugins/config.py +0 -0
  29. {aient-1.2.0 → aient-1.2.2}/aient/plugins/excute_command.py +0 -0
  30. {aient-1.2.0 → aient-1.2.2}/aient/plugins/get_time.py +0 -0
  31. {aient-1.2.0 → aient-1.2.2}/aient/plugins/image.py +0 -0
  32. {aient-1.2.0 → aient-1.2.2}/aient/plugins/list_directory.py +0 -0
  33. {aient-1.2.0 → aient-1.2.2}/aient/plugins/read_file.py +0 -0
  34. {aient-1.2.0 → aient-1.2.2}/aient/plugins/read_image.py +0 -0
  35. {aient-1.2.0 → aient-1.2.2}/aient/plugins/readonly.py +0 -0
  36. {aient-1.2.0 → aient-1.2.2}/aient/plugins/registry.py +0 -0
  37. {aient-1.2.0 → aient-1.2.2}/aient/plugins/run_python.py +0 -0
  38. {aient-1.2.0 → aient-1.2.2}/aient/plugins/websearch.py +0 -0
  39. {aient-1.2.0 → aient-1.2.2}/aient/plugins/write_file.py +0 -0
  40. {aient-1.2.0 → aient-1.2.2}/aient/utils/__init__.py +0 -0
  41. {aient-1.2.0 → aient-1.2.2}/aient/utils/prompt.py +0 -0
  42. {aient-1.2.0 → aient-1.2.2}/aient/utils/scripts.py +0 -0
  43. {aient-1.2.0 → aient-1.2.2}/aient.egg-info/SOURCES.txt +0 -0
  44. {aient-1.2.0 → aient-1.2.2}/aient.egg-info/dependency_links.txt +0 -0
  45. {aient-1.2.0 → aient-1.2.2}/aient.egg-info/requires.txt +0 -0
  46. {aient-1.2.0 → aient-1.2.2}/aient.egg-info/top_level.txt +0 -0
  47. {aient-1.2.0 → aient-1.2.2}/setup.cfg +0 -0
  48. {aient-1.2.0 → aient-1.2.2}/test/test_Web_crawler.py +0 -0
  49. {aient-1.2.0 → aient-1.2.2}/test/test_ddg_search.py +0 -0
  50. {aient-1.2.0 → aient-1.2.2}/test/test_google_search.py +0 -0
  51. {aient-1.2.0 → aient-1.2.2}/test/test_ollama.py +0 -0
  52. {aient-1.2.0 → aient-1.2.2}/test/test_plugin.py +0 -0
  53. {aient-1.2.0 → aient-1.2.2}/test/test_search.py +0 -0
  54. {aient-1.2.0 → aient-1.2.2}/test/test_url.py +0 -0
  55. {aient-1.2.0 → aient-1.2.2}/test/test_whisper.py +0 -0
  56. {aient-1.2.0 → aient-1.2.2}/test/test_yjh.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aient
3
- Version: 1.2.0
3
+ Version: 1.2.2
4
4
  Summary: Aient: The Awakening of Agent.
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -10,6 +10,30 @@ from dataclasses import dataclass
10
10
  from abc import ABC, abstractmethod
11
11
  from typing import List, Dict, Any, Optional, Union, Callable
12
12
 
13
+ # A wrapper to manage multiple providers with the same name
14
+ class ProviderGroup:
15
+ """A container for multiple providers that share the same name, allowing for bulk operations."""
16
+ def __init__(self, providers: List['ContextProvider']):
17
+ self._providers = providers
18
+ def __getitem__(self, key: int) -> 'ContextProvider':
19
+ """Allows accessing providers by index, e.g., group[-1]."""
20
+ return self._providers[key]
21
+ def __iter__(self):
22
+ """Allows iterating over the providers."""
23
+ return iter(self._providers)
24
+ def __len__(self) -> int:
25
+ """Returns the number of providers in the group."""
26
+ return len(self._providers)
27
+ @property
28
+ def visible(self) -> List[bool]:
29
+ """Gets the visibility of all providers in the group."""
30
+ return [p.visible for p in self._providers]
31
+ @visible.setter
32
+ def visible(self, value: bool):
33
+ """Sets the visibility for all providers in the group."""
34
+ for p in self._providers:
35
+ p.visible = value
36
+
13
37
  # Global, thread-safe registry for providers created within f-strings
14
38
  _fstring_provider_registry = {}
15
39
  _registry_lock = threading.Lock()
@@ -273,6 +297,16 @@ class Message(ABC):
273
297
  self._items: List[ContextProvider] = processed_items
274
298
  self._parent_messages: Optional['Messages'] = None
275
299
 
300
+ @property
301
+ def content(self) -> Optional[Union[str, List[Dict[str, Any]]]]:
302
+ """
303
+ Renders the message content.
304
+ For simple text messages, returns a string.
305
+ For multimodal messages, returns a list of content blocks.
306
+ """
307
+ rendered_dict = self.to_dict()
308
+ return rendered_dict.get('content') if rendered_dict else None
309
+
276
310
  def _render_content(self) -> str:
277
311
  final_parts = []
278
312
  for item in self._items:
@@ -420,15 +454,17 @@ class ToolCalls(Message):
420
454
  class ToolResults(Message):
421
455
  """Represents a tool message with the result of a single tool call."""
422
456
  def __init__(self, tool_call_id: str, content: str):
423
- super().__init__("tool")
457
+ # We pass a Texts provider to the parent so it can be rendered,
458
+ # but the primary way to access content for ToolResults is via its dict representation.
459
+ super().__init__("tool", Texts(text=content))
424
460
  self.tool_call_id = tool_call_id
425
- self.content = content
461
+ self._content = content
426
462
 
427
463
  def to_dict(self) -> Dict[str, Any]:
428
464
  return {
429
465
  "role": self.role,
430
466
  "tool_call_id": self.tool_call_id,
431
- "content": self.content
467
+ "content": self._content
432
468
  }
433
469
 
434
470
  # 4. 顶层容器: Messages
@@ -436,22 +472,40 @@ class Messages:
436
472
  def __init__(self, *initial_messages: Message):
437
473
  from typing import Tuple
438
474
  self._messages: List[Message] = []
439
- self._providers_index: Dict[str, Tuple[ContextProvider, Message]] = {}
475
+ self._providers_index: Dict[str, List[Tuple[ContextProvider, Message]]] = {}
440
476
  if initial_messages:
441
477
  for msg in initial_messages:
442
478
  self.append(msg)
443
479
 
444
480
  def _notify_provider_added(self, provider: ContextProvider, message: Message):
445
481
  if provider.name not in self._providers_index:
446
- self._providers_index[provider.name] = (provider, message)
482
+ self._providers_index[provider.name] = []
483
+ self._providers_index[provider.name].append((provider, message))
447
484
 
448
485
  def _notify_provider_removed(self, provider: ContextProvider):
449
486
  if provider.name in self._providers_index:
450
- del self._providers_index[provider.name]
487
+ # Create a new list excluding the provider to be removed.
488
+ # Comparing by object identity (`is`) is crucial here.
489
+ providers_list = self._providers_index[provider.name]
490
+ new_list = [(p, m) for p, m in providers_list if p is not provider]
491
+
492
+ if not new_list:
493
+ # If the list becomes empty, remove the key from the dictionary.
494
+ del self._providers_index[provider.name]
495
+ else:
496
+ # Otherwise, update the dictionary with the new list.
497
+ self._providers_index[provider.name] = new_list
451
498
 
452
- def provider(self, name: str) -> Optional[ContextProvider]:
453
- indexed = self._providers_index.get(name)
454
- return indexed[0] if indexed else None
499
+ def provider(self, name: str) -> Optional[Union[ContextProvider, ProviderGroup]]:
500
+ indexed_list = self._providers_index.get(name)
501
+ if not indexed_list:
502
+ return None
503
+
504
+ providers = [p for p, m in indexed_list]
505
+ if len(providers) == 1:
506
+ return providers[0]
507
+ else:
508
+ return ProviderGroup(providers)
455
509
 
456
510
  def pop(self, key: Optional[Union[str, int]] = None) -> Union[Optional[ContextProvider], Optional[Message]]:
457
511
  # If no key is provided, pop the last message.
@@ -459,10 +513,13 @@ class Messages:
459
513
  key = len(self._messages) - 1
460
514
 
461
515
  if isinstance(key, str):
462
- indexed = self._providers_index.get(key)
463
- if not indexed:
516
+ indexed_list = self._providers_index.get(key)
517
+ if not indexed_list:
464
518
  return None
465
- _provider, parent_message = indexed
519
+ # Pop the first one found, which is consistent with how pop usually works
520
+ _provider, parent_message = indexed_list[0]
521
+ # The actual removal from _providers_index happens in _notify_provider_removed
522
+ # which is called by message.pop()
466
523
  return parent_message.pop(key)
467
524
  elif isinstance(key, int):
468
525
  try:
@@ -481,7 +538,10 @@ class Messages:
481
538
  return None
482
539
 
483
540
  async def refresh(self):
484
- tasks = [provider.refresh() for provider, _ in self._providers_index.values()]
541
+ tasks = []
542
+ for provider_list in self._providers_index.values():
543
+ for provider, _ in provider_list:
544
+ tasks.append(provider.refresh())
485
545
  await asyncio.gather(*tasks)
486
546
 
487
547
  def render(self) -> List[Dict[str, Any]]:
@@ -1053,6 +1053,88 @@ Current time: {Texts(lambda: datetime.now().strftime("%Y-%m-%d %H:%M:%S"))}
1053
1053
  self.assertEqual(len(rendered_visible_again), 1)
1054
1054
  self.assertEqual(rendered_visible_again[0]['content'], "Hello, World!")
1055
1055
 
1056
+ async def test_z8_bulk_provider_visibility_control(self):
1057
+ """测试通过名称批量控制和豁免provider的可见性"""
1058
+ # 1. 创建多个同名 provider
1059
+ messages = Messages(
1060
+ UserMessage(
1061
+ Texts("First explanation.", name="explanation"),
1062
+ Texts("Second explanation.", name="explanation"),
1063
+ Texts("Some other text."),
1064
+ Texts("Third explanation.", name="explanation")
1065
+ )
1066
+ )
1067
+
1068
+ # 2. 初始渲染,所有 "explanation" 都应该可见
1069
+ rendered_initial = await messages.render_latest()
1070
+ self.assertIn("First explanation.", rendered_initial[0]['content'])
1071
+ self.assertIn("Second explanation.", rendered_initial[0]['content'])
1072
+ self.assertIn("Third explanation.", rendered_initial[0]['content'])
1073
+
1074
+ # 3. 获取所有名为 "explanation" 的 provider
1075
+ explanation_providers = messages.provider("explanation")
1076
+ self.assertIsInstance(explanation_providers, ProviderGroup)
1077
+ self.assertEqual(len(explanation_providers), 3)
1078
+
1079
+ # 4. 将所有 "explanation" provider 设置为不可见
1080
+ # 这是需要实现的新语法
1081
+ explanation_providers.visible = False
1082
+ for p in explanation_providers:
1083
+ self.assertFalse(p.visible)
1084
+
1085
+ # 5. 渲染,所有 "explanation" 的内容都应该消失
1086
+ rendered_hidden = await messages.render_latest()
1087
+ self.assertNotIn("First explanation.", rendered_hidden[0]['content'])
1088
+ self.assertNotIn("Second explanation.", rendered_hidden[0]['content'])
1089
+ self.assertNotIn("Third explanation.", rendered_hidden[0]['content'])
1090
+ self.assertIn("Some other text.", rendered_hidden[0]['content'])
1091
+
1092
+ # 6. 将最后一个 "explanation" provider 设置回可见
1093
+ # 这是需要实现的另一个新语法
1094
+ explanation_providers[-1].visible = True
1095
+ self.assertTrue(explanation_providers[-1].visible)
1096
+ self.assertFalse(explanation_providers[0].visible)
1097
+
1098
+ # 7. 最终渲染,只应看到最后一个 "explanation"
1099
+ rendered_final = await messages.render_latest()
1100
+ self.assertNotIn("First explanation.", rendered_final[0]['content'])
1101
+ self.assertNotIn("Second explanation.", rendered_final[0]['content'])
1102
+ self.assertIn("Third explanation.", rendered_final[0]['content'])
1103
+ self.assertIn("Some other text.", rendered_final[0]['content'])
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, "通过工厂创建的内容")
1056
1138
 
1057
1139
  # ==============================================================================
1058
1140
  # 6. 演示
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aient
3
- Version: 1.2.0
3
+ Version: 1.2.2
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.0"
3
+ version = "1.2.2"
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes