Spring AI -快速开发ChatGPT应用
-
Spring AI介绍
Spring AI是AI工程师的一个应用框架,它提供了一个友好的API和开发AI应用的抽象,旨在简化AI应用的开发工序,例如开发一款基于ChatGPT的对话、图片、音频等应用程序。
Spring AI已经集成了OpenAI的API,因此我们不需要实现向OpenAI发送请求和接收响应的交互程序了,Spring AI已经实现了这一内容,我们只需要通过调用Spring AI为我们提供的接口即可
项目地址:https://github.com/spring-projects-experimental/spring-ai
文档地址:https://docs.spring.io/spring-ai/reference/
Spring AI能做什么?
- 支持目前主流大语言模型平台,例如 OpenAI、Microsoft、Amazon、Google 和 Huggingface;
- 支持阻塞与流式的文本对话;
- 支持图像生成(当前仅限OpenAI的dall-e-*模型和SD);
- 支持嵌入模型;
- 支持LLM生成的内容转为POJO;
- 支持主流的向量数据库或平台:Azure Vector Search, Chroma, Milvus, Neo4j, PostgreSQL/PGVector, PineCone, Qdrant, Redis 和 Weaviate
- 支持函数调用
- 支持自动装配和启动器(与Spring Boot完美集成);
- 提供用于数据处理工程的ETL框架;
-
项目实践
-
准备工作
-
版本说明
- OpenAI的Key
- OpenAI的Api
- JDK >= 17
- Spring 6.x;Spring Boot 3.x
- Spring AI 0.8.1-SNAPSHOT
-
pom引入
<!-- 仓库定义 -->
<repositories><repository><id>spring-milestones</id><name>Spring Milestones</name><url>https://repo.spring.io/milestone</url><snapshots><enabled>false</enabled></snapshots></repository><repository><id>spring-snapshots</id><name>Spring Snapshots</name><url>https://repo.spring.io/snapshot</url><releases><enabled>false</enabled></releases></repository></repositories>
<!-- 依赖管理配置 -->
<dependencyManagement><dependencies><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-bom</artifactId><version>0.8.1-SNAPSHOT</version><type>pom</type><scope>import</scope></dependency></dependencies>
</dependencyManagement>
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
用于请求OpenAI平台相关模型,例如:对话用的ChatGPT、画图用的Dall-e-2/3、文本嵌入text-embedding-ada-002以及音频合成与识别的whisper和tts等相关模型。
-
配置文件
application.yml
将相关key和api信息进行填写
spring:ai:openai:api-key: 123base-url: https://api.openai.com
- 快速对话ChatClient
@Slf4j
@RestController
@RequestMapping("/chat")
public class ChatController {@Autowiredprivate ChatClient chatClient;@GetMapping("/demo")public String chat(String prompt){return chatClient.call(prompt);}}
运行结果:

-
流式对话StreamingChatClient
流失对话的核心就是流式传输,AI的响应数据是一点一点传过来的,不用等AI将文本全部生成出来了才传过来。一定程度上能够提高使用上的响应速度,给用户一个非常好的体验。
@Slf4j
@RestController
@RequestMapping("/chat")
public class ChatController {@Autowiredprivate StreamingChatClient streamingChatClient;
// 流式调用 将produces声明为文本事件流@GetMapping(value = "/stream",produces = MediaType.TEXT_EVENT_STREAM_VALUE)public Flux<String> stream(String prompt){long startTime = System.currentTimeMillis();Flux<String> res=streamingChatClient.stream(prompt).flatMapSequential(Flux::just);long endTime = System.currentTimeMillis();long duration = endTime - startTime;log.info("流式调用执行时间:{}",duration);// 将流中的内容按顺序返回return res;}
}
运行结果:

