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
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 = "这是一组从视频中提取的图片帧,请描述此视频中的内容。";
@Autowired
private 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
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:[email protected]">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());
}
}
@Override
public void run(ApplicationArguments args) throws Exception {
log.info("Starting to extract video frames");
getVideoPic();
log.info("Extracting video frames is complete");
}
@PreDestroy
public 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
yaml
spring:
ai:
openai:
# API Key Configuration。
api-key: ${ARK_API_KEY:your-api-key}
# Ark LLM API Base URL
base-url: https://ark.cn-beijing.volces.com/api/
chat:
options:
# Model ID, replace with actual access point ID
model: ${ARK_MODEL_ID:your-model-id}
# Chat API path, consistent with OpenAI interface
completions-path: /v3/chat/completions
server:
port: 8080
logging:
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 构造参数
java
@RestController
@RequestMapping("/api")
public class MultiModelController {
private static final String DEFAULT_PROMPT = "这些是什么?";
private static final String DEFAULT_VIDEO_PROMPT = "这是一组从视频中提取的图片帧,请描述此视频中的内容。";
@Autowired
private 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=这是一张什么照片?
,响应结果如下:
json
{
"result": "这是一张在海滩上拍摄的照片,照片中有一个人和一只狗。"
}
6.2 视频帧提取功能测试
测试步骤:
- 调用
FrameExtraHelper.createMediaList(10)
方法,提取 10 帧图片。 - 检查返回的
Media
列表是否正确。
测试结果 : 成功返回 10 个 Media
对象,每个对象包含一张视频帧图片。
7. 总结
本文详细介绍了 Spring AI Alibaba ARK Multi-Model
的实现原理、架构设计以及关键参数的配置和使用方法。通过具体的代码示例和测试验证,我们验证了项目的功能正确性和稳定性。希望本文能为读者理解和应用 Spring AI 和 Alibaba ARK 平台提供有价值的参考。
8. 参考资料
以上就是本次技术博客的全部内容,感谢阅读!如果有任何问题或建议,请随时留言交流。