SpringAI

简介: SpringAI整合全球主流大模型,支持多种技术架构,提供对话、函数调用、RAG等丰富功能,封装完善,开发便捷。支持多模态、流式传输、会话记忆与知识库集成,助力快速构建AI应用。

SpringAI整合了全球(主要是国外)的大多数大模型,而且对于大模型开发的三种技术架构都有比较好的封装和支持,开发起来非常方便。
不同的模型能够接收的输入类型、输出类型不一定相同。SpringAI根据模型的输入和输出类型不同对模型进行了分类:
大模型应用开发大多数情况下使用的都是基于对话模型(Chat Model),也就是输出结果为自然语言或代码的模型。
目前SpringAI支持的大约19种对话模型,以下是一些功能对比:
Provider
Multimodality
Tools/Functions
Streaming
Retry
Built-in JSON
Local
OpenAI API Compatible
Anthropic Claude
text, pdf, image






Azure OpenAI
text, image






DeepSeek (OpenAI-proxy)
text






Google VertexAI Gemini
text, pdf, image, audio, video






Groq (OpenAI-proxy)
text, image






HuggingFace
text






Mistral AI
text, image






MiniMax
text






Moonshot AI
text






NVIDIA (OpenAI-proxy)
text, image






OCI GenAI/Cohere
text






Ollama
text, image






OpenAI
In: text, image, audio Out: text, audio






Perplexity (OpenAI-proxy)
text






QianFan
text






ZhiPu AI
text






Watsonx.AI
text






Amazon Bedrock Converse
text, image, video, docs (pdf, html, md, docx …)






