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。
因此可能出现这种情况:
- TTS 请求失败。
ResultDecorateStage 捕获异常。
- 日志输出
TTS 失败,使用文本发送。。
- 原始
Plain 文本被 append 回 result.chain。
- 但
ResultDecorateStage 没有实际 yield。
- 调度器没有进入
RespondStage。
- 用户收不到兜底文本。
我本地也 fetch 了当前 AstrBotDevs/AstrBot 的 origin/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 / 如何复现?
- 在 AstrBot 中启用 TTS。
- 配置一个 OpenAI 兼容的 TTS Provider。
- 让该 TTS Provider 的
/audio/speech 接口返回错误,例如 HTTP 502。
- 发送一条普通消息,让 AstrBot 触发 LLM 回复并进入 TTS 转换。
- 观察日志中出现
TTS 失败,使用文本发送。。
- 预期行为:TTS 失败后,AstrBot 应该把原始文本回复发送给用户。
- 实际行为:用户没有收到音频,也没有收到兜底文本。
可以通过日志进一步确认:TTS 失败后,没有看到同一条回复继续进入 RespondStage 的 Prepare 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 吗?
Code of Conduct
What happened / 发生了什么
开启 TTS 后,如果配置的 OpenAI 兼容 TTS 接口返回错误,AstrBot 会记录
TTS 失败,使用文本发送。,但实际用户端收不到兜底文本。TTS 接口返回 502 本身不是这里要报告的核心问题。这个错误理论上应该是可恢复的,因为
ResultDecorateStage已经捕获了 TTS Provider 异常,并把原始Plain文本段重新 append 回new_chain。但实际现象是:日志显示已经进入文本兜底逻辑,用户却没有收到文本。进一步检查代码后,怀疑原因是
ResultDecorateStage.process()的异步生成器控制流导致后续RespondStage没有执行。相关代码逻辑如下:
因为
ResultDecorateStage.process()内部存在yield,所以整个函数会被 Python 视为 async generator。但是普通装饰/TTS 路径不一定会实际执行到yield。而PipelineScheduler对 async generator stage 的处理方式是:只有该 stage 实际yield时,才递归进入后续 stage,也就是RespondStage。因此可能出现这种情况:
ResultDecorateStage捕获异常。TTS 失败,使用文本发送。。Plain文本被 append 回result.chain。ResultDecorateStage没有实际yield。RespondStage。我本地也 fetch 了当前
AstrBotDevs/AstrBot的origin/master检查,似乎仍然存在同样的控制流结构:补充说明:
context_utils.call_handler()里确实有_has_yielded保护,可以在插件 handler 的 async generator 没有 yield 时补一次 yield。但ResultDecorateStage是 pipeline stage,由PipelineScheduler直接调用,不经过call_handler(),所以这个保护对这里无效。Reproduce / 如何复现?
/audio/speech接口返回错误,例如 HTTP 502。TTS 失败,使用文本发送。。可以通过日志进一步确认:TTS 失败后,没有看到同一条回复继续进入
RespondStage的Prepare to send - ...: <fallback text>日志。AstrBot version, deployment method (e.g., Windows Docker Desktop deployment), provider used, and messaging platform used. / AstrBot 版本、部署方式(如 Windows Docker Desktop 部署)、使用的提供商、使用的消息平台适配器
v4.26.4。同时检查了当前origin/master,看起来仍然有类似控制流结构。/usr/local/lib/python3.12/site-packages/...。openai_tts_api_source.py。aiocqhttp/ OneBot 兼容适配器。astrbot_plugin_tts_sanitizer,它包装了get_audio(),但异常最终仍然被 AstrBot core 捕获,理论上应走文本兜底。OS
Linux
Logs / 报错日志
Debug 级别日志节选如下:
关键点是:TTS 异常已经被捕获,并且日志明确显示要使用文本发送,但用户没有收到文本兜底。
Are you willing to submit a PR? / 你愿意提交 PR 吗?
Code of Conduct