☰
Spring AI多模态视觉理解:从图片问答到结构化输出实战
2026/10/21 7:01:15 网站建设 项目流程

先把结论放前面:Spring AI 的多模态能力,并不只是“给模型传一张图”那么简单。真正把它接入业务时,你会遇到消息结构、图片格式、上下文保留、结构化输出等一系列问题,这些问题单看文档容易踩坑,实际跑一遍才能体会到。

这篇写的是多模态系列里的第四种 API——视觉理解,也就是同时把文本和图片交给模型,让它看图回答问题。前几篇分别整理过图像生成、语音转文字和语音合成,这篇轮到视觉理解,正好把 Spring AI 里四类常见多模态能力补全。文章会从定位和原理讲起,再给出完整可跑的 Spring Boot 实现,最后是高频问题和排查方式。

适合谁看?已经用 Spring AI 跑通过普通文本对话,现在想把图片输入加进项目里的开发者。看完你应该能独立写出一个支持“文字+图片”问答的服务接口。

1. 先弄懂第四多模态API在Spring AI里的定位

1.1 多模态能力全景梳理

Spring AI 作为一套面向大模型应用的抽象层,把市面上主流模型的能力收敛成了几个统一入口。站在项目接入的角度看,常见多模态能力大致可以分成四个方向:

能力方向输入输出典型场景
图像生成文本提示词图片海报生成、配图生产
语音转文字音频文件文本会议转写、客服质检
语音合成文本音频语音播报、虚拟人
视觉理解文本+图片文本图片问答、截图提取信息、审核辅助

前三种相对容易理解:输入和输出之间是“单向转换”。视觉理解看着也简单,但真正落地时它是四类里少有的“双向交叉”场景——图片内容影响回答,文本问题又限制回答方向,两者必须同时在一次请求里发给模型。

这也是我把它排到第四篇才写的原因。前三种多模态 API 只要处理好“把媒体数据塞进请求”就行,视觉理解还涉及消息的 content 结构拆分、提示词策略、上下文管理等更抽象的问题。

1.2 视觉理解解决的核心问题

视觉理解解决的核心问题只有一个:让模型读取图片里的非结构化信息,再输出结构化内容。

这句话听起来平淡,实际业务价值很大。比如:

  • 用户上传一张商品图片,系统自动生成描述文案和标签
  • 截图里包含表格数据,直接让模型提取成 JSON
  • 设计稿自动转成页面代码时的 UI 描述生成
  • 审核系统里识别图片中是否有违规元素

这些场景的共同点是,图片不是给人看的,而是给程序用的。程序要拿到图片里的信息,以前必须靠人工标注或者写一堆 OCR 和规则逻辑,现在直接问模型就行。

和纯文本对话相比,视觉理解的核心差异在于“模态融合”。文本消息里只有纯字符串,视觉理解的消息必须同时携带文字指令和图片数据。Spring AI 对这个过程的封装做得比较干净:你只要按它的消息模型组织好内容,底层会把图片转换并拼装成模型 API 认可的格式。

提示:视觉理解不是“把图片描述一遍”这个简单功能。它在工程上的用法,更多是作为一个“信息抽取器”,把图片变成下游程序能消费的结构化数据。

2. 依赖配置与消息结构:视觉理解前的必修课

2.1 依赖引入与版本选择

我建议直接基于 Spring Boot 3.2 / 3.3 工程来加依赖。Spring AI 的场景化 starter 已经做得比较完善,不需要手动拼底层客户端。

Maven 引入方式是在pom.xml里加 BOM 和对应 starter:

<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>1.0.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>

然后在 dependencies 区域加入 model-chat 的通用 starter:

<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-chat</artifactId> </dependency>

这个通用 starter 在 Spring AI 1.0 里是推荐的接入方式,它不绑定特定厂商,而是通过 base-url 和 api-key 指向你实际使用的模型网关或云端服务。好处很明显:以后从一家服务商切换到另一家,业务代码基本不用动。

2.2 关键配置项说明

接入视觉理解时,application.yml里核心配置如下:

