Skip to content

[Bug] TTS 失败后日志显示“使用文本发送”,但文本兜底没有实际发出 #9131

Description

@Luna-channel

What happened / 发生了什么

开启 TTS 后,如果配置的 OpenAI 兼容 TTS 接口返回错误,AstrBot 会记录 TTS 失败,使用文本发送。,但实际用户端收不到兜底文本。

TTS 接口返回 502 本身不是这里要报告的核心问题。这个错误理论上应该是可恢复的,因为 ResultDecorateStage 已经捕获了 TTS Provider 异常,并把原始 Plain 文本段重新 append 回 new_chain

但实际现象是:日志显示已经进入文本兜底逻辑,用户却没有收到文本。进一步检查代码后,怀疑原因是 ResultDecorateStage.process() 的异步生成器控制流导致后续 RespondStage 没有执行。

相关代码逻辑如下:

# astrbot/core/pipeline/result_decorate/stage.py
async def process(...):
    ...
    if self.content_safe_check_reply and self.content_safe_check_stage:
        async for _ in self.content_safe_check_stage.process(...):
            yield

    ...

    if should_tts and tts_provider:
        new_chain = []
        for comp in result.chain:
            if isinstance(comp, Plain) and len(comp.text) > 1:
                try:
                    audio_path = await tts_provider.get_audio(comp.text)
                    ...
                except Exception:
                    logger.error(traceback.format_exc())
                    logger.error("TTS 失败,使用文本发送。")
                    new_chain.append(comp)
        result.chain = new_chain
# astrbot/core/pipeline/scheduler.py
if isinstance(coroutine, AsyncGenerator):
    async for _ in coroutine:
        await self._process_stages(event, i + 1)

因为 ResultDecorateStage.process() 内部存在 yield,所以整个函数会被 Python 视为 async generator。但是普通装饰/TTS 路径不一定会实际执行到 yield。而 PipelineScheduler 对 async generator stage 的处理方式是:只有该 stage 实际 yield 时,才递归进入后续 stage,也就是 RespondStage

因此可能出现这种情况:

  1. TTS 请求失败。
  2. ResultDecorateStage 捕获异常。
  3. 日志输出 TTS 失败,使用文本发送。
  4. 原始 Plain 文本被 append 回 result.chain
  5. ResultDecorateStage 没有实际 yield
  6. 调度器没有进入 RespondStage
  7. 用户收不到兜底文本。

我本地也 fetch 了当前 AstrBotDevs/AstrBotorigin/master 检查,似乎仍然存在同样的控制流结构:

origin/master:astrbot/core/pipeline/result_decorate/stage.py:156:                    yield
origin/master:astrbot/core/pipeline/result_decorate/stage.py:342:                            logger.error("TTS 失败,使用文本发送。")
origin/master:astrbot/core/pipeline/result_decorate/stage.py:343:                            new_chain.append(comp)
origin/master:astrbot/core/pipeline/scheduler.py:50:            if isinstance(coroutine, AsyncGenerator):

补充说明:context_utils.call_handler() 里确实有 _has_yielded 保护,可以在插件 handler 的 async generator 没有 yield 时补一次 yield。但 ResultDecorateStage 是 pipeline stage,由 PipelineScheduler 直接调用,不经过 call_handler(),所以这个保护对这里无效。

Reproduce / 如何复现?

  1. 在 AstrBot 中启用 TTS。
  2. 配置一个 OpenAI 兼容的 TTS Provider。
  3. 让该 TTS Provider 的 /audio/speech 接口返回错误,例如 HTTP 502。
  4. 发送一条普通消息,让 AstrBot 触发 LLM 回复并进入 TTS 转换。
  5. 观察日志中出现 TTS 失败,使用文本发送。
  6. 预期行为:TTS 失败后,AstrBot 应该把原始文本回复发送给用户。
  7. 实际行为:用户没有收到音频,也没有收到兜底文本。

可以通过日志进一步确认:TTS 失败后,没有看到同一条回复继续进入 RespondStagePrepare to send - ...: <fallback text> 日志。

AstrBot version, deployment method (e.g., Windows Docker Desktop deployment), provider used, and messaging platform used. / AstrBot 版本、部署方式(如 Windows Docker Desktop 部署)、使用的提供商、使用的消息平台适配器

  • AstrBot 版本:日志中显示为 v4.26.4。同时检查了当前 origin/master,看起来仍然有类似控制流结构。
  • 部署方式:Docker 部署。
  • Python:3.12,日志路径中可见 /usr/local/lib/python3.12/site-packages/...
  • TTS Provider:OpenAI 兼容 TTS Provider,核心代码路径为 openai_tts_api_source.py
  • 消息平台适配器:aiocqhttp / OneBot 兼容适配器。
  • 相关插件:安装了 astrbot_plugin_tts_sanitizer,它包装了 get_audio(),但异常最终仍然被 AstrBot core 捕获,理论上应走文本兜底。

