aient 1.2.44__tar.gz → 1.2.46__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.44 → aient-1.2.46}/PKG-INFO +1 -1
- {aient-1.2.44 → aient-1.2.46}/aient/architext/architext/core.py +83 -40
- {aient-1.2.44 → aient-1.2.46}/aient/architext/test/test.py +101 -0
- {aient-1.2.44 → aient-1.2.46}/aient/core/request.py +3 -0
- {aient-1.2.44 → aient-1.2.46}/aient/core/utils.py +1 -1
- {aient-1.2.44 → aient-1.2.46}/aient/models/chatgpt.py +1 -1
- {aient-1.2.44 → aient-1.2.46}/aient.egg-info/PKG-INFO +1 -1
- {aient-1.2.44 → aient-1.2.46}/pyproject.toml +1 -1
- {aient-1.2.44 → aient-1.2.46}/LICENSE +0 -0
- {aient-1.2.44 → aient-1.2.46}/README.md +0 -0
- {aient-1.2.44 → aient-1.2.46}/aient/__init__.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/aient/architext/architext/__init__.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/aient/architext/test/openai_client.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/aient/architext/test/test_save_load.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/aient/core/__init__.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/aient/core/log_config.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/aient/core/models.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/aient/core/response.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/aient/core/test/test_base_api.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/aient/core/test/test_geminimask.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/aient/core/test/test_image.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/aient/core/test/test_payload.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/aient/models/__init__.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/aient/models/audio.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/aient/models/base.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/aient/plugins/__init__.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/aient/plugins/arXiv.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/aient/plugins/config.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/aient/plugins/excute_command.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/aient/plugins/get_time.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/aient/plugins/image.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/aient/plugins/list_directory.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/aient/plugins/read_image.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/aient/plugins/readonly.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/aient/plugins/registry.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/aient/plugins/run_python.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/aient/plugins/websearch.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/aient/utils/__init__.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/aient/utils/prompt.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/aient/utils/scripts.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/aient.egg-info/SOURCES.txt +0 -0
- {aient-1.2.44 → aient-1.2.46}/aient.egg-info/dependency_links.txt +0 -0
- {aient-1.2.44 → aient-1.2.46}/aient.egg-info/requires.txt +0 -0
- {aient-1.2.44 → aient-1.2.46}/aient.egg-info/top_level.txt +0 -0
- {aient-1.2.44 → aient-1.2.46}/setup.cfg +0 -0
- {aient-1.2.44 → aient-1.2.46}/test/test_Web_crawler.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/test/test_ddg_search.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/test/test_google_search.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/test/test_ollama.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/test/test_plugin.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/test/test_url.py +0 -0
- {aient-1.2.44 → aient-1.2.46}/test/test_whisper.py +0 -0
@@ -233,73 +233,116 @@ class Files(ContextProvider):
|
|
233
233
|
def __init__(self, *paths: Union[str, List[str]], name: str = "files", visible: bool = True):
|
234
234
|
super().__init__(name, visible=visible)
|
235
235
|
self._files: Dict[str, str] = {}
|
236
|
+
self._file_sources: Dict[str, Dict] = {}
|
236
237
|
|
237
238
|
file_paths: List[str] = []
|
238
239
|
if paths:
|
239
|
-
# Handle the case where the first argument is a list of paths, e.g., Files(['a', 'b'])
|
240
240
|
if len(paths) == 1 and isinstance(paths[0], list):
|
241
241
|
file_paths.extend(paths[0])
|
242
|
-
# Handle the case where arguments are individual string paths, e.g., Files('a', 'b')
|
243
242
|
else:
|
244
243
|
file_paths.extend(paths)
|
245
244
|
|
246
245
|
if file_paths:
|
247
246
|
for path in file_paths:
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
247
|
+
self.update(path)
|
248
|
+
|
249
|
+
def _read_from_disk(self, path: str, head: Optional[int] = None) -> str:
|
250
|
+
"""Reads content from a file on disk, respecting the head parameter."""
|
251
|
+
try:
|
252
|
+
with open(path, 'r', encoding='utf-8') as f:
|
253
|
+
if head is not None and head > 0:
|
254
|
+
lines = []
|
255
|
+
for _ in range(head):
|
256
|
+
try:
|
257
|
+
lines.append(next(f))
|
258
|
+
except StopIteration:
|
259
|
+
break
|
260
|
+
return "".join(lines).rstrip('\n')
|
261
|
+
else:
|
262
|
+
return f.read()
|
263
|
+
except FileNotFoundError:
|
264
|
+
raise
|
265
|
+
except Exception as e:
|
266
|
+
logging.error(f"Error reading file {path}: {e}")
|
267
|
+
return f"[Error: Could not read file at path '{path}': {e}]"
|
255
268
|
|
256
269
|
async def refresh(self):
|
257
270
|
"""
|
258
|
-
|
259
|
-
|
260
|
-
content is updated to reflect the error.
|
271
|
+
Synchronizes content for files sourced from disk.
|
272
|
+
Content set manually is overwritten if the file exists, but preserved if it does not.
|
261
273
|
"""
|
262
274
|
is_changed = False
|
263
|
-
for path in list(self.
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
self._files
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
self._files
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
275
|
+
for path, spec in list(self._file_sources.items()):
|
276
|
+
if spec.get('source') == 'disk':
|
277
|
+
try:
|
278
|
+
head = spec.get('head')
|
279
|
+
new_content = self._read_from_disk(path, head)
|
280
|
+
if self._files.get(path) != new_content:
|
281
|
+
self._files[path] = new_content
|
282
|
+
is_changed = True
|
283
|
+
except FileNotFoundError:
|
284
|
+
error_msg = f"[Error: File not found at path '{path}']"
|
285
|
+
if self._files.get(path) != error_msg:
|
286
|
+
self._files[path] = error_msg
|
287
|
+
is_changed = True
|
288
|
+
elif spec.get('source') == 'manual':
|
289
|
+
try:
|
290
|
+
# File exists, so we must overwrite manual content.
|
291
|
+
head = spec.get('head')
|
292
|
+
new_content = self._read_from_disk(path, head)
|
293
|
+
if self._files.get(path) != new_content:
|
294
|
+
self._files[path] = new_content
|
295
|
+
is_changed = True
|
296
|
+
# If we are here, manual content was overwritten by disk content,
|
297
|
+
# so we should update the source.
|
298
|
+
self._file_sources[path] = {'source': 'disk'}
|
299
|
+
|
300
|
+
except FileNotFoundError:
|
301
|
+
# File does not exist, so we keep the manual content. No change.
|
302
|
+
pass
|
280
303
|
|
281
304
|
if is_changed:
|
282
305
|
self.mark_stale()
|
283
|
-
|
284
306
|
await super().refresh()
|
285
307
|
|
286
|
-
def update(self, path: str, content: Optional[str] = None):
|
308
|
+
def update(self, path: str, content: Optional[str] = None, head: Optional[Union[int, str]] = None):
|
287
309
|
"""
|
288
|
-
Updates a single file
|
289
|
-
memory. If content is None, it reads the file from disk.
|
310
|
+
Updates a single file's content and its source specification.
|
290
311
|
"""
|
312
|
+
if head is not None:
|
313
|
+
try:
|
314
|
+
head = int(head)
|
315
|
+
except (ValueError, TypeError):
|
316
|
+
logging.warning(f"Invalid 'head' parameter for file '{path}': {head}. Must be an integer. Ignoring.")
|
317
|
+
head = None
|
318
|
+
|
291
319
|
if content is not None:
|
292
|
-
|
320
|
+
# New logic: if head is also provided, decide what to do with content.
|
321
|
+
if head is not None and head > 0:
|
322
|
+
try:
|
323
|
+
# If file exists, prioritize reading from disk.
|
324
|
+
self._files[path] = self._read_from_disk(path, head)
|
325
|
+
self._file_sources[path] = {'source': 'disk', 'head': head}
|
326
|
+
except FileNotFoundError:
|
327
|
+
# If file does not exist, use the provided content's head.
|
328
|
+
lines = content.split('\n')
|
329
|
+
self._files[path] = "\n".join(lines[:head])
|
330
|
+
self._file_sources[path] = {'source': 'manual', 'head': head}
|
331
|
+
else:
|
332
|
+
# Original logic for when only content is provided.
|
333
|
+
self._files[path] = content
|
334
|
+
self._file_sources[path] = {'source': 'manual'}
|
293
335
|
else:
|
336
|
+
# Original logic for when only path (and optional head) is provided.
|
294
337
|
try:
|
295
|
-
|
296
|
-
|
338
|
+
self._files[path] = self._read_from_disk(path, head)
|
339
|
+
spec = {'source': 'disk'}
|
340
|
+
if head is not None and head > 0:
|
341
|
+
spec['head'] = head
|
342
|
+
self._file_sources[path] = spec
|
297
343
|
except FileNotFoundError:
|
298
|
-
logging.error(f"File not found for update: {path}.")
|
299
344
|
self._files[path] = f"[Error: File not found at path '{path}']"
|
300
|
-
|
301
|
-
logging.error(f"Error reading file for update {path}: {e}.")
|
302
|
-
self._files[path] = f"[Error: Could not read file at path '{path}': {e}]"
|
345
|
+
self._file_sources[path] = {'source': 'disk'}
|
303
346
|
self.mark_stale()
|
304
347
|
async def render(self) -> str:
|
305
348
|
if not self._files: return None
|
@@ -1783,6 +1783,107 @@ Files: {Files(visible=True, name="files")}
|
|
1783
1783
|
self.assertEqual(len(rendered_separated), 1, "被空消息隔开的同角色消息在渲染时应该合并")
|
1784
1784
|
self.assertEqual(rendered_separated[0]['content'], "hihi2", "被空消息隔开的同角色消息合并后内容不正确")
|
1785
1785
|
|
1786
|
+
async def test_zzc_files_update_with_head(self):
|
1787
|
+
"""测试 Files.update 是否支持 head 参数以及新的 refresh 逻辑"""
|
1788
|
+
test_file = "test_file_head.txt"
|
1789
|
+
file_content = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5"
|
1790
|
+
with open(test_file, "w", encoding='utf-8') as f:
|
1791
|
+
f.write(file_content)
|
1792
|
+
|
1793
|
+
try:
|
1794
|
+
messages = Messages(UserMessage(Files(name="files")))
|
1795
|
+
|
1796
|
+
# 1. Test update with head=3 using the provider from Messages
|
1797
|
+
files_provider = messages.provider("files")
|
1798
|
+
self.assertIsInstance(files_provider, Files)
|
1799
|
+
files_provider.update(path=test_file, head=3)
|
1800
|
+
|
1801
|
+
rendered_head = await messages.render_latest()
|
1802
|
+
expected_head_content = "Line 1\nLine 2\nLine 3"
|
1803
|
+
self.assertIn(f"<file_content>{expected_head_content}</file_content>", rendered_head[0]['content'])
|
1804
|
+
self.assertNotIn("Line 4", rendered_head[0]['content'])
|
1805
|
+
|
1806
|
+
# 2. Test update without head (default behavior)
|
1807
|
+
files_provider.update(path=test_file)
|
1808
|
+
rendered_full = await messages.render_latest()
|
1809
|
+
self.assertIn(f"<file_content>{file_content}</file_content>", rendered_full[0]['content'])
|
1810
|
+
|
1811
|
+
# 3. Test that refresh overwrites manual content when file exists
|
1812
|
+
files_provider.update(path=test_file, content="manual content")
|
1813
|
+
rendered_overwritten = await messages.render_latest()
|
1814
|
+
self.assertIn(f"<file_content>{file_content}</file_content>", rendered_overwritten[0]['content'])
|
1815
|
+
self.assertNotIn("manual content", rendered_overwritten[0]['content'])
|
1816
|
+
|
1817
|
+
# 4. Test that manual content is kept if file does not exist
|
1818
|
+
non_existent_file = "non_existent_for_sure.txt"
|
1819
|
+
files_provider.update(path=non_existent_file, content="manual content to keep")
|
1820
|
+
rendered_kept = await messages.render_latest()
|
1821
|
+
self.assertIn("<file_content>manual content to keep</file_content>", rendered_kept[0]['content'])
|
1822
|
+
|
1823
|
+
finally:
|
1824
|
+
if os.path.exists(test_file):
|
1825
|
+
os.remove(test_file)
|
1826
|
+
|
1827
|
+
async def test_zzd_files_update_with_string_head(self):
|
1828
|
+
"""测试 Files.update 是否能自动将字符串 head 参数转换为整数"""
|
1829
|
+
test_file = "test_file_string_head.txt"
|
1830
|
+
file_content = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5"
|
1831
|
+
with open(test_file, "w", encoding='utf-8') as f:
|
1832
|
+
f.write(file_content)
|
1833
|
+
|
1834
|
+
try:
|
1835
|
+
messages = Messages(UserMessage(Files(name="files")))
|
1836
|
+
|
1837
|
+
# Test update with head="3"
|
1838
|
+
files_provider = messages.provider("files")
|
1839
|
+
self.assertIsInstance(files_provider, Files)
|
1840
|
+
# This is the part that will likely fail before implementation
|
1841
|
+
files_provider.update(path=test_file, head="3")
|
1842
|
+
|
1843
|
+
rendered_head = await messages.render_latest()
|
1844
|
+
expected_head_content = "Line 1\nLine 2\nLine 3"
|
1845
|
+
self.assertIn(f"<file_content>{expected_head_content}</file_content>", rendered_head[0]['content'])
|
1846
|
+
self.assertNotIn("Line 4", rendered_head[0]['content'])
|
1847
|
+
|
1848
|
+
finally:
|
1849
|
+
if os.path.exists(test_file):
|
1850
|
+
os.remove(test_file)
|
1851
|
+
|
1852
|
+
async def test_zzc_files_update_with_head_and_content(self):
|
1853
|
+
"""测试update同时设置path, content, head时的行为"""
|
1854
|
+
existing_file = "test_file_existing.txt"
|
1855
|
+
non_existent_file = "test_file_non_existent.txt"
|
1856
|
+
existing_content = "Line 1 (from file)\nLine 2 (from file)\nLine 3 (from file)\nLine 4 (from file)"
|
1857
|
+
manual_content = "Line 1 (manual)\nLine 2 (manual)\nLine 3 (manual)\nLine 4 (manual)"
|
1858
|
+
|
1859
|
+
# --- 场景1: 文件存在 ---
|
1860
|
+
with open(existing_file, "w", encoding='utf-8') as f:
|
1861
|
+
f.write(existing_content)
|
1862
|
+
try:
|
1863
|
+
messages = Messages(UserMessage(Files(name="files")))
|
1864
|
+
files_provider = messages.provider("files")
|
1865
|
+
files_provider.update(path=existing_file, content=manual_content, head=2)
|
1866
|
+
rendered_existing = await messages.render_latest()
|
1867
|
+
expected_existing_head = "Line 1 (from file)\nLine 2 (from file)"
|
1868
|
+
self.assertIn(f"<file_content>{expected_existing_head}</file_content>", rendered_existing[0]['content'])
|
1869
|
+
self.assertNotIn("manual", rendered_existing[0]['content'])
|
1870
|
+
finally:
|
1871
|
+
if os.path.exists(existing_file):
|
1872
|
+
os.remove(existing_file)
|
1873
|
+
|
1874
|
+
# --- 场景2: 文件不存在 ---
|
1875
|
+
try:
|
1876
|
+
messages = Messages(UserMessage(Files(name="files")))
|
1877
|
+
files_provider = messages.provider("files")
|
1878
|
+
files_provider.update(path=non_existent_file, content=manual_content, head=2)
|
1879
|
+
rendered_non_existent = await messages.render_latest()
|
1880
|
+
expected_manual_head = "Line 1 (manual)\nLine 2 (manual)"
|
1881
|
+
self.assertIn(f"<file_content>{expected_manual_head}</file_content>", rendered_non_existent[0]['content'])
|
1882
|
+
self.assertNotIn("from file", rendered_non_existent[0]['content'])
|
1883
|
+
finally:
|
1884
|
+
if os.path.exists(non_existent_file):
|
1885
|
+
os.remove(non_existent_file)
|
1886
|
+
|
1786
1887
|
# ==============================================================================
|
1787
1888
|
# 6. 演示
|
1788
1889
|
# ==============================================================================
|
@@ -1116,6 +1116,9 @@ async def get_gpt_payload(request, engine, provider, api_key=None):
|
|
1116
1116
|
if "temperature" in payload:
|
1117
1117
|
payload.pop("temperature")
|
1118
1118
|
|
1119
|
+
if "v1/responses" in url:
|
1120
|
+
payload.pop("stream_options", None)
|
1121
|
+
|
1119
1122
|
# 代码生成/数学解题 0.0
|
1120
1123
|
# 数据抽取/分析 1.0
|
1121
1124
|
# 通用对话 1.3
|
@@ -52,7 +52,7 @@ class BaseAPI:
|
|
52
52
|
self.v1_models: str = urlunparse(parsed_url[:2] + (before_v1 + "models",) + ("",) * 3)
|
53
53
|
|
54
54
|
if "v1/responses" in parsed_url.path:
|
55
|
-
self.chat_url: str =
|
55
|
+
self.chat_url: str = api_url
|
56
56
|
else:
|
57
57
|
self.chat_url: str = urlunparse(parsed_url[:2] + (before_v1 + "chat/completions",) + ("",) * 3)
|
58
58
|
self.image_url: str = urlunparse(parsed_url[:2] + (before_v1 + "images/generations",) + ("",) * 3)
|
@@ -615,7 +615,7 @@ class chatgpt(BaseLLM):
|
|
615
615
|
final_tool_response = tool_response
|
616
616
|
if "<tool_error>" not in tool_response:
|
617
617
|
if tool_name == "read_file":
|
618
|
-
self.conversation[convo_id].provider("files").update(tool_info['parameter']["file_path"], tool_response)
|
618
|
+
self.conversation[convo_id].provider("files").update(tool_info['parameter']["file_path"], tool_response, head=safe_get(tool_info, 'parameter', "head", default=None))
|
619
619
|
final_tool_response = "Read file successfully! The file content has been updated in the tag <latest_file_content>."
|
620
620
|
elif tool_name == "get_knowledge_graph_tree":
|
621
621
|
self.conversation[convo_id].provider("knowledge_graph").visible = True
|
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
|