Spring AI快速入门

文章目录

  • 1 介绍
    • 1_大模型对比
    • 2_开发框架对比
  • 2 快速入门
    • 1_引入依赖
    • 2 配置模型
    • 3 配置客户端
    • 4 测试
  • 3 会话日志
    • 1_Advisor
    • 2 添加日志Advisor
  • 4 会话记忆
    • 1_定义会话存储方式
    • 2 配置会话记忆Advisor
  • 5 会话历史
    • 1_管理会话历史
    • 2 保存会话id
    • 3 查询会话历史
  • 6 后续

1 介绍

SpringAI整合了全球(主要是国外)的大多数大模型,而且对于大模型开发的三种技术架构都有比较好的封装和支持,开发起来非常方便。

不同的模型能够接收的输入类型、输出类型不一定相同,SpringAI 根据模型的输入和输出类型不同对模型进行了分类:

大模型应用开发大多数情况下使用的都是基于对话模型(Chat Model),也就是输出结果为自然语言或代码的模型。

官网文档:https://docs.spring.io/spring-ai/reference/

1_大模型对比

目前 SpringAI 支持的大约 19 种对话模型,以下是一些功能对比:

ProviderMultimodalityTools/FunctionsStreamingRetryBuilt-in JSONLocalOpenAI API Compatible
Anthropic Claudetext, pdf, image
Azure OpenAItext, image
DeepSeek (OpenAI-proxy)text
Google VertexAI Geminitext, pdf, image, audio, video
Groq (OpenAI-proxy)text, image
HuggingFacetext
Mistral AItext, image
MiniMaxtext
Moonshot AItext
NVIDIA (OpenAI-proxy)text, image
OCI GenAI/Coheretext
Ollamatext, image
OpenAIIn: text, image, audio Out: text, audio
Perplexity (OpenAI-proxy)text
QianFantext
ZhiPu AItext
Watsonx.AItext
Amazon Bedrock Conversetext, image, video, docs (pdf, html, md, docx …)

其中功能最完整的就是OpenAI和Ollama平台的模型了。

2_开发框架对比

Spring Ai VS LangChain4j:

功能项Spring AILangChain4j
Chat支持支持
Function支持支持
RAG支持支持
对话模型15+15+
向量模型10+15+
向量数据库15+20+
多模态模型5+1
JDK178

2 快速入门

以快速开发一个对话机器人为例。

1_引入依赖

首先,在项目中添加 spring-ai 的版本信息:

<spring-ai.version>1.0.0</spring-ai.version>

然后,添加 spring-ai 的依赖管理项:

<!--所有spring-ai的包管理-->
<dependencyManagement><dependencies><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-bom</artifactId><version>${spring-ai.version}</version><type>pom</type><scope>import</scope></dependency></dependencies>
</dependencyManagement>

最后,引入 spring-ai-ollama 的依赖:

<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-model-ollama</artifactId>
</dependency>

还有一些其它模型的依赖坐标:https://docs.spring.io/spring-ai/reference/api/chat/comparison.html

2 配置模型

配置属性如下,会自动向 IOC 注入一个 Ollama 的对话模型

spring:ai:ollama:base-url: http://192.168.200.129:11434 # ollama服务地址, 这就是默认值chat:model: deepseek-r1:1.5b # 模型名称options:temperature: 0.8 # 模型温度,影响模型生成结果的随机性,越小越稳定

3 配置客户端

客户端类型为 ChatClient,通过配置客户端可以向大模型发起请求。

ChatClient 中封装了与大模型对话的各种 API,同时支持同步式或响应式交互。

@Bean
public ChatClient chatClient(OllamaChatModel model){//模型也可以是 openAIreturn ChatClient.builder(model)// 创建ChatClient工厂.defaultSystem("系统提示词,支持文件的形式").build();// 构建ChatClient实例
}

代码解读:

  • ChatClient.builder:会得到一个ChatClient.Builder工厂对象,利用它可以自由选择模型、添加各种自定义配置;
  • OllamaChatModel:如果引入了 ollama 的 starter,这里就可以自动注入 OllamaChatModel 对象。同理,OpenAI 也是一样的用法。
  • defaultSystem:用于设定背景信息的系统指令,先于用户消息。

4 测试

直接编写一个 controller,在其中接收用户发送的提示词,然后把提示词发送给大模型,交给大模型处理,最后查看结果