spring: application: name: multimodal-vision-demo ai: chat: base-url: ${AI_BASE_URL:http://localhost:8080/v1} api-key: ${AI_API_KEY:sk-local-demo} options: model: ${AI_VISION_MODEL:multimodal-latest} temperature: 0.2 max-tokens: 1024

几个参数背后的原理要理解清楚:

  • base-url:指向兼容大模型协议的服务地址。如果本地起了网关代理,填网关地址;如果直接用云端服务,填云端地址。这个地址必须支持多模态消息格式。
  • api-key:身份凭证,建议用环境变量注入,不要提交到代码仓库。
  • model:指定模型实例。注意,不是所有模型都支持图片输入,模型名字里通常能看到视觉、多模态这类标识。务必替换成你实际账号可用的视觉模型名。
  • temperature:随机性控制。视觉理解任务大多数是信息提取场景,温度可以调低一些,建议 0.1 到 0.3 之间,减少模型自由发挥。
  • max-tokens:限制返回最大长度,避免一次请求返回超长无效内容造成成本浪费。

2.3 理解多模态消息的内容结构

很多人的第一反应是把图片塞到 prompt 字符串里,这是不对的。视觉理解的消息必须按结构化对象组织,文本和图片各自作为 content 的一部分。

模型 API 看到的实际请求结构类似这样:

{ "model": "multimodal-latest", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "描述这张图片里有什么" }, { "type": "image_url", "image_url": { "url": "https://example.com/image.png" } } ] } ] }

也就是说,user 角色的消息里,content 不再是单个字符串,而是一个数组,里面可以交替放文本块和图片块。图片块既可以放一个公网图片链接,也可以放 base64 编码后的图片数据。

Spring AI 给你封装好了对应的消息模型。你不需要自己拼 JSON,而是要会用它的UserMessage和Media类型。

注意:消息结构是视觉理解最常见的翻车点。如果你自己拼接字符串传图片,八成会收到模型服务端的格式错误;正确做法是让UserMessage来承载文本与媒体列表。

3. 手把手实现图片问答服务与结构化输出

3.1 初始化 ChatClient

进入代码阶段。Spring AI 面向业务开发的主入口是ChatClient,它把 prompt、消息组装、调用模型、解析响应串成一条链。

先注册一个 Bean:

import org.springframework.ai.chat.client.ChatClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class AiConfig { @Bean ChatClient chatClient(ChatClient.Builder builder) { return builder.build(); } }

如果你的项目里暂时不需要调用多个模型实例,用默认构建方式就够了。直接注入ChatClient到业务类即可。

3.2 URL 图片 + 文本的问答接口

场景一:图片已经放在公网 URL 上。你只需要构造Media对象并加进UserMessage。

代码如下:

import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.model.Media; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import java.net.URL; import java.util.List; @Service public class VisionService { private final ChatClient chatClient; public VisionService(ChatClient chatClient) { this.chatClient = chatClient; } public String describeImage(String instruction, String imageUrl) throws Exception { Media imageMedia = new Media(MediaType.IMAGE_PNG, new URL(imageUrl)); UserMessage userMessage = new UserMessage( instruction, List.of(imageMedia) ); return chatClient.prompt(userMessage) .call() .content(); } }

这里要做三点说明:

第一,Media的构造函数在不同版本里可能有差异。早期版本接收MimeType和Object data,新一点版本可能提供 builder 风格。核心不变:mimeType 告诉模型图片格式,data 是图片资源来源。

第二,URL 图片适合模型服务端可以直接访问的场景。如果图片在本地网络,模型服务端拉不到,就不能用 URL,改成传图片字节。

第三,instruction要写成明确的指令,比如“用中文描述这张图片的内容,并列出主要物体”。不要只写“看看这张图”,那会让模型输出不可控。

3.3 本地图片转 Base64 传入

本地文件场景更常见。程序读文件、转字节、再交给Media:

import org.springframework.core.io.ClassPathResource; import org.springframework.http.MediaType; import java.io.InputStream; byte[] imageBytes = readImageBytes("sample.png"); Media imageMedia = new Media(MediaType.IMAGE_PNG, imageBytes); UserMessage userMessage = new UserMessage( "识别图中文字并输出为 JSON 列表", List.of(imageMedia) );

