beswarm 0.2.82__py3-none-any.whl → 0.2.83__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.
- beswarm/agents/planact.py +24 -71
- beswarm/aient/aient/architext/architext/core.py +314 -56
- beswarm/aient/aient/architext/test/test.py +511 -28
- beswarm/aient/aient/models/chatgpt.py +9 -8
- beswarm/prompt.py +44 -17
- {beswarm-0.2.82.dist-info → beswarm-0.2.83.dist-info}/METADATA +1 -1
- {beswarm-0.2.82.dist-info → beswarm-0.2.83.dist-info}/RECORD +9 -9
- {beswarm-0.2.82.dist-info → beswarm-0.2.83.dist-info}/WHEEL +0 -0
- {beswarm-0.2.82.dist-info → beswarm-0.2.83.dist-info}/top_level.txt +0 -0
@@ -143,11 +143,12 @@ class TestContextManagement(unittest.IsolatedAsyncioTestCase):
|
|
143
143
|
popped_image = messages.pop("image")
|
144
144
|
self.assertIsNotNone(popped_image)
|
145
145
|
|
146
|
-
# 3. Render again, should fall back to string content
|
146
|
+
# 3. Render again, should fall back to string content and seamlessly join texts
|
147
147
|
rendered_str = messages.render() # No refresh needed
|
148
148
|
content_str = rendered_str[0]['content']
|
149
149
|
self.assertIsInstance(content_str, str)
|
150
|
-
|
150
|
+
# With the new render logic, adjacent texts are joined with ""
|
151
|
+
self.assertEqual(content_str, "Look at this:Any thoughts?")
|
151
152
|
|
152
153
|
# Clean up
|
153
154
|
import os
|
@@ -203,7 +204,7 @@ class TestContextManagement(unittest.IsolatedAsyncioTestCase):
|
|
203
204
|
|
204
205
|
# 验证是否真的从 Message 对象中弹出了
|
205
206
|
self.assertIs(popped_provider, self.tools_provider, "应该从 SystemMessage 中成功弹出 provider")
|
206
|
-
self.assertNotIn(self.tools_provider, system_message.
|
207
|
+
self.assertNotIn(self.tools_provider, system_message.provider(), "provider 不应再存在于 SystemMessage 的 provider 列表中")
|
207
208
|
|
208
209
|
# 3. 核心问题:检查顶层 Messages 的索引
|
209
210
|
# 在理想情况下,直接修改子消息应该同步更新顶层索引。
|
@@ -236,8 +237,8 @@ class TestContextManagement(unittest.IsolatedAsyncioTestCase):
|
|
236
237
|
|
237
238
|
# 验证弹出的消息是否正确
|
238
239
|
self.assertIsInstance(popped_message, UserMessage)
|
239
|
-
self.assertEqual(len(popped_message.
|
240
|
-
self.assertEqual(popped_message.
|
240
|
+
self.assertEqual(len(popped_message.provider()), 1)
|
241
|
+
self.assertEqual(popped_message.provider()[0].name, user_provider.name)
|
241
242
|
|
242
243
|
# 验证 Messages 对象的当前状态
|
243
244
|
self.assertEqual(len(messages), 2)
|
@@ -417,13 +418,13 @@ class TestContextManagement(unittest.IsolatedAsyncioTestCase):
|
|
417
418
|
user_message = UserMessage(self.files_provider, "This is a raw string.")
|
418
419
|
|
419
420
|
# 验证 _items 列表中的第二个元素是否是 Texts 类的实例
|
420
|
-
self.assertEqual(len(user_message.
|
421
|
-
self.assertIsInstance(user_message.
|
422
|
-
self.assertIsInstance(user_message.
|
421
|
+
self.assertEqual(len(user_message.provider()), 2)
|
422
|
+
self.assertIsInstance(user_message.provider()[0], Files)
|
423
|
+
self.assertIsInstance(user_message.provider()[1], Texts)
|
423
424
|
|
424
425
|
# 验证转换后的 Texts provider 内容是否正确
|
425
426
|
# 我们需要异步地获取内容
|
426
|
-
text_provider = user_message.
|
427
|
+
text_provider = user_message.provider()[1]
|
427
428
|
await text_provider.refresh() # 手动刷新以获取内容
|
428
429
|
content_block = text_provider.get_content_block()
|
429
430
|
self.assertIsNotNone(content_block)
|
@@ -445,7 +446,7 @@ class TestContextManagement(unittest.IsolatedAsyncioTestCase):
|
|
445
446
|
# 3. 测试RoleMessage工厂类
|
446
447
|
factory_user_msg = RoleMessage('user', "Factory-created string.")
|
447
448
|
self.assertIsInstance(factory_user_msg, UserMessage)
|
448
|
-
self.assertIsInstance(factory_user_msg.
|
449
|
+
self.assertIsInstance(factory_user_msg.provider()[0], Texts)
|
449
450
|
|
450
451
|
# 4. 测试无效类型
|
451
452
|
with self.assertRaises(TypeError):
|
@@ -460,12 +461,12 @@ class TestContextManagement(unittest.IsolatedAsyncioTestCase):
|
|
460
461
|
]
|
461
462
|
user_message_mixed = UserMessage(mixed_content_list)
|
462
463
|
|
463
|
-
self.assertEqual(len(user_message_mixed.
|
464
|
-
self.assertIsInstance(user_message_mixed.
|
465
|
-
self.assertIsInstance(user_message_mixed.
|
464
|
+
self.assertEqual(len(user_message_mixed.provider()), 2)
|
465
|
+
self.assertIsInstance(user_message_mixed.provider()[0], Texts)
|
466
|
+
self.assertIsInstance(user_message_mixed.provider()[1], Images)
|
466
467
|
|
467
468
|
# 验证内容
|
468
|
-
providers = user_message_mixed.
|
469
|
+
providers = user_message_mixed.provider()
|
469
470
|
await asyncio.gather(*[p.refresh() for p in providers]) # 刷新所有providers
|
470
471
|
self.assertEqual(providers[0].get_content_block().content, 'Describe the following image.')
|
471
472
|
self.assertEqual(providers[1].get_content_block().content, 'data:image/png;base64,VGhpcyBpcyBhIGR1bW15IGltYWdlIGZpbGUu')
|
@@ -477,9 +478,9 @@ class TestContextManagement(unittest.IsolatedAsyncioTestCase):
|
|
477
478
|
]
|
478
479
|
user_message_text_only = UserMessage(text_only_list)
|
479
480
|
|
480
|
-
self.assertEqual(len(user_message_text_only.
|
481
|
-
self.assertIsInstance(user_message_text_only.
|
482
|
-
self.assertIsInstance(user_message_text_only.
|
481
|
+
self.assertEqual(len(user_message_text_only.provider()), 2)
|
482
|
+
self.assertIsInstance(user_message_text_only.provider()[0], Texts)
|
483
|
+
self.assertIsInstance(user_message_text_only.provider()[1], Texts)
|
483
484
|
|
484
485
|
# 3. 在 Messages 容器中测试
|
485
486
|
messages = Messages(UserMessage(mixed_content_list))
|
@@ -522,9 +523,9 @@ class TestContextManagement(unittest.IsolatedAsyncioTestCase):
|
|
522
523
|
|
523
524
|
# 3. 验证新消息的类型和内容
|
524
525
|
self.assertIsInstance(new_message, UserMessage, "结果应该是一个 UserMessage 实例")
|
525
|
-
self.assertEqual(len(new_message.
|
526
|
+
self.assertEqual(len(new_message.provider()), 2, "新消息应该包含两个 provider")
|
526
527
|
|
527
|
-
providers = new_message.
|
528
|
+
providers = new_message.provider()
|
528
529
|
self.assertIsInstance(providers[0], Texts, "第一个 provider 应该是 Texts 类型")
|
529
530
|
self.assertIsInstance(providers[1], Texts, "第二个 provider 应该是 Texts 类型")
|
530
531
|
|
@@ -535,14 +536,14 @@ class TestContextManagement(unittest.IsolatedAsyncioTestCase):
|
|
535
536
|
self.assertEqual(providers[1].get_content_block().content, "hello", "第二个 provider 的内容应该是 'hello'")
|
536
537
|
|
537
538
|
# 4. 验证原始消息没有被修改
|
538
|
-
self.assertEqual(len(original_message.
|
539
|
+
self.assertEqual(len(original_message.provider()), 1, "原始消息不应该被修改")
|
539
540
|
|
540
541
|
# 5. 测试 UserMessage + "string"
|
541
542
|
new_message_add = original_message + "world"
|
542
543
|
self.assertIsInstance(new_message_add, UserMessage)
|
543
|
-
self.assertEqual(len(new_message_add.
|
544
|
+
self.assertEqual(len(new_message_add.provider()), 2)
|
544
545
|
|
545
|
-
providers_add = new_message_add.
|
546
|
+
providers_add = new_message_add.provider()
|
546
547
|
await asyncio.gather(*[p.refresh() for p in providers_add])
|
547
548
|
self.assertEqual(providers_add[0].get_content_block().content, "hello")
|
548
549
|
self.assertEqual(providers_add[1].get_content_block().content, "world")
|
@@ -552,9 +553,9 @@ class TestContextManagement(unittest.IsolatedAsyncioTestCase):
|
|
552
553
|
# 1. 测试 "str" + UserMessage
|
553
554
|
combined_message = "hi" + UserMessage("hello")
|
554
555
|
self.assertIsInstance(combined_message, UserMessage)
|
555
|
-
self.assertEqual(len(combined_message.
|
556
|
+
self.assertEqual(len(combined_message.provider()), 2)
|
556
557
|
|
557
|
-
providers = combined_message.
|
558
|
+
providers = combined_message.provider()
|
558
559
|
await asyncio.gather(*[p.refresh() for p in providers])
|
559
560
|
self.assertEqual(providers[0].get_content_block().content, "hi")
|
560
561
|
self.assertEqual(providers[1].get_content_block().content, "hello")
|
@@ -562,9 +563,9 @@ class TestContextManagement(unittest.IsolatedAsyncioTestCase):
|
|
562
563
|
# 2. 测试 UserMessage(UserMessage(...)) 扁平化
|
563
564
|
# 按照用户的要求,UserMessage(UserMessage(...)) 应该被扁平化
|
564
565
|
nested_message = UserMessage(UserMessage("item1", "item2"))
|
565
|
-
self.assertEqual(len(nested_message.
|
566
|
+
self.assertEqual(len(nested_message.provider()), 2)
|
566
567
|
|
567
|
-
providers_nested = nested_message.
|
568
|
+
providers_nested = nested_message.provider()
|
568
569
|
self.assertIsInstance(providers_nested[0], Texts)
|
569
570
|
self.assertIsInstance(providers_nested[1], Texts)
|
570
571
|
|
@@ -575,9 +576,9 @@ class TestContextManagement(unittest.IsolatedAsyncioTestCase):
|
|
575
576
|
# 3. 结合 1 和 2,测试用户的完整场景
|
576
577
|
final_message = UserMessage("hi" + UserMessage("hello"))
|
577
578
|
self.assertIsInstance(final_message, UserMessage)
|
578
|
-
self.assertEqual(len(final_message.
|
579
|
+
self.assertEqual(len(final_message.provider()), 2)
|
579
580
|
|
580
|
-
providers_final = final_message.
|
581
|
+
providers_final = final_message.provider()
|
581
582
|
await asyncio.gather(*[p.refresh() for p in providers_final])
|
582
583
|
self.assertEqual(providers_final[0].get_content_block().content, "hi")
|
583
584
|
self.assertEqual(providers_final[1].get_content_block().content, "hello")
|
@@ -819,6 +820,488 @@ class TestContextManagement(unittest.IsolatedAsyncioTestCase):
|
|
819
820
|
if os.path.exists(test_file):
|
820
821
|
os.remove(test_file)
|
821
822
|
|
823
|
+
async def test_z_dynamic_texts_provider(self):
|
824
|
+
"""测试 Texts provider 是否支持可调用对象以实现动态内容"""
|
825
|
+
import time
|
826
|
+
from datetime import datetime
|
827
|
+
|
828
|
+
# 1. 使用 lambda 函数创建一个动态的 Texts provider
|
829
|
+
# 每次调用 render 时,它都应该返回当前时间
|
830
|
+
dynamic_text_provider = Texts(lambda: datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
|
831
|
+
messages = Messages(UserMessage(dynamic_text_provider))
|
832
|
+
|
833
|
+
# 2. 第一次渲染
|
834
|
+
rendered1 = await messages.render_latest()
|
835
|
+
time1_str = rendered1[0]['content']
|
836
|
+
self.assertIsNotNone(time1_str)
|
837
|
+
|
838
|
+
# 3. 等待一秒钟
|
839
|
+
time.sleep(1)
|
840
|
+
|
841
|
+
# 4. 第二次渲染,并期望内容已更新
|
842
|
+
rendered2 = await messages.render_latest()
|
843
|
+
time2_str = rendered2[0]['content']
|
844
|
+
self.assertIsNotNone(time2_str)
|
845
|
+
|
846
|
+
# 5. 验证两次渲染的时间戳不同
|
847
|
+
self.assertNotEqual(time1_str, time2_str, "动态 Texts provider 的内容在两次渲染之间应该更新")
|
848
|
+
|
849
|
+
async def test_z2_dynamic_texts_with_prefix(self):
|
850
|
+
"""测试动态 Texts provider 包含静态前缀时也能正确更新"""
|
851
|
+
import time
|
852
|
+
from datetime import datetime
|
853
|
+
import platform
|
854
|
+
|
855
|
+
# 1. 创建一个包含静态前缀和动态内容的 provider
|
856
|
+
# 正确的用法是将整个表达式放入 lambda
|
857
|
+
dynamic_provider = Texts(lambda: f"平台信息:{platform.platform()}, 时间:{datetime.now().isoformat()}")
|
858
|
+
messages = Messages(UserMessage(dynamic_provider))
|
859
|
+
|
860
|
+
# 2. 第一次渲染
|
861
|
+
rendered1 = await messages.render_latest()
|
862
|
+
content1 = rendered1[0]['content']
|
863
|
+
self.assertIn("平台信息:", content1)
|
864
|
+
|
865
|
+
# 3. 等待一秒
|
866
|
+
time.sleep(1)
|
867
|
+
|
868
|
+
# 4. 第二次渲染
|
869
|
+
rendered2 = await messages.render_latest()
|
870
|
+
content2 = rendered2[0]['content']
|
871
|
+
self.assertIn("平台信息:", content2)
|
872
|
+
|
873
|
+
# 5. 验证两次内容不同(因为时间戳变了)
|
874
|
+
self.assertNotEqual(content1, content2, "包含静态前缀的动态 provider 内容应该更新")
|
875
|
+
|
876
|
+
async def test_z3_deferred_text_update_via_provider(self):
|
877
|
+
"""测试 Texts(name=...) 初始化, 然后通过 provider 更新内容"""
|
878
|
+
# This test is expected to fail with a TypeError on the next line
|
879
|
+
# because the current Texts.__init__ requires 'text'.
|
880
|
+
deferred_text_provider = Texts(name="deferred_content")
|
881
|
+
|
882
|
+
messages = Messages(UserMessage(deferred_text_provider))
|
883
|
+
|
884
|
+
# Initial render: with no text, it should probably render to an empty string.
|
885
|
+
# If there's no content, the message itself might not be rendered.
|
886
|
+
# Let's assume an empty provider results in the message not rendering.
|
887
|
+
await deferred_text_provider.refresh()
|
888
|
+
# With the new logic, it should return a block with an empty string
|
889
|
+
content_block = deferred_text_provider.get_content_block()
|
890
|
+
self.assertIsNotNone(content_block)
|
891
|
+
self.assertEqual(content_block.content, "")
|
892
|
+
|
893
|
+
|
894
|
+
rendered_initial = await messages.render_latest()
|
895
|
+
self.assertEqual(len(rendered_initial), 0)
|
896
|
+
|
897
|
+
# 3. Get provider and update content
|
898
|
+
provider = messages.provider("deferred_content")
|
899
|
+
self.assertIsNotNone(provider)
|
900
|
+
provider.update("This is the new content.")
|
901
|
+
|
902
|
+
# 4. Re-render and validate
|
903
|
+
rendered_updated = await messages.render_latest()
|
904
|
+
self.assertEqual(len(rendered_updated), 1)
|
905
|
+
self.assertEqual(rendered_updated[0]['content'], "This is the new content.")
|
906
|
+
|
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 的内容应该为空
|
925
|
+
rendered_initial = await messages.render_latest()
|
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 = (
|
930
|
+
"<user_info>\n"
|
931
|
+
"The user's OS version is .\n"
|
932
|
+
"Tools: \n"
|
933
|
+
"Files: \n"
|
934
|
+
"Current time: \n"
|
935
|
+
"</user_info>"
|
936
|
+
)
|
937
|
+
self.assertEqual(rendered_initial[0]['content'].strip(), expected_initial.strip())
|
938
|
+
|
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")
|
943
|
+
|
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]
|
997
|
+
|
998
|
+
# 验证两次渲染的时间戳不同
|
999
|
+
self.assertNotEqual(time1_str_part, time2_str_part, "f-string 中的动态 lambda 内容在两次渲染之间应该更新")
|
1000
|
+
|
1001
|
+
def test_z6_direct_content_access(self):
|
1002
|
+
"""测试通过 .content 属性直接访问 Texts 内容"""
|
1003
|
+
# 1. 测试静态内容
|
1004
|
+
static_text = Texts("Hello, Architext!")
|
1005
|
+
self.assertEqual(static_text.content, "Hello, Architext!")
|
1006
|
+
|
1007
|
+
# 2. 测试动态内容
|
1008
|
+
from datetime import datetime
|
1009
|
+
current_time_str = datetime.now().isoformat()
|
1010
|
+
dynamic_text = Texts(lambda: current_time_str)
|
1011
|
+
self.assertEqual(dynamic_text.content, current_time_str)
|
1012
|
+
|
1013
|
+
# 3. 测试更新后的内容访问
|
1014
|
+
static_text.update("Updated content.")
|
1015
|
+
self.assertEqual(static_text.content, "Updated content.")
|
1016
|
+
|
1017
|
+
# 4. 测试 None 和空字符串
|
1018
|
+
none_text = Texts(name="none_text") # Provide a name when text is None
|
1019
|
+
self.assertEqual(none_text.content, "")
|
1020
|
+
|
1021
|
+
empty_text = Texts("")
|
1022
|
+
self.assertEqual(empty_text.content, "")
|
1023
|
+
|
1024
|
+
async def test_z7_provider_visibility(self):
|
1025
|
+
"""测试 provider 的可见性标志是否能正常工作"""
|
1026
|
+
# 1. 初始化 provider,visible 默认为 True
|
1027
|
+
text_provider = Texts("Hello, World!", name="greeting")
|
1028
|
+
self.assertTrue(text_provider.visible)
|
1029
|
+
|
1030
|
+
messages = Messages(SystemMessage(text_provider))
|
1031
|
+
|
1032
|
+
# 2. 初始渲染,内容应该可见
|
1033
|
+
rendered_visible = await messages.render_latest()
|
1034
|
+
self.assertEqual(len(rendered_visible), 1)
|
1035
|
+
self.assertEqual(rendered_visible[0]['content'], "Hello, World!")
|
1036
|
+
|
1037
|
+
# 3. 设置为不可见
|
1038
|
+
provider = messages.provider("greeting")
|
1039
|
+
provider.visible = False
|
1040
|
+
self.assertFalse(provider.visible)
|
1041
|
+
|
1042
|
+
# 4. 再次渲染,内容应该消失
|
1043
|
+
# 因为 visibility 变化会 mark_stale,所以需要 render_latest
|
1044
|
+
rendered_invisible = await messages.render_latest()
|
1045
|
+
self.assertEqual(len(rendered_invisible), 0, "设置为不可见后,消息应该不被渲染")
|
1046
|
+
|
1047
|
+
# 5. 再次设置为可见
|
1048
|
+
provider.visible = True
|
1049
|
+
self.assertTrue(provider.visible)
|
1050
|
+
|
1051
|
+
# 6. 渲染,内容应该再次出现
|
1052
|
+
rendered_visible_again = await messages.render_latest()
|
1053
|
+
self.assertEqual(len(rendered_visible_again), 1)
|
1054
|
+
self.assertEqual(rendered_visible_again[0]['content'], "Hello, World!")
|
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.provider():
|
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.provider():
|
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.provider():
|
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
|
+
|
1161
|
+
async def test_zb_message_provider_by_name(self):
|
1162
|
+
"""测试是否可以通过名称从 Message 对象中获取 provider"""
|
1163
|
+
# 1. 创建一个包含命名 provider 的 Message
|
1164
|
+
message = UserMessage(
|
1165
|
+
Texts("Some instruction", name="instruction"),
|
1166
|
+
Tools([{"name": "a_tool"}], name="tools"),
|
1167
|
+
Texts("Another instruction", name="instruction")
|
1168
|
+
)
|
1169
|
+
|
1170
|
+
# 2. 测试获取单个 provider
|
1171
|
+
tools_provider = message.provider("tools")
|
1172
|
+
self.assertIsInstance(tools_provider, Tools)
|
1173
|
+
self.assertEqual(tools_provider.name, "tools")
|
1174
|
+
|
1175
|
+
# 3. 测试获取多个同名 provider
|
1176
|
+
instruction_providers = message.provider("instruction")
|
1177
|
+
self.assertIsInstance(instruction_providers, ProviderGroup)
|
1178
|
+
self.assertEqual(len(instruction_providers), 2)
|
1179
|
+
self.assertTrue(all(isinstance(p, Texts) for p in instruction_providers))
|
1180
|
+
|
1181
|
+
# 4. 测试获取不存在的 provider
|
1182
|
+
non_existent_provider = message.provider("non_existent")
|
1183
|
+
self.assertIsNone(non_existent_provider)
|
1184
|
+
|
1185
|
+
async def test_zc_slicing_support(self):
|
1186
|
+
"""测试 Messages 对象是否支持切片操作"""
|
1187
|
+
m1 = SystemMessage("1")
|
1188
|
+
m2 = UserMessage("2")
|
1189
|
+
m3 = AssistantMessage("3")
|
1190
|
+
m4 = UserMessage("4")
|
1191
|
+
messages = Messages(m1, m2, m3, m4)
|
1192
|
+
|
1193
|
+
# 1. Test basic slicing
|
1194
|
+
sliced_messages = messages[1:3]
|
1195
|
+
self.assertIsInstance(sliced_messages, Messages)
|
1196
|
+
self.assertEqual(len(sliced_messages), 2)
|
1197
|
+
self.assertIs(sliced_messages[0], m2)
|
1198
|
+
self.assertIs(sliced_messages[1], m3)
|
1199
|
+
|
1200
|
+
# 2. Test slicing with open end
|
1201
|
+
sliced_messages_open = messages[2:]
|
1202
|
+
self.assertIsInstance(sliced_messages_open, Messages)
|
1203
|
+
self.assertEqual(len(sliced_messages_open), 2)
|
1204
|
+
self.assertIs(sliced_messages_open[0], m3)
|
1205
|
+
self.assertIs(sliced_messages_open[1], m4)
|
1206
|
+
|
1207
|
+
# 3. Test slicing with open start
|
1208
|
+
sliced_messages_start = messages[:2]
|
1209
|
+
self.assertIsInstance(sliced_messages_start, Messages)
|
1210
|
+
self.assertEqual(len(sliced_messages_start), 2)
|
1211
|
+
self.assertIs(sliced_messages_start[0], m1)
|
1212
|
+
self.assertIs(sliced_messages_start[1], m2)
|
1213
|
+
|
1214
|
+
# 4. Test slicing a single element
|
1215
|
+
sliced_single = messages[2:3]
|
1216
|
+
self.assertIsInstance(sliced_single, Messages)
|
1217
|
+
self.assertEqual(len(sliced_single), 1)
|
1218
|
+
self.assertIs(sliced_single[0], m3)
|
1219
|
+
|
1220
|
+
async def test_zd_slice_assignment(self):
|
1221
|
+
"""测试 Messages 对象的切片赋值功能"""
|
1222
|
+
# 1. Setup initial Messages objects
|
1223
|
+
m1 = SystemMessage("1")
|
1224
|
+
m2 = UserMessage("2")
|
1225
|
+
m3 = AssistantMessage("3")
|
1226
|
+
m4 = UserMessage("4")
|
1227
|
+
messages1 = Messages(m1, m2, m3, m4)
|
1228
|
+
|
1229
|
+
m5 = SystemMessage("5")
|
1230
|
+
m6 = UserMessage("6")
|
1231
|
+
messages2 = Messages(m5, m6)
|
1232
|
+
|
1233
|
+
# 2. Perform slice assignment
|
1234
|
+
# This should replace elements from index 1 onwards in messages1
|
1235
|
+
# with all elements from messages2
|
1236
|
+
messages1[1:] = messages2
|
1237
|
+
|
1238
|
+
# 3. Verify the result
|
1239
|
+
self.assertEqual(len(messages1), 3) # Should be m1, m5, m6
|
1240
|
+
self.assertIs(messages1[0], m1)
|
1241
|
+
self.assertIs(messages1[1], m5)
|
1242
|
+
self.assertIs(messages1[2], m6)
|
1243
|
+
|
1244
|
+
# 4. Test assigning from a slice, with different roles to prevent merging
|
1245
|
+
messages3 = Messages(UserMessage("A"), AssistantMessage("B"), UserMessage("C"))
|
1246
|
+
messages4 = Messages(SystemMessage("X"), AssistantMessage("Y"))
|
1247
|
+
|
1248
|
+
self.assertEqual(len(messages3), 3) # Verify length before assignment
|
1249
|
+
|
1250
|
+
messages3[1:2] = messages4[1:] # Replace AssistantMessage("B") with AssistantMessage("Y")
|
1251
|
+
|
1252
|
+
# We need to refresh to access .content property correctly
|
1253
|
+
await messages3.refresh()
|
1254
|
+
|
1255
|
+
self.assertEqual(len(messages3), 3)
|
1256
|
+
self.assertEqual(messages3[0].content, "A")
|
1257
|
+
self.assertEqual(messages3[1].content, "Y")
|
1258
|
+
self.assertEqual(messages3[2].content, "C")
|
1259
|
+
self.assertIsInstance(messages3[1], AssistantMessage)
|
1260
|
+
|
1261
|
+
async def test_ze_fstring_lambda_serialization(self):
|
1262
|
+
"""测试包含 lambda 的 f-string 消息是否可以被序列化和反序列化"""
|
1263
|
+
import platform
|
1264
|
+
import os
|
1265
|
+
|
1266
|
+
# 1. 创建一个使用 f-string 和 lambda 的动态消息
|
1267
|
+
f_string_message = f"""系统信息: {Texts(lambda: platform.platform())}"""
|
1268
|
+
messages_to_save = Messages(SystemMessage(f_string_message))
|
1269
|
+
|
1270
|
+
# 2. 定义一个临时文件路径
|
1271
|
+
test_file_path = "test_lambda_serialization.pkl"
|
1272
|
+
|
1273
|
+
# 3. 序列化和反序列化
|
1274
|
+
try:
|
1275
|
+
# 保存
|
1276
|
+
messages_to_save.save(test_file_path)
|
1277
|
+
|
1278
|
+
# 确认文件已创建
|
1279
|
+
self.assertTrue(os.path.exists(test_file_path))
|
1280
|
+
|
1281
|
+
# 加载
|
1282
|
+
messages_loaded = Messages.load(test_file_path)
|
1283
|
+
|
1284
|
+
# 验证加载的对象
|
1285
|
+
self.assertIsNotNone(messages_loaded)
|
1286
|
+
self.assertIsInstance(messages_loaded, Messages)
|
1287
|
+
self.assertEqual(len(messages_loaded), 1)
|
1288
|
+
|
1289
|
+
# 4. 渲染加载后的消息以验证 lambda 是否仍然有效
|
1290
|
+
rendered = await messages_loaded.render_latest()
|
1291
|
+
|
1292
|
+
self.assertEqual(len(rendered), 1)
|
1293
|
+
self.assertIn("系统信息:", rendered[0]['content'])
|
1294
|
+
# 验证 platform.platform() 的结果是否在渲染内容中
|
1295
|
+
self.assertIn(platform.platform(), rendered[0]['content'])
|
1296
|
+
|
1297
|
+
except Exception as e:
|
1298
|
+
# 如果出现任何异常,测试失败
|
1299
|
+
self.fail(f"序列化或反序列化带有 lambda 的 f-string 消息时出错: {e}")
|
1300
|
+
finally:
|
1301
|
+
# 5. 清理临时文件
|
1302
|
+
if os.path.exists(test_file_path):
|
1303
|
+
os.remove(test_file_path)
|
1304
|
+
|
822
1305
|
|
823
1306
|
# ==============================================================================
|
824
1307
|
# 6. 演示
|
@@ -97,13 +97,8 @@ class chatgpt(BaseLLM):
|
|
97
97
|
Initialize Chatbot with API key (from https://platform.openai.com/account/api-keys)
|
98
98
|
"""
|
99
99
|
super().__init__(api_key, engine, api_url, system_prompt, proxy, timeout, max_tokens, temperature, top_p, presence_penalty, frequency_penalty, reply_count, truncate_limit, use_plugins=use_plugins, print_log=print_log)
|
100
|
-
self.conversation: dict[str,
|
101
|
-
"default":
|
102
|
-
{
|
103
|
-
"role": "system",
|
104
|
-
"content": self.system_prompt,
|
105
|
-
},
|
106
|
-
],
|
100
|
+
self.conversation: dict[str, Messages] = {
|
101
|
+
"default": Messages(SystemMessage(self.system_prompt)),
|
107
102
|
}
|
108
103
|
if cache_messages:
|
109
104
|
self.conversation["default"] = cache_messages
|
@@ -261,10 +256,16 @@ class chatgpt(BaseLLM):
|
|
261
256
|
"image": True
|
262
257
|
}
|
263
258
|
|
259
|
+
done_message = self.conversation[convo_id].provider("done")
|
260
|
+
if self.check_done and done_message:
|
261
|
+
done_message.visible = False
|
262
|
+
if self.conversation[convo_id][-1][-1].name == "done":
|
263
|
+
self.conversation[convo_id][-1][-1].visible = True
|
264
|
+
|
264
265
|
# 构造请求数据
|
265
266
|
request_data = {
|
266
267
|
"model": model or self.engine,
|
267
|
-
"messages": self.conversation[convo_id].render_latest() if pass_history else Messages(
|
268
|
+
"messages": await self.conversation[convo_id].render_latest() if pass_history else Messages(
|
268
269
|
SystemMessage(self.system_prompt, self.conversation[convo_id].provider("files")),
|
269
270
|
UserMessage(prompt)
|
270
271
|
),
|