
本示例演示了如何使用 Spring AI Alibaba Starter 与 Dashscope 的多模态服务,实现图像识别、视频分析和音频处理功能。
1. 案例目标
我们将创建一个包含多个核心功能的 Web 应用:
- 图像识别 (/dashscope/multi/image):通过 URL 或二进制数据提供图像,让 AI 识别并描述图像内容。
- 视频分析 (/dashscope/multi/video):提取视频帧并让 AI 分析视频内容。
- 音频处理 (/dashscope/multi/audio):提供音频文件,让 AI 分析音频内容。
- 流式图像识别 (/dashscope/multi/stream/image):以流式方式获取图像识别结果。
2. 技术栈与核心依赖
- Spring Boot 3.x
- Spring AI Alibaba (用于对接阿里云 DashScope 通义大模型)
- JavaCV (用于视频帧提取)
- Maven (项目构建工具)
在 pom.xml 中,你需要引入以下核心依赖:
<dependencies>
    <!-- Spring Boot Web 用于构建 RESTful API -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- Spring AI Alibaba 核心启动器,集成 DashScope -->
    <dependency>
        <groupId>com.alibaba.cloud.ai</groupId>
        <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
        <version>${spring-ai-alibaba.version}</version>
    </dependency>
    
    <!-- JavaCV 用于视频帧提取 -->
    <dependency>
        <groupId>org.bytedeco</groupId>
        <artifactId>javacv-platform</artifactId>
        <version>1.5.9</version>
    </dependency>
</dependencies>3. 项目配置
在 src/main/resources/application.yml 文件中,配置你的 DashScope API Key。
server:
  port: 10013
spring:
  application:
    name: spring-ai-alibaba-dashscope-multi-model-example-application
  ai:
    dashscope:
      api-key: ${AI_DASHSCOPE_API_KEY} # 建议使用环境变量,更安全重要提示 :请将 AI_DASHSCOPE_API_KEY 环境变量设置为你从阿里云获取的有效 API Key。你也可以直接将其写在配置文件中,但这不推荐用于生产环境。
4. 初始化客户端
在控制器中,我们需要初始化一个 Spring AI Alibaba 的 ChatClient 对象实例:
@RestController
@RequestMapping("/dashscope/multi")
public class MultiModelController {
    
    private final ChatClient dashScopeChatClient;
    
    public MultiModelController(ChatModel chatModel) {
        this.dashScopeChatClient = ChatClient.builder(chatModel).build();
    }
    
    // 其他方法...
}5. 实现多模型功能
5.1 图像识别功能
实现基于 URL 的图像识别功能:
@GetMapping("/image")
public String image(@RequestParam(value = "prompt", required = false, defaultValue = DEFAULT_PROMPT) String prompt)
        throws Exception {
    
    List<Media> mediaList = List.of(new Media(MimeTypeUtils.IMAGE_PNG,
            new URI("https://dashscope.oss-cn-beijing.aliyuncs.com/images/dog_and_girl.jpeg").toURL().toURI()));
    
    UserMessage message =
            UserMessage.builder().text(prompt).media(mediaList).metadata(new HashMap<>()).build();
    message.getMetadata().put(DashScopeApiConstants.MESSAGE_FORMAT, MessageFormat.IMAGE);
    
    ChatResponse response = dashScopeChatClient
            .prompt(new Prompt(message,
                    DashScopeChatOptions.builder().withModel(DEFAULT_MODEL).withMultiModel(true).build()))
            .call()
            .chatResponse();
    
    return response.getResult().getOutput().getText();
}5.2 视频分析功能
实现视频分析功能,首先需要提取视频帧:
@Component
public final class FrameExtraHelper implements ApplicationRunner {
    
    private static final Map<String, List<String>> IMAGE_CACHE = new ConcurrentHashMap<>();
    
    private static final File videoUrl = new File(
            "spring-ai-alibaba-multi-model-example/dashscope-multi-model/src/main/resources/multimodel/video.mp4");
    
    private static final String framePath = "spring-ai-alibaba-multi-model-example/dashscope-multi-model/src/main/resources/multimodel/frame/";
    
    public static void getVideoPic() {
        List<String> strList = new ArrayList<>();
        File dir = new File(framePath);
        if (!dir.exists()) {
            dir.mkdirs();
        }
        try (FFmpegFrameGrabber ff = new FFmpegFrameGrabber(videoUrl.getPath());
                Java2DFrameConverter converter = new Java2DFrameConverter()) {
            ff.start();
            ff.setFormat("mp4");
            int length = ff.getLengthInFrames();
            Frame frame;
            for (int i = 1; i < length; i++) {
                frame = ff.grabFrame();
                if (frame.image == null) {
                    continue;
                }
                BufferedImage image = converter.getBufferedImage(frame);
                String path = framePath + i + ".png";
                File picFile = new File(path);
                ImageIO.write(image, "png", picFile);
                strList.add(path);
            }
            IMAGE_CACHE.put("img", strList);
            ff.stop();
        }
        catch (Exception e) {
            log.error(e.getMessage());
        }
    }
    
    // 其他方法...
}然后实现视频分析接口:
@GetMapping("/video")
public String video(
        @RequestParam(value = "prompt", required = false, defaultValue = DEFAULT_VIDEO_PROMPT) String prompt) {
    
    List<Media> mediaList = FrameExtraHelper.createMediaList(10);
    
    UserMessage message =
    UserMessage.builder().text(prompt).media(mediaList).metadata(new HashMap<>()).build();
    message.getMetadata().put(DashScopeApiConstants.MESSAGE_FORMAT, MessageFormat.VIDEO);
    
    ChatResponse response = dashScopeChatClient
            .prompt(new Prompt(message,
                    DashScopeChatOptions.builder().withModel(DEFAULT_MODEL).withMultiModel(true).build()))
            .call()
            .chatResponse();
    
    return response.getResult().getOutput().getText();
}5.3 音频处理功能
实现音频处理功能:
@GetMapping("/audio")
public String audio(
        @RequestParam(value = "prompt", required = false, defaultValue = DEFAULT_AUDIO_PROMPT) String prompt) {
    Media media = new Media(MediaType.parseMediaType("audio/mpeg"),
            URI.create("https://dashscope.oss-cn-beijing.aliyuncs.com/audios/welcome.mp3"));
    UserMessage message =
    UserMessage.builder().text(prompt).media(media).metadata(new HashMap<>()).build();
    message.getMetadata().put(DashScopeApiConstants.MESSAGE_FORMAT, MessageFormat.AUDIO);
    ChatResponse response = dashScopeChatClient
            .prompt(new Prompt(message,
                    DashScopeChatOptions.builder().withModel("qwen-audio-turbo-latest").withMultiModel(true).build()))
            .call()
            .chatResponse();
    return response.getResult().getOutput().getText();
}5.4 二进制图像识别功能
实现基于二进制数据的图像识别功能:
@GetMapping("/image/bin")
public String imagesBinary(
        @RequestParam(value = "prompt", required = false, defaultValue = DEFAULT_PROMPT) String prompt) throws Exception {
    
    List<Media> mediaList = List.of(new Media(MimeTypeUtils.IMAGE_JPEG,
            resourceLoader.getResource("classpath:/multimodel/dog_and_girl.jpeg")));
    UserMessage message =
            UserMessage.builder().text(prompt).media(mediaList).metadata(new HashMap<>()).build();
    
    message.getMetadata().put(DashScopeApiConstants.MESSAGE_FORMAT, MessageFormat.IMAGE);
    
    ChatResponse response = dashScopeChatClient
            .prompt(new Prompt(message,
                    DashScopeChatOptions.builder().withModel(DEFAULT_MODEL).withMultiModel(true).build()))
            .call()
            .chatResponse();
    
    return response.getResult().getOutput().getText();
}5.5 流式图像识别功能
实现流式图像识别功能:
@GetMapping("/stream/image")
public String streamImage(
        @RequestParam(value = "prompt", required = false, defaultValue = DEFAULT_PROMPT) String prompt) {
    List<Media> mediaList = List.of(new Media(MimeTypeUtils.IMAGE_JPEG,
            resourceLoader.getResource("classpath:/multimodel/dog_and_girl.jpeg")));
    UserMessage message = UserMessage.builder().text(prompt).media(mediaList).metadata(new HashMap<>()).build();
    message.getMetadata().put(DashScopeApiConstants.MESSAGE_FORMAT, MessageFormat.IMAGE);
    
    List<ChatResponse> response = dashScopeChatClient
            .prompt(new Prompt(message,
                    DashScopeChatOptions.builder().withModel(DEFAULT_MODEL).withMultiModel(true).build()))
            .stream()
            .chatResponse()
            .collectList()
            .block();
    
    StringBuilder result = new StringBuilder();
    if (response != null) {
        for (ChatResponse chatResponse : response) {
            String outputContent = chatResponse.getResult().getOutput().getText();
            result.append(outputContent);
        }
    }
    
    return result.toString();
}6. 运行与测试
- 启动应用 :运行 Spring Boot 主程序 DashScopeMultiModelApplication。
- 等待视频帧提取完成:应用启动后会自动提取视频帧,请等待日志显示"Extracting video frames is complete"。
- 使用浏览器或 API 工具(如 Postman, curl)进行测试。
测试 1:图像识别
访问以下 URL,测试图像识别功能:
http://localhost:10013/dashscope/multi/image预期响应:
这是一张在海滩上拍摄的照片,照片中有一位女士和一只狗。女士坐在沙滩上,微笑着与狗互动,狗伸出前爪与她握手。背景是大海和天空,阳光洒在她们身上,营造出温暖和谐的氛围。
测试 2:视频分析
访问以下 URL,测试视频分析功能:
http://localhost:10013/dashscope/multi/video预期响应:
这组图片展示了一位女士坐在沙滩上,背对着镜头,面向大海。她穿着一件粉色的连衣裙,头发自然垂下。背景是广阔的海洋和蓝天白云,天气晴朗,海面平静。这位女士似乎在享受宁静的海滩时光,可能是在思考或放松。整个场景给人一种宁静和平静的感觉。
测试 3:音频处理
访问以下 URL,测试音频处理功能:
http://localhost:10013/dashscope/multi/audio预期响应:
这是一个欢迎音频,内容为"欢迎使用阿里云通义千问大模型服务"。
测试 4:二进制图像识别
访问以下 URL,测试二进制图像识别功能:
http://localhost:10013/dashscope/multi/image/bin预期响应:与测试 1 相同,因为使用的是同一张图片。
测试 5:流式图像识别
访问以下 URL,测试流式图像识别功能:
http://localhost:10013/dashscope/multi/stream/image预期响应:与测试 1 相同,但响应是以流式方式获取的。
7. 实现思路与扩展建议
实现思路
本案例的核心思想是**"多模态数据处理"**。我们通过 Spring AI Alibaba 提供的统一接口,处理不同类型的媒体数据(图像、视频、音频),并利用 DashScope 的多模态能力进行分析和理解。这使得:
- 接口统一:不同类型的媒体数据可以通过相同的 API 接口进行处理,简化了开发流程。
- 功能强大:利用大模型的多模态能力,可以实现对图像、视频、音频内容的深度理解。
- 易于扩展:基于 Spring Boot 的架构,可以方便地添加新的功能模块。
扩展建议
- 批量处理:扩展接口以支持批量处理多个图像或视频文件。
- 自定义模型:根据业务需求,选择不同的 DashScope 模型进行处理。
- 结果缓存:对于相同的输入,可以增加缓存层,避免重复调用 API,提升性能。
- 异步处理:对于耗时的视频处理,可以实现异步任务队列,提升用户体验。
- 安全性增强:添加文件上传大小限制、类型验证等安全措施。
- 集成更多模态:扩展支持更多类型的媒体数据,如 3D 模型、AR/VR 内容等。