private final ChatClient chatClient;
@RequestMapping("/chat")
public String chat(String prompt) {// 阻塞式return chatClient.prompt().user(prompt).call().content();
}
@RequestMapping(value = "/chatStream", produces = "text/html;charset=utf-8")
public Flux<String> chatStream(String prompt) {//流式return chatClient.prompt().user(prompt).stream().content();
}

注意,基于 call() 方法的调用属于同步调用,需要所有响应结果全部返回后才能返回给前端。

stream() 方法属于流式调用实现,可以逐步拿到响应结果,需要结合 WebFlux 才能返回给前端。

访问结果:

img

3 会话日志

默认情况下,应用于AI的交互时不记录日志的,我们无法得知 SpringAI 组织的提示词到底长什么样,有没有问题,这不方便我们调试。

1_Advisor

SpringAI 基于 AOP 机制实现与大模型对话过程的增强、拦截、修改等功能。

所有的增强通知都需要实现 Advisor 接口。

不止是日志,其他功能也可以靠这个环绕增强来实现

常见的有如下几种:

  • CallAdvisor:非流式场景的接口。
  • StreamAdvisor:流式场景的接口。
  • SimpleLoggerAdvisor:日志记录的Advisor
  • MessageChatMemoryAdvisor:会话记忆的Advisor

也可以自定义 Advisor,具体可以参考:

https://docs.spring.io/spring-ai/reference/api/advisors.html#_implementing_an_advisor

注意:这个链和过滤器差不多都是有顺序的,另外添加时的顺序也会有影响。

2 添加日志Advisor

配置日志 Advisor:

@Bean
public ChatClient chatClient(OllamaChatModel model) {return ChatClient.builder(model).defaultSystem("你是我的助手,名字叫小微").defaultAdvisors(new SimpleLoggerAdvisor())// 配置日志Advisor.build();
}

在配置文件中设置日志级别:

logging:level:org.springframework.ai.chat.client.advisor: debugcom.duration.ai: debug

访问测试查看日志:

4 会话记忆

大模型是不具备记忆能力的,要想让大模型记住之前的聊天内容,唯一的办法就是把之前聊天的内容与新的提示词一起发给大模型。

原理就是利用下面这三中不同的消息类型,拼接成数组一起发送给大模型:

角色描述
‌system‌设定大模型角色和任务背景的系统指令,位于用户指令之前
‌user‌终端用户输入的指令,类似于聊天框中的输入内容
‌assistant‌由大模型生成的消息,可能包含上一轮对话的结果

使用步骤如下:

1_定义会话存储方式

会话记忆功能同样基于 AOP 实现,Spring 提供了一个 MessageChatMemoryAdvisor 的通知,将其添加到 ChatClient 即可。

不过,要注意的是,MessageChatMemoryAdvisor 需要指定一个 ChatMemory 实例,也就是会话历史保存的方式。

ChatMemory 接口声明如下:

public interface ChatMemory {//将指定的消息保存在指定对话的聊天内存中。default void add(String conversationId, Message message) {Assert.hasText(conversationId, "conversationId cannot be null or empty");Assert.notNull(message, "message cannot be null");this.add(conversationId, List.of(message));}// 添加会话信息到指定conversationId的会话历史中void add(String conversationId, List<Message> messages);// 根据conversationId查询历史会话List<Message> get(String conversationId);// 清除指定conversationId的会话历史void clear(String conversationId);
}

可以看到,所有的会话记忆都是与 conversationId 有关联的,也就是会话Id,将来不同会话id的记忆自然是分开管理的。

当前版本中,在 Spring AI 中只有一个基于内存的实现,默认也会使用它:

MessageWindowChatMemory 只能将会话历史保存在内存中(原理是一个 Map)。

2 配置会话记忆Advisor

首先,在 IOC 容器中注册 ChatMemory 对象:

@Bean
public ChatMemory chatMemory() {return MessageWindowChatMemory.builder().maxMessages(10)//设置最大的会话消息数量 FIFO.build();
}

然后将其添加到 ChatClient 中:

@Bean
public ChatClient chatClient(OllamaChatModel model,ChatMemory chatMemory) {return ChatClient.builder(model).defaultSystem("你是我的助手,名字叫小微").defaultAdvisors(new SimpleLoggerAdvisor())// 配置日志Advisor.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build()).build();
}

补充:

MessageChatMemoryAdvisor 每次交互时,都会从内存中检索对话历史记录,并将其作为消息集合包含在提示中。

除此之外还有:

  • PromptChatMemoryAdvisor:从内存中检索对话历史记录,并将其以纯文本形式附加到系统提示中。
  • VectorStoreChatMemoryAdvisor:从向量存储中检索对话历史记录,并将其以纯文本形式附加到系统消息中。

