问题描述
在 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():
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=true、max_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.py 的 ProviderRequest.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:
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 的模块注释声明:
"""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。
如何复现
- 使用内置 Agent,
provider_settings.image_compress_enabled 与 image_compress_options.max_size 保持默认(true / 1280)。
- 准备满足「PNG 或 JPEG + EXIF 方向正常 + 长边不超过 1280」但体积显著大于 5 MB 的高熵图片(例如用随机内容编码的 1280×1280 RGBA PNG,约 6.26 MiB)。
- 通过任一带图片的消息平台适配器发送;在同一会话中重复发送若干张此类图片。
- 请求体随历史累积增大,最终返回 413,并抛出
All chat models failed。
不依赖消息平台的最小验证(直接调用内部函数):
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'}}}
检查清单
问题描述
在 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():任何 PNG/JPEG、EXIF 方向正常、长边不超过
max_size的图片都会被字节级原样返回,不存在绝对体积上限。实测(当前代码,全部默认配置
image_compress_enabled=true、max_size=1280):第一行说明:不修改任何配置,一张 1280×1280 的高熵 PNG 就有 6.26 MiB,base64 后约 8.7 MB,已超过 5 MB 请求体上限。第二行对应 CUA 沙箱模式(见第 3 点)。
2. 组装请求时只做 base64 编码,不再缩放
astrbot/core/provider/entities.py的ProviderRequest.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:由于判定比较的是长边像素,任何真实图片都远小于 1e6,等效于对所有静图关闭缩放。此时仅当图片超过
_CUA_IMAGE_WARN_BYTES(5 MB,恰好等于服务商图片上限)时输出一条 warning,不采取任何实际措施:意味着代码已经知晓该阈值,但仍会把超限图片发出去。
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的模块注释声明:"""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。如何复现
provider_settings.image_compress_enabled与image_compress_options.max_size保持默认(true/1280)。All chat models failed。不依赖消息平台的最小验证(直接调用内部函数):
输出
6561745 6561745 True,即未做任何处理。期望行为
MODEL_IMAGE_PNG_FALLBACK_MAX_BYTES已对带透明度的 PNG 做过 1 MB 回退,可沿用同一思路。request_too_large后不再遍历回退模型,改为触发图片降级重发,或抛出可读错误。相关 issue
#7791 报告了「图片压缩无效,大于 2048*2048 的图无法处理」,由 #7807 修复;但该修复针对的是像素尺寸绕过(
compress_image()仅在文件大于 1 MB 时触发,导致尺寸超限但体积小的图绕过缩放)。本 issue 属于字节体积维度,未被覆盖。环境
错误日志
检查清单