readImageBytes的常规写法是读取资源文件或上传文件流,转成字节数组即可:

private byte[] readImageBytes(String path) throws Exception { ClassPathResource resource = new ClassPathResource(path); try (InputStream is = resource.getInputStream()) { return is.readAllBytes(); } }

这里有一个细节:字节数组和 URL 字符串在底层会被 Spring AI 转成不同的请求格式。URL 字符串会作为图片地址下发,字节数组会转成 base64 内嵌进请求体。这个转换过程程序自动处理,但你心里要有概念,否则排查请求体过大问题时无从下手。

注意:本地图片转字节传入时,mimeType 必须和文件格式一致。png 图填image/jpeg,一部分模型服务端不会主动纠正,会直接报格式错误。

3.4 多图输入与多轮对话

视觉理解也支持同时传多张图片。做法很简单,把所有图片塞进一个Media列表:

List<Media> mediaList = List.of( new Media(MediaType.IMAGE_JPEG, firstImageBytes), new Media(MediaType.IMAGE_JPEG, secondImageBytes) ); UserMessage userMessage = new UserMessage( "对比这两张图中的差异,输出差异点清单", mediaList );

多个图片在消息里的顺序是敏感的。模型按消息序列感知图片先后,“图一”“图二”这种指代在提示词里要写清楚,否则模型容易混淆。

多轮对话方面,Spring AI 的ChatMemory可以保存历史消息。但建议注意一点:很多模型服务端在处理历史消息时,对图片历史的支持比文本历史弱。常见做法是只把用户文本问题和模型文本回答存入记忆,图片数据不带入历史。这样既能保持上下文,又避免请求体膨胀和部分平台对图片历史的兼容问题。

3.5 结构化输出:让模型返回 JSON 而不是散文

信息提取类场景里,直接调.content()会得到一段自然语言文本,这对接程序不友好。更推荐用.entity()方法做结构化映射。

先定义一个输出结构:

public record ImageAnalysisResult( String summary, java.util.List<String> objects, java.util.List<String> ocrText, boolean containsPeople ) { }

调用方式:

ImageAnalysisResult result = chatClient.prompt(userMessage) .call() .entity(ImageAnalysisResult.class);

底层逻辑是 Spring AI 在请求里附加了“以指定 JSON 结构返回”的约束,再把模型输出反序列化成对象。这样一来,下游程序可以直接用result.ocrText()拿到识别文字列表,而不是自己解析一段自然语言。

提示:结构化输出是视觉理解落地最关键的技巧。纯文本回答适合人看,不适合程序。加上entity()之后,图片识别结果才能真正进入业务逻辑。

3.6 完整服务层参考

把上面几段组合成一个稍完整的服务类:

@Service public class VisionQaService { private final ChatClient chatClient; public VisionQaService(ChatClient chatClient) { this.chatClient = chatClient; } public ImageAnalysisResult analyzePng(byte[] imageBytes, String question) { Media media = new Media(MediaType.IMAGE_PNG, imageBytes); UserMessage message = new UserMessage(question, List.of(media)); return chatClient.prompt(message) .call() .entity(ImageAnalysisResult.class); } }

配合一个简单的 Controller 暴露接口:

