Spring AI应用系列——基于ARK实现多模态模型应用
ARK 在这里指的是阿里云推出的 AIGC 研发平台 ARK,是阿里云面向开发者和企业用户打造的一站式 AIGC(AI Generated Content,人工智能生成内容)开发平台。
1. 引言
本文将深入探讨 ARK Multi-Model
的实现原理、架构设计以及关键参数的配置和使用方法。通过具体的代码示例和测试验证,我们将全面理解如何利用 Spring AI 和 Alibaba ARK 平台构建一个多模态模型应用。
2. 项目概述
Spring AI Alibaba ARK Multi-Model Example
是一个基于 Spring AI 和 Alibaba ARK 平台的多模态模型应用示例。它集成了聊天、图片生成、文本向量等多种模型能力,并通过 REST 接口提供服务。
3. 项目架构
项目的核心组件包括:
- Spring Boot:作为基础框架,提供便捷的 Web 开发支持。
- Spring AI:封装了多种 AI 模型的调用接口,简化了模型集成的复杂度。
- Alibaba ARK:阿里云推出的一站式 AIGC 开发平台,提供了大模型 API 服务。
4. 核心功能模块
4.1 图片处理功能
MultiModelController.java
/** Licensed to the Apache Software Foundation (ASF) under one or more* contributor license agreements. See the NOTICE file distributed with* this work for additional information regarding copyright ownership.* The ASF licenses this file to You under the Apache License, Version 2.0* (the "License"); you may not use this file except in compliance with* the License. You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.alibaba.cloud.ai.example.controller;import com.alibaba.cloud.ai.example.controller.helper.FrameExtraHelper;
import org.apache.catalina.User;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.image.Image;
import org.springframework.ai.model.Media;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.MimeTypeUtils;
import org.springframework.web.bind.annotation.*;import java.net.URI;
import java.util.List;
import java.util.Map;/*** ark Multi-Model REST Controller* 提供聊天、图片生成、文本向量等多个模型能力的API接口** @author brian xiadong*/
@RestController
@RequestMapping("/api")
public class MultiModelController {private static final String DEFAULT_PROMPT = "这些是什么?";private static final String DEFAULT_VIDEO_PROMPT = "这是一组从视频中提取的图片帧,请描述此视频中的内容。";@Autowiredprivate ChatModel chatModel;private ChatClient openAiChatClient;public MultiModelController(ChatModel chatModel) {this.chatModel = chatModel;// 构造时,可以设置 ChatClient 的参数// {@link org.springframework.ai.chat.client.ChatClient};this.openAiChatClient = ChatClient.builder(chatModel)// 实现 Chat Memory 的 Advisor// 在使用 Chat Memory 时,需要指定对话 ID,以便 Spring AI 处理上下文。.defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory()))// 实现 Logger 的 Advisor.defaultAdvisors(new SimpleLoggerAdvisor())// 设置 ChatClient 中 ChatModel 的 Options 参数.defaultOptions(OpenAiChatOptions.builder().topP(0.7).build()).build();}@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()));UserMessage message = new UserMessage(prompt, mediaList);ChatResponse response = openAiChatClient.prompt(new Prompt(message)).call().chatResponse();return response.getResult().getOutput().getText();}@GetMapping("/stream/image")public String streamImage(@RequestParam(value = "prompt", required = false, defaultValue = DEFAULT_PROMPT)String prompt) {UserMessage message = new UserMessage(prompt,new Media(MimeTypeUtils.IMAGE_JPEG,new ClassPathResource("multimodel/dog_and_girl.jpeg")));List<ChatResponse> response = openAiChatClient.prompt(new Prompt(message)).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();}@GetMapping("/video")public String video(@RequestParam(value = "prompt", required = false, defaultValue = DEFAULT_VIDEO_PROMPT)String prompt) {List<Media> mediaList = FrameExtraHelper.createMediaList(10);UserMessage message = new UserMessage(prompt, mediaList);ChatResponse response = openAiChatClient.prompt(new Prompt(message)).call().chatResponse();return response.getResult().getOutput().getText();}
}
功能解析:
- 参数说明:
@RequestParam(value = "prompt", required = false, defaultValue = DEFAULT_PROMPT) String prompt
:用户输入的提示文本,默认值为“这些是什么?”。mediaList
:包含图片资源的媒体列表,这里使用了一个固定 URL 的图片资源。
- 实现逻辑:
- 创建
UserMessage
对象,包含提示文本和媒体列表。 - 通过
openAiChatClient.prompt()
方法发送请求,并获取响应结果。
- 创建
4.2 视频帧提取功能
FrameExtraHelper.java
/** Copyright 2024 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** https://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.alibaba.cloud.ai.example.controller.helper;import jakarta.annotation.PreDestroy;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.model.Media;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.io.PathResource;
import org.springframework.stereotype.Component;
import org.springframework.util.MimeType;import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.IntStream;import static org.bytedeco.javacpp.Loader.deleteDirectory;/*** @author yuluo* @author <a href="mailto:yuluo08290126@gmail.com">yuluo</a>*/@Component
public final class FrameExtraHelper implements ApplicationRunner {private FrameExtraHelper() {}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/";private static final Logger log = LoggerFactory.getLogger(FrameExtraHelper.class);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());}}@Overridepublic void run(ApplicationArguments args) throws Exception {log.info("Starting to extract video frames");getVideoPic();log.info("Extracting video frames is complete");}@PreDestroypublic void destroy() {try {deleteDirectory(new File(framePath));}catch (IOException e) {log.error(e.getMessage());}log.info("Delete temporary files...");}public static List<String> getFrameList() {assert IMAGE_CACHE.get("img") != null;return IMAGE_CACHE.get("img");}public static List<Media> createMediaList(int numberOfImages) {List<String> imgList = IMAGE_CACHE.get("img");int totalFrames = imgList.size();int interval = Math.max(totalFrames / numberOfImages, 1);return IntStream.range(0, numberOfImages).mapToObj(i -> imgList.get(i * interval)).map(image -> new Media(MimeType.valueOf("image/png"),new PathResource(image))).collect(Collectors.toList());}}
功能解析:
- 参数说明:
int numberOfImages
:需要提取的图片帧数量。
- 实现逻辑:
- 从
IMAGE_CACHE
中获取所有图片帧路径。 - 根据
numberOfImages
计算间隔,均匀选择图片帧。 - 将选中的图片帧转换为
Media
对象并返回。
- 从
5. 参数配置与使用
5.1 application.yml 配置
application.yml
spring:ai:openai:# API Key Configuration。api-key: ${ARK_API_KEY:your-api-key}# Ark LLM API Base URLbase-url: https://ark.cn-beijing.volces.com/api/chat:options:# Model ID, replace with actual access point IDmodel: ${ARK_MODEL_ID:your-model-id}# Chat API path, consistent with OpenAI interfacecompletions-path: /v3/chat/completionsserver:port: 8080logging:level:org:springframework:ai:chat:client:advisor: DEBUG
配置解析:
api-key
:ARK 平台的 API 密钥,用于身份验证。base-url
:ARK LLM API 的基础 URL。model
:使用的模型 ID。completions-path
:Chat API 的路径,与 OpenAI 接口保持一致。
5.2 ChatClient 构造参数
@RestController
@RequestMapping("/api")
public class MultiModelController {private static final String DEFAULT_PROMPT = "这些是什么?";private static final String DEFAULT_VIDEO_PROMPT = "这是一组从视频中提取的图片帧,请描述此视频中的内容。";@Autowiredprivate ChatModel chatModel;private ChatClient openAiChatClient;public MultiModelController(ChatModel chatModel) {this.chatModel = chatModel;// 构造时,可以设置 ChatClient 的参数// {@link org.springframework.ai.chat.client.ChatClient};this.openAiChatClient = ChatClient.builder(chatModel)// 实现 Chat Memory 的 Advisor// 在使用 Chat Memory 时,需要指定对话 ID,以便 Spring AI 处理上下文。.defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory()))// 实现 Logger 的 Advisor.defaultAdvisors(new SimpleLoggerAdvisor())// 设置 ChatClient 中 ChatModel 的 Options 参数.defaultOptions(OpenAiChatOptions.builder().topP(0.7).build()).build();}}
参数解析:
MessageChatMemoryAdvisor
:实现 Chat Memory 的 Advisor,用于管理对话上下文。SimpleLoggerAdvisor
:实现 Logger 的 Advisor,用于日志记录。OpenAiChatOptions
:设置 ChatModel 的选项参数,如topP
(采样策略)。
6. 测试验证
为了验证功能的正确性,我们进行以下测试:
6.1 图片处理功能测试
测试步骤:
- 发送 GET 请求至
/api/image
,携带提示文本参数。 - 检查响应结果是否符合预期。
测试结果:
假设请求 URL 为 http://localhost:8080/api/image?prompt=这是一张什么照片?
,响应结果如下:
{"result": "这是一张在海滩上拍摄的照片,照片中有一个人和一只狗。"
}
6.2 视频帧提取功能测试
测试步骤:
- 调用
FrameExtraHelper.createMediaList(10)
方法,提取 10 帧图片。 - 检查返回的
Media
列表是否正确。
测试结果:
成功返回 10 个 Media
对象,每个对象包含一张视频帧图片。
7. 总结
本文详细介绍了 Spring AI Alibaba ARK Multi-Model
的实现原理、架构设计以及关键参数的配置和使用方法。通过具体的代码示例和测试验证,我们验证了项目的功能正确性和稳定性。希望本文能为读者理解和应用 Spring AI 和 Alibaba ARK 平台提供有价值的参考。
8. 参考资料
- Spring AI 官方文档
- Alibaba ARK 平台文档
以上就是本次技术博客的全部内容,感谢阅读!如果有任何问题或建议,请随时留言交流。