Skip to content

fix: 改进微信公众号被动回复处理机制,引入缓冲与分片回复,并优化超时行为#5224

Merged
Soulter merged 2 commits intoAstrBotDevs:masterfrom
Trance-0:master
Feb 19, 2026
Merged

fix: 改进微信公众号被动回复处理机制,引入缓冲与分片回复,并优化超时行为#5224
Soulter merged 2 commits intoAstrBotDevs:masterfrom
Trance-0:master

Conversation

@Trance-0
Copy link
Contributor

@Trance-0 Trance-0 commented Feb 19, 2026

fixes: #2076

Modifications / 改动点

  • 保留所有主动回复逻辑,(即使我个人认为这些方法在大多数个人公众号上没有办法实现。
  • 修改了被动回复的逻辑,针对微信平台添加了user_buffer实现异步存储功能,修复了 [Bug]接入微信公众号平台经常漏消息 #2076
  • 缩小了wechat的分段逻辑来保留系统指引所需要的字符,原本2048字符在部分特殊情况下(中文字符过多)时可能导致发送失败。
  • 根据新增的方法添加了debug log和额外的info
  • 增加了额外的preview和被动回复模板,保证微信用户在不访问后台的情况下查看目前正在思考的进程。
  • This is NOT a breaking change. / 这不是一个破坏性变更。
    • 所有文件修改仅局限于wechat-offical,未修改其他代码逻辑。

Screenshots or Test Results / 运行截图或测试结果

在本人个人公众号上测试后修复有效,新增代码能正常回复提示消息和在微信公众号post窗口内进行分段回复。并能正常返回pipline的所有信息,包括报错消息。

以下是客户端截图
4fde1be39b5ed950366cf6fd5905784d_0
30c85920762dcf41c7d9e1196bd7d486_0
37c3675b51695542dd92bcfc276a6ca1_0

以下是服务器端截图

error_report partition

Checklist / 检查清单

  • 😊 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。/ If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
  • 👀 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”。/ My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
  • 🤓 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到了 requirements.txtpyproject.toml 文件相应位置。/ I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in requirements.txt and pyproject.toml.
  • 😮 我的更改没有引入恶意代码。/ My changes do not introduce malicious code.

测试时发现的额外补充

  • 由于在被动回复模式下,微信服务器仍然可能发送部分重复的信息,本人对本项目pipline和AstrBotMessage相关的future class的了解比较有限,针对极端条件下微信服务器在被动模式下反复发送同一条信息时可能导致部分回复消息丢失,目前没有较好的解决方案。
  • 目前拆分的回复buffer只支持文本消息,后续对message chain的支持没有想到特别好的解决方案。

Summary by Sourcery

改进微信公众号被动回复处理机制,引入缓冲与分片回复,并优化超时行为。

New Features:

  • 为微信公众号消息的多步被动回复增加用户级缓冲区和预览占位符支持。

Bug Fixes:

  • 通过严格遵守 5 秒响应限制,并更健壮地处理微信重试机制,修复微信公众号被动回复失败问题。

Enhancements:

  • 优化微信公众号长文本拆分逻辑,支持可配置的更小分片,以减少边缘情况下的发送失败。
  • 统一公众号在主动发送与被动回复模式下的回调处理方式,并延长回调超时时间以适配更长链路的处理流程。
  • 改进与微信公众号被动回复缓冲和超时相关的日志与错误信息,便于问题排查与调试。
Original summary in English

Summary by Sourcery

Improve WeChat Official Account passive reply handling with buffered, chunked responses and better timeout behavior.

New Features:

  • Add user-level buffer and preview placeholders to support multi-step passive replies for WeChat Official Account messages.

Bug Fixes:

  • Fix WeChat Official Account passive reply failures by aligning with the 5-second response constraint and handling WeChat retries more robustly.

Enhancements:

  • Refine long-text splitting for WeChat messages with configurable smaller chunks to reduce send failures in edge cases.
  • Unify WeChat Official Account callback handling for active and passive send modes and extend callback timeout duration for longer pipelines.
  • Improve logging and error messages around WeChat passive reply buffering and timeout conditions for easier debugging.

@dosubot dosubot bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Feb 19, 2026
@dosubot
Copy link

dosubot bot commented Feb 19, 2026

Related Documentation

Checked 1 published document(s) in 1 knowledge base(s). No updates required.

How did I do? Any feedback?  Join Discord

@dosubot dosubot bot added the area:platform The bug / feature is about IM platform adapter, such as QQ, Lark, Telegram, WebChat and so on. label Feb 19, 2026
@Trance-0 Trance-0 changed the title 修复wechat official 被动回复功能 fix: 修复wechat official 被动回复功能 Feb 19, 2026
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

嗨,我发现了 2 个问题,并给出了一些整体层面的反馈:

  • 在 adapter 的回调里,result 只在 else 分支(future 被新创建时)被赋值,但在 if/else 之后总是会被使用;如果某个 future 已经存在,这里会触发 UnboundLocalError——应当把 wait_for(...) 和对 result 的赋值移动到分支外面,这样两个分支都会对其进行赋值。
  • 引入新的被动回复逻辑后,WeixinOfficialAccountPlatformEvent.send 在处理纯文本消息时不再调用 future.set_result(...),但 callback 仍然会创建并等待一个 future 的结果;这意味着该 future 永远不会被 resolve,180 秒的超时时间路径会始终触发——要么在被动模式下移除 future/wexin_event_workers 机制,要么保证仍然有某段代码会设置这个 future 的结果。
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the adapter callback, `result` is only assigned in the `else` branch (when `future` is newly created), but it is always used after the `if/else`; if a `future` already exists, this will raise an `UnboundLocalError`—move the `wait_for(...)` and `result` assignment outside the branch so both paths set it.
- With the new passive reply logic, `WeixinOfficialAccountPlatformEvent.send` no longer calls `future.set_result(...)` for plain-text messages, but `callback` still creates and awaits a `future` result; this means the future will never be resolved and the 180s timeout path will always trigger—either remove the `future`/`wexin_event_workers` mechanism for passive mode or ensure some code still sets the future result.

## Individual Comments

### Comment 1
<location> `astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py:41-42` </location>
<code_context>
         self.callback: Callable[[BaseMessage], Awaitable[None]] | None = None
         self.shutdown_event = asyncio.Event()

+        self._wx_msg_time_out = 4.0  # 微信服务器要求 5 秒内回复
+        self.user_buffer: dict[str, dict[str, Any]] = user_buffer  # from_user -> state
+        self.active_send_mode = False  # 是否启用主动发送模式,启用后 callback 将直接返回回复内容,无需等待微信回调
+
</code_context>

<issue_to_address>
**suggestion:** 统一构造函数参数和实例属性中 user_buffer 的类型注解。

参数类型是 `dict[Any, dict[str, Any]]`,而属性类型是 `dict[str, dict[str, Any]]`。请将两者对齐:如果 `from_user` 始终为字符串,就收窄参数类型;如果允许非字符串键,则应将属性类型放宽为 `dict[Any, dict[str, Any]]````suggestion
class WeixinOfficialAccountServer:
    def __init__(self, event_queue: asyncio.Queue, config: dict, user_buffer: dict[str, dict[str, Any]]) -> None:
```
</issue_to_address>

### Comment 2
<location> `astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py:90-97` </location>
<code_context>
+                plain_chunks = await self.split_plain(comp.text)
</code_context>

<issue_to_address>
**issue (bug_risk):** 缓存的纯文本分片被覆盖而不是追加,可能导致较早的分片丢失。

每个 `Plain` 组件都会执行 `self.message_out['cached_xml'] = plain_chunks` 赋值操作。如果存在多个 `Plain` 组件(或对同一条消息多次调用 `send()`),这会覆盖之前的值并丢弃早先的文本。相反,当 `'cached_xml'` 已经存在时,应当在现有列表上追加(例如通过 `extend`),而不是重新赋值覆盖。
</issue_to_address>

Sourcery 对开源项目是免费的——如果你觉得这次代码审查有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈持续改进审查质量。
Original comment in English

Hey - I've found 2 issues, and left some high level feedback:

  • In the adapter callback, result is only assigned in the else branch (when future is newly created), but it is always used after the if/else; if a future already exists, this will raise an UnboundLocalError—move the wait_for(...) and result assignment outside the branch so both paths set it.
  • With the new passive reply logic, WeixinOfficialAccountPlatformEvent.send no longer calls future.set_result(...) for plain-text messages, but callback still creates and awaits a future result; this means the future will never be resolved and the 180s timeout path will always trigger—either remove the future/wexin_event_workers mechanism for passive mode or ensure some code still sets the future result.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the adapter callback, `result` is only assigned in the `else` branch (when `future` is newly created), but it is always used after the `if/else`; if a `future` already exists, this will raise an `UnboundLocalError`—move the `wait_for(...)` and `result` assignment outside the branch so both paths set it.
- With the new passive reply logic, `WeixinOfficialAccountPlatformEvent.send` no longer calls `future.set_result(...)` for plain-text messages, but `callback` still creates and awaits a `future` result; this means the future will never be resolved and the 180s timeout path will always trigger—either remove the `future`/`wexin_event_workers` mechanism for passive mode or ensure some code still sets the future result.

## Individual Comments

### Comment 1
<location> `astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py:41-42` </location>
<code_context>
         self.callback: Callable[[BaseMessage], Awaitable[None]] | None = None
         self.shutdown_event = asyncio.Event()

+        self._wx_msg_time_out = 4.0  # 微信服务器要求 5 秒内回复
+        self.user_buffer: dict[str, dict[str, Any]] = user_buffer  # from_user -> state
+        self.active_send_mode = False  # 是否启用主动发送模式,启用后 callback 将直接返回回复内容,无需等待微信回调
+
</code_context>

<issue_to_address>
**suggestion:** Unify type annotation for user_buffer between constructor arg and instance attribute.

The parameter is typed as `dict[Any, dict[str, Any]]` but the attribute as `dict[str, dict[str, Any]]`. Please align these: if `from_user` is always a string, narrow the parameter type; if non-string keys are valid, widen the attribute type to `dict[Any, dict[str, Any]]` instead.

```suggestion
class WeixinOfficialAccountServer:
    def __init__(self, event_queue: asyncio.Queue, config: dict, user_buffer: dict[str, dict[str, Any]]) -> None:
```
</issue_to_address>

### Comment 2
<location> `astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py:90-97` </location>
<code_context>
+                plain_chunks = await self.split_plain(comp.text)
</code_context>

<issue_to_address>
**issue (bug_risk):** Buffered plain chunks are overwritten rather than appended, which may drop earlier segments.

Each `Plain` component assigns `self.message_out['cached_xml'] = plain_chunks`. With multiple `Plain` components (or repeated `send()` calls for the same message), this overwrites the prior value and drops earlier text. Instead, when `'cached_xml'` already exists, append to the existing list (e.g., via `extend`) rather than reassigning.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@dosubot dosubot bot added the lgtm This PR has been approved by a maintainer label Feb 19, 2026
@Soulter Soulter merged commit bf3fa3e into AstrBotDevs:master Feb 19, 2026
1 check passed
@Soulter Soulter changed the title fix: 修复wechat official 被动回复功能 fix: 改进微信公众号被动回复处理机制,引入缓冲与分片回复,并优化超时行为 Feb 19, 2026
astrbot-doc-agent bot pushed a commit to AstrBotDevs/AstrBot-docs that referenced this pull request Feb 19, 2026
@astrbot-doc-agent
Copy link

Generated docs update PR (pending manual review):
AstrBotDevs/AstrBot-docs#129
Trigger: PR merged


AI change summary:

  • 更新了 zh/deploy/platform/weixin-official-account.mden/deploy/platform/weixin-official-account.md
  • 新增微信公众号 5 秒响应限制的处理方案说明,引入超时占位消息机制(如“正在思考...”)。
  • 详细说明了消息缓冲与获取机制:AI 响应将存入缓冲区,用户发送任意消息即可获取后续内容。
  • 补充了长文本自动分片逻辑说明,单次回复上限约为 1024 字符以提高成功率。
  • 同步更新了中英文(zh/en)双语文档内容。

Experimental bot notice:

  • This output is generated by AstrBot-Doc-Agent for review only.
  • It does not represent the final documentation form.

YukiRa1n pushed a commit to YukiRa1n/AstrBot that referenced this pull request Feb 24, 2026
* 修复wechat official 被动回复功能

* ruff format

---------

Co-authored-by: Soulter <905617992@qq.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:platform The bug / feature is about IM platform adapter, such as QQ, Lark, Telegram, WebChat and so on. lgtm This PR has been approved by a maintainer size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]接入微信公众号平台经常漏消息

2 participants