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.
@@ -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
- self.assertEqual(content_str, "Look at this:\n\nAny thoughts?")
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.providers(), "provider 不应再存在于 SystemMessage 的 providers 列表中")
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.providers()), 1)
240
- self.assertEqual(popped_message.providers()[0].name, user_provider.name)
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.providers()), 2)
421
- self.assertIsInstance(user_message.providers()[0], Files)
422
- self.assertIsInstance(user_message.providers()[1], Texts)
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.providers()[1]
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.providers()[0], Texts)
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.providers()), 2)
464
- self.assertIsInstance(user_message_mixed.providers()[0], Texts)
465
- self.assertIsInstance(user_message_mixed.providers()[1], Images)
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.providers()
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.providers()), 2)
481
- self.assertIsInstance(user_message_text_only.providers()[0], Texts)
482
- self.assertIsInstance(user_message_text_only.providers()[1], Texts)
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.providers()), 2, "新消息应该包含两个 provider")
526
+ self.assertEqual(len(new_message.provider()), 2, "新消息应该包含两个 provider")
526
527
 
527
- providers = new_message.providers()
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.providers()), 1, "原始消息不应该被修改")
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.providers()), 2)
544
+ self.assertEqual(len(new_message_add.provider()), 2)
544
545
 
545
- providers_add = new_message_add.providers()
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.providers()), 2)
556
+ self.assertEqual(len(combined_message.provider()), 2)
556
557
 
557
- providers = combined_message.providers()
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.providers()), 2)
566
+ self.assertEqual(len(nested_message.provider()), 2)
566
567
 
567
- providers_nested = nested_message.providers()
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.providers()), 2)
579
+ self.assertEqual(len(final_message.provider()), 2)
579
580
 
580
- providers_final = final_message.providers()
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, list[dict]] = {
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
  ),