@RestController @RequestMapping("/api/vision") public class VisionController { private final VisionQaService visionQaService; public VisionController(VisionQaService visionQaService) { this.visionQaService = visionQaService; } @PostMapping(value = "/analyze", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ImageAnalysisResult analyze( @RequestPart("file") MultipartFile file, @RequestParam("question") String question) throws IOException { return visionQaService.analyzePng( file.getBytes(), question ); } }

到这里,一个可以接受图片文件和文本问题、返回结构化分析结果的接口就完整了。

4. 踩坑实录:视觉理解高频问题排查清单

4.1 模型不支持图片输入

现象:调用返回的错误明确提示当前模型不支持 image 或 vision 输入。

原因:选错了模型名。并非所有文本模型都带视觉能力。

排查思路:先到你的模型网关或云端控制台查看可用模型列表,找带 vision / multimodal / omni 标识的型号。很多平台是同一系列分不同版本,比如基础语言模型和视觉模型是两个独立模型名。

注意:不要只在配置里改一个参数名,要确认 base-url 对应的服务端确实支持多模态消息传输。某些本地网关可能还没开启图片透传。

4.2 图片 URL 拉取失败

现象:请求报超时,或模型返回“image not found”“URL 无法访问”。

原因:模型服务端在请求时需要主动访问图片 URL,如果你的图片地址是内网地址或带鉴权的临时链接,服务端根本拉不到。

排查思路:用浏览器或无痕窗口直接打开图片 URL,确认公网可访问。需要鉴权的图片链接,要么改造为带过期签名的公开链接,要么直接改用字节数组传入。

我实际使用时更偏向字节数组方案。它不需要考虑服务端网络,也不用担心 URL 过期,只是会占一点请求体大小。

4.3 请求体过大导致失败

现象:本地图片传进去后请求报 413,或模型服务端返回 payload too large。

原因:图片字节数组转 base64 后体积会增加约三分之一。一张 10MB 的图片,转完 base64 接近 14MB,加上 JSON 包装,很容易突破请求体上限。

处理方式:

  • 限制上传文件大小,建议单张图 5MB 以内
  • 服务端先压缩再送模型
  • 长边缩放到 1024 或 2048 像素以内

图片缩放不一定要引入复杂的图像处理库,Java 自带ImageIO就能做基础缩放。压缩后再转字节,质量和体积平衡更好。

4.4 模型回答与图片无关、出现幻觉

现象:请求成功返回,但模型没按图片内容回答,而是顺着提示词编了一段。

原因:多数时候是提示词缺少约束。模型在信息不足时倾向于用常识补全,这在视觉任务里特别明显。

处理技巧:

  • 明确要求“只根据图片内容回答”
  • 如果图片没有相关信息,要求模型回复“未识别到相关信息”
  • 用结构化输出把回答范围框住

比如指令写成“你是图片信息抽取器,严格从图片中提取信息,不要推测图片外内容”,模型跑偏概率会降低很多。

4.5 中文内容乱码或编码问题

现象:图片里的中文识别出来是乱码,或者返回内容出现奇怪的转义符。

原因:有几层可能性。一是上传时传错了 charset,但多数现代框架会正确处理 UTF-8;二是图片本身分辨率太低,中文小字识别不全;三是模型对低质量图片中文识别能力不足。

处理方式:优先检查原图分辨率,其次在提示词里要求“按 UTF-8 输出所有文本”,最后再考虑是不是模型本身 OCR 能力偏弱。真遇到后者,建议先接独立的 OCR 服务,把识别文本作为上下文传给模型,而不是直接把图丢给视觉模型。

4.6 API Key 或路由权限问题

现象:401 Unauthorized 或 403 Forbidden。

原因:api-key 配置不对,或者当前 key 没有视觉模型调用权限。

排查思路:

  1. 用 curl 直接请求一次多模态接口,验证 key 和模型名是否可用
  2. 确认 base-url 路径完整,有的服务地址需要以/v1结尾
  3. 查看服务商后台的模型权限控制,部分平台需要单独开通视觉模型权限

这一步最关键的是排除干扰。大多数时候不是代码问题,而是最简单不过的配置问题。

我在实际接入中的体会是,视觉理解 API 调试时最好先用一张内容简单、文字清晰的测试图跑通最小链路,确认消息结构和密钥没有问题,再叠加结构化输出和复杂业务逻辑。先小后大,能省下大量盲改时间。

这个系列到第四篇,Spring AI 里常见的四类多模态能力算梳理完了。如果你接下来想在项目里落地多模态应用,我建议从视觉理解入手,因为它和文本对话共享大部分代码结构,改造门槛最低。后面有时间的话,我打算再写一篇多模态能力和向量检索结合的内容,把“图片转化为可检索的文本描述”这条链路串起来。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询