虽然聊天已经有了记忆功能了,但是还存在一定的问题:不同用户间默认共用一个会话记忆对象,所以会存在互相干扰的问题(上下文跨页面影响)。

解决方式,使用如下代码,当执行对 ChatClient 的调用时,内存将由 MessageChatMemoryAdvisor 自动管理,对话历史记录将根据指定的对话 ID 从内存中检索:

@RequestMapping(value = "/chat", produces = "text/html;charset=utf-8")
public Flux<String> chat(String prompt, String chatId) {return chatClient.prompt().user(prompt).advisors(a -> a.param(ChatMemory.CONVERSATION_ID, chatId)).stream().content();
}

ChatMemory.CONVERSATION_ID 是 ChatMemory 中定义的常量 key。

5 会话历史

会话历史与会话记忆是两个不同的事情:

会话记忆:是指让大模型记住每一轮对话的内容,不至于前一句刚问完,下一句就忘了。

会话历史:是指要记录总共有多少不同的对话,每个对话历史(记忆)是独立分开的,互不干扰、互不可见。

以 DeepSeek 为例,页面上的会话历史:

img

在 ChatMemory 中,会记录一个会话中的所有消息,记录方式是以 conversationId 为 key,以List<Message>为 value,根据这些历史消息,大模型就能继续回答问题,这就是所谓的会话记忆。

而会话历史,就是每一个会话的 conversationId,将来根据 conversationId 再去查询 List<Message>(自己的消息)。

比如上图中,有3个不同的会话历史,就会有3个 conversationId,管理会话历史,就是记住这些 conversationId,当需要的时候查询出 conversationId 的列表。

注意,在接下来业务中,我们以 chatId 来代指 conversationId,实现一个与 DeepSeek 类似的会话历史框。

1_管理会话历史

由于会话记忆是以 conversationId 来管理的,也就是会话 id(以后简称为 chatId),将来要查询会话历史,其实就是查询历史中有哪些 chatId

因此,为了实现查询会话历史记录,需要记录所有的 chatId,必须定义一个管理会话历史的标准接口,同时根据业务类型对会话历史进行拆分。

public interface ChatHistoryRepository {/*** 保存会话历史** @param type   业务类型* @param chatId 会话id*/void save(String type, String chatId);/*** 获取会话id列表** @param type 业务类型* @return 会话id集合*/List<String> getChatIds(String type);
}

定义实现类:

@Component
public class InMemoryChatHistoryRepository implements ChatHistoryRepository {private final Map<String, List<String>> chatHistory = new HashMap<>();@Overridepublic void save(String type, String chatId) {if (!chatHistory.containsKey(type)) {chatHistory.put(type, new ArrayList<>());}List<String> chatIds = chatHistory.get(type);// 防止重复添加 chatIdif (chatIds.contains(chatId)) {return;}chatIds.add(chatId);}@Overridepublic List<String> getChatIds(String type) {return chatHistory.getOrDefault(type, List.of());}}

注意:此实现比较简单,没有用户的概念,但是区分了不同业务,因此简单采用内存保存typechatId关系。

根据自身情况,可能需要持久化会话 ID,如果业务中有用户的概念,还需要记录userIdchatId的关联关系。

2 保存会话id

接下来,修改 ChatController 中的 chat 方法,做到 3 点:

  • 添加一个请求参数:chatId,每次前端请求AI时都需要传递 chatId。
  • 每次处理请求时,将 chatId 存储到 ChatRepository。
  • 每次发请求到 AI 大模型时,都传递自定义的 chatId
private final ChatHistoryRepository chatHistoryRepository;
@RequestMapping(value = "/chat", produces = "text/html;charset=utf-8")
public Flux<String> chat(String prompt, String chatId) {// 请求聊天前先保存会话id,已经做了重复添加的校验chatHistoryRepository.save("chat", chatId);// 请求模型return chatClient.prompt().user(prompt).advisors(a -> a.param(ChatMemory.CONVERSATION_ID, chatId)).stream().content();
}

3 查询会话历史

定义一个新的 Controller,专门实现会话历史的查询,包含两个接口:

