Skip to content

[Bug]: QQ Official adapter may reuse msg_seq during multi-part replies, causing 40054005 duplicate-message errors #2290

Description

@jry21223

Runtime environment

LangBot v4.10.x / latest, Docker deployment, Community Edition, QQ Official Bot API (qqofficial).

Observed in a Docker runtime with the QQ Official adapter while handling Agent / Skill / streaming / multi-part reply output.

Deployment version

Community Edition

Exception

When LangBot sends multiple replies or stream chunks for the same inbound QQ Official message, QQ may reject subsequent sends with duplicate-message detection:

ValueError: QQ private send failed: status=400,
body={
  'message': '消息被去重,请检查请求msgseq',
  'code': 40054005,
  'err_code': 40054005,
  'trace_id': '...'
},
content_len=...,
msg_id=ROBOT1.0_...,
msg_seq=1

Expected behavior:

For multiple outbound messages or stream chunks associated with the same inbound QQ msg_id, LangBot should allocate a fresh msg_seq for every send attempt.

same inbound msg_id -> msg_seq: 1, 2, 3, ...

This should apply to normal text replies, mixed message chains that are split into multiple sends, rich media sends, streaming reply chunks, and retry attempts after failed sends.

Actual behavior:

In multi-part reply scenarios, the QQ Official adapter may reuse msg_seq=1 or omit proper per-reply msg_seq allocation. As a result, QQ Official API treats later sends as duplicate messages and returns:

40054005 消息被去重,请检查请求msgseq

Suspected code locations:

src/langbot/libs/qq_official_api/api.py
src/langbot/pkg/platform/sources/qqofficial.py

Possible related areas:

  • send_private_text_msg
  • send_group_text_msg
  • send_channle_group_text_msg
  • send_channle_private_text_msg
  • _send_media_msg
  • send_stream_msg
  • reply_message_chunk

In the current behavior, stream replies appear especially likely to trigger the issue because msg_seq can remain fixed while index changes.

Suggested fix direction:

Introduce a QQ Official reply sequence allocator keyed by the inbound source msg_id.

from collections import defaultdict
import asyncio

class QQOfficialClient:
    def __init__(...):
        ...
        self._reply_msg_seq = defaultdict(int)
        self._reply_msg_seq_lock = asyncio.Lock()

    async def next_reply_msg_seq(self, msg_id: str | None) -> int:
        if not msg_id:
            # fallback for proactive messages or unsupported contexts
            self._msg_seq_counter += 1
            return self._msg_seq_counter

        async with self._reply_msg_seq_lock:
            self._reply_msg_seq[str(msg_id)] += 1
            return self._reply_msg_seq[str(msg_id)]

Then every outbound send associated with an inbound msg_id should call this allocator:

body = {
    "content": content,
    "msg_type": 0,
    "msg_id": msg_id,
    "msg_seq": await self.next_reply_msg_seq(msg_id),
}

For stream replies, index can continue to represent the stream chunk index, but msg_seq should also advance for each HTTP request:

chunk 0 -> index=0, msg_seq=1
chunk 1 -> index=1, msg_seq=2
chunk 2 -> index=2, msg_seq=3

Why this fix is needed:

QQ Official API uses msg_seq for duplicate-message detection. If multiple sends under the same source msg_id reuse the same msg_seq, later messages can be rejected as duplicates.

A per-source-message msg_seq allocator would make QQ Official delivery safer for streaming, Agent, Skill, rich media, and other multi-part reply scenarios.

Reproduction steps

  1. Configure a QQ Official bot.
  2. Enable a mode that can produce multiple outbound replies for one inbound message, such as streaming output, Agent output, or Skill/tool-assisted output.
  3. Send one user message that causes multiple chunks or multiple reply sends.
  4. Observe that the first send may succeed, while subsequent sends may fail with:
40054005 消息被去重,请检查请求msgseq

Enabled plugins

A concrete plugin-assisted scenario that exposed the issue was a LangBot plugin using tool / skill assisted output. The outbound QQ delivery is handled by LangBot's QQ Official adapter, and the issue appears to be in adapter-level message sequencing rather than plugin business logic.

Metadata

Metadata

Assignees

Labels

IM: qqofficialQQ 官方 API Webhook 适配器相关 / QQ API (Webhook) adapter relatedbug?Bug或Bug修复相关 / maybe a bug

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions