Spring AI Alibaba 多模态开发:集成视觉理解与视频分析能力

Spring AI Alibaba 多模态开发:集成视觉理解与视频分析能力

导读:多模态是当前大模型最热的能力扩展方向------不只是"聊天",而是真正能"看图"、"看视频"、"生图"。本文基于 Spring AI Alibaba 1.1 版,系统讲解多模态架构设计、图文混合 API 调用、视频理解以及文生图能力,并结合三个真实业务场景给出完整代码示例。


一、多模态的价值与应用场景

在和客户聊需求时,有一句话我说了很多次:"文本对话只是 AI 能力的冰山一角。"一旦模型能理解图像和视频,能处理的业务场景会呈指数级扩大:

复制代码
传统文本 AI 能做的:
    问答 / 摘要 / 翻译 / 代码生成 ...

多模态 AI 能做的:
    电商商品识别 → 自动生成详情页文案
    监控视频分析 → 异常行为检测告警  
    医疗影像描述 → 辅助医生初步筛查
    产品图片质检 → 自动化缺陷识别
    证件 OCR     → 信息结构化提取
    ...

通义千问视觉系列(qwen-vl)是国内开放多模态能力最完善的模型之一,Spring AI Alibaba 1.1 版对其做了完整的 API 适配。


二、多模态架构基础

2.1 核心概念:Media 对象

在 Spring AI 的多模态设计中,Media 是承载非文本内容(图片、音频、视频)的核心对象:

java 复制代码
// Media 对象的核心字段
public class Media {
    MimeType mimeType;   // 内容类型,如 IMAGE_JPEG、VIDEO_MP4
    Object data;         // 实际数据:URL 字符串、byte[]、或 Resource
}

UserMessage 支持携带多个 Media 附件,实现图文混合输入:

java 复制代码
// 文字 + 图片 = 多模态消息
UserMessage multimodalMessage = UserMessage.builder()
        .text("请描述这张图片的内容")
        .media(new Media(MimeTypeUtils.IMAGE_JPEG, imageResource))
        .build();

2.2 多模态处理流程

复制代码
输入阶段                    处理阶段                   输出阶段
+----------+               +-------------+             +---------+
| 文本消息  |               |             |             |         |
+----------+  ------->     | qwen-vl-max |  ------->   |  文本   |
| 图片/URL  |               |             |             | 回复    |
+----------+  (组装成      +-------------+             |         |
| 视频文件  |   UserMessage)    |                       +---------+
+----------+               | Spring AI
                            | 自动序列化
                            | (URL/Base64)

2.3 MIME 类型速查

复制代码
+----------------+----------------------------------+
| 类型            | Spring AI MimeType               |
+----------------+----------------------------------+
| JPEG 图片       | MimeTypeUtils.IMAGE_JPEG         |
| PNG 图片        | MimeTypeUtils.IMAGE_PNG          |
| GIF 图片        | MimeTypeUtils.IMAGE_GIF          |
| WEBP 图片       | MimeType.valueOf("image/webp")   |
| MP4 视频        | MimeType.valueOf("video/mp4")    |
| AVI 视频        | MimeType.valueOf("video/avi")    |
| PDF 文档        | MimeTypeUtils.APPLICATION_PDF    |
+----------------+----------------------------------+

三、依赖与配置

3.1 Maven 依赖

xml 复制代码
<dependencies>
    <!-- Spring AI Alibaba DashScope(含多模态支持) -->
    <dependency>
        <groupId>com.alibaba.cloud.ai</groupId>
        <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
    </dependency>

    <!-- Spring Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- 文件上传支持 -->
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.15.1</version>
    </dependency>
</dependencies>

3.2 YAML 配置

yaml 复制代码
spring:
  ai:
    dashscope:
      api-key: ${AI_DASHSCOPE_API_KEY}
      chat:
        options:
          # 多模态任务默认使用 qwen-vl-max
          model: qwen-vl-max
          max-tokens: 2048

  # 文件上传大小限制(视频文件可能较大)
  servlet:
    multipart:
      max-file-size: 100MB
      max-request-size: 100MB

四、视觉理解:图片分析实战

4.1 图片 URL 模式(推荐)

通过 URL 传图是最简单的方式,适合图片已经上传至 CDN 或公网可访问的场景:

java 复制代码
@Service
@RequiredArgsConstructor
@Slf4j
public class VisionService {

    private final ChatClient chatClient;

    /**
     * 图片 URL 模式:分析公网图片
     *
     * @param imageUrl 图片的公网 URL
     * @param question 关于图片的问题
     */
    public String analyzeImageByUrl(String imageUrl, String question) {
        log.info("分析图片:{}", imageUrl);

        // 构建携带图片 URL 的 UserMessage
        UserMessage userMessage = UserMessage.builder()
                .text(question)
                .media(new Media(
                        MimeTypeUtils.IMAGE_JPEG,
                        new URL(imageUrl)  // 直接传 URL 对象
                ))
                .build();

        return chatClient.prompt()
                .messages(userMessage)
                .options(DashScopeChatOptions.builder()
                        .withModel("qwen-vl-max")
                        .build())
                .call()
                .content();
    }

    /**
     * 多图对比分析:同时传入多张图片
     *
     * @param imageUrls 图片 URL 列表
     * @param question  关于这些图片的问题
     */
    public String compareImages(List<String> imageUrls, String question) {
        // 构建多 Media 附件的 UserMessage
        List<Media> mediaList = imageUrls.stream()
                .map(url -> {
                    try {
                        return new Media(MimeTypeUtils.IMAGE_JPEG, new URL(url));
                    } catch (Exception e) {
                        throw new RuntimeException("无效的图片 URL: " + url, e);
                    }
                })
                .toList();

        // UserMessage 构建器支持传入 Media 列表
        UserMessage message = UserMessage.builder()
                .text(question)
                .media(mediaList.toArray(new Media[0]))
                .build();

        return chatClient.prompt()
                .messages(message)
                .call()
                .content();
    }
}

4.2 本地文件模式(Base64 编码)

当图片存储在本地或无法提供公网 URL 时,可以读取文件并以 Base64 形式传输:

java 复制代码
/**
 * 本地文件模式:读取本地图片进行分析
 *
 * @param imageFile 上传的图片文件
 * @param question  分析问题
 */
public String analyzeLocalImage(MultipartFile imageFile, String question) throws IOException {
    // 获取文件 MimeType
    String contentType = imageFile.getContentType();
    MimeType mimeType = MimeType.valueOf(
            contentType != null ? contentType : "image/jpeg");

    // 读取文件内容为 byte[]
    byte[] imageBytes = imageFile.getBytes();

    // 构建 Media 对象(传 byte[] 时,Spring AI 自动 Base64 编码)
    Media imageMedia = new Media(mimeType, imageBytes);

    UserMessage message = UserMessage.builder()
            .text(question)
            .media(imageMedia)
            .build();

    return chatClient.prompt()
            .messages(message)
            .call()
            .content();
}

4.3 REST 接口封装

java 复制代码
@RestController
@RequestMapping("/api/vision")
@RequiredArgsConstructor
public class VisionController {

    private final VisionService visionService;

    /**
     * URL 图片分析接口
     */
    @GetMapping("/analyze")
    public ResponseEntity<Map<String, String>> analyzeByUrl(
            @RequestParam String imageUrl,
            @RequestParam(defaultValue = "请详细描述图片内容") String question) {
        try {
            String result = visionService.analyzeImageByUrl(imageUrl, question);
            return ResponseEntity.ok(Map.of("result", result));
        } catch (Exception e) {
            return ResponseEntity.badRequest()
                    .body(Map.of("error", e.getMessage()));
        }
    }

    /**
     * 本地图片上传分析接口
     */
    @PostMapping("/upload-analyze")
    public ResponseEntity<Map<String, String>> analyzeUpload(
            @RequestParam("file") MultipartFile file,
            @RequestParam(defaultValue = "请详细描述图片内容") String question) {
        try {
            String result = visionService.analyzeLocalImage(file, question);
            return ResponseEntity.ok(Map.of("result", result));
        } catch (Exception e) {
            return ResponseEntity.internalServerError()
                    .body(Map.of("error", "图片分析失败:" + e.getMessage()));
        }
    }
}

五、视频分析:时序理解与 VideoQA

5.1 视频分析架构

视频分析比图片复杂,qwen-vl 处理视频的方式是对视频进行帧采样,然后将关键帧序列连同问题一起传入模型进行时序理解。

复制代码
原始视频文件
     |
     | (上传至 OSS/视频服务)
     v
视频 URL(公网可访问)
     |
     v
Spring AI Media 对象 (video/mp4)
     |
     v
qwen-vl 模型(内部进行帧采样与时序分析)
     |
     v
视频内容描述 / 问题回答

5.2 视频分析代码实现

java 复制代码
@Service
@RequiredArgsConstructor
@Slf4j
public class VideoAnalysisService {

    private final ChatClient chatClient;

    /**
     * 视频内容理解:通过 URL 分析视频
     *
     * @param videoUrl 视频的公网 URL(需先上传至 OSS)
     * @param question 关于视频的问题
     */
    public String analyzeVideo(String videoUrl, String question) {
        log.info("开始分析视频:{}", videoUrl);
        long startTime = System.currentTimeMillis();

        try {
            Media videoMedia = new Media(
                    MimeType.valueOf("video/mp4"),
                    new URL(videoUrl)
            );

            UserMessage message = UserMessage.builder()
                    .text(question)
                    .media(videoMedia)
                    .build();

            String result = chatClient.prompt()
                    .messages(message)
                    .options(DashScopeChatOptions.builder()
                            // 视频理解使用专用模型
                            .withModel("qwen-vl-max")
                            // 视频分析通常需要更多输出空间
                            .withMaxTokens(4096)
                            .build())
                    .call()
                    .content();

            log.info("视频分析完成,耗时:{}ms", System.currentTimeMillis() - startTime);
            return result;

        } catch (Exception e) {
            log.error("视频分析失败:{}", e.getMessage(), e);
            throw new RuntimeException("视频分析失败", e);
        }
    }

    /**
     * 监控视频异常检测
     * 专门针对安防监控场景优化的 Prompt
     */
    public String detectAnomalies(String videoUrl) {
        String prompt = """
                请分析这段监控视频,重点关注以下异常行为:
                1. 人员聚集或打架斗殴
                2. 可疑物品遗留
                3. 未授权区域入侵
                4. 异常跌倒或昏厥事件
                5. 火灾烟雾迹象
                
                请以结构化方式输出:
                - 是否检测到异常:是/否
                - 异常类型(如有):
                - 发生时间段(大约):
                - 建议处理方式:
                """;

        return analyzeVideo(videoUrl, prompt);
    }

    /**
     * 教学视频内容摘要
     */
    public String summarizeVideo(String videoUrl) {
        String prompt = """
                请观看这段教学视频,并提供:
                1. 视频主题(一句话概括)
                2. 主要知识点(3-5个要点)
                3. 视频结构(时间线摘要)
                4. 适合人群建议
                """;

        return analyzeVideo(videoUrl, prompt);
    }
}

六、文生图:wanx 模型集成

通义千问 wanx 模型提供文生图能力,Spring AI Alibaba 通过 ImageClient 进行集成(注意:图像生成使用的是 ImageModel,不是 ChatModel)。

6.1 文生图配置

yaml 复制代码
spring:
  ai:
    dashscope:
      api-key: ${AI_DASHSCOPE_API_KEY}
      image:
        options:
          # 文生图模型
          model: wanx-v1
          # 图片尺寸:1024x1024 / 720x1280 / 1280x720
          size: 1024x1024
          # 生成数量(1-4张)
          n: 1
          # 图片质量:standard / hd
          style: <auto>

6.2 文生图实现

java 复制代码
@Service
@RequiredArgsConstructor
@Slf4j
public class ImageGenerationService {

    // 注入 ImageModel(与 ChatModel 不同的接口)
    private final ImageModel imageModel;

    /**
     * 文生图:根据文字描述生成图片
     *
     * @param prompt 图片描述 Prompt
     * @return 生成图片的 URL 列表
     */
    public List<String> generateImages(String prompt) {
        log.info("文生图请求:{}", prompt);

        ImageOptions options = DashScopeImageOptions.builder()
                .withModel("wanx-v1")
                .withN(1)
                .withWidth(1024)
                .withHeight(1024)
                .build();

        ImagePrompt imagePrompt = new ImagePrompt(prompt, options);
        ImageResponse response = imageModel.call(imagePrompt);

        List<String> imageUrls = response.getResults().stream()
                .map(result -> result.getOutput().getUrl())
                .filter(url -> url != null && !url.isEmpty())
                .toList();

        log.info("生成了 {} 张图片", imageUrls.size());
        return imageUrls;
    }

    /**
     * 商品图生成:针对电商场景优化的 Prompt 工程
     *
     * @param productName 商品名称
     * @param style       风格描述(如:简约、奢华、科技感)
     * @param background  背景描述(如:白色背景、户外场景)
     */
    public List<String> generateProductImage(
            String productName, String style, String background) {

        // 电商图片专用 Prompt 模板
        String enhancedPrompt = String.format(
                "%s,%s风格,%s,产品摄影,高清质感,专业布光," +
                "细节清晰,适合电商展示,8K超高清",
                productName, style, background
        );

        return generateImages(enhancedPrompt);
    }
}

七、三大业务场景实战

场景一:电商商品图文描述

java 复制代码
@Service
@RequiredArgsConstructor
public class EcommerceVisionService {

    private final VisionService visionService;
    private final ImageGenerationService imageGenerationService;

    /**
     * 上传商品图片,自动生成详情页文案
     */
    public Map<String, Object> processProductImage(String imageUrl, String productCategory) {
        String prompt = String.format("""
                这是一张%s类目的商品图片,请分析并生成以下内容:
                
                1. 【商品标题】(30字以内,包含核心卖点)
                2. 【卖点提炼】(5个核心卖点,每条不超过15字)
                3. 【商品描述】(200字左右,突出品质和使用场景)
                4. 【规格参数】(从图片可识别的尺寸、颜色、材质等)
                5. 【适用人群】(目标消费者描述)
                
                请以 JSON 格式输出。
                """, productCategory);

        String rawResult = visionService.analyzeImageByUrl(imageUrl, prompt);

        Map<String, Object> result = new HashMap<>();
        result.put("imageUrl", imageUrl);
        result.put("rawAnalysis", rawResult);
        result.put("category", productCategory);
        result.put("timestamp", LocalDateTime.now().toString());

        return result;
    }
}

场景二:医疗影像初筛(辅助)

java 复制代码
/**
 * 医疗影像描述辅助(注意:仅作参考,不替代专业医学诊断)
 */
public String describeMedicalImage(String imageUrl, String imageType) {
    String prompt = String.format("""
            这是一张%s医学影像图,请以专业但通俗的方式描述:
            
            1. 图像基本信息(检查类型、扫描部位)
            2. 图像质量评估
            3. 可见的主要解剖结构
            4. 明显可见的影像特征描述
            
            重要提示:以上描述仅为影像特征的客观描述,
            不构成医学诊断建议,请以专业医生判断为准。
            """, imageType);

    return visionService.analyzeImageByUrl(imageUrl, prompt);
}

场景三:文档图片 OCR 与结构化提取

java 复制代码
/**
 * 证件/票据 OCR 信息提取
 */
public Map<String, String> extractDocumentInfo(MultipartFile documentImage,
                                                 String documentType) throws IOException {
    String prompt = String.format("""
            这是一张%s图片,请提取其中所有可见的文字信息,
            并以 JSON 格式输出,key 为字段名称,value 为字段值。
            对于无法识别的字段,value 设为 null。
            只输出 JSON,不要其他解释文字。
            """, documentType);

    String jsonResult = visionService.analyzeLocalImage(documentImage, prompt);

    // 解析 JSON 结果
    try {
        ObjectMapper mapper = new ObjectMapper();
        return mapper.readValue(
                jsonResult.replaceAll("```json|```", "").trim(),
                new TypeReference<>() {});
    } catch (JsonProcessingException e) {
        log.warn("JSON 解析失败,返回原始文本");
        return Map.of("rawText", jsonResult);
    }
}

八、多模态 Prompt 工程技巧

8.1 图像分析 Prompt 最佳实践

复制代码
❌ 差的 Prompt:
   "描述这张图"

✅ 好的 Prompt:
   "这是一张[场景类型]图片,请从以下维度进行分析:
    1. [维度1]:具体说明
    2. [维度2]:具体说明
    请以结构化方式输出结果。"

关键原则:

  • 明确任务:告诉模型你期望的输出格式;
  • 提供上下文:说明图片类型、场景背景;
  • 分维度提问:复杂图片按维度拆解;
  • 指定专业视角:如"以产品经理视角"、"以安防工程师视角"。

