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

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 图片处理功能测试

测试步骤

  1. 发送 GET 请求至 /api/image,携带提示文本参数。
  2. 检查响应结果是否符合预期。

测试结果 : 假设请求 URL 为 http://localhost:8080/api/image?prompt=这是一张什么照片?,响应结果如下:

json 复制代码
{
    "result": "这是一张在海滩上拍摄的照片,照片中有一个人和一只狗。"
}
6.2 视频帧提取功能测试

测试步骤

  1. 调用 FrameExtraHelper.createMediaList(10) 方法,提取 10 帧图片。
  2. 检查返回的 Media 列表是否正确。

测试结果 : 成功返回 10 个 Media 对象,每个对象包含一张视频帧图片。

7. 总结

本文详细介绍了 Spring AI Alibaba ARK Multi-Model 的实现原理、架构设计以及关键参数的配置和使用方法。通过具体的代码示例和测试验证,我们验证了项目的功能正确性和稳定性。希望本文能为读者理解和应用 Spring AI 和 Alibaba ARK 平台提供有价值的参考。

8. 参考资料


以上就是本次技术博客的全部内容,感谢阅读!如果有任何问题或建议,请随时留言交流。

相关推荐
陈奕昆1 分钟前
大模型微调之LLaMA-Factory 系列教程大纲
人工智能·llama·大模型微调·llama-factory
ss2734 分钟前
基于Springboot + vue实现的中医院问诊系统
java·spring boot·后端
上海云盾商务经理杨杨24 分钟前
AI如何重塑DDoS防护行业?六大变革与未来展望
人工智能·安全·web安全·ddos
一刀到底21135 分钟前
ai agent(智能体)开发 python3基础8 网页抓取中 selenium 和 Playwright 区别和联系
人工智能·python
每天都要写算法(努力版)40 分钟前
【神经网络与深度学习】改变随机种子可以提升模型性能?
人工智能·深度学习·神经网络
烟锁池塘柳01 小时前
【计算机视觉】三种图像质量评价指标详解:PSNR、SSIM与SAM
人工智能·深度学习·计算机视觉
左灯右行的爱情1 小时前
Redis 缓存并发问题深度解析:击穿、雪崩与穿透防治指南
java·数据库·redis·后端·缓存
南玖yy1 小时前
C++ 成员变量缺省值:引用、const 与自定义类型的初始化规则详解,引用类型和const类型的成员变量自定义类型成员是否可以用缺省值?
c语言·开发语言·c++·后端·架构·c++基础语法
小森77671 小时前
(六)机器学习---聚类与K-means
人工智能·机器学习·数据挖掘·scikit-learn·kmeans·聚类
不爱总结的麦穗2 小时前
面试常问!Spring七种事务传播行为一文通关
后端·spring·面试