1. 从文本到视觉多模态能力到底解决了什么痛点大模型能写代码、能编故事、能陪你聊天但很长一段时间里它其实是个“瞎子”。你丢一张图过去它只能看到文件名或者干脆告诉你“我无法处理图片”。这个限制在实际项目里非常要命——用户上传一张发票截图想让你提取金额上传一张报错截图想让你分析原因上传一张手写笔记想让你转成文字传统纯文本模型统统无能为力。Spring AI 的多模态能力就是来解决这个问题的。它给大模型接上了一双“眼睛”让模型不仅能读文字还能看图、理解图、基于图里的内容做推理和回答。这个能力在 Spring AI 里的核心抽象叫Multimodal配合Media类型来承载图片数据最终通过ChatClient或ChatModel把图文混合的消息发给支持视觉的模型。适合谁来参考这篇内容如果你已经在用 Spring Boot 做后端开发想给自己的应用加上“图片理解”能力比如做智能客服、文档解析、内容审核、教育辅助、电商商品图分析那这篇就是给你写的。如果你还没接触过 Spring AI但用过 RestTemplate 或者 WebClient 调过模型接口那理解起来会更快因为 Spring AI 本质上就是把那些零散的 HTTP 调用封装成了统一的 Java API。我先把结论放前面Spring AI 多模态的核心链路是“构造 UserMessage → 挂载 Media 图片 → 调用支持视觉的 ChatModel → 拿到文本响应”。听起来简单但实际落地时图片怎么传、传什么格式、模型怎么选、token 怎么算、异常怎么处理每一步都有坑。下面我按实际项目里的推进顺序把设计思路、核心细节、实操过程和排查经验完整拆一遍。2. 整体设计思路与方案选型拆解2.1 为什么 Spring AI 要用 Media 抽象来承载图片很多人第一反应是图片不就是字节数组吗直接塞进消息里不就行了Spring AI 没有这么干而是设计了一个Media类型。这个选择背后有明确的工程考量。图片在不同场景下的来源是多样的可能是本地文件、可能是网络 URL、可能是 Base64 字符串、也可能是从数据库读出来的 byte 数组。如果 API 只接受 byte 数组那调用方每次都要自己做转换代码里会散落大量重复的编解码逻辑。Media抽象把这些差异统一掉了它内部用MimeType标识图片格式用Data承载实际数据调用方只需要关心“我有一张图”而不需要关心“这张图从哪来、怎么编码”。另一个原因是多模态消息的结构本身就需要区分“文本部分”和“媒体部分”。一条 UserMessage 里可能既有文字指令又有图片附件甚至可能有多张图。用Media列表来组织语义清晰扩展性也好——将来要支持音频、视频只需要扩展 Media 的类型体系消息结构不用大改。提示Spring AI 里Media的构造通常通过Media.Format和Media.Data配合完成实际写代码时建议封装一个工具方法把“文件路径转 Media”“URL 转 Media”“Base64 转 Media”这三种常见场景统一处理避免在业务代码里到处写转换逻辑。2.2 模型选型不是所有大模型都能看图这是最容易踩的坑。你代码写得再对如果调的模型不支持视觉输入结果就是报错或者模型直接忽略图片。Spring AI 本身是模型无关的抽象层它提供了统一的ChatModel接口但底层能不能处理图片取决于你接入的具体模型。目前主流支持视觉的模型大致分几类一类是原生多模态模型文本和图像在同一个模型里联合训练理解能力比较均衡另一类是在纯文本模型基础上扩展视觉编码器的版本图像理解能力也不错但在复杂推理场景下可能略弱。选型时要重点看三个指标图像分辨率支持上限、单次请求能带几张图、以及图像 token 的计费方式。我个人的经验是如果你的场景是“图片内容描述”“简单 OCR”“物体识别”大部分视觉模型都能胜任但如果是“图表数据提取”“复杂文档版面理解”“多图对比推理”就要选视觉理解能力更强的模型并且做好效果测试。不要只看官方 demo 的效果一定要拿你自己的真实业务图片去跑一批因为官方 demo 往往用的是清晰、规整、光照良好的图片而真实业务里的图片可能是模糊的、倾斜的、带水印的。2.3 同步调用还是流式返回多模态请求因为要传输图片数据请求体比纯文本大得多响应时间也通常更长。如果你的场景是“用户上传图片后等待分析结果”同步调用简单直接但用户可能要等好几秒甚至十几秒。如果场景允许建议用流式返回让模型边生成边推送用户能更快看到第一个字体验会好很多。Spring AI 对这两种模式都支持。同步用chatClient.call()流式用chatClient.stream()。流式模式下图片依然是在请求阶段一次性发出去的只是响应变成了流。这里要注意流式返回时如果模型在生成过程中报错错误处理逻辑和同步模式不太一样需要单独处理Flux的异常信号。3. 核心细节解析与实操要点3.1 图片格式与编码Base64 还是 URLSpring AI 支持两种主要的图片传递方式一种是把图片编码成 Base64 字符串直接放在请求里另一种是传一个可访问的图片 URL。这两种方式各有适用场景。Base64 的好处是不依赖外部可访问性图片数据自包含在请求里适合本地文件、用户刚上传还没存储的图片、或者内网环境下外部服务访问不到的情况。缺点是请求体会膨胀大约三分之一因为 Base64 编码会把每 3 个字节变成 4 个字符。一张 1MB 的图片编码后大约 1.33MB如果一次传多张图请求体可能很快就到几 MB。URL 方式的好处是请求体小图片由模型服务端去拉取适合图片已经存储在对象存储或者 CDN 上的场景。缺点是模型服务端必须能访问到这个 URL如果图片在内网或者需要鉴权就可能拉取失败。另外 URL 方式下图片的可用性依赖于外部服务如果图片被删了或者链接过期了请求就会失败。对比维度Base64 内嵌URL 引用请求体大小膨胀约 33%仅 URL 字符串外部依赖无需模型侧可访问适用场景本地文件、临时图片已存储的公开图片鉴权支持天然支持需 URL 自带鉴权或公开失败风险编码错误、体积超限链接失效、访问被拒注意不管用哪种方式都要控制图片体积。大多数视觉模型对图片有分辨率上限超过会被压缩或者直接拒绝。建议在客户端或服务端先做一轮压缩把长边控制在模型支持的最大分辨率以内既省 token 又避免报错。3.2 消息构造图文混合的正确姿势构造一条带图片的消息核心是把文本和媒体组织到同一个 UserMessage 里。Spring AI 的UserMessage支持传入文本和Media列表。实际写的时候文本部分要写清楚你的指令比如“请描述这张图片的内容”“请提取图中的文字”“这张图里有什么异常”。指令的写法直接影响模型的表现。我试过同样的图片用“描述这张图”和“请以 JSON 格式输出图中所有物体的名称、位置和数量”得到的结果质量差别很大。前者模型会给你一段泛泛的描述后者会给你结构化的数据。所以多模态场景下提示词工程依然重要甚至比纯文本场景更重要因为图片本身信息量大模型需要你的指令来聚焦。还有一个细节是图片的顺序。如果一条消息里有多张图模型通常会按顺序理解。如果你的指令里提到“第一张图”“第二张图”那图片在列表里的顺序就必须和指令对应。我建议在文本里明确标注每张图的用途比如“图一是用户上传的发票图二是系统里的订单记录请对比两者金额是否一致”这样模型不容易搞混。3.3 Token 消耗与成本控制图片是要消耗 token 的而且消耗量不小。不同模型的图片 token 计算方式不一样有的按图片数量固定收费有的按图片分辨率分档计算。一般来说一张 1024x1024 的图片消耗的 token 可能在几百到一千多不等具体取决于模型的分块策略。这意味着如果你的应用有大量图片请求成本会比纯文本场景高不少。控制成本有几个实用手段第一压缩图片分辨率很多场景下 512x512 就够用了没必要传原图第二如果只需要图片里的文字可以先做 OCR 再走纯文本模型成本会低很多第三对图片做缓存同一张图片的相同问题不要重复请求第四设置合理的超时和重试策略避免因为重试导致重复计费。我在一个文档解析项目里做过测算同样一批 200 张文档图片直接走视觉模型和先 OCR 再走文本模型成本差了将近一个数量级。当然 OCR 方案会损失版面理解能力所以最终选型要看业务对“理解深度”的要求。4. 实操过程与核心环节实现4.1 环境准备与依赖引入先确认你的 Spring Boot 版本和 Spring AI 版本匹配。Spring AI 迭代比较快不同版本之间 API 有变化建议锁定一个稳定版本不要盲目追新。在pom.xml里引入 Spring AI 的 starter具体 artifact 名称取决于你接入的模型提供商。dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-openai-spring-boot-starter/artifactId version你的版本号/version /dependency配置文件里配置模型服务的地址、密钥和默认模型名称。这里要注意模型名称必须选支持视觉的版本不要配成纯文本版本否则图片会被忽略或者直接报错。spring: ai: openai: api-key: ${你的密钥} base-url: ${模型服务地址} chat: options: model: 支持视觉的模型名称 temperature: 0.7提示密钥不要硬编码在配置文件里用环境变量或者配置中心注入。多模态请求的请求体大如果密钥泄露被滥用的成本比纯文本更高。4.2 图片加载与 Media 构造我封装了一个ImageLoader工具类把三种常见来源统一转成Media。这样业务代码里只需要调ImageLoader.fromFile(path)或者ImageLoader.fromUrl(url)不用关心底层细节。public class ImageLoader { public static Media fromFile(String filePath) throws IOException { byte[] bytes Files.readAllBytes(Path.of(filePath)); String mimeType Files.probeContentType(Path.of(filePath)); return new Media(MimeType.valueOf(mimeType), bytes); } public static Media fromUrl(String imageUrl) { return new Media(MimeType.valueOf(image/png), new URI(imageUrl)); } public static Media fromBase64(String base64Data, String format) { byte[] bytes Base64.getDecoder().decode(base64Data); return new Media(MimeType.valueOf(image/ format), bytes); } }这里有个细节MimeType必须和图片实际格式一致。我遇到过把 JPEG 图片标成 PNG 的情况模型虽然没报错但理解结果出现了偏差。所以从文件加载时最好用Files.probeContentType自动探测不要手动写死。4.3 构造多模态请求并调用下面是一个完整的调用示例场景是“用户上传一张商品图要求提取商品名称、颜色和大致类别”。Service public class ProductImageAnalyzer { private final ChatClient chatClient; public ProductImageAnalyzer(ChatClient.Builder builder) { this.chatClient builder.build(); } public String analyze(byte[] imageBytes, String mimeType) { Media imageMedia new Media(MimeType.valueOf(mimeType), imageBytes); String prompt 请分析这张商品图片以 JSON 格式输出以下字段 - productName: 商品名称 - color: 主要颜色 - category: 商品类别 只输出 JSON不要额外解释。 ; UserMessage message new UserMessage(prompt, List.of(imageMedia)); return chatClient.prompt() .messages(message) .call() .content(); } }这段代码里有几个关键点。第一UserMessage的构造函数接受文本和 Media 列表文本在前媒体在后这是推荐顺序。第二提示词里明确要求 JSON 输出并给出字段定义这样模型返回的结果更容易解析。第三call()是同步调用如果图片大、模型慢这个方法会阻塞生产环境要配合超时和线程池使用。4.4 流式返回的实现如果要做流式把call()换成stream()返回类型变成FluxString。public FluxString analyzeStream(byte[] imageBytes, String mimeType) { Media imageMedia new Media(MimeType.valueOf(mimeType), imageBytes); UserMessage message new UserMessage(请描述这张图片的内容。, List.of(imageMedia)); return chatClient.prompt() .messages(message) .stream() .content(); }流式模式下前端可以用 SSE 接收用户能更快看到内容逐步出现。但要注意流式响应如果中途出错已经推送出去的部分内容无法撤回所以错误处理要在服务端做好尽量在请求阶段就把明显的问题拦掉比如图片格式不对、体积超限。4.5 多图对比场景的实现有些场景需要同时传多张图做对比比如“对比两张设计稿的差异”。实现上就是把多个 Media 放进列表。public String compareImages(byte[] img1, byte[] img2) { Media media1 new Media(MimeType.valueOf(image/png), img1); Media media2 new Media(MimeType.valueOf(image/png), img2); String prompt 图一是旧版设计稿图二是新版设计稿。 请对比两张图列出所有视觉差异按区域分类说明。 ; UserMessage message new UserMessage(prompt, List.of(media1, media2)); return chatClient.prompt() .messages(message) .call() .content(); }多图场景下token 消耗会明显增加而且模型对多图的处理能力参差不齐。我实测下来两张图的对比效果通常不错但超过四张图后模型容易遗漏细节或者混淆图片顺序。如果业务需要处理很多张图建议分批处理或者先做一轮筛选。5. 常见问题与排查技巧实录5.1 图片传了但模型说“看不到图片”这是最高频的问题。排查顺序如下第一确认模型本身支持视觉纯文本模型不会报错但会忽略图片第二确认 Media 的 MimeType 正确格式标错可能导致模型无法解码第三确认图片数据完整Base64 解码后字节数是否和原图一致第四确认请求体没有超过模型服务的体积限制有些网关会静默截断大请求。我踩过一次坑图片从对象存储下载后直接转 Base64但下载流没有完全读完就关闭了导致字节数组不完整。模型返回的结果是“图片似乎损坏”排查了半天才发现是流读取的问题。所以图片数据的完整性校验很有必要可以在加载后检查字节数组长度是否和文件大小一致。5.2 返回结果不稳定同样的图每次描述不一样多模态模型的输出本身有一定随机性尤其是temperature设置较高时。如果业务需要稳定输出把temperature调低比如 0.2 到 0.3。另外提示词里明确约束输出格式比如“只输出 JSON”“不要添加额外解释”能显著提升一致性。还有一个原因是图片分辨率。如果图片被模型内部压缩后细节丢失模型每次“看到”的信息可能略有差异。解决办法是控制输入图片的分辨率不要太大也不要太小找到一个模型处理效果稳定的区间。5.3 请求超时或响应特别慢多模态请求比纯文本慢是正常的因为图片编码、传输、模型推理都需要时间。但如果慢到超时就要排查图片是不是太大了压缩到合理分辨率网络到模型服务的链路是不是稳定模型服务本身是不是负载高。我一般会设置一个分级超时连接超时 10 秒读取超时 60 秒。如果 60 秒还没返回基本可以判定这次请求有问题直接失败重试比干等更划算。重试时要注意幂等性避免重复计费。5.4 常见问题速查表问题现象可能原因排查方向解决手段模型忽略图片模型不支持视觉确认模型名称换支持视觉的模型图片解码失败MimeType 错误检查格式标识用探测工具自动识别请求体过大图片未压缩查看图片体积压缩到合理分辨率结果不稳定temperature 过高检查参数配置降低 temperature响应超时图片大或网络慢分段计时排查压缩图片、调超时多图混淆图片顺序不清检查指令对应文本中明确标注图序5.5 几个我踩过的坑和对应技巧第一个坑是图片旋转。手机拍的图片经常带 EXIF 旋转信息有些模型读取时会忽略这个信息导致图片方向不对识别结果自然就错了。解决办法是在服务端先做一次方向校正把 EXIF 里的旋转信息应用到像素上再传给模型。第二个坑是透明背景。PNG 图片如果带透明通道有些模型处理时会出问题背景变成黑色或者识别异常。如果业务不依赖透明度建议转成白底 JPEG 再传。第三个坑是并发限制。视觉模型的并发处理能力通常比纯文本模型低如果应用并发量高要做好限流和排队。我见过因为并发太高导致大量请求超时的情况后来加了信号量控制并发数稳定性明显提升。第四个坑是成本监控。多模态请求的 token 消耗波动大同样的图片不同问题消耗可能差很多。建议在应用层记录每次请求的图片数量、分辨率和 token 消耗定期分析找出高消耗场景做优化。6. 多模态能力的扩展方向与个人体会Spring AI 的多模态抽象目前主要覆盖图片但这个设计是开放的。理解了 Media 和 UserMessage 的组合方式后将来如果框架扩展支持音频、视频接入思路是一样的把媒体数据封装成对应的 Media 类型挂到消息上剩下的交给模型。从应用层面看图片理解只是第一步。真正有价值的场景往往是多模态和业务逻辑的结合。比如电商场景里用户拍一张衣服照片系统识别出款式和颜色然后去商品库匹配相似商品教育场景里学生拍一道数学题系统识别题目内容然后给出解题步骤客服场景里用户发一张报错截图系统识别错误信息然后从知识库检索解决方案。这些场景的核心不是“模型能看图”而是“看完图之后能做什么”。我在实际项目里的体会是多模态能力的落地难点不在技术接入而在效果调优和成本控制。技术接入按照文档走一两天就能跑通但要让识别准确率满足业务要求需要反复测试提示词、调整图片预处理策略、对比不同模型的效果。成本控制更是持续的工作因为图片 token 的消耗很容易在业务量上来后失控。最后分享一个实用技巧如果你的业务图片类型比较固定比如都是发票、都是身份证、都是商品图可以针对每种类型单独调优提示词和预处理流程效果会比通用方案好很多。通用方案适合探索阶段专用方案才是生产环境的归宿。