8.2 文生图 Prompt 优化技巧

文生图的 Prompt 质量直接决定生成效果,以下是提升图片质量的关键词组合:

复制代码
基础结构:主体描述 + 风格修饰 + 技术参数 + 质量标签

示例:
"[主体] 一只在樱花树下打盹的橘猫
 [风格] 日本动漫风格,吉卜力工作室风格
 [场景] 春天下午,阳光明媚
 [技术] 8K超高清,专业插画,细节丰富
 [质量] 最佳质量,杰作"

效果对比:
"一只猫" → 效果普通
"在樱花树下打盹的橘猫,日本动漫风格,8K超高清,最佳质量" → 效果明显更好

九、常见问题与注意事项

Q:图片大小有限制吗?

DashScope API 对单张图片的限制通常是 20MB,建议超大图片先压缩再传输:

java 复制代码
// 使用 ImageIO 压缩图片
private byte[] compressImage(byte[] originalBytes, float quality) throws IOException {
    BufferedImage originalImage = ImageIO.read(new ByteArrayInputStream(originalBytes));
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    ImageWriter writer = ImageIO.getImageWritersByFormatName("jpeg").next();
    ImageWriteParam param = writer.getDefaultWriteParam();
    param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
    param.setCompressionQuality(quality); // 0.0~1.0
    writer.setOutput(ImageIO.createImageOutputStream(outputStream));
    writer.write(null, new IIOImage(originalImage, null, null), param);
    return outputStream.toByteArray();
}

Q:视频长度有限制吗?

目前 qwen-vl 对视频长度有一定限制,通常建议控制在 5 分钟以内。超长视频建议先切片处理。

Q:Base64 还是 URL,哪个更推荐?

  • 开发测试:URL 更方便,直接用图床链接;
  • 生产环境:先上传到 OSS,再传 URL,更稳定且节省带宽;
  • 隐私数据:Base64 适合不便公开的敏感图片,但会增加请求体积。

十、总结

Spring AI Alibaba 的多模态能力让 Java 应用从"纯文本 AI"升级到了"全感知 AI"。核心要点回顾:

  1. Media 对象是多模态的核心,封装了 MimeType + 内容数据;
  2. 图片输入支持 URL 和 Base64 两种模式,URL 模式更推荐;
  3. 视频分析通过帧采样实现时序理解,适合监控、教育等场景;
  4. 文生图 使用 ImageModel 接口(非 ChatModel),通过 wanx 模型实现;
  5. Prompt 工程对多模态质量影响极大,要明确维度、指定格式。

下一篇将进入多模型路由策略,探讨如何在通义千问、DeepSeek、OpenAI 之间实现动态选择、Fallback 降级和成本优化。


参考资料

相关推荐
前端摸鱼匠1 小时前
面试题3:自注意力机制(Self-Attention)的计算流程是什么?
人工智能·ai·面试·职场和发展
出门吃三碗饭2 小时前
CARLA: 如何在 CARLA 中回放自动驾驶场景
人工智能·机器学习·自动驾驶
Axis tech2 小时前
第二届人形机器人半程马拉松即将于4月开赛,对比去年技术进步有哪些?
人工智能·机器人
志栋智能2 小时前
超自动化巡检,如何成为业务稳定的“压舱石”?
大数据·运维·网络·人工智能·自动化
lifallen2 小时前
从零推导一个现代 ReAct Agent框架
人工智能·算法·语言模型
我的offer在哪里2 小时前
腾讯 Ardot 深度博客:AI 重构 UI/UX 全链路,从 “描述即界面” 到设计工业化的腾讯范式
人工智能·ui·重构
AEIC学术交流中心2 小时前
【快速EI检索 | IEEE出版】第六届信号图像处理与通信国际学术会议(ICSIPC 2026)
图像处理·人工智能
康世行2 小时前
IDEA集成AI辅助工具推荐(好用不卡顿)
java·人工智能·intellij-idea
柯儿的天空2 小时前
【OpenClaw 全面解析:从零到精通】第007篇:流量枢纽——OpenClaw Gateway 网关深度解析
人工智能·gpt·ai作画·gateway·aigc·ai编程·ai写作