From 8ad7fa3f262bf3c20ed0a9ba923bb082dc4d9fac Mon Sep 17 00:00:00 2001 From: sjh9714 <163989462+sjh9714@users.noreply.github.com> Date: Tue, 7 Jul 2026 00:13:56 +0900 Subject: [PATCH 1/2] fix: continue pipeline after TTS fallback --- .../core/pipeline/result_decorate/stage.py | 2 + tests/test_qqofficial_group_message_create.py | 68 +++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/astrbot/core/pipeline/result_decorate/stage.py b/astrbot/core/pipeline/result_decorate/stage.py index 5956c8b5ef..4444a2e7e3 100644 --- a/astrbot/core/pipeline/result_decorate/stage.py +++ b/astrbot/core/pipeline/result_decorate/stage.py @@ -420,3 +420,5 @@ async def process( # 引用回复 if self.reply_with_quote: result.chain.insert(0, Reply(id=event.message_obj.message_id)) + + yield diff --git a/tests/test_qqofficial_group_message_create.py b/tests/test_qqofficial_group_message_create.py index ee064646a9..dadb87f45a 100644 --- a/tests/test_qqofficial_group_message_create.py +++ b/tests/test_qqofficial_group_message_create.py @@ -429,3 +429,71 @@ async def test_result_decorate_segments_qqofficial_ws_plain_result(): "第一段", "第二段", ] + + +@pytest.mark.asyncio +async def test_result_decorate_yields_after_tts_fallback(monkeypatch): + stage = ResultDecorateStage() + stage.reply_prefix = "" + stage.content_safe_check_reply = False + stage.enable_segmented_reply = False + stage.only_llm_result = False + stage.content_cleanup_rule = "" + stage.show_reasoning = False + stage.tts_trigger_probability = 1 + stage.reply_with_mention = False + stage.reply_with_quote = False + stage.forward_threshold = 1000 + tts_provider = SimpleNamespace( + get_audio=AsyncMock(side_effect=RuntimeError("tts failed")), + ) + setattr( + stage, + "ctx", + SimpleNamespace( + plugin_manager=SimpleNamespace( + context=SimpleNamespace( + get_using_tts_provider=lambda _umo: tts_provider, + ), + ), + astrbot_config={ + "provider_tts_settings": { + "enable": True, + "use_file_service": False, + "dual_output": False, + }, + "callback_api_base": "", + "t2i": False, + }, + ), + ) + result = MessageEventResult( + chain=[Plain("hello")], + result_content_type=ResultContentType.LLM_RESULT, + ) + event = SimpleNamespace( + plugins_name=None, + unified_msg_origin="qq_official:GroupMessage:group-1", + get_result=lambda: result, + get_platform_name=lambda: "qq_official", + is_stopped=lambda: False, + get_extra=lambda *_args, **_kwargs: None, + track_temporary_local_file=lambda _path: None, + ) + + async def should_process_tts_request(_event): + return True + + monkeypatch.setattr( + "astrbot.core.pipeline.result_decorate.stage." + "SessionServiceManager.should_process_tts_request", + should_process_tts_request, + ) + + yielded_count = 0 + async for _ in cast(Any, stage.process(cast(Any, event))): + yielded_count += 1 + + assert yielded_count == 1 + tts_provider.get_audio.assert_awaited_once_with("hello") + assert [comp.text for comp in result.chain if isinstance(comp, Plain)] == ["hello"] From 1f2aade0781c48984df08ac4fe436e82d85cee90 Mon Sep 17 00:00:00 2001 From: sjh9714 <163989462+sjh9714@users.noreply.github.com> Date: Tue, 7 Jul 2026 00:36:20 +0900 Subject: [PATCH 2/2] fix: keep t2i fallback result yielding --- .../core/pipeline/result_decorate/stage.py | 2 +- tests/test_qqofficial_group_message_create.py | 66 +++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/astrbot/core/pipeline/result_decorate/stage.py b/astrbot/core/pipeline/result_decorate/stage.py index 4444a2e7e3..172e1d97cd 100644 --- a/astrbot/core/pipeline/result_decorate/stage.py +++ b/astrbot/core/pipeline/result_decorate/stage.py @@ -358,6 +358,7 @@ async def process( plain_str = "".join(parts) if plain_str and len(plain_str) > self.t2i_word_threshold: render_start = time.time() + url = None try: url = await html_renderer.render_t2i( plain_str, @@ -367,7 +368,6 @@ async def process( ) except BaseException: logger.error("文本转图片失败,使用文本发送。") - return if time.time() - render_start > 3: logger.warning( "文本转图片耗时超过了 3 秒,如果觉得很慢可以在 WebUI 中关闭文本转图片模式。", diff --git a/tests/test_qqofficial_group_message_create.py b/tests/test_qqofficial_group_message_create.py index dadb87f45a..fe8409d720 100644 --- a/tests/test_qqofficial_group_message_create.py +++ b/tests/test_qqofficial_group_message_create.py @@ -497,3 +497,69 @@ async def should_process_tts_request(_event): assert yielded_count == 1 tts_provider.get_audio.assert_awaited_once_with("hello") assert [comp.text for comp in result.chain if isinstance(comp, Plain)] == ["hello"] + + +@pytest.mark.asyncio +async def test_result_decorate_yields_after_t2i_fallback(monkeypatch): + stage = ResultDecorateStage() + stage.reply_prefix = "" + stage.content_safe_check_reply = False + stage.enable_segmented_reply = False + stage.only_llm_result = False + stage.content_cleanup_rule = "" + stage.show_reasoning = False + stage.tts_trigger_probability = 1 + stage.reply_with_mention = False + stage.reply_with_quote = False + stage.forward_threshold = 1000 + stage.t2i_word_threshold = 5 + stage.t2i_use_network = False + stage.t2i_active_template = "default" + setattr( + stage, + "ctx", + SimpleNamespace( + plugin_manager=SimpleNamespace( + context=SimpleNamespace( + get_using_tts_provider=lambda _umo: None, + ), + ), + astrbot_config={ + "provider_tts_settings": { + "enable": False, + "use_file_service": False, + "dual_output": False, + }, + "callback_api_base": "", + "t2i": True, + "t2i_use_file_service": False, + }, + ), + ) + result = MessageEventResult( + chain=[Plain("hello fallback text")], + result_content_type=ResultContentType.LLM_RESULT, + ) + event = SimpleNamespace( + plugins_name=None, + unified_msg_origin="qq_official:GroupMessage:group-1", + get_result=lambda: result, + get_platform_name=lambda: "qq_official", + is_stopped=lambda: False, + get_extra=lambda *_args, **_kwargs: None, + ) + render_t2i = AsyncMock(side_effect=RuntimeError("t2i failed")) + monkeypatch.setattr( + "astrbot.core.pipeline.result_decorate.stage.html_renderer.render_t2i", + render_t2i, + ) + + yielded_count = 0 + async for _ in cast(Any, stage.process(cast(Any, event))): + yielded_count += 1 + + assert yielded_count == 1 + render_t2i.assert_awaited_once() + assert [comp.text for comp in result.chain if isinstance(comp, Plain)] == [ + "hello fallback text", + ]