Skip to content

[Bug] 开启分段回复(segmented_reply)时,消息链中的 Face(QQ表情)会被作为独立消息切碎单独发送 #10047

Description

@luckjokerwang

问题描述

当在 platform_settings 中启用了**分段回复(segmented_reply.enable: true)**时,若 LLM 返回的消息链中包含内嵌的 QQ 原生表情(Face 组件,例如在文字中间通过表情插件或正则将 [狗头] 转为 Face(id=277)),该表情无法与句子中的文字保持在同一个消息气泡中,而是会被作为一条独立的消息单独发送。

在 QQ 群聊中这会导致原本自然的一句话(如 好的[狗头],我知道了!)被生硬切碎成 3 条消息:

  1. 好的
  2. [Face:277](独立发送一个黄豆表情)
  3. ,我知道了!

不仅影响阅读体验,还会触发单组件发送延迟造成断断续续刷屏。

源码定位与分析

定位在核心响应管道文件 astrbot/core/pipeline/respond/stage.pyRespondStage.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 或其他行内组件时,它们本该与前后文本同属一个分段气泡,却被强制拆分成单条消息。

修复建议

在分段回复遍历前,将相邻的行内组件(如 PlainFace 等非必须单独发送组件)打包合并为一个子消息链,按组进行分段发送:

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)

如何复现?

  1. 在全局设置 cmd_config.json 中开启分段回复(platform_settings.segmented_reply.enable: true)。
  2. 在 LLM 回复链中构造或通过插件生成包含内嵌表情的消息链,例如:result.chain = [Plain("好的"), Face(id=277), Plain(",我知道了!")]
  3. 观察 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(",我知道了!")])

检查清单

  • 我已在 Issue 列表中搜索过相关问题仍无法解决,或该问题从未被报告过。
  • 我已尝试过禁用所有插件,排除了可能是因插件导致的问题。
  • 我报告的问题与 AstrBot 本体相关,而非在报告某一插件的问题。
  • 我已阅读并同意本项目的贡献者行为准则
  • (可选)我愿意提交 PR 以帮助修复该问题。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions