#10092·AstrBot

[Bug] 历史中的 base64 图片导致内存耗尽(MemoryError):图片体积对上下文压缩不可见 + 轮数裁剪默认关闭

Author: MostimaBridgesCreated Sep 15, 2026Updated Sep 15, 2026

问题描述

在 v4.28.1 上,一个长期对话在上下文变长后,机器人开始持续报错,最终连纯文本消息也无法处理:

LLM 响应错误: All chat models failed: MemoryError:

随后同一会话中,用户发送任意纯文本(如「好多问题」)都会失败,且错误信息为空:

[2026-09-15 10:29:56.833] [Core][ERRO][v4.28.1] [agent_sub_stages.internal:541]: Error occurred while processing agent:

对用户可见的两条报错文本分别是:

LLM 响应错误: All chat models failed: MemoryError:
Error occurred while processing agent request:

MemoryError 由 AstrBot 自身进程抛出(不是服务商返回的),说明进程内存被耗尽。而发送纯文本也会失败这一事实说明问题不在当前消息,而在每轮都要重建的会话历史。

复核 v4.28.1 代码后,确认历史中的 base64 图片会持续累积,且现有的两道"安全网"都拦不住它。

1. 图片体积对 token 计数完全不可见

astrbot/core/agent/context/token_counter.py:34

python
IMAGE_TOKEN_ESTIMATE = 765

EstimateTokenCounter.count_tokens() 对每个 ImageURLPart 一律累加该常量:

python
elif isinstance(part, ImageURLPart):
    total += IMAGE_TOKEN_ESTIMATE

一张 3 KB 的图与一张 30 MB 的图,token 计数完全相同。上下文压缩的触发条件依赖该计数,因此图片再大也不会触发压缩

2. 轮数裁剪默认关闭

astrbot/core/config/agent_runner.py:114

python
max_turns = compression_config.get("max_turns", -1)

astrbot/core/agent/context/manager.py:60

python
# 1. 基于轮次的截断 (Enforce max turns)
if self.config.enforce_max_turns != -1:
    result = self.truncator.truncate_by_turns(...)

默认值为 -1(不限制),该整段被跳过。于是两道限制同时失效:轮数不裁剪,token 压缩又看不见图片体积。

3. base64 确实会进入持久化历史

astrbot/core/provider/entities.py:192ProviderRequest.assemble_context() 把图片引用内联为 base64 data URI:

python
# 3. Read image references without resizing or transcoding.
...
image_data = await resolve_image_ref_to_base64_data(image_url)
content_blocks.append(
    {"type": "image_url", "image_url": {"url": image_data.to_data_url()}},
)

同一函数中的注释明确说明这些 data URI 也会进入持久化历史:

python
# Capture bytes before event cleanup. Extra image paths must also
# reach providers and persisted history as portable data URIs.

astrbot/core/agent/runners/tool_loop_agent_runner.py:339/344/351 调用该方法生成运行期消息,随后由 dump_messages_with_checkpoints(all_messages) 写入会话历史;下一轮又由 astrbot/core/astr_main_agent.py:1358 原样读回:

python
req.contexts = json.loads(req.conversation.history)

4. 放大效应

每个请求路径上,同一份历史会被反复复制:SQLite 读出字符串 → json.loads 构建对象图 → 序列化为 HTTP 请求体 → provider SDK 再包一层。一份数十 MB 的 base64 历史,峰值内存可达数倍。

5. 加重问题的几处实现

  • 内存耗尽被当作模型故障重试。 tool_loop_agent_runner.py:625 使用 except Exception 捕获异常后 continue,会对同一份超大 payload 依次尝试全部 fallback provider,越重试越吃内存;最终在 :642 抛出 All chat models failed: {type}: {e}

  • 错误信息丢失异常类型。 agent_sub_stages/internal.py:541

    python
    logger.error(f"Error occurred while processing agent: {e}")

    以及紧随其后的 error_text 同样只拼 {e}MemoryError() 无参数、str() 为空串,用户因此只看到 Error occurred while processing agent request:,完全无法判断是内存问题。

  • WebUI 预览会原样返回整份历史。 astrbot/dashboard/services/conversation_service.py:143get_conversation_detail() 直接返回 conversation.history,无分页与体积控制;超大会话会触发 astrbot/dashboard/api/app.py:178 的全局兜底,表现为 Internal server error

  • Dashboard 与机器人在同一进程。 astrbot/core/core_lifecycle.py:358asyncio.gather(*self.curr_tasks) 同时运行包括 Hypercorn 在内的所有任务,因此在 WebUI 打开超大会话可以把机器人本体一起拖垮。

补充一点:is_recoverable_image_error() 的设计是正确的——它明确把资源耗尽排除在"可跳过"之外(OSError + errno.ENOMEM 时返回 False),但上层又用 except ExceptionMemoryError 捞了回来。

如何复现

  1. 使用内置 Agent,保持 agent_runner.config.compression 的默认值(max_turns 未设置)。
  2. 在同一会话中多轮发送图片(尤其大图),穿插纯文本消息。
  3. 观察 req.contexts:每轮都会携带此前全部轮次的图片 data URI,且长度持续增长。
  4. 当累计体积超过可用内存时,任意消息(包括纯文本)都会抛出 MemoryError

本机观测:data/data_v4.db 体积 141 MB,文件修改时间为 2026-09-15 09:18:29,而首次 MemoryError 出现在 09:19:51。单会话的体积明细仍在统计中。

期望行为

  • 图片占用应纳入上下文体积核算,例如按 data URI 实际长度参与 token 估算,或提供独立的字节预算,使压缩/裁剪能够及时触发。
  • max_turns 建议有非无限的安全默认值,或至少在启用图片的场景下生效。
  • 资源耗尽不应进入 fallback 重试except Exception 应排除 MemoryError 等资源类异常,直接向上抛出。
  • 错误信息应保留异常类型{e} 对无参异常会得到空串,建议改为 {type(e).__name__}: {e} 或等价形式。
  • WebUI 会话预览应做分页或体积限制,避免单个超大会话影响服务端。

相关 issue

  • #4296 报告了同一现象(图片 base64 滞留历史导致 token 爆炸),已以 completed 关闭;但 v4.28.1 中 base64 仍会进入历史(见上文代码),且 token 计数改为按固定值计入,因此 token 口径的消耗被掩盖,而内存层面的问题并未随之消失。
  • #8032 建议剥离历史消息中的图片(token 优化方向),与本 issue 的诉求部分重合。
  • #7372 报告了长上下文在 WebUI 预览卡住,与本 issue 第 5 点相关,但本 issue 包含服务端 Internal server error 与进程级内存耗尽。

环境

  • AstrBot 版本:v4.28.1
  • 操作系统:Windows
  • 部署方式:Windows 源码部署(uv / venv)
  • 消息平台适配器:Onebot v11(NapCat)

错误日志

LLM 响应错误: All chat models failed: MemoryError:
[2026-09-15 10:29:56.787] [Core] [DBUG] [agent_sub_stages.internal:193]: ready to request llm provider
[2026-09-15 10:29:56.787] [Core] [DBUG] [agent_sub_stages.internal:221]: acquired session lock for llm request
[2026-09-15 10:29:56.833] [Core][ERRO][v4.28.1] [agent_sub_stages.internal:541]: Error occurred while processing agent:

检查清单

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