其中功能最完整的就是OpenAI和Ollama平台的模型了。
接下来,我们就以这两个平台为例给大家讲解SpringAI的应用。
1.SpringAI入门(对话机器人)
接下来,我们就利用SpringAI发起与大模型的第一次对话。
1.1.快速入门
1.1.1.创建工程
创建一个新的SpringBoot工程,勾选Web、MySQL驱动即可:
工程结构如图:
原始pom.xml如下:
1.1.2.引入依赖
SpringAI完全适配了SpringBoot的自动装配功能,而且给不同的大模型提供了不同的starter,比如:
模型/平台
starter
Anthropic
Azure OpenAI
DeepSeek
Hugging Face
Ollama
OpenAI
我们可以根据自己选择的平台来选择引入不同的依赖。这里我们先以Ollama为例。
首先,在项目pom.xml中添加spring-ai的版本信息:
然后,添加spring-ai的依赖管理项:
最后,引入spring-ai-ollama的依赖:
为了方便后续开发,我们再手动引入一个Lombok依赖:
注意: 千万不要用start.spring.io提供的lombok,有bug!!
最终,完整依赖如下:
1.1.3.配置模型信息
接下来,我们还要在配置文件中配置模型的参数信息。
以ollama为例,我们将application.properties修改为application.yaml,然后添加下面的内容:
1.1.4.ChatClient
ChatClient中封装了与AI大模型对话的各种API,同时支持同步式或响应式交互。
不过,在使用之前,首先我们需要声明一个ChatClient。
在com.itheima.ai.config包下新建一个CommonConfiguration类:
完整代码如下:
代码解读:
ChatClient.builder:会得到一个ChatClient.Builder工厂对象,利用它可以自由选择模型、添加各种自定义配置
OllamaChatModel:如果你引入了ollama的starter,这里就可以自动注入OllamaChatModel对象。同理,OpenAI也是一样的用法。
1.1.5.同步调用
接下来,我们定义一个Controller,在其中接收用户发送的提示词,然后把提示词发送给大模型,交给大模型处理,拿到结果后返回。
代码如下:
注意,基于call()方法的调用属于同步调用,需要所有响应结果全部返回后才能返回给前端。
启动项目,在浏览器中访问:http://localhost:8080/ai/chat?prompt=你好
1.1.6.流式调用
同步调用需要等待很长时间页面才能看到结果,用户体验不好。为了解决这个问题,我们可以改进调用方式为流式调用。
在SpringAI中使用了WebFlux技术实现流式调用。
修改刚才ChatController中的chat方法:
重启测试,再次访问:
1.1.7.System设定
可以发现,当我们询问AI你是谁的时候,它回答自己是DeepSeek-R1,这是大模型底层的设定。如果我们希望AI按照新的设定工作,就需要给它设置System背景信息。
在SpringAI中,设置System信息非常方便,不需要在每次发送时封装到Message,而是创建ChatClient时指定即可。
我们修改CommonConfiguration中的代码,给ChatClient设定默认的System信息:
我们再次询问“你是谁?”
注意,前面是DeepSeek的深度思考内容,绿色高亮部分才是最终的回答。
现在,AI已经能够以黑马客服小黑的身份来回答问题了~
1.2.日志功能
默认情况下,应用于AI的交互时不记录日志的,我们无法得知SpringAI组织的提示词到底长什么样,有没有问题。这样不方便我们调试。
1.2.1.Advisor
SpringAI基于AOP机制实现与大模型对话过程的增强、拦截、修改等功能。所有的增强通知都需要实现Advisor接口。
Spring提供了一些Advisor的默认实现,来实现一些基本的增强功能:
SimpleLoggerAdvisor:日志记录的Advisor
MessageChatMemoryAdvisor:会话记忆的Advisor
QuestionAnswerAdvisor:实现RAG的Advisor
当然,我们也可以自定义Advisor,具体可以参考:
https://docs.spring.io/spring-ai/reference/1.0/api/advisors.html#_implementing_an_advisor
1.2.2.添加日志Advisor
首先,我们需要修改CommonConfiguration,给ChatClient添加日志Advisor:
1.2.3.修改日志级别
接下来,我们在application.yaml中添加日志配置,更新日志级别:
重启项目,再次聊天就能看到AI对话的日志信息了~
1.3.对接前端
在浏览器通过地址访问,非常麻烦,也不够优雅。如果能有一个优美的前端页面就好了。
别着急,我提前给大家准备了一个前端页面。而且有两种不同的运行方式。
1.3.1.npm运行
在资料中给大家提供了前端的源代码:
你只需要解压缩(最好放到非中文目录),然后进入解压后的目录,依次执行命令即可运行:
启动后,访问 http://localhost:5173即可看到页面:
1.3.2.Nginx运行
如果你不关心源码,我也给大家提供了构建好的Nginx程序:
解压缩到一个不包含中文、空格、特殊字符的目录中,然后通过命令启动Nginx:
注意:
如果是使用MacOS的同学,可以把nginx中的html目录内容拷贝出来,到你自己的Nginx安装目录即可。
启动后,访问 http://localhost:5173即可看到页面:
1.3.3.解决CORS问题
前后端在不同域名,存在跨域问题,因此我们需要在服务端解决cors问题。在com.itheima.ai.config包中添加一个MvcConfiguration类:
内容如下:
重启服务,如果你的服务端接口正确,那么应该就可以聊天了。
注意: 前端访问服务端的默认路径是:http://localhost:8080
聊天对话的接口是:POST /ai/chat
请确保你的服务端接口也是这样。
1.3.4.测试
启动前端后,访问 http://localhost:5173即可看到页面:
点击第一个卡片《AI聊天》进入对话机器人页面:
恭喜您,你的第一个AI对话机器人就完成了。
1.4.会话记忆功能
现在,我们的AI聊天机器人是没有记忆功能的,上一次聊天的内容,下一次就忘掉了。
我们之前说过,让AI有会话记忆的方式就是把每一次历史对话内容拼接到Prompt中,一起发送过去。是不是还挺麻烦的。
别担心,好消息是,我们并不需要自己来拼接,SpringAI自带了会话记忆功能,可以帮我们把历史会话保存下来,下一次请求AI时会自动拼接,非常方便。
1.4.1.ChatMemory
会话记忆功能同样是基于AOP实现,Spring提供了一个MessageChatMemoryAdvisor的通知,我们可以像之前添加日志通知一样添加到ChatClient即可。
不过,要注意的是,MessageChatMemoryAdvisor需要指定一个ChatMemory实例,也就是会话历史保存的方式。
ChatMemory接口声明如下:
可以看到,所有的会话记忆都是与conversationId有关联的,也就是会话Id,将来不同会话id的记忆自然是分开管理的。
目前,在SpringAI中有两个ChatMemory的实现:
InMemoryChatMemory:会话历史保存在内存中
CassandraChatMemory:会话保存在Cassandra数据库中(需要引入额外依赖,并且绑定了向量数据库,不够灵活)
我们暂时选择用InMemoryChatMemory来实现。
1.4.2.添加会话记忆Advisor
在CommonConfiguration中注册ChatMemory对象:
然后添加MessageChatMemoryAdvisor到ChatClient:
OK,现在聊天会话已经有记忆功能了,不过现在的会话记忆还是不完善的,我们还有继续补充。
1.5.会话历史
会话历史与会话记忆是两个不同的事情:
会话记忆:是指让大模型记住每一轮对话的内容,不至于前一句刚问完,下一句就忘了。
会话历史:是指要记录总共有多少不同的对话
以DeepSeek为例,页面上的会话历史:
在ChatMemory中,会记录一个会话中的所有消息,记录方式是以conversationId为key,以List为value,根据这些历史消息,大模型就能继续回答问题,这就是所谓的会话记忆。
而会话历史,就是每一个会话的conversationId,将来根据conversationId再去查询List。
比如上图中,有3个不同的会话历史,就会有3个conversationId,管理会话历史,就是记住这些conversationId,当需要的时候查询出conversationId的列表。
注意,在接下来业务中,我们以chatId来代指conversationId.
1.5.1.管理会话id(会话历史)
由于会话记忆是以conversationId来管理的,也就是会话id(以后简称为chatId)。将来要查询会话历史,其实就是查询历史中有哪些chatId.
因此,为了实现查询会话历史记录,我们必须记录所有的chatId,我们需要定义一个管理会话历史的标准接口。
我们定义一个com.itheima.ai.repository包,然后新建一个ChatHistoryRepository接口:
然后定义一个实现类InMemoryChatHistoryRepository:
注意:
目前我们业务比较简单,没有用户概念,但是将来会有不同业务,因此简单采用内存保存type与chatId关系。
将来大家也可以根据业务需要把会话id持久化保存到Redis、MongoDB、MySQL等数据库。
如果业务中有user的概念,还需要记录userId、chatId、time等关联关系
1.5.2.保存会话id
接下来,修改ChatController中的chat方法,做到3点:
添加一个请求参数:chatId,每次前端请求AI时都需要传递chatId
每次处理请求时,将chatId存储到ChatRepository
每次发请求到AI大模型时,都传递自定义的chatId
注意,这里传递chatId给Advisor的方式是通过AdvisorContext,也就是以key-value形式存入上下文:
其中的CHAT_MEMORY_CONVERSATION_ID_KEY是AbstractChatMemoryAdvisor中定义的常量key,将来MessageChatMemoryAdvisor执行的过程中就可以拿到这个chatId了。
1.5.3.查询会话历史
接着,我们定义一个新的Controller,专门实现会话历史的查询。包含两个接口:
根据业务类型查询会话历史列表(我们将来有3个不同业务,需要分别记录历史。大家的业务可能是按userId记录,根据UserId查询)
根据chatId查询指定会话的历史消息
其中,查询会话历史消息,也就是Message集合。但是由于Message并不符合页面的需要,我们需要自己定义一个VO.
定义一个com.itheima.entity.vo包,在其中定义一个MessageVO类:
然后在com.itheima.ai.controller包下新建一个ChatHistoryController:
OK,重启服务,现在AI聊天机器人就具备会话记忆和会话历史功能了!
2.纯Prompt开发(哄哄模拟器)
之前说过,开发有四种模式,其中第一种就是纯Prompt模式,只要我们设定好System提示词,就能让大模型实现很强大的功能。
接下来,我们就来看看如何才能写好提示词。
2.1.提示词工程
在OpenAI的官方文档中,对于写提示词专门有一篇文档,还给出了大量的例子,大家可以看看:
https://platform.openai.com/docs/guides/prompt-engineering
通过优化提示词,让大模型生成出尽可能理想的内容,这一过程就称为提示词工程(Project Engineering)。
以下是OpenAI官方Prompt Engineering指南的核心要点总结(基于公开资料整理):
以下是对OpenAI官方Prompt Engineering指南的简洁总结,包含关键策略及详细示例:
2.1.1.核心策略
清晰明确的指令
直接说明任务类型(如总结、分类、生成),避免模糊表述。
示例:
使用分隔符标记输入内容
、"""或XML标签分隔用户输入,防止提示注入。 示例: 分步骤拆解复杂任务 将任务分解为多个步骤,逐步输出结果。 示例: 提供示例(Few-shot Learning) 通过输入-输出示例指定格式或风格。 示例: 指定输出格式 明确要求JSON、HTML或特定结构。 示例: 给模型设定一个角色 设定角色可以让模型在正确的角色背景下回答问题,减少幻觉。 示例: 2.1.2.减少模型“幻觉”的技巧 引用原文:要求答案基于提供的数据(如“根据以下文章...”)。 限制编造:添加指令如“若不确定,回答‘无相关信息’”。 通过以上策略,可显著提升模型输出的准确性与可控性,适用于内容生成、数据分析等场景。 2.2.提示词攻击防范 ChatGPT刚刚出来时就存在很多漏洞,比如知名的“奶奶漏洞”。所以,防范Prompt攻击也是非常必要的。以下是常见的Prompt攻击手段及对应的防范措施: 2.2.1. 提示注入(Prompt Injection) 攻击方式:在用户输入中插入恶意指令,覆盖原始Prompt目标。 示例: 模型输出:可能执行用户指令而非翻译。 防范措施: 输入分隔符:用、"""等标记用户输入区域。
权限控制:在系统Prompt中明确限制任务范围。
改进Prompt:
2.2.2. 越狱攻击(Jailbreaking)
攻击方式:绕过模型安全限制生成违法/有害内容。
示例:
防范措施:
内容过滤:使用Moderation API检测违规内容。
道德约束:在Prompt中强化安全声明。
改进Prompt:
2.2.3. 数据泄露攻击(Data Extraction)
攻击方式:诱导模型透露训练数据中的隐私信息。
示例:
防范措施:
数据隔离:禁止模型访问内部数据。
回复模板:对敏感问题固定应答。
改进Prompt:
2.2.4. 模型欺骗(Model Manipulation)
攻击方式:通过虚假前提误导模型输出错误答案。
示例:
模型输出:可能基于虚构的2100年视角编造错误信息。
防范措施:
事实校验:要求模型优先验证输入真实性。
改进Prompt:
2.2.5. 拒绝服务攻击(DoS via Prompt)
攻击方式:提交超长/复杂Prompt消耗计算资源。
示例:
防范措施:
输入限制:设置最大token长度(如4096字符)。
复杂度检测:自动拒绝循环/递归请求。
改进响应:
2.2.6. 案例综合应用
系统提示词:
用户输入:
模型回复:
通过组合技术手段和策略设计,可有效降低Prompt攻击风险。
2.3.编写提示词
OK,了解完提示词工程,接下来我们就可以尝试开发功能了。
ChatGPT刚刚出来时,有一个非常知名的游戏,叫做哄哄模拟器,就是通过纯Prompt模式开发的。
游戏规则很简单,就是说你的女友生气了,你需要使用语言技巧和沟通能力,让对方原谅你。
接下来,我们就尝试使用Prompt模式来开发一个哄哄模拟器。
首先,我们需要写好一段提示词,这里我给大家准备好了,一起来看看:
我们可以直接使用这段提示词了。
2.4.创建ChatClient
本地部署的DeepSeek模型只有7B,难以处理这样复杂的业务场景,再加上DeepSeek模型默认是带有思维链输出的,如果每次都输出思维链,就会破坏游戏体验。所以我们这次换一个大模型。
我们采用阿里巴巴的qwen-max模型(当然,大家也可以选择其他模型),虽然SpringAI不支持qwen模型,但是阿里云百炼平台是兼容OpenAI的,因此我们可以使用OpenAI的相关依赖和配置。
2.4.1.引入OpenAI依赖
在项目的pom.xml中引入OpenAI依赖:
2.4.2.配置OpenAI参数
修改application.yaml文件,添加OpenAI的模型参数:
注意:
此处为了防止api-key泄露,我们使用了${OPENAI_API_KEY}来读取环境变量。
大家需要可以在启动项中配置环境变量。
首先,点击启动项下拉箭头,然后点击Edit Configurations:
然后,在弹出的窗口中点击Modify options:
在弹出窗口中,选择Environment variables:
然后,在刚才的Run/Debug Configurations窗口中,就会多出环境变量配置栏:
在其中配置自己阿里云百炼上的API_KEY:
2.4.3.配置ChatClient
修改CommonConfiguration,添加一个新的ChatClient:
注意,这里我们使用的模型是OpenAIChatModel,不要搞错了。
另外,由于System提示词太长,我们定义到了一个常量中SystemConstants.HONG_HONG_SYSTEM:
2.5.编写Controller
接下来,我们在com.itheima.ai.controller定义一个GameController,作为哄哄模拟器的聊天接口:
注意:
这里的请求路径必须是/ai/game,因为前端已经写死了请求的路径。
2.7.测试
与之前类似,我们也提供了前端页面,现在一起去试试吧:
点击哄哄模拟器卡片,进入页面:
这里需要输入女友生气原因,如果不输入则是由AI自动生成原因。
点击开始游戏后,就可以跟AI女友聊天了:
OK,基于纯Prompt模式开发的一款小游戏就完成了。
3.Function Calling(智能客服)
由于AI擅长的是非结构化数据的分析,如果需求中包含严格的逻辑校验或需要读写数据库,纯Prompt模式就难以实现了。
接下来我们会通过智能客服的案例来学习FunctionCalling
3.1.思路分析
假如我要开发一个24小时在线的AI智能客服,可以给用户提供黑马的培训课程咨询服务,帮用户预约线下课程试听。
整个业务的流程如图:
这里就涉及到了很多数据库操作,比如:
查询课程信息
查询校区信息
新增课程试听预约单
可以看出整个业务流程有一部分任务是负责与用户沟通,获取用户意图的,这些是大模型擅长的事情:
大模型的任务:
了解、分析用户的兴趣、学历等信息
给用户推荐课程
引导用户预约试听
引导学生留下联系方式
还有一些任务是需要操作数据库的,这些任务是传统的Java程序擅长的:
传统应用需要完成的任务:
根据条件查询课程
查询校区信息
新增预约单
与用户对话并理解用户意图是AI擅长的,数据库操作是Java擅长的。为了能实现智能客服功能,我们就需要结合两者的能力。
Function Calling就是起到这样的作用。
首先,我们可以把数据库的操作都定义成Function,或者也可以叫Tool,也就是工具。
然后,我们可以在提示词中,告诉大模型,什么情况下需要调用什么工具。
比如,我们可以这样来定义提示词:
也就是说,在提示词中告诉大模型,什么情况下需要调用什么工具,将来用户在与大模型交互的时候,大模型就可以在适当的时候调用工具了。
流程如下:
流程解读:
提前把这些操作定义为Function(SpringAI中叫Tool),
然后将Function的名称、作用、需要的参数等信息都封装为Prompt提示词与用户的提问一起发送给大模型
大模型在与用户交互的过程中,根据用户交流的内容判断是否需要调用Function
如果需要则返回Function名称、参数等信息
Java解析结果,判断要执行哪个函数,代码执行Function,把结果再次封装到Prompt中发送给AI
AI继续与用户交互,直到完成任务
听起来是不是挺复杂,还要解析响应结果,调用对应函数。
不过,有了SpringAI,中间这些复杂的步骤大家就都不用做了!
由于解析大模型响应,找到函数名称、参数,调用函数等这些动作都是固定的,所以SpringAI再次利用AOP的能力,帮我们把中间调用函数的部分自动完成了。
我们要做的事情就简化了:
编写基础提示词(不包括Tool的定义)
编写Tool(Function)
配置Advisor(SpringAI利用AOP帮我们拼接Tool定义到提示词,完成Tool调用动作)
是不是简单多了~
接下来,我们就一起来实现智能客服功能吧。
3.1.基础CRUD
下面,我们先实现课程、校区、预约单的CRUD功能
3.1.1.数据库表
首先,我们来准备几张数据库表:
3.1.2.引入依赖
接下来,我们在项目引入MybatisPlus的依赖:
3.1.3.配置数据库
然后,修改application.yaml,添加数据库配置:
3.1.4.基础代码
接下来就是CRUD的基础代码了。
3.1.4.1.实体类
在com.itheima.ai.entity包下添加一个po包,向其中添加三张表对应的实体类:
学科表:
校区表:
课程预约表:
3.1.4.2.Mapper接口
然后是Mapper接口,创建一个com.itheima.ai.mapper包,然后在其中写三个Mapper:
CourseMapper:
SchoolMapper:
CourseReservationMapper:
3.1.4.3.Service
创建com.itheima.ai.service包,添加3个接口:
学科Service接口:
校区Service接口:
课程预约Service接口:
然后创建com.itheima.ai.service.impl包,写3个实现类:
3.2.定义Function
接下来,我们来定义AI要用到的Function,在SpringAI中叫做Tool
我们需要定义三个Function:
根据条件筛选和查询课程
查询校区列表
新增试听预约单
3.2.1.查询条件分析
先来看下课程表的字段:
课程并不是适用于所有人,会有一些限制条件,比如:学历、课程类型、价格、学习时长等
学生在与智能客服对话时,会有一定的偏好,比如兴趣不同、对价格敏感、对学习时长敏感、学历等。如果把这些条件用SQL来表示,是这样的:
edu:例如学生学历是高中,则查询时要满足 edu <= 2
type:学生的学习兴趣,要跟类型精确匹配,type = '自媒体'
price:学生对价格敏感,则查询时需要按照价格升序排列:order by price asc
duration: 学生对学习时长敏感,则查询时要按照时长升序:order by duration asc
我们需要定义一个类,封装这些可能的查询条件。
在com.itheima.ai.entity下新建一个query包,其中新建一个类:
注意:
这里的@ToolParam注解是SpringAI提供的用来解释Function参数的注解。其中的信息都会通过提示词的方式发送给AI模型。
同样的道理,大家也可以给Function定义专门的VO,作为返回值给到大模型。这里我们就省略了。。
3.2.2.定义Function
所谓的Function,就是一个个的函数,SpringAI提供了一个@Tool注解来标记这些特殊的函数。我们可以任意定义一个Spring的Bean,然后将其中的方法用@Tool标记即可:
接下来,我们就来定义上一节说的三个Function:
根据条件筛选和查询课程
查询校区列表
新增试听预约单
定义一个com.itheima.ai.tools包,在其中新建一个类:
3.3.System提示词
同样,我们也需要给AI设定一个System背景,告诉它需要调用工具来实现复杂功能。
在之前的SystemConstants类中添加一个常量:
你会注意到,在提示词中虽然提到了要调用工具,但是工具是什么,有哪些参数,完全没有说明。
AI怎么知道要调用哪些工具呢?
别着急,下一节就会说明了。
3.4.配置ChatClient
接下来,我们需要为智能客服定制一个ChatClient,同样具备会话记忆、日志记录等功能。
不过这一次,要多一个工具调用的功能,修改CommonConfiguration,添加下面代码:
特别需要注意的是,我们配置了一个defaultTools(),将我们定义的工具配置到了ChatClient中。
SpringAI依然是基于AOP的能力,在请求大模型时会把我们定义的工具信息拼接到提示词中,所以就帮我们省去了大量工作。
3.5.编写Controller
接下来,就可以编写与前端对接的接口了。
我们在com.itheima.ai.controller包下新建一个CustomerServiceController类:
注意:
这里的请求路径必须是/ai/service,因为前端已经写死了请求的路径。
目前SpringAI的OpenAI客户端与阿里云百炼存在兼容性问题,所以FunctionCalling功能无法使用stream模式,这里使用call来调用。解决方案放到本章最后。
3.6.总结测试
最终,完整项目结构如图:
打开前端页面,访问智能客服卡片:
点击卡片,进入智能客服聊天页面,就可以咨询课程了:
AI客服可以智能的自己查询数据库、查询校区,给学生推荐课程、生成预约单:
看看后台调用数据库的记录:
数据库中确实有预约的数据了:
当然,这只是基础的示例,有了这样的FunctionCalling功能,我们就可以实现更多更复杂的业务了。
大家大胆尝试去吧!
4.RAG(知识库 ChatPDF)
由于训练大模型非常耗时,再加上训练语料本身比较滞后,所以大模型存在知识限制问题:
知识数据比较落后,往往是几个月之前的
不包含太过专业领域或者企业私有的数据
为了解决这些问题,我们就需要用到RAG了。下面我们简单回顾下RAG原理
4.1.RAG原理
要解决大模型的知识限制问题,其实并不复杂。
解决的思路就是给大模型外挂一个知识库,可以是专业领域知识,也可以是企业私有的数据。
不过,知识库不能简单的直接拼接在提示词中。
因为通常知识库数据量都是非常大的,而大模型的上下文是有大小限制的,早期的GPT上下文不能超过2000token,现在也不到200k token,因此知识库不能直接写在提示词中。
怎么办?
思路很简单,庞大的知识库中与用户问题相关的其实并不多。
所以,我们需要想办法从庞大的知识库中找到与用户问题相关的一小部分,组装成提示词,发送给大模型就可以了。
那么问题来了,我们该如何从知识库中找到与用户问题相关的内容呢?
可能有同学会相到全文检索,但是在这里是不合适的,因为全文检索是文字匹配,这里我们要求的是内容上的相似度。
而要从内容相似度来判断,这就不得不提到向量模型的知识了。
4.1.1.向量模型
先说说向量,向量是空间中有方向和长度的量,空间可以是二维,也可以是多维。
向量既然是在空间中,两个向量之间就一定能计算距离。
我们以二维向量为例,向量之间的距离有两种计算方法:
通常,两个向量之间欧式距离越近,我们认为两个向量的相似度越高。(余弦距离相反,越大相似度越高)
所以,如果我们能把文本转为向量,就可以通过向量距离来判断文本的相似度了。
现在,有不少的专门的向量模型,就可以实现将文本向量化。一个好的向量模型,就是要尽可能让文本含义相似的向量,在空间中距离更近:
接下来,我们就准备一个向量模型,用于将文本向量化。
阿里云百炼平台就提供了这样的模型:
这里我们选择通用文本向量-v3,这个模型兼容OpenAI,所以我们依然采用OpenAI的配置。
修改application.yaml,添加向量模型配置:
4.1.2.向量模型测试
前面说过,文本向量化以后,可以通过向量之间的距离来判断文本相似度。
接下来,我们就来测试下阿里百炼提供的向量大模型好不好用。
首先,我们在项目中写一个工具类,用以计算向量之间的欧氏距离和余弦距离。
新建一个com.itheima.ai.util包,在其中新建一个类:
由于SpringBoot的自动装配能力,刚才我们配置的向量模型可以直接使用。
接下来,我们写一个测试类:
注意: 运行单元测试通用需要配置OPENAI_API_KEY的环境变量
首先,点击单元测试左侧运行按钮:
然后配置环境变量:
运行结果:
可以看到,向量相似度确实符合我们的预期。
OK,有了比较文本相似度的办法,知识库的问题就可以解决了。
前面说了,知识库数据量很大,无法全部写入提示词。但是庞大的知识库中与用户问题相关的其实并不多。
所以,我们需要想办法从庞大的知识库中找到与用户问题相关的一小部分,组装成提示词,发送给大模型就可以了。
现在,利用向量大模型就可以帮助我们比较文本相似度。
但是新的问题来了:向量模型是帮我们生成向量的,如此庞大的知识库,谁来帮我们从中比较和检索数据呢?
这就需要用到向量数据库了。
4.1.3.向量数据库
向量数据库的主要作用有两个:
存储向量数据
基于相似度检索数据
刚好符合我们的需求。
SpringAI支持很多向量数据库,并且都进行了封装,可以用统一的API去访问:
Azure Vector Search - The Azure vector store.
Apache Cassandra - The Apache Cassandra vector store.
Chroma Vector Store - The Chroma vector store.
Elasticsearch Vector Store - The Elasticsearch vector store.
GemFire Vector Store - The GemFire vector store.
MariaDB Vector Store - The MariaDB vector store.
Milvus Vector Store - The Milvus vector store.
MongoDB Atlas Vector Store - The MongoDB Atlas vector store.
Neo4j Vector Store - The Neo4j vector store.
OpenSearch Vector Store - The OpenSearch vector store.
Oracle Vector Store - The Oracle Database vector store.
PgVector Store - The PostgreSQL/PGVector vector store.
Pinecone Vector Store - PineCone vector store.
Qdrant Vector Store - Qdrant vector store.
Redis Vector Store - The Redis vector store.
SAP Hana Vector Store - The SAP HANA vector store.
Typesense Vector Store - The Typesense vector store.
Weaviate Vector Store - The Weaviate vector store.
SimpleVectorStore - A simple implementation of persistent vector storage, good for educational purposes.
这些库都实现了统一的接口:VectorStore,因此操作方式一模一样,大家学会任意一个,其它就都不是问题。
不过,除了最后一个库以外,其它所有向量数据库都是需要安装部署的。每个企业用的向量库都不一样,这里我就不一一演示了。
4.1.3.1.SimpleVectorStore
最后一个SimpleVectorStore向量库是基于内存实现,是一个专门用来测试、教学用的库,非常适合我们。
我们直接修改CommonConfiguration,添加一个VectorStore的Bean:
4.1.3.2.VectorStore接口
接下来,你就可以使用VectorStore中的各种功能了,可以参考SpringAI官方文档:
https://docs.spring.io/spring-ai/reference/api/vectordbs.html
这是VectorStore中声明的方法:
注意,VectorStore操作向量化的基本单位是Document,我们在使用时需要将自己的知识库分割转换为一个个的Document,然后写入VectorStore.
那么问题来了,我们该如何把各种不同的知识库文件转为Document呢?
4.1.4.文件读取和转换
前面说过,知识库太大,是需要拆分成文档片段,然后再做向量化的。而且SpringAI中向量库接收的是Document类型的文档,也就是说,我们处理文档还要转成Document格式。
不过,文档读取、拆分、转换的动作并不需要我们亲自完成。在SpringAI中提供了各种文档读取的工具,可以参考官网:
https://docs.spring.io/spring-ai/reference/api/etl-pipeline.html#_pdf_paragraph
比如PDF文档读取和拆分,SpringAI提供了两种默认的拆分原则:
PagePdfDocumentReader :按页拆分,推荐使用
ParagraphPdfDocumentReader :按pdf的目录拆分,不推荐,因为很多PDF不规范,没有章节标签
当然,大家也可以自己实现PDF的读取和拆分功能。
这里我们选择使用PagePdfDocumentReader。
首先,我们需要在pom.xml中引入依赖:
然后就可以利用工具把PDF文件读取并处理成Document了。
我们写一个单元测试(别忘了配置API_KEY):
4.1.5.RAG原理总结
OK,现在我们有了这些工具:
PDFReader:读取文档并拆分为片段
向量大模型:将文本片段向量化
向量数据库:存储向量,检索向量
让我们梳理一下要解决的问题和解决思路:
要解决大模型的知识限制问题,需要外挂知识库
受到大模型上下文限制,知识库不能简单的直接拼接在提示词中
我们需要从庞大的知识库中找到与用户问题相关的一小部分,再组装成提示词
这些可以利用文档读取器、向量大模型、向量数据库来解决。
所以RAG要做的事情就是将知识库分割,然后利用向量模型做向量化,存入向量数据库,然后查询的时候去检索:
第一阶段(存储知识库):
将知识库内容切片,分为一个个片段
将每个片段利用向量模型向量化
将所有向量化后的片段写入向量数据库
第二阶段(检索知识库):
每当用户询问AI时,将用户问题向量化
拿着问题向量去向量数据库检索最相关的片段
第三阶段(对话大模型):
将检索到的片段、用户的问题一起拼接为提示词
发送提示词给大模型,得到响应
4.1.6.目标
好了,现在RAG所需要的基本工具都有了。
接下来,我们就来实现一个非常火爆的个人知识库AI应用,ChatPDF,原网站如下:
这个网站其实就是把你个人的PDF文件作为知识库,让AI基于PDF内容来回答你的问题,对于大学生、研究人员、专业人士来说,非常方便。
当你学会了这个功能,实现其它知识库也都是类似的流程了。
来吧,我们一起动起来!
4.2.PDF上传下载、向量化
既然是ChatPDF,也就是说所有知识库都是PDF形式的,由用户提交给我们。所以,我们需要先实现一个上传PDF的接口,在接口中实现下列功能:
校验文件格式是否为PDF
保存文件信息
保存文件(可以是oss或本地保存)
保存会话ID和文件路径的映射关系(方便查询会话历史的时候再次读取文件)
文档拆分和向量化(文档太大,需要拆分为一个个片段,分别向量化)
另外,将来用户查询会话历史,我们还需要返回pdf文件给前端用于预览,所以需要实现一个下载PDF接口,包含下面功能:
读取文件
返回文件给前端
4.2.1.PDF文件管理
由于将来要实现PDF下载功能,我们需要记住每一个chatId对应的PDF文件名称。
所以,我们定义一个类,记录chatId与pdf文件的映射关系,同时实现基本的文件保存功能。
先在com.itheima.ai.repository中定义接口:
再写一个实现类:
注意:
由于我们选择了基于内存的SimpleVectorStore,重启就会丢失向量数据。所以这里我依然是将pdf文件与chatId的对应关系、VectorStore都持久化到了磁盘。
实际开发中,如果你选择了RedisVectorStore,或者CassandraVectorStore,则无序自己持久化。但是chatId和PDF文件之间的对应关系,还是需要自己维护的。
4.2.2.上传文件响应结果
由于前端文件上传需要返回响应结果,我们先在com.itheima.ai.entity.vo中定义一个Result类:
4.2.3.文件上传、下载
接下来,我们实现上传和下载文件接口。
在com.itheima.ai.controller中创建一个PdfController:
4.2.4.上传大小限制
SpringMVC有默认的文件大小限制,只有10M,很多知识库文件都会超过这个值,所以我们需要修改配置,增加文件上传允许的上限。
修改application.yaml文件,添加配置:
4.2.5.暴露响应头
默认情况下跨域请求的响应头是不暴露的,这样前端就拿不到下载的文件名,我们需要修改CORS配置,暴露响应头:
4.3.配置ChatClient
接下来就是最后的环节了,实现RAG的对话流程。
理论上来说,我们每次与AI对话的完整流程是这样的:
将用户的问题利用向量大模型做向量化 OpenAiEmbeddingModel
去向量数据库检索相关的文档 VectorStore
拼接提示词,发送给大模型
解析响应结果
不过,SpringAI同样基于AOP技术帮我们完成了全部流程,用到的是一个名QuestionAnswerAdvisor的Advisor。我们只需要把VectorStore配置到Advisor即可。
我们在CommonConfiguration中给ChatPDF也单独定义一个ChatClient:
我们也可以自己自定义RAG查询的流程,不使用Advisor,具体可参考官网:
https://docs.spring.io/spring-ai/reference/api/retrieval-augmented-generation.html
4.4.对话接口
最后,就是对接前端,然后与大模型对话了。修改PdfController,添加一个接口:
4.5.总结测试
最终,项目结构如下:
打开浏览器,访问http://localhost:5173
点击ChatPDF卡片,进入对应页面:
上传一个PDF文件之后,就可以对PDF提问了,AI也会根据文档来回答问题
5.多模态
多模态是指不同类型的数据输入,如文本、图像、声音、视频等。目前为止,我们与大模型交互都是基于普通文本输入,这跟我们选择的大模型有关。
deepseek、qwen-max等模型都是纯文本模型,在ollama和百炼平台,我们也能找到很多多模态模型。
以ollama为例,在搜索时点击vison,就能找到支持图像识别的模型:
在阿里云百炼平台也一样:
阿里云的qwen-omni模型是支持文本、图像、音频、视频输入的全模态模型,还能支持语音合成功能,非常强大。
注意:
在SpringAI的当前版本(1.0.0-m6)中,qwen-omni与SpringAI中的OpenAI模块的兼容性有问题,目前仅支持文本和图片两种模态。音频会有数据格式错误问题,视频完全不支持。
目前的解决方案有两种:
一是使用spring-ai-alibaba来替代。
二是重写OpenAIModel的实现,参考第6节
接下来,我们拓展入门时写的对话机器人,让他支持多模态效果。
5.1.切换模型
首先,我们需要修改CommonConfiguration中用于AI对话的ChatClient,将模型修改为OpenAIChatModel,不仅如此,由于其它业务使用的是qwen-max模型,不能改变。所以这里我们还需添加自定义配置,将模型改为qwen-omni-turbo:
5.2.多模态对话
接下来,我们需要修改原来的/ai/chat接口,让它支持文件上传和多模态对话。
修改ChatController:
5.3.测试
访问页面中的AI聊天卡片:
点击卡片,进入聊天页面,可以上传图片让AI来识别了:
图片原图:
6.拓展(选学)
最好一章是拓展部分,大家可以选择性学习。
6.1.兼容百炼平台
截止SpringAI的1.0.0-M6版本为止,SpringAI的OpenAiModel和阿里云百炼的部分接口存在兼容性问题,包括但不限于以下两个问题:
FunctionCalling的stream模式,阿里云百炼返回的tool-arguments是不完整的,需要拼接,而OpenAI则是完整的,无需拼接。
音频识别中的数据格式,阿里云百炼的qwen-omni模型要求的参数格式为data:;base64,${media-data},而OpenAI是直接{media-data}
由于SpringAI的OpenAI模块是遵循OpenAI规范的,所以即便版本升级也不会去兼容阿里云,除非SpringAI单独为阿里云开发starter,所以目前解决方案有两个:
等待阿里云官方推出的spring-alibaba-ai升级到最新版本
自己重写OpenAiModel的实现逻辑。
接下来,我们就用重写OpenAiModel的方式,来解决上述两个问题。
6.1.1.AlibabaOpenAIChatModel
首先,我们自己写一个遵循阿里巴巴百炼平台接口规范的ChatModel,其中大部分代码来自SpringAI的OpenAiChatModel,只需要重写接口协议不匹配的地方即可,重写部分会以黄色高亮显示。
新建一个AlibabaOpenAiChatModel类:
6.1.2.配置ChatModel
接下来,我们要把AliababaOpenAiChatModel配置到Spring容器。
修改CommonConfiguration,添加配置:
6.1.3.修改ChatClient
最后,让之前的ChatClient都使用自定义的AlibabaOpenAiChatModel.
修改CommonConfiguration中的ChatClient配置:
6.1.4.重启测试
OK,现在我们的应用能支持stream版本的FunctionCalling和音频识别了。
6.2.完善会话记忆(选学)
目前,会话记忆是基于内存,重启就没了。
如果要持久化保存,就需要自己动手了。这里提供3种办法:
依然是基于InMemoryChatMemory,但是在项目停机时,或者定时人物自动持久化。
自定义基于Redis的ChatMemory
基于SpringAI官方提供的CassandraChatMemory,同时会自动启用CassandraVectorStore
6.2.1.定义可序列化的Message
前面的两种方案,都面临一个问题,SpringAI中的Message类未实现Serializable接口,也没提供public的构造方法,因此无法基于任何形式做序列化。
我们必须定义一个可序列化的Message类,方便后续持久化。
我们定义一个com.itheima.ai.entity.po包,新建一个Msg类:
这个类中有两个关键方法:
构造方法:实现将SpringAI的Message转为我们的Msg的功能
toMessage方法:实现将我们的Msg转为SpringAI的Message
OK,准备工作就绪,接下来我们就用两种方案分别实现持久化。
6.2.2.方案一(定期持久化)
接下来,我会将SpringAI提供的InMemoryChatMemory中的数据持久化到本地磁盘,并且在项目启动时加载。
不仅如此,我们将ChatHistoryRepository也持久化了。
注意:
本方案中,我们采用Spring的生命周期方法,在项目启动时加载持久化文件,在项目停机时持久化数据。
大家也可以考虑使用定时任务完成持久化,项目启动加载的方案。
修改com.itheima.ai.repository.InMemoryChatHistoryRepository类,添加持久化功能:
6.2.3.方案二(自定义ChatMemory)
接下来,我们基于Redis来实现自定义ChatMemory.
首先,在项目中引入spring-data-redis的starter依赖:
然后,在com.itheima.ai.repository包中新建一个RedisChatMemory类:
同时,为了保证会话历史持久化,我们再定义一个RedisChatHistory类,用于实现会话历史持久化:
注意:
使用Redis方案时,需要将之前内存方案定义的ChatMemory、ChatHistoryRepository从Spring容器中移除。
由于使用的是Redis的Set结构,无序的,因此要确保chatId是单调递增的
6.2.4.方案三(Cassandra)
SpringAI官方提供了CassandraChatMemory,但是是跟CassandraVectorStore绑定的,不太灵活。
首先,需要安装一个Cassandra访问。
我们使用Docker安装:
接下来,我们在项目中添加cassandra依赖:
配置Cassandra地址:
OK,基于Cassandra的ChatMemory已经实现了,其它不变。
注意:
多种ChatMemory实现方案不能共存,只能选择其一。
6.3.持久化VectorStore(选学)
SpringAI提供了很多持久化的VectorStore,我们以其中两个为例来介绍:
RedisVectorStore : 目前测试metafiled过滤有异常
CassandraVectorStore
6.3.1.RedisVectorStore
首先,你需要安装一个Redis Stack,这是Redis官方提供的拓展版本,其中有向量库的功能。
可以使用Docker安装:
安装完成后,你可以通过命令行访问:
也可以通过浏览器访问控制台:http://localhost:8001,注意,这里的IP要换成你自己的
然后,你可以在项目中引入RedisVectorStore的依赖:
在application.yml配置Redis:
YAML
复制代码
1
2
3
4
5
6
7
8
9
10
spring:
ai:
vectorstore:
redis:
index: spring_ai_index # 向量库索引名
initialize-schema: true # 是否初始化向量库索引结构
prefix: "doc:" # 向量库key前缀
data:
redis:
host: 192.168.150.101 # redis地址
接下来,无需声明bean,直接就可以直接使用VectorStore了。
6.3.2.CassandraVectorStore
首先,需要安装一个Cassandra访问。
我们使用Docker安装:
TypeScript
运行代码
复制代码
1
docker run -d --name cas -p 9042:9042 cassandra
接下来,我们在项目中添加cassandra依赖:
TypeScript
运行代码
复制代码
1
2
3
4