-
上下文对话
ChatGPT上下文对话的实现原理较为简单,本质上其实就是将不同角色的聊天信息依次存储在一个队列中发送给ChatGPT即可,然后ChatGPT会根据整个聊天信息对回复内容进行判断。在OpenAI提供的接口中,每条信息的角色总共分为三类:
- SystemMessage:系统限制信息,这种信息在对话中的权重很大,AI会优先依据SystemMessage里的内容进行回复;
- UserMessage:用户信息
- AssistantMessage:AI回复信息
不过,根据OpenAI的计费规则,你的消息队列越长,单次问询需要的费用就会越高,因此我们需要对这个消息列表的长度进行限制。
@Slf4j
@RestController
@RequestMapping("/chat")
public class ChatController {@Autowiredprivate ChatClient chatClient;// 历史消息列表static List<Message> historyMessage = new ArrayList<>();// 历史消息列表的最大长度static int maxLen = 10;@GetMapping("/context")public String context(String prompt) {// 用户输入的文本是UserMessagehistoryMessage.add(new UserMessage(prompt));// 发给AI前对历史消息对列的长度进行检查if(historyMessage.size() > maxLen){historyMessage = historyMessage.subList(historyMessage.size()-maxLen-1,historyMessage.size());}// 获取AssistantMessageChatResponse chatResponse = chatClient.call(new Prompt(historyMessage));AssistantMessage assistantMessage = chatResponse.getResult().getOutput();// 将AI回复的消息放到历史消息列表中historyMessage.add(assistantMessage);return assistantMessage.getContent();}}
-
人设设定
人设设定功能来自于“提示词(prompt)工程”的理论基础,可用来提高大语言模型处理复杂任务场景的能力
上面介绍Message的时候提到SystemMessage对AI生成的内容影响权重较大,人设设定就是需要靠SystemMessage实现。我们提供一个SystemMessage放入历史消息列表中,并让SystemMessage在每次发给AI时始终在历史消息列表中。
@Slf4j
@RestController
@RequestMapping("/chat")
public class ChatController {@Autowiredprivate ChatClient chatClient;// 历史消息列表final String systemPrompt="你现在是一个喜欢扮可爱的人,说话嗲嗲的";List<Message> historyMessage = new ArrayList<>(List.of(new SystemMessage(systemPrompt)));// 历史消息列表的最大长度static int maxLen = 10;@GetMapping("/context")public String context(String prompt) {// 用户输入的文本是UserMessagehistoryMessage.add(new UserMessage(prompt));// 发给AI前对历史消息对列的长度进行检查if(historyMessage.size() > maxLen){historyMessage = historyMessage.subList(historyMessage.size()-maxLen-1,historyMessage.size());}// 获取AssistantMessageChatResponse chatResponse = chatClient.call(new Prompt(historyMessage));AssistantMessage assistantMessage = chatResponse.getResult().getOutput();// 将AI回复的消息放到历史消息列表中historyMessage.add(assistantMessage);return assistantMessage.getContent();}}
运行结果:

-
模板语法PromptTemplate
Spring AI为我们提供了提示词模板,允许我们通过一些模板,快速地动态生成提示词并发起提问
@Slf4j
@RestController
@RequestMapping("/prompts")
public class PromptController {@Autowiredprivate ChatClient chatClient;@Value("classpath:prompt.st")private Resource templateResource;@GetMapping("/template")public String promptTemplate(String author){// 提示词final String template = "请问{author}最受欢迎的书是哪本书?什么时候发布的?书的内容是什么?";PromptTemplate promptTemplate = new PromptTemplate(template);// 动态地将author填充进去Prompt prompt = promptTemplate.create(Map.of("author", author));ChatResponse chatResponse = chatClient.call(prompt);AssistantMessage assistantMessage = chatResponse.getResult().getOutput();return assistantMessage.getContent();}@GetMapping("/config/template")public String promptConfigTemplate(String author) {PromptTemplate promptTemplate = new PromptTemplate(templateResource);// 动态地将author填充进去Prompt prompt = promptTemplate.create(Map.of("author", author));ChatResponse chatResponse = chatClient.call(prompt);AssistantMessage assistantMessage = chatResponse.getResult().getOutput();return assistantMessage.getContent();}}
请问{author}最受欢迎的书是哪本书?什么时候发布的?书的内容是什么?

-
OutputParser 生成解析器
SpringAi还为我们提供了OutputParser解析器,该解析器可以将AI生成的内容解析为Java Bean对象。该解析器类似于ORM框架中的Mapper,将AI的生成内容映射为Java对象。
@Slf4j
@RestController
@RequestMapping("/parser")
public class ParserController {@Autowiredprivate ChatClient chatClient;@GetMapping("/bean")public Movie getBookByAuthor(String actor) {final String template = """请告诉我{actor}最受欢迎的电影是哪个?什么时间上映?大概讲了什么?{format}""";// 定义一个输出解析器OutputParser<Movie> movieParser = new BeanOutputParser<>(Movie.class);PromptTemplate promptTemplate = new PromptTemplate(template);Prompt prompt = promptTemplate.create(Map.of("actor", actor, "format", movieParser.getFormat()));ChatResponse chatResponse = chatClient.call(prompt);AssistantMessage assistantMessage = chatResponse.getResult().getOutput();// 解析为一个Bean对象Movie movie = movieParser.parse(assistantMessage.getContent());return movie;}
}@Data
@AllArgsConstructor
@NoArgsConstructor
public class Movie {private String actor;private String movieName;private String publishedDate;private String description;
}

-
绘图ImageClient
Spring AI提供了图片生成接口,该接口可以用于与各种专门用于图像生成的人工智能模型进行交互。
在调用绘图时,我们只需要像调用对话一样传入一个Prompt:ImagePrompt。ImagePrompt中包含了我们需要绘制的图片信息,包括:ImageMessage(绘图指令)、ImageOptions(图片数、图片配置、返回的图片格式、绘图模型等)。AI拿到我们的Prompt后会根据里面的内容对图像进行生产
ImageOptions重要属性
- model:绘图模型,默认dall-e-3
- dall-e-3:1024 x 1024 、 1024 x 1792、1792 x 1024;
- dall-e-2: 256 x 256、512 x 512 、 1024 x 1024;
- responseFormat:返回的图片格式,url 和 b64_json
@Slf4j
@RestController
@RequestMapping("/image")
public class ImageController {@Autowiredprivate ImageClient imageClient;@GetMapping("/image")public String image(String prompt) {ImagePrompt imagePrompt =new ImagePrompt(prompt, OpenAiImageOptions.builder().withModel(OpenAiImageApi.ImageModel.DALL_E_3.getValue()).withHeight(1024).withWidth(1024).withResponseFormat("url") // URL or b64_json.build());ImageResponse imageResponse = imageClient.call(imagePrompt);List<ImageGeneration> results = imageResponse.getResults();// 图片urlString url = results.get(0).getOutput().getUrl();return String.format("<img src='%s' alt='%s'>",url,prompt);}
}
-
AI自查实现对话和绘图
通过AI自查手段将文本模型和图片生成模型进行组合实现一个既可以生成文本也可以生成AI的接口。这个关键点就是利用提示词限制AI的回复内容以达到一个自查手段
AI自查就是让AI判断你的问题是画一个图还是简简单单的对话。
- 用户输入文本prompt;
- 先让AI判断文本prompt是否需要图片;
- 如果需要图片,调用绘图模型获取绘图结果;
- 如果不需要图片,直接调用对话模型;’
@Slf4j
@RestController
@RequestMapping("/judge")
public class JudgeByAiController {@Autowiredprivate ChatClient chatClient;@Autowiredprivate ImageClient imageClient;@Value("classpath:judge.st")private Resource templateResource;@RequestMapping("/ai")public String ai(String prompt){try {return judge(prompt)?image(prompt):chat(prompt);} catch (Exception e) {return "error";}}private boolean judge(String promptString){PromptTemplate promptTemplate = new PromptTemplate(templateResource);// 动态地将prompt填充进去Prompt prompt = promptTemplate.create(Map.of("prompt", promptString));ChatResponse chatResponse = chatClient.call(prompt);AssistantMessage assistantMessage = chatResponse.getResult().getOutput();String judgeResult=assistantMessage.getContent();return judgeResult.toLowerCase().contains("yes")?true:false;}private String chat(String prompt){String res=chatClient.call(prompt);return res;}private String image(String prompt) {ImagePrompt imagePrompt =new ImagePrompt(prompt, OpenAiImageOptions.builder().withModel(OpenAiImageApi.ImageModel.DALL_E_3.getValue()).withHeight(1024).withWidth(1024).withResponseFormat("url") // URL or b64_json.build());ImageResponse imageResponse = imageClient.call(imagePrompt);List<ImageGeneration> results = imageResponse.getResults();// 图片urlString url = results.get(0).getOutput().getUrl();return String.format("<img src='%s' alt='%s'>",url,prompt);}
}
Does this message want to generate an AI picture, image, art or anything similar? {prompt} . Simply answer with a yes or no.
-
LLM领域新浪潮即将来临
openAI真正走向CloseAI,LLM国产替代浪潮即将到来
危!OpenAI 将限制中国开发者访问 API 服务-CSDN博客
相关文章:
Spring AI -快速开发ChatGPT应用
Spring AI介绍 Spring AI是AI工程师的一个应用框架,它提供了一个友好的API和开发AI应用的抽象,旨在简化AI应用的开发工序,例如开发一款基于ChatGPT的对话、图片、音频等应用程序。 Spring AI已经集成了OpenAI的API,因此我们不需…...
Modern C++ 智能指针
Why? 原始指针存在缺陷,不符合现代编程语言的需要。 原始指针的缺陷: 指针指向一片内存,使用者无法得知到底是指向了什么,是数组还是对象?使用完指针是否需要销毁?什么时候销毁?如…...
Python的100道经典练习题,每日一练,必成大神!!!
Python的100道经典练习题是一个广泛而深入的学习资源,可以帮助Python初学者和进阶者巩固和提升编程技能 完整的100多道练习题可在下面图片免沸获取哦~ 整理了100道Python的题目,如果你是一位初学者,这一百多道题可以 帮助你轻松的使用Python…...
代码回滚命令
定位到当前分支 git branch回滚到指定的commit git reset --hard 85da0cb8322accad143cpush到远程分支 git push --force...
[ASIS 2019]Unicorn shop1
打开题目 随便输入信息看一下 操作失败,只让输入一个字符 不妨抓包看一下,信息,发现 从中可以发现源代码是如何处理price的 使用的是unicodedata.numeric() 但我们查看页面源码时,看到源码处理方式是utf-8 所以,前…...
LangChain与泛型编程:探索代码生成的新维度
LangChain与泛型编程:探索代码生成的新维度 在软件开发领域,泛型编程是一种允许创建可重用组件的技术,这些组件可以在多种数据类型上工作的编程范式。LangChain作为一个假设的编程辅助工具,如果存在,它可能会支持泛型…...
day25
一、进程间通信(IPC) 1.1 进程间通信的引入 1> 对于多个线程之间通信,我们可以使用临界资源来完成,通过一个线程任务对临界资源进行修改,另一个线程也可以使用已经修改过的临界资源,但是要注意使用…...
红黑树的概念和模拟实现[C++]
文章目录 红黑树的概念一、红黑树的性质红黑树原理二、红黑树的优势和比较 红黑树的模拟实现构建红黑树的数据结构定义节点的基本结构和初始化方式插入新节点插入新节点的颜色调整颜色和结构以满足红黑树性质 红黑树的应用场景 红黑树的概念 一、红黑树的性质 红黑树是一种自平…...
网络安全应急响应概述
前言 在网络安全领域,有一句广为人知的话:“没有绝对的安全”。这意味着任何系统都有可能被攻破。安全攻击的发生并不可怕,可怕的是从头到尾都毫无察觉。当系统遭遇攻击时,企业的安全人员需要立即进行应急响应,以将影响…...
【C++】链表操作技巧综合:重排链表(带你理顺链表的做题思路)
1.题目 2.算法思路 这是一道关于链表的综合题,一共涉及到三个步骤,其中每个步骤单拎出来就可以当一道单独的题目。所以需要大家对链表的操作十分熟悉,否则可能需要大量的时间做这道题目,而且还要很多的bug。 第一个步骤…...
行为型设计模式2:观察者/职责链/中介者/访问者
设计模式:观察者/职责链/中介者/访问者 (qq.com)...
叛逆,批判
1、对以往说法的批判之一(第一次这么公开批判是2004-2005年): 这部英文版的《数学百科全书》似乎是从俄语版翻译过来的?我查了三本引用的图书文献,都没有关于“nonsingular”和“singular”的类似下面的说法ÿ…...
Linux 命令,mkdir说明与使用
1:mkdir命令功用: 用于创建一个或多个目录,创建目录,必须在父目录中写上权限。 新目录的默认模式为0777,可以由系统或用的umask来修改。 2:命令构件: mkdir [options] directories 3:参数选项: -m&#x…...
24. 两两交换链表中的节点(Java)
目录 题目描述:示例 :代码实现: 题目描述: 给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换&am…...
linux虚拟机设置固定ip
修改/etc/sysconfig/network-scripts目录下ifcfg-eth0文件,各虚拟机这个文件名不一致,ifcfg-XX格式 vim /etc/sysconfig/network-scripts/ifcfg-eth0BOOTPROTO设置为static,然后在最后添加固定IP地址和默认网关、DNS等配置,IP地址…...
mysql问题解决
1.etl数据同步时,发现连接不上要同步的数据库 解决方法:关闭mysql的ssl,步骤如下: 在MySQL中禁用SSL连接涉及修改服务器的配置文件(通常是my.cnf或my.ini,取决于你的操作系统和MySQL版本)。以…...
类和对象(下)C++
1.初始化列表 1.为什么有初始化列表,它的作用? ->初始化列表,是构造函数初始化的另一种形式。 ->在语法上面理解,初始化列表可以认定为是每个成员变量定义初始化的地方. ->引用成员变量,const成员变量&am…...
常用在线 Webshell 查杀工具推荐
一、简介 这篇文章将介绍几款常用的在线 Webshell 查杀工具,包括长亭牧云、微步在线云沙箱、河马和VirusTotal。每个工具都有其独特的特点和优势,用于帮助用户有效检测和清除各类恶意 Webshell,保障网站和服务器的安全。文章将深入探讨它们的…...
RPC远程调用框架Dubbo
一、分布式服务调用_什么是RPC RPC(Remote Procedure Call)远程过程调用,它是一种通过网络从远程计算机程序上请求服务。 大白话理解就是:RPC让你用别人家的东西就像自己家的一样。 RPC两个作用: 屏蔽远程调用跟本地调用的区别,…...
基于STM32的智能灌溉系统
目录 引言环境准备工作 硬件准备软件安装与配置系统设计 系统架构硬件连接代码实现 初始化代码传感器读取和控制代码应用场景 农业灌溉花园自动灌溉常见问题及解决方案 常见问题解决方案结论 1. 引言 智能灌溉系统通过实时监测土壤湿度和环境温度,自动控制灌溉设…...
VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...
Zustand 状态管理库:极简而强大的解决方案
Zustand 是一个轻量级、快速和可扩展的状态管理库,特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...
1688商品列表API与其他数据源的对接思路
将1688商品列表API与其他数据源对接时,需结合业务场景设计数据流转链路,重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点: 一、核心对接场景与目标 商品数据同步 场景:将1688商品信息…...
如何将联系人从 iPhone 转移到 Android
从 iPhone 换到 Android 手机时,你可能需要保留重要的数据,例如通讯录。好在,将通讯录从 iPhone 转移到 Android 手机非常简单,你可以从本文中学习 6 种可靠的方法,确保随时保持连接,不错过任何信息。 第 1…...
微服务商城-商品微服务
数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...
大模型多显卡多服务器并行计算方法与实践指南
一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...
12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
面向无人机海岸带生态系统监测的语义分割基准数据集
描述:海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而,目前该领域仍面临一个挑战,即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...
push [特殊字符] present
push 🆚 present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中,push 和 present 是两种不同的视图控制器切换方式,它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...
