aient 1.1.95__tar.gz → 1.1.97__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.1.95 → aient-1.1.97}/PKG-INFO +1 -1
- {aient-1.1.95 → aient-1.1.97}/aient/architext/architext/core.py +53 -33
- {aient-1.1.95 → aient-1.1.97}/aient/architext/test/test.py +86 -49
- {aient-1.1.95 → aient-1.1.97}/aient.egg-info/PKG-INFO +1 -1
- {aient-1.1.95 → aient-1.1.97}/pyproject.toml +1 -1
- {aient-1.1.95 → aient-1.1.97}/LICENSE +0 -0
- {aient-1.1.95 → aient-1.1.97}/README.md +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient/__init__.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient/architext/architext/__init__.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient/architext/test/openai_client.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient/architext/test/test_save_load.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient/core/__init__.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient/core/log_config.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient/core/models.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient/core/request.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient/core/response.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient/core/test/test_base_api.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient/core/test/test_geminimask.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient/core/test/test_image.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient/core/test/test_payload.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient/core/utils.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient/models/__init__.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient/models/audio.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient/models/base.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient/models/chatgpt.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient/plugins/__init__.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient/plugins/arXiv.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient/plugins/config.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient/plugins/excute_command.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient/plugins/get_time.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient/plugins/image.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient/plugins/list_directory.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient/plugins/read_file.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient/plugins/read_image.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient/plugins/readonly.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient/plugins/registry.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient/plugins/run_python.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient/plugins/websearch.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient/plugins/write_file.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient/utils/__init__.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient/utils/prompt.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient/utils/scripts.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient.egg-info/SOURCES.txt +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient.egg-info/dependency_links.txt +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient.egg-info/requires.txt +0 -0
- {aient-1.1.95 → aient-1.1.97}/aient.egg-info/top_level.txt +0 -0
- {aient-1.1.95 → aient-1.1.97}/setup.cfg +0 -0
- {aient-1.1.95 → aient-1.1.97}/test/test_Web_crawler.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/test/test_ddg_search.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/test/test_google_search.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/test/test_ollama.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/test/test_plugin.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/test/test_search.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/test/test_url.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/test/test_whisper.py +0 -0
- {aient-1.1.95 → aient-1.1.97}/test/test_yjh.py +0 -0
@@ -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:
|
@@ -70,15 +93,20 @@ class Texts(ContextProvider):
|
|
70
93
|
return self._text if self._text is not None else ""
|
71
94
|
|
72
95
|
class Tools(ContextProvider):
|
73
|
-
def __init__(self, tools_json: List[Dict]
|
96
|
+
def __init__(self, tools_json: Optional[List[Dict]] = None, name: str = "tools"):
|
97
|
+
super().__init__(name)
|
98
|
+
self._tools_json = tools_json or []
|
74
99
|
def update(self, tools_json: List[Dict]):
|
75
100
|
self._tools_json = tools_json
|
76
101
|
self.mark_stale()
|
77
|
-
async def render(self) -> str:
|
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>"
|
78
106
|
|
79
107
|
class Files(ContextProvider):
|
80
|
-
def __init__(self, *paths: Union[str, List[str]]):
|
81
|
-
super().__init__(
|
108
|
+
def __init__(self, *paths: Union[str, List[str]], name: str = "files"):
|
109
|
+
super().__init__(name)
|
82
110
|
self._files: Dict[str, str] = {}
|
83
111
|
|
84
112
|
file_paths: List[str] = []
|
@@ -179,7 +207,23 @@ class Message(ABC):
|
|
179
207
|
processed_items = []
|
180
208
|
for item in initial_items:
|
181
209
|
if isinstance(item, str):
|
182
|
-
|
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
|
+
|
183
227
|
elif isinstance(item, Message):
|
184
228
|
processed_items.extend(item.providers())
|
185
229
|
elif isinstance(item, ContextProvider):
|
@@ -204,37 +248,13 @@ class Message(ABC):
|
|
204
248
|
self._parent_messages: Optional['Messages'] = None
|
205
249
|
|
206
250
|
def _render_content(self) -> str:
|
207
|
-
|
208
|
-
blocks_with_types = []
|
251
|
+
final_parts = []
|
209
252
|
for item in self._items:
|
210
253
|
block = item.get_content_block()
|
211
|
-
if block and
|
212
|
-
|
213
|
-
provider_type = Texts if type(item) is Texts else type(item)
|
214
|
-
blocks_with_types.append((block, provider_type))
|
215
|
-
|
216
|
-
if not blocks_with_types:
|
217
|
-
return ""
|
218
|
-
|
219
|
-
# Build the final content string
|
220
|
-
# Start with the first block's content
|
221
|
-
final_content_parts = [blocks_with_types[0][0].content]
|
222
|
-
|
223
|
-
for i in range(1, len(blocks_with_types)):
|
224
|
-
current_block, current_type = blocks_with_types[i]
|
225
|
-
_, prev_type = blocks_with_types[i-1]
|
226
|
-
|
227
|
-
# If both the previous and current rendered blocks are simple text, concatenate them.
|
228
|
-
# Otherwise, join with newlines.
|
229
|
-
if prev_type is Texts and current_type is Texts:
|
230
|
-
separator = ""
|
231
|
-
else:
|
232
|
-
separator = "\n\n"
|
233
|
-
|
234
|
-
final_content_parts.append(separator)
|
235
|
-
final_content_parts.append(current_block.content)
|
254
|
+
if block and block.content is not None:
|
255
|
+
final_parts.append(block.content)
|
236
256
|
|
237
|
-
return "".join(
|
257
|
+
return "".join(final_parts)
|
238
258
|
|
239
259
|
def pop(self, name: str) -> Optional[ContextProvider]:
|
240
260
|
popped_item = None
|
@@ -904,62 +904,99 @@ class TestContextManagement(unittest.IsolatedAsyncioTestCase):
|
|
904
904
|
self.assertEqual(len(rendered_updated), 1)
|
905
905
|
self.assertEqual(rendered_updated[0]['content'], "This is the new content.")
|
906
906
|
|
907
|
-
async def
|
908
|
-
"""
|
909
|
-
|
910
|
-
#
|
911
|
-
#
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
</user_info>
|
920
|
-
|
921
|
-
#
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
# Let's test the intended-like usage pattern.
|
926
|
-
# We create a template and providers separately.
|
927
|
-
|
928
|
-
os_provider = Texts(name="os_version")
|
929
|
-
path_provider = Texts(name="workspace_path")
|
930
|
-
shell_provider = Texts(name="shell")
|
931
|
-
|
932
|
-
messages = Messages(
|
933
|
-
UserMessage(
|
934
|
-
"<user_info>\n"
|
935
|
-
"The user's OS version is ", os_provider, ". The absolute path of the user's workspace is ",
|
936
|
-
path_provider, " which is also the project root directory. The user's shell is ", shell_provider, ".\n"
|
937
|
-
"</user_info>"
|
938
|
-
)
|
939
|
-
)
|
940
|
-
|
941
|
-
# 2. 初始渲染,此时 placeholders 应该为空字符串
|
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 的内容应该为空
|
942
925
|
rendered_initial = await messages.render_latest()
|
943
|
-
|
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 = (
|
944
930
|
"<user_info>\n"
|
945
|
-
"The user's OS version is
|
946
|
-
"
|
931
|
+
"The user's OS version is .\n"
|
932
|
+
"Tools: \n"
|
933
|
+
"Files: \n"
|
934
|
+
"Current time: \n"
|
947
935
|
"</user_info>"
|
948
936
|
)
|
949
|
-
self.assertEqual(rendered_initial[0]['content'],
|
937
|
+
self.assertEqual(rendered_initial[0]['content'].strip(), expected_initial.strip())
|
950
938
|
|
951
|
-
#
|
952
|
-
messages.provider("os_version").update("
|
953
|
-
messages.provider("
|
954
|
-
messages.provider("
|
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")
|
955
943
|
|
956
|
-
|
957
|
-
|
958
|
-
|
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]
|
959
997
|
|
960
|
-
|
961
|
-
self.
|
962
|
-
self.assertIn("The user's shell is /bin/zsh.", final_content)
|
998
|
+
# 验证两次渲染的时间戳不同
|
999
|
+
self.assertNotEqual(time1_str_part, time2_str_part, "f-string 中的动态 lambda 内容在两次渲染之间应该更新")
|
963
1000
|
|
964
1001
|
|
965
1002
|
# ==============================================================================
|
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
|