org.springframework.ai
spring-ai-cassandra-store-spring-boot-starter

配置Cassandra地址:
Plain Text
复制代码
1
2
3
4
spring:
cassandra:
contact-points: 192.168.150.101:9042
local-datacenter: datacenter1
配置VectorStore:
TypeScript
运行代码
复制代码
1
2
3
4
5
6
7
8
public CassandraVectorStore vectorStore(OpenAiEmbeddingModel embeddingModel, CqlSession cqlSession) {
return CassandraVectorStore.builder(embeddingModel)
.session(cqlSession)
.addMetadataColumn(
new CassandraVectorStore.SchemaColumn("file_name", DataTypes.TEXT, CassandraVectorStore.SchemaColumnTags.INDEXED)
)
.build();
}

相关文章
|
1天前
|
机器学习/深度学习 人工智能 自然语言处理
认识AI
本文介绍了AI核心概念与大模型开发原理,涵盖人工智能发展历程及Transformer神经网络的关键作用。通过注意力机制,Transformer实现对文本、图像、音频的高效处理,成为GPT等大模型的技术基础。文章解析了LLM如何利用Transformer进行持续推理生成,逐字输出连贯内容,揭示ChatGPT类模型的工作机制。
|
Java 容器
如何在SpringBoot项目中使用过滤器和拦截器
过滤器和拦截器是日常开发中常用技术,用于对特定请求进行增强处理,如插入自定义代码以实现特定功能。过滤器在请求到达 `servlet` 前执行,而拦截器在请求到达 `servlet` 后执行。`SpringBoot` 中的拦截器依赖于 `SpringBoot` 容器,过滤器则由 `servlet` 提供。通过实现 `Filter` 接口并重写 `doFilter()` 方法可实现过滤器;通过实现 `HandlerInterceptor` 接口并重写相应方法可实现拦截器。两者的主要区别在于执行时机的不同,需根据具体场景选择使用。
780 4
如何在SpringBoot项目中使用过滤器和拦截器
|
3天前
|
监控 数据可视化 数据挖掘
数据可视化软件推荐:10款释放数据价值的高效工具指南
在数字经济时代,数据可视化已成为企业决策的核心工具。本指南深度盘点10款主流软件,涵盖瓴羊Quick BI、Tableau、Power BI等,从智能分析、生态协同到成本适配,全面解析各工具优势与适用场景,助力企业按需选型,让数据真正赋能业务决策。其中,瓴羊 Quick BI(阿里云旗下BI产品) 凭借连续6年中国唯一入选Gartner魔力象限的成熟度、“智能小Q”AI模块的实战价值及阿里云生态协同优势,成为多场景分析的重要选择,同时其他工具也各有侧重,可满足不同生态背景与业务需求的企业。
|
NoSQL Redis
使用slowapi对FastApi的接口进行限速
使用slowapi对FastApi的接口进行限速
3186 0
|
18天前
|
人工智能 JSON 自然语言处理
2025年测试工程师的核心竞争力:会用Dify工作流编排AI测试智能体
测试工程师正从脚本执行迈向质量策略设计。借助Dify等AI工作流平台,可编排“AI测试智能体”,实现用例生成、语义校验、自动报告等全流程自动化,应对AI应用的动态与不确定性,构建智能化、可持续集成的测试新体系。
|
2月前
|
Web App开发 数据可视化 前端开发
当Dify遇见Selenium:可视化编排UI自动化测试,原来如此简单
Dify与Selenium融合,打造可视化UI自动化测试新范式。无需编码,通过拖拽构建复杂测试流程,降低技术门槛,提升协作效率。智能元素定位、自适应等待、视觉验证等特性显著增强测试稳定性与维护性,结合CI/CD实现高效回归,推动测试智能化演进。
|
1月前
|
人工智能 数据可视化 测试技术
提升测试效率5倍!Dify驱动的可视化工作流实现自动化测试“开箱即用”
本文介绍如何利用Dify可视化工作流快速构建自动化测试体系,涵盖用例生成、API测试和UI测试等核心场景。通过拖拽式设计降低技术门槛,显著提升测试效率与覆盖率,助力团队实现质量保障的智能化转型。
|
17天前
|
人工智能 数据可视化 测试技术
Coze, Dify, N8N:三款主流AI工作流平台在测试中的应用对比
在敏捷开发背景下,Coze、Dify和n8n三大AI工作流平台正革新测试自动化。Coze零代码易上手,适合AI密集型任务;Dify支持私有化部署,适配企业级复杂流程;n8n开源可控,擅长系统集成。三者各有优势,助力测试团队实现高效人机协同,提升测试效能。
|
8月前
|
人工智能 Java API
MCP协议重大升级,Spring AI Alibaba联合Higress发布业界首个Streamable HTTP实现方案
本文由Spring AI Alibaba Contributor刘军、张宇撰写,探讨MCP官方引入的全新Streamable HTTP传输层对原有HTTP+SSE机制的重大改进。文章解析Streamable HTTP的设计思想与技术细节,并介绍Spring AI Alibaba开源框架提供的Java实现,包含无状态服务器模式、流式进度反馈模式等多种场景的应用示例。同时,文章还展示了Spring AI Alibaba + Higress的完整可运行示例,分析当前实现限制及未来优化方向,为开发者提供参考。