|
9 | 9 | from astrbot.core.agent.mcp_client import MCPTool |
10 | 10 | from astrbot.core.agent.tool import FunctionTool, ToolSet |
11 | 11 | from astrbot.core.conversation_mgr import Conversation |
12 | | -from astrbot.core.message.components import File, Image, Plain, Reply |
| 12 | +from astrbot.core.message.components import File, Image, Plain, Reply, Video |
13 | 13 | from astrbot.core.platform.astr_message_event import AstrMessageEvent |
14 | 14 | from astrbot.core.platform.platform_metadata import PlatformMetadata |
15 | 15 | from astrbot.core.provider import Provider |
@@ -1067,6 +1067,146 @@ async def test_build_main_agent_with_images( |
1067 | 1067 |
|
1068 | 1068 | assert result is not None |
1069 | 1069 |
|
| 1070 | + @pytest.mark.asyncio |
| 1071 | + async def test_build_main_agent_with_video_attachment( |
| 1072 | + self, mock_event, mock_context, mock_provider |
| 1073 | + ): |
| 1074 | + """Test building main agent with video attachments.""" |
| 1075 | + module = ama |
| 1076 | + mock_video = Video(file="file:///path/to/video.mp4") |
| 1077 | + mock_event.message_obj.message = [mock_video] |
| 1078 | + |
| 1079 | + mock_context.get_provider_by_id.return_value = None |
| 1080 | + mock_context.get_using_provider.return_value = mock_provider |
| 1081 | + mock_context.get_config.return_value = {} |
| 1082 | + |
| 1083 | + conv_mgr = mock_context.conversation_manager |
| 1084 | + _setup_conversation_for_build(conv_mgr) |
| 1085 | + |
| 1086 | + with ( |
| 1087 | + patch("astrbot.core.astr_main_agent.AgentRunner") as mock_runner_cls, |
| 1088 | + patch("astrbot.core.astr_main_agent.AstrAgentContext"), |
| 1089 | + ): |
| 1090 | + mock_runner = MagicMock() |
| 1091 | + mock_runner.reset = AsyncMock() |
| 1092 | + mock_runner_cls.return_value = mock_runner |
| 1093 | + |
| 1094 | + result = await module.build_main_agent( |
| 1095 | + event=mock_event, |
| 1096 | + plugin_context=mock_context, |
| 1097 | + config=module.MainAgentBuildConfig(tool_call_timeout=60), |
| 1098 | + ) |
| 1099 | + |
| 1100 | + assert result is not None |
| 1101 | + assert [ |
| 1102 | + part.text for part in result.provider_request.extra_user_content_parts |
| 1103 | + ] == ["[Video Attachment: name video.mp4, path path/to/video.mp4]"] |
| 1104 | + |
| 1105 | + @pytest.mark.asyncio |
| 1106 | + async def test_build_main_agent_with_quoted_video_attachment( |
| 1107 | + self, mock_event, mock_context, mock_provider |
| 1108 | + ): |
| 1109 | + """Test building main agent with quoted video attachments.""" |
| 1110 | + module = ama |
| 1111 | + mock_video = Video(file="file:///path/to/quoted-video.mp4") |
| 1112 | + mock_reply = Reply( |
| 1113 | + id="reply-1", |
| 1114 | + chain=[mock_video], |
| 1115 | + sender_nickname="", |
| 1116 | + message_str="quoted message", |
| 1117 | + ) |
| 1118 | + mock_event.message_obj.message = [Plain(text="Hello"), mock_reply] |
| 1119 | + |
| 1120 | + mock_context.get_provider_by_id.return_value = None |
| 1121 | + mock_context.get_using_provider.return_value = mock_provider |
| 1122 | + mock_context.get_config.return_value = {} |
| 1123 | + |
| 1124 | + conv_mgr = mock_context.conversation_manager |
| 1125 | + _setup_conversation_for_build(conv_mgr) |
| 1126 | + |
| 1127 | + with ( |
| 1128 | + patch("astrbot.core.astr_main_agent.AgentRunner") as mock_runner_cls, |
| 1129 | + patch("astrbot.core.astr_main_agent.AstrAgentContext"), |
| 1130 | + ): |
| 1131 | + mock_runner = MagicMock() |
| 1132 | + mock_runner.reset = AsyncMock() |
| 1133 | + mock_runner_cls.return_value = mock_runner |
| 1134 | + |
| 1135 | + result = await module.build_main_agent( |
| 1136 | + event=mock_event, |
| 1137 | + plugin_context=mock_context, |
| 1138 | + config=module.MainAgentBuildConfig(tool_call_timeout=60), |
| 1139 | + ) |
| 1140 | + |
| 1141 | + assert result is not None |
| 1142 | + assert ( |
| 1143 | + "[Video Attachment in quoted message: " |
| 1144 | + "name quoted-video.mp4, path path/to/quoted-video.mp4]" |
| 1145 | + ) in [part.text for part in result.provider_request.extra_user_content_parts] |
| 1146 | + |
| 1147 | + @pytest.mark.asyncio |
| 1148 | + async def test_build_main_agent_skips_video_attachment_when_conversion_fails( |
| 1149 | + self, mock_event, mock_context, mock_provider |
| 1150 | + ): |
| 1151 | + """Test video attachment failures do not abort request construction.""" |
| 1152 | + module = ama |
| 1153 | + mock_video = Video(file="file:///path/to/direct.mp4") |
| 1154 | + mock_quoted_video = Video(file="file:///path/to/quoted.mp4") |
| 1155 | + mock_reply = Reply( |
| 1156 | + id="reply-1", |
| 1157 | + chain=[mock_quoted_video], |
| 1158 | + sender_nickname="", |
| 1159 | + message_str="quoted message", |
| 1160 | + ) |
| 1161 | + mock_event.message_obj.message = [mock_video, mock_reply] |
| 1162 | + |
| 1163 | + mock_context.get_provider_by_id.return_value = None |
| 1164 | + mock_context.get_using_provider.return_value = mock_provider |
| 1165 | + mock_context.get_config.return_value = {} |
| 1166 | + |
| 1167 | + conv_mgr = mock_context.conversation_manager |
| 1168 | + _setup_conversation_for_build(conv_mgr) |
| 1169 | + |
| 1170 | + async def _raise_video_conversion_error(self): |
| 1171 | + if self.file.endswith("direct.mp4"): |
| 1172 | + raise RuntimeError("direct") |
| 1173 | + raise RuntimeError("quoted") |
| 1174 | + |
| 1175 | + with ( |
| 1176 | + patch("astrbot.core.astr_main_agent.AgentRunner") as mock_runner_cls, |
| 1177 | + patch("astrbot.core.astr_main_agent.AstrAgentContext"), |
| 1178 | + patch("astrbot.core.astr_main_agent.logger") as mock_logger, |
| 1179 | + patch.object( |
| 1180 | + Video, |
| 1181 | + "convert_to_file_path", |
| 1182 | + AsyncMock(side_effect=_raise_video_conversion_error), |
| 1183 | + ), |
| 1184 | + ): |
| 1185 | + mock_runner = MagicMock() |
| 1186 | + mock_runner.reset = AsyncMock() |
| 1187 | + mock_runner_cls.return_value = mock_runner |
| 1188 | + |
| 1189 | + result = await module.build_main_agent( |
| 1190 | + event=mock_event, |
| 1191 | + plugin_context=mock_context, |
| 1192 | + config=module.MainAgentBuildConfig(tool_call_timeout=60), |
| 1193 | + ) |
| 1194 | + |
| 1195 | + assert result is not None |
| 1196 | + assert not any( |
| 1197 | + "Video Attachment" in part.text |
| 1198 | + for part in result.provider_request.extra_user_content_parts |
| 1199 | + ) |
| 1200 | + assert mock_logger.error.call_count == 2 |
| 1201 | + assert ( |
| 1202 | + "Error processing video attachment" |
| 1203 | + in mock_logger.error.call_args_list[0][0][0] |
| 1204 | + ) |
| 1205 | + assert ( |
| 1206 | + "Error processing quoted video attachment" |
| 1207 | + in mock_logger.error.call_args_list[1][0][0] |
| 1208 | + ) |
| 1209 | + |
1070 | 1210 | @pytest.mark.asyncio |
1071 | 1211 | async def test_build_main_agent_no_prompt_no_images( |
1072 | 1212 | self, mock_event, mock_context, mock_provider |
|
0 commit comments