OS

Linux

Logs / 报错日志

Debug 级别日志节选如下:

[2026-07-04 00:57:14.288] [Core][INFO][core.event_bus:74]: [aulus] [aulus-beta(aiocqhttp)] 49025031/49025031:

[2026-07-04 00:57:14.289] [Core] [DBUG] [waking_check.stage:158]: enabled_plugins_name: ['*']

[2026-07-04 00:57:14.293] [Core] [DBUG] [method.star_request:46]: plugin -> astrbot - handle_session_control_agent
[2026-07-04 00:57:14.293] [Core] [DBUG] [method.star_request:46]: plugin -> astrbot_plugin_blacklist_tools - on_all_message
[2026-07-04 00:57:14.293] [Core] [DBUG] [method.star_request:46]: plugin -> astrbot - handle_empty_mention
[2026-07-04 00:57:14.294] [Core] [DBUG] [method.star_request:46]: plugin -> astrbot_plugin_mimostt - on_message
[2026-07-04 00:57:14.294] [Core] [DBUG] [method.star_request:46]: plugin -> astrbot_plugin_continuous_message - handle_private_msg
[2026-07-04 00:57:14.294] [Plug] [DBUG] [astrbot_plugin_continuous_message.main:120]: [消息防抖动] 输入状态通知 | user_id: 49025031 | status_text: 对方正在输入... | is_typing: True | 有活跃会话: False | event_type: 2
[2026-07-04 00:57:14.294] [Core] [DBUG] [pipeline.scheduler:55]: 阶段 ProcessStage 已终止事件传播。
[2026-07-04 00:57:14.295] [Core][INFO][respond.stage:206]: Prepare to send - 49025031/49025031:
[2026-07-04 00:57:14.295] [Core] [DBUG] [pipeline.context_utils:95]: hook(OnAfterMessageSentEvent) -> input_state_by_napcat - after_message_sent
[2026-07-04 00:57:14.295] [Core][INFO][pipeline.context_utils:103]: input_state_by_napcat - after_message_sent 终止了事件传播。
[2026-07-04 00:57:14.295] [Core] [DBUG] [pipeline.scheduler:75]: 阶段 RespondStage 已终止事件传播。
[2026-07-04 00:57:14.295] [Core] [DBUG] [pipeline.scheduler:93]: pipeline execution completed.

[2026-07-04 00:57:21.468] [Core][ERRO][v4.26.4] [result_decorate.stage:341]: Traceback (most recent call last):
  File "/AstrBot/astrbot/core/pipeline/result_decorate/stage.py", line 302, in process
    audio_path = await tts_provider.get_audio(comp.text)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/AstrBot/data/plugins/astrbot_plugin_tts_sanitizer/main.py", line 151, in wrapped_get_audio
    return await original_get_audio(filtered)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/AstrBot/astrbot/core/provider/sources/openai_tts_api_source.py", line 51, in get_audio
    async with self.client.audio.speech.with_streaming_response.create(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/openai/_response.py", line 653, in __aenter__
    self.__response = await self._api_request
                      ^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/openai/resources/audio/speech.py", line 207, in create
    return await self._post(
           ^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/openai/_base_client.py", line 1913, in post
    return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/openai/_base_client.py", line 1698, in request
    raise self._make_status_error_from_response(err.response) from None
openai.InternalServerError: upstream returned HTTP 502 Bad Gateway

[2026-07-04 00:57:21.470] [Core][ERRO][v4.26.4] [result_decorate.stage:342]: TTS 失败,使用文本发送。
[2026-07-04 00:57:21.470] [Core][INFO][result_decorate.stage:301]: TTS 请求: 或者如果你记得之前的任务名叫什么,我也可以搜一下日志
[2026-07-04 00:57:21.470] [Plug] [DBUG] [astrbot_plugin_tts_sanitizer.main:131]: TTS过滤: 包装函数被调用,原文: 或者如果你记得之前的任务名叫什么,我也可以搜一下日志...

关键点是:TTS 异常已经被捕获,并且日志明确显示要使用文本发送,但用户没有收到文本兜底。

Are you willing to submit a PR? / 你愿意提交 PR 吗?

  • Yes!

Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:coreThe bug / feature is about astrbot's core, backendbugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions