SpringAI多模态的基本使用

SpringAI 多模态

一、多模态的概念

模态是指表达或感知事务的方式,例如视觉、听觉、嗅觉。对应的信息传递媒介可以是文本、图像、声音、视频等

多模态是指从多个模态表达或者感知事物。

表达可以理解为输出,而感知则是输入。

deepseek、qwen-max等模型都是纯文本模型,不过在ollama和阿里云百炼等大模型

平台,能找到很多多模态模型。

以阿里云百炼平台为例:

有很多模态的模型,甚至还有一个分类是全模态模型。

阿里云的qwen3-omni-flash模型是支持文本、图像、音频、视频输入的全模态模

型,还能支持语音合成功能,非常强大。

二、常见使用

像deepseek、文心一言、千问等都支持文件上传的同时去回答问题,这就相当于是一种多模态,使用文字和文件的方式去感知,然后做出输出。

三、代码实现

下面代码基于SpringAI1.0.03。

3.1 定义bean

首先,定义一个用于AI对话的ChatClient的bean,将模型修改为OpenAIChatModel,不仅如此,由于其它业务使用的是不同模型,不能改变,只需添加自定义配置,将模型改为qwen3-omni-flash:

java 复制代码
@Bean("chatClient")
public ChatClient chatClient(OpenAiChatModel model, ChatMemory chatMemory) {
    return ChatClient.builder(model)
        .defaultOptions(ChatOptions.builder().model("qwen3-omni-flash").build())
        .defaultSystem("你是可爱且热情、人见人爱,花见花开的AI助手,你的名字是墩墩,请用墩墩的身份回答用户的问题!")
        .defaultAdvisors(
        new SimpleLoggerAdvisor(),
        MessageChatMemoryAdvisor.builder(chatMemory).build()         // 如果失去它,系统就不再有会话记忆功能了,不过这个会话记忆是在内存里的,重启项目就不存在了
    )
        .build();
}

在这里配置defaultOptions(ChatOptions.builder().model("qwen3-omni-flash").build()) 可以独立配置这个ChatClient使用的大模型,而不是使用yaml中配置的全局模型了。

3.2 编写controller

java 复制代码
    // 再弄一个流式的,但是这里一定要设置字符编码,要不然是会乱码的
    @RequestMapping(value = "/chat", produces = "text/html;charset=UTF-8")
    public Flux<String> chatStream(@RequestParam("prompt") String prompt,
                                   @RequestParam("chatId") String chatId,
                                   @RequestParam(value = "files", required = false) List<MultipartFile> files
    ) {
        // 1.保存会话id (这一步如果是走的redis就不需要自己实现了)
        chatHistoryService.save(ServiceTypeEnum.CHAT.getType(), chatId);
        // 2.请求模型
        if (files == null || files.isEmpty()) {
            // 没有附件,纯文本聊天
            return textChat(prompt, chatId);
        } else {
            // 有附件,多模态聊天
            return multiModalChat(prompt, chatId, files);
        }
    }

    private Flux<String> multiModalChat(String prompt, String chatId, List<MultipartFile> files) {
        // 1.解析多媒体, 其实就是把文件对象解析成没提对象
        List<Media> medias = files.stream()
                .map(file -> new Media(
                                MimeType.valueOf(Objects.requireNonNull(file.getContentType())),
                                file.getResource()
                        )
                )
                .toList();
        // 2.请求模型
        return chatClient.prompt()
                .user(p -> p.text(prompt).media(medias.toArray(Media[]::new)))
                .advisors(a -> a.param(CONVERSATION_ID, chatId))
                .stream()
                .content();
    }

    private Flux<String> textChat(String prompt, String chatId) {
        return chatClient.prompt()
                .user(prompt)
                .advisors(a -> a.param(CONVERSATION_ID, chatId))
                .stream()
                .content();
    }

四、测试效果

现在我传递这张图片给AI,询问图片的内容:

可以看到现在AI能够同时理解文字和图片这两种模态的信息,并且进行理解和输入。

相关推荐
Chase_______几秒前
【Java杂项】为什么 long 可以自动转 float?宽化基本类型转换与精度丢失详解
java·开发语言·python
invicinble2 分钟前
java数组相关的信息量
java·开发语言·python
AIGC大时代2 分钟前
Skill真只是Prompt吗
科技·ai·科普
小江的记录本2 分钟前
【Java基础】Java 8-21新特性 :JDK17:密封类、模式匹配、Record类(附《思维导图》+《面试高频考点清单》)
java·数据结构·后端·python·mysql·面试·职场和发展
尘埃落定wf2 分钟前
五大主流 Agent 架构模式详解
ai·langchain
老王谈企服4 分钟前
AI Agent将如何重构制造业的安全生产隐患识别模式?深度理解与实在Agent闭环实战
人工智能·安全·ai·重构
小江的记录本4 分钟前
【Java基础】集合框架: ArrayList vs LinkedList 核心区别、扩容机制(附《思维导图》+《面试高频考点清单》)
java·数据库·python·mysql·spring·面试·maven
夕除5 分钟前
spring boot 10
java·python·spring
牧瀬クリスだ7 分钟前
Java线程——从创建第一个线程到休眠线程
java·开发语言
清水白石0088 分钟前
从“点一下导出”到生产级任务队列:Python 异步导出系统设计全景解析
java·数据库·python