  • 根据业务类型查询会话历史列表(不同业务需要分别记录历史,大家的业务可能是按userId记录,根据 userId 查询)。
  • 根据 chatId 查询指定会话的历史消息。

查询会话历史消息,也就是 Message 集合。

不过 Message 可能不符合前端页面的需求,因此需要我们自定义一个 VO。

@NoArgsConstructor
@Data
public class MessageVO {private String role;private String content;public MessageVO(Message message) {// MessageType 有四种枚举类型:user assistant function systemthis.role = switch (message.getMessageType()) {case USER -> "user";case ASSISTANT -> "assistant";case SYSTEM -> "system";//系统消息前端可能不需要default -> "";};this.content = message.getText();}
}

最后定义 ChatHistoryController:

@RequiredArgsConstructor
@RestController
@RequestMapping("/ai/history")
public class ChatHistoryController {private final ChatHistoryRepository chatHistoryRepository;private final ChatMemory chatMemory;/*** 查询会话历史列表* @param type 业务类型,如:chat,service,pdf* @return chatId列表*/@GetMapping("/{type}")public List<String> getChatIds(@PathVariable("type") String type) {return chatHistoryRepository.getChatIds(type);}/*** 根据业务类型、chatId查询会话历史* @param type 业务类型,如:chat,service,pdf* @param chatId 会话id* @return 指定会话的历史消息*/@GetMapping("/{type}/{chatId}")public List<MessageVO> getChatHistory(@PathVariable("type") String type, @PathVariable("chatId") String chatId) {List<Message> messages = chatMemory.get(chatId);return messages.stream().map(MessageVO::new).toList();}
}

6 后续

之后会更新提示词、FunctionCalling、多模态、VectorStore、MCP

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/89149.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/89149.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Windows下编译pthreads

本文记录在Windows下编译pthreads的流程。 零、环境 操作系统Windows 11VS Code1.92.1Git2.34.1MSYS2msys2-x86_64-20240507Visual StudioVisual Studio Community 2022CMake3.22.1 一、编译安装 1.1 下载 git clone https://git.code.sf.net/p/pthreads4w/code 1.2 构建…

WP Force SSL Pro – HTTPS SSL Redirect Boost Your Website‘s Trust in Minutes!

In the vast digital landscape where security and user trust are paramount, ensuring your WordPress site uses HTTPS is not just a recommendation—it’s a necessity. That’s where WP Force SSL Pro – HTTPS SSL Redirect steps in as your silent guardian, makin…

jvm--java代码对照字节码图解

java代码&#xff1a;无静态方法&#xff1b;&#xff08;对应字节码没有方法&#xff09; 任何一个类&#xff0c;至少有一个构造器&#xff0c;默认是无参构造java代码包含&#xff1a;静态方法java代码包含&#xff1a;静态方法、显示构造方法public class ClassInitTest {p…

动态规划题解_打家劫舍【LeetCode】

198. 打家劫舍 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋。每间房内都藏有一定的现金&#xff0c;影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统&#xff0c;如果两间相邻的房屋在同一晚上被小偷闯入&#xff0c;系统会自动报警。 给定一个代表每个…

电脑安装 Win10 提示无法在当前分区上安装Windows的解决办法

原因&#xff1a; win10系统均添加快速启动功能&#xff0c;预装的win10电脑默认都是UEFI引导和GPT硬盘&#xff0c;传统的引导方式为Legacy引导和MBR硬盘&#xff0c;UEFI必须跟GPT对应&#xff0c;同理Legacy必须跟MBR对应。如果BIOS开启UEFI&#xff0c;而硬盘分区表格式为M…

大端序与小端序

理解大端序&#xff08;Big-Endian&#xff09;和小端序&#xff08;Little-Endian&#xff09;的关键在于数据在内存中存储时字节的排列顺序&#xff0c;特别是在存储多字节数据类型&#xff08;如整数、浮点数&#xff09;时。以下是清晰易懂的解释&#xff1a;核心概念 假设…

PyTorch笔记5----------Autograd、nn库

1.Autograd grad和grad_fn grad&#xff1a;该tensor的梯度值&#xff0c;每次在计算backward时都需要将前一时刻的梯度归零&#xff0c;否则梯度值会一直累加grad_fn&#xff1a;叶子结点通常为None&#xff0c;只有结果节点的grad_fn才有效&#xff0c;用于只是梯度函数时哪…

Perl 格式化输出

Perl 格式化输出 引言 Perl 是一种通用、解释型、动态编程语言&#xff0c;广泛应用于文本处理、系统管理、网络编程等领域。在Perl编程中&#xff0c;格式化输出是一种常见的需求&#xff0c;它可以帮助开发者更好地展示和打印信息。本文将详细讲解Perl中格式化输出的方法&…

Python爬虫实战:研究markdown2库相关技术

一、引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上的信息量呈指数级增长。如何高效地获取和整理这些信息成为了一个重要的研究课题。网络爬虫作为一种自动获取网页内容的技术,能够按照一定的规则,自动地抓取万维网信息,为信息的收集提供了有力手段。 Markdown …

【Linux】基本指令详解(二) 输入\输出重定向、一切皆文件、认识管道、man、cp、mv、echo、cat

文章目录一、man指令二、输入/输出重定向(echo、一切皆文件&#xff09;三、cp指令四、mv指令五、cat指令六、more/less指令七、head/tail指令八、管道初见一、man指令 Linux的指令有很多参数&#xff0c;我们不可能全记住&#xff0c;可以通过查看联机手册获取帮助。 man 指令…

MVC HTML 帮助器

MVC HTML 帮助器 引言 MVC&#xff08;模型-视图-控制器&#xff09;是一种流行的软件架构模式&#xff0c;它将应用程序的逻辑分解为三个主要组件&#xff1a;模型&#xff08;Model&#xff09;、视图&#xff08;View&#xff09;和控制器&#xff08;Controller&#xff09…

linux下手工安装ollama0.9.6

1、去下载ollama的linux版的压缩包&#xff1a; 地址&#xff1a;https://github.com/ollama/ollama/releases2、上传到linux中。3、解压&#xff1a; tar zxvf ollama-linux-amd64-0.9.6.tgz -C /usr/local/4、如果仅仅是要手工执行&#xff0c;已经可以了&#xff1a; ollama…

kotlin布局交互

将 wrapContentSize() 方法链接到 Modifier 对象&#xff0c;然后传递 Alignment.Center 作为实参以将组件居中。Alignment.Center 会指定组件同时在水平和垂直方向上居中。 DiceWithButtonAndImage(modifier Modifier.fillMaxSize().wrapContentSize(Alignment.Center) )创建…

50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | ToastNotification(推送通知)

&#x1f4c5; 我们继续 50 个小项目挑战&#xff01;—— ToastNotification组件 仓库地址&#xff1a;https://github.com/SunACong/50-vue-projects 项目预览地址&#xff1a;https://50-vue-projects.vercel.app/ 使用 Vue 3 的 Composition API&#xff08;<script s…

学习笔记(34):matplotlib绘制图表-房价数据分析与可视化

学习笔记(34):matplotlib绘制图表-房价数据分析与可视化分析房价分布情况&#xff0c;通过直方图、核密度估计和正态分布拟合来直观展示房价的分布特征&#xff0c;并进行统计检验。一、房价数据分析与可视化&#xff0c;代码分析1.1、导入必要的库import pandas as pd import …

前端三剑客之CSS

1. CSS 简介1) CSS 简述CSS&#xff0c;即层叠样式表&#xff08;英文全称&#xff1a;Cascading Style Sheets&#xff09;&#xff0c;是一种专门用于修饰 HTML 文档呈现样式的计算机语言。它的功能不仅限于静态美化网页&#xff0c;还能与各类脚本语言配合&#xff0c;实现对…

力扣25.7.11每日一题——无需开会的工作日

Description 这题类似合并区间&#xff0c;题意你们都能看懂吧…… Solution 这道题就需要用到合并区间的方法。 答案等于 daysdaysdays 减「有会议安排的天数」。 对左端点进行排序&#xff0c;计算有会议安排的天数&#xff0c;累加每个区间的长度&#xff0c;即为有会议…

每日一SQL 【销售分析 III】

文章目录问题案例执行顺序使用分组解决问题 案例 执行顺序 SQL 语句的执行顺序&#xff08;核心步骤&#xff09; 同一层级的select查询内部, 别名在整个 SELECT 计算完成前不生效 使用分组解决 select distinct s.product_id, Product.product_name from Sales sleft join …

轻轻松松带你进行-负载均衡LVS实战

8. LVS部署命令介绍 8.1 LVS软件相关信息 1.程序包&#xff1a;ipvsadm 2.Unit File: ipvsadm.service 3.主程序&#xff1a;/usr/sbin/ipvsadm 4.规则保存工具&#xff1a;/usr/sbin/ipvsadm-save 5.规则重载工具&#xff1a;/usr/sbin/ipvsadm-restore 6.配置文件&#xff1a…

C#.NET 集合框架详解

简介 C# 集合框架是处理数据集合的核心组件&#xff0c;位于 System.Collections 和 System.Collections.Generic 命名空间。它提供了多种数据结构来高效存储和操作数据。 集合框架概览 System.Collections (非泛型老版) └─ System.Collections.Generic (泛…