问题描述
当在 platform_settings 中启用了**分段回复(segmented_reply.enable: true)**时,若 LLM 返回的消息链中包含内嵌的 QQ 原生表情(Face 组件,例如在文字中间通过表情插件或正则将 [狗头] 转为 Face(id=277)),该表情无法与句子中的文字保持在同一个消息气泡中,而是会被作为一条独立的消息单独发送。
在 QQ 群聊中这会导致原本自然的一句话(如 好的[狗头],我知道了!)被生硬切碎成 3 条消息:
好的
[Face:277](独立发送一个黄豆表情)
,我知道了!
不仅影响阅读体验,还会触发单组件发送延迟造成断断续续刷屏。
源码定位与分析
定位在核心响应管道文件 astrbot/core/pipeline/respond/stage.py 的 RespondStage.process:
if self.is_seg_reply_required(event):
header_comps = self._extract_comp(
result.chain,
{ComponentType.Reply, ComponentType.At},
modify_raw_chain=True,
)
...
for comp in result.chain: # 问题根源:简单遍历了 chain 里的每一个组件
i = await self._calc_comp_interval(comp)
await asyncio.sleep(i)
try:
if comp.type in need_separately:
await event.send(result.derive([comp]))
else:
await event.send(result.derive([*header_comps, comp])) # 每个非单独组件独立发送
header_comps.clear()
分段回复的初衷应当是按句子/标点分段发送,但此处直接按组件 for comp in result.chain: 逐个发送。当链中存在 Face 或其他行内组件时,它们本该与前后文本同属一个分段气泡,却被强制拆分成单条消息。
修复建议
在分段回复遍历前,将相邻的行内组件(如 Plain 和 Face 等非必须单独发送组件)打包合并为一个子消息链,按组进行分段发送:
sub_chains = []
current_group = []
for comp in result.chain:
if comp.type in need_separately:
if current_group:
sub_chains.append(current_group)
current_group = []
sub_chains.append([comp])
else:
# 行内组件(Plain, Face 等)合并在同一个消息气泡
current_group.append(comp)
if current_group:
sub_chains.append(current_group)
for group in sub_chains:
i = await self._calc_comp_interval(group[0])
await asyncio.sleep(i)
try:
if any(c.type in need_separately for c in group):
await event.send(result.derive(group))
else:
await event.send(result.derive([*header_comps, *group]))
header_comps.clear()
except Exception as e:
logger.error(f"Failed to send grouped message: {e}", exc_info=True)
如何复现?
- 在全局设置
cmd_config.json 中开启分段回复(platform_settings.segmented_reply.enable: true)。
- 在 LLM 回复链中构造或通过插件生成包含内嵌表情的消息链,例如:
result.chain = [Plain("好的"), Face(id=277), Plain(",我知道了!")]。
- 观察 AstrBot 发送到群聊的实际行为,会发现
Face 被拆解成一条单独的消息单独发出。
AstrBot 版本
v4.27.5
操作系统
Linux
部署方式
Docker
使用的消息平台适配器
Onebot v11 (NapCat)
错误日志
# 观察到 AstrBot 在分段回复时为每个组件单独调用一次 send:
[Core] [INFO] Prepare to send - User/123456: [Plain: 好的, Face: 277, Plain: ,我知道了!]
# 实际输出到 OneBot 的是连续的单元素调用:
# 1. send([Plain("好的")])
# 2. send([Face(277)]) -> 表现为独立的单黄豆表情消息气泡
# 3. send([Plain(",我知道了!")])
检查清单
问题描述
当在
platform_settings中启用了**分段回复(segmented_reply.enable: true)**时,若 LLM 返回的消息链中包含内嵌的 QQ 原生表情(Face组件,例如在文字中间通过表情插件或正则将[狗头]转为Face(id=277)),该表情无法与句子中的文字保持在同一个消息气泡中,而是会被作为一条独立的消息单独发送。在 QQ 群聊中这会导致原本自然的一句话(如
好的[狗头],我知道了!)被生硬切碎成 3 条消息:好的[Face:277](独立发送一个黄豆表情),我知道了!不仅影响阅读体验,还会触发单组件发送延迟造成断断续续刷屏。
源码定位与分析
定位在核心响应管道文件
astrbot/core/pipeline/respond/stage.py的RespondStage.process:分段回复的初衷应当是按句子/标点分段发送,但此处直接按组件
for comp in result.chain:逐个发送。当链中存在Face或其他行内组件时,它们本该与前后文本同属一个分段气泡,却被强制拆分成单条消息。修复建议
在分段回复遍历前,将相邻的行内组件(如
Plain和Face等非必须单独发送组件)打包合并为一个子消息链,按组进行分段发送:如何复现?
cmd_config.json中开启分段回复(platform_settings.segmented_reply.enable: true)。result.chain = [Plain("好的"), Face(id=277), Plain(",我知道了!")]。Face被拆解成一条单独的消息单独发出。AstrBot 版本
v4.27.5
操作系统
Linux
部署方式
Docker
使用的消息平台适配器
Onebot v11 (NapCat)
错误日志
检查清单