#10089·AstrBot

[Bug] 图片预处理缺少字节体积兜底:合规图片原样直通 + 历史图片不重新处理,导致 413 request_too_large

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

问题描述

在 v4.28.1 中向多模态模型发送大尺寸图片时,请求体超出服务商上限,返回 413 request_too_large,且以 All chat models failed 形式抛出,无法从错误得知原因是图片过大。

实测请求体 42838045 字节,按 base64 膨胀 4/3 反推图片数据约 32 MB。复核 v4.28.1 代码后,图片预处理链路存在字节体积盲区,链路如下。

1. 预处理只判定像素长边,不判定文件体积

astrbot/core/utils/media_utils.py_convert_image_bytes_sync()

python
if (
    image.format in {"PNG", "JPEG"}
    and image.getexif().get(274, 1) == 1
    and max(image.size) <= max_size
):
    return source_bytes

任何 PNG/JPEG、EXIF 方向正常、长边不超过 max_size 的图片都会被字节级原样返回,不存在绝对体积上限。

实测(当前代码,全部默认配置 image_compress_enabled=truemax_size=1280):

1280x1280 PNG   src=6561745 bytes  ->  out=6561745 bytes  passthrough=True
4000x3000 JPEG  src=23735999 bytes ->  out=23735999 bytes passthrough=True

第一行说明:不修改任何配置,一张 1280×1280 的高熵 PNG 就有 6.26 MiB,base64 后约 8.7 MB,已超过 5 MB 请求体上限。第二行对应 CUA 沙箱模式(见第 3 点)。

2. 组装请求时只做 base64 编码,不再缩放

astrbot/core/provider/entities.pyProviderRequest.assemble_context() 注释为 "Read image references without resizing or transcoding",直接 resolve_image_ref_to_base64_data(...).to_data_url() 内联为 base64 data URI。

3. CUA 沙箱模式等效关闭缩放

astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py

python
if cua_pixel_mode:
    # CUA pixel tools read coordinates 1:1 on stills, so the
    # still-image resize is lifted; ...
    max_size = 1_000_000

由于判定比较的是长边像素,任何真实图片都远小于 1e6,等效于对所有静图关闭缩放。此时仅当图片超过 _CUA_IMAGE_WARN_BYTES(5 MB,恰好等于服务商图片上限)时输出一条 warning,不采取任何实际措施:

CUA session sends N image(s) larger than 5 MB (largest X MB) without resize;
this may exceed provider image upload limits.

意味着代码已经知晓该阈值,但仍会把超限图片发出去。

4. 历史消息中的图片不参与预处理,请求体随会话累积

assemble_context() 的代码注释指出内联的 data URI 会同时进入持久化历史("must also reach providers and persisted history as portable data URIs"),history_saver.py / internal.py 通过 dump_messages_with_checkpoints(all_messages) 将模型实际看到的消息写入会话历史,下一轮 astr_main_agent.py 又直接 req.contexts = json.loads(req.conversation.history) 原样送入请求。

同时 image_input.py 的模块注释声明:

python
"""Prepare current local-agent input; history and tool results are not inputs here."""

即历史图片不会被重新预处理。两者叠加后,会话中每新增一张直通的大图,其 base64 都会被之后每一轮请求重复携带,请求体随会话单调增长,可达数十 MB 量级(本次观测到 42.8 MB)。

5. 413 后遍历全部回退模型,错误不可读

astrbot/core/agent/runners/tool_loop_agent_runner.py 会以同一份超大请求体依次尝试所有 fallback provider。413 属请求体积问题,回退模型无法缓解,最终只抛出 All chat models failed: APIStatusError: 413

如何复现

  1. 使用内置 Agent,provider_settings.image_compress_enabledimage_compress_options.max_size 保持默认(true / 1280)。
  2. 准备满足「PNG 或 JPEG + EXIF 方向正常 + 长边不超过 1280」但体积显著大于 5 MB 的高熵图片(例如用随机内容编码的 1280×1280 RGBA PNG,约 6.26 MiB)。
  3. 通过任一带图片的消息平台适配器发送;在同一会话中重复发送若干张此类图片。
  4. 请求体随历史累积增大,最终返回 413,并抛出 All chat models failed

不依赖消息平台的最小验证(直接调用内部函数):

bash
python -c "import io,os; from PIL import Image; \
from astrbot.core.utils.media_utils import _convert_image_bytes_sync; \
b=io.BytesIO(); Image.frombytes('RGBA',(1280,1280),os.urandom(1280*1280*4)).save(b,'PNG'); \
src=b.getvalue(); out=_convert_image_bytes_sync(src,1280,95); \
print(len(src), len(out), out is src)"

输出 6561745 6561745 True,即未做任何处理。

期望行为

  • 预处理增加绝对字节上限:无论尺寸是否「合规」,超过阈值即强制降质或缩放。现有 MODEL_IMAGE_PNG_FALLBACK_MAX_BYTES 已对带透明度的 PNG 做过 1 MB 回退,可沿用同一思路。
  • 历史消息中的图片在发送前也应有体积校验,或提供会话级清理手段,避免请求体随会话单调增长。
  • 识别 413 / request_too_large 后不再遍历回退模型,改为触发图片降级重发,或抛出可读错误。
  • CUA 沙箱下对超过体积阈值的图片提供可操作选项(例如允许打破像素 1:1 换取可发送),而非仅打印 warning。

相关 issue

#7791 报告了「图片压缩无效,大于 2048*2048 的图无法处理」,由 #7807 修复;但该修复针对的是像素尺寸绕过(compress_image() 仅在文件大于 1 MB 时触发,导致尺寸超限但体积小的图绕过缩放)。本 issue 属于字节体积维度,未被覆盖。

环境

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

错误日志

LLM 响应错误: All chat models failed: APIStatusError: Error code: 413 -
{'detail': {'error': {'message': 'Request body is 42838045 bytes, over the 5242880 byte limit',
'type': 'invalid_request_error', 'code': 'request_too_large'}}}

检查清单

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