[Bug] 历史中的 base64 图片导致内存耗尽(MemoryError):图片体积对上下文压缩不可见 + 轮数裁剪默认关闭
问题描述
在 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:
IMAGE_TOKEN_ESTIMATE = 765EstimateTokenCounter.count_tokens() 对每个 ImageURLPart 一律累加该常量:
elif isinstance(part, ImageURLPart):
total += IMAGE_TOKEN_ESTIMATE一张 3 KB 的图与一张 30 MB 的图,token 计数完全相同。上下文压缩的触发条件依赖该计数,因此图片再大也不会触发压缩。
2. 轮数裁剪默认关闭
astrbot/core/config/agent_runner.py:114:
max_turns = compression_config.get("max_turns", -1)astrbot/core/agent/context/manager.py:60:
# 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:192 的 ProviderRequest.assemble_context() 把图片引用内联为 base64 data URI:
# 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 也会进入持久化历史:
# 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 原样读回:
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: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:143的get_conversation_detail()直接返回conversation.history,无分页与体积控制;超大会话会触发astrbot/dashboard/api/app.py:178的全局兜底,表现为Internal server error。Dashboard 与机器人在同一进程。
astrbot/core/core_lifecycle.py:358用asyncio.gather(*self.curr_tasks)同时运行包括 Hypercorn 在内的所有任务,因此在 WebUI 打开超大会话可以把机器人本体一起拖垮。
补充一点:is_recoverable_image_error() 的设计是正确的——它明确把资源耗尽排除在"可跳过"之外(OSError + errno.ENOMEM 时返回 False),但上层又用 except Exception 把 MemoryError 捞了回来。
如何复现
- 使用内置 Agent,保持
agent_runner.config.compression的默认值(max_turns未设置)。 - 在同一会话中多轮发送图片(尤其大图),穿插纯文本消息。
- 观察
req.contexts:每轮都会携带此前全部轮次的图片 data URI,且长度持续增长。 - 当累计体积超过可用内存时,任意消息(包括纯文本)都会抛出
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 本体相关,而非在报告某一插件的问题。
- 我已阅读并同意本项目的贡献者行为准则。
Source: AstrBotDevs/AstrBot