多模态传图报 400,先分清两种:
image_url既不是公网 URL,也不是data:image/png;base64,...这种 data URI;或者你用了个根本不支持视觉的模型。前者用 Spring AI 的Media配Resource交给框架转 base64,后者换成gpt-4o系列等支持视觉的模型。
Issue & Env(案发现场与环境)
- 业务场景:用 Spring AI 多模态消息给模型传图,请求直接弹 400。
- 运行环境:Spring AI 1.1.x,OpenAI Chat 接口;多模态图片走
content数组里的image_url。 - 触发条件:
image_url不是合法 URL 或 data URI,或者模型不支持图片输入。
StackTrace(堆栈日志)
接口返回的第一种形态,值本身不合法:
Error code: 400 - {'error': {'message': 'Invalid content type. image_url must be a valid URL or data URI.', 'type': 'invalid_request_error', 'param': 'messages[0].content[1].image_url', 'code': None}}
第二种形态,模型不支持视觉:
Error code: 400 - {'error': {'message': "Unsupported value: 'image_url' is not supported with this model.", 'type': 'invalid_request_error', 'param': 'messages[0].content[1]', 'code': 'unsupported_value'}}
这两条是 OpenAI 视觉接口的常见报错形态,具体文案以实际接口返回为准。落到 Spring AI 侧,它会把它包成不可重试异常往外抛,因为 4xx 默认不重试。
Reproduce(复现代码)
最典型的错法,把本地文件路径当成 URL:
String response = ChatClient.create(chatModel).prompt()
.user(u -> u.text("描述这张图片")
.media(MimeTypeUtils.IMAGE_PNG, "file:///Users/me/pic.png"))
.call()
.content();
或者手工拼 base64 时漏了前缀:
String b64 = Base64.getEncoder()
.encodeToString(Files.readAllBytes(Path.of("/tmp/pic.png")));
// 少了 data:image/png;base64, 前缀,接口认不出来
Root Cause(根因分析)
image_url 这个字段只认两种值:公网可访问的 http(s) URL,或 data:image/xxx;base64,... 形式的 data URI。本地文件路径、file://、内网地址、裸 base64 字符串都不合法,校验阶段直接 400。
用 Media 传 Resource 时,Spring AI 会读资源,并按 MIME 类型转成 data URI。所以 ClassPathResource、FileSystemResource 都能用,框架替你补了前缀和编码。你要是手动拼 base64,却忘了加 data:image/png;base64,,就等于把一个裸字符串塞进 image_url,报的正是第一条。
第二条是另一个维度的事:模型不支持视觉输入。像 gpt-3.5 系列就不接受图片,请求里出现 image_url 会被判 unsupported_value。这跟图片合不合法无关,纯粹是模型选错了。
还有个常被忽略的成本点:图片进上下文会按 token 计费。大图转 base64 后请求体动辄几 MB,既拖慢请求,也抬高 token 消耗。
The Fix(解决方案)
交给框架:Media + MimeTypeUtils + Resource
首选这条,让 Spring AI 自己读文件、转 data URI:
String response = ChatClient.create(chatModel).prompt()
.user(u -> u.text("描述这张图片")
.media(MimeTypeUtils.IMAGE_PNG, new ClassPathResource("/images/pic.png")))
.call()
.content();
磁盘文件用 FileSystemResource,类路径资源用 ClassPathResource。别把路径字符串直接当 URL 传。
手工拼 base64 别漏前缀
确实要自己编码时,data URI 前缀必须带上:
String dataUri = "data:image/png;base64," + b64;
MIME 类型要和图片真实格式一致,png 配 image/png,jpg 配 image/jpeg。
换成支持视觉的模型
先确认模型的输入模态里有图像。gpt-4o 系列支持视觉,gpt-3.5 系列不支持。别拿纯文本模型试图片。
大图先压缩
base64 会让体积涨约三分之一,再加上按 token 计费,传原图很亏。先缩放、裁剪再传。真需要原图细节,就用支持 URL 的方式,让服务端自己去拉取。
References(参考来源)
- Images and vision — OpenAI 官方指南openai-docs
- Multimodality API — Spring AI 官方文档spring-docs
- OpenAI Chat(Multimodal / Vision)— Spring AI 官方文档spring-docs
