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. 引言 智能灌溉系统通过实时监测土壤湿度和环境温度,自动控制灌溉设…...
从零配置ARM交叉编译环境:如何避免GLIBC版本陷阱(附工具链命名解析)
从零配置ARM交叉编译环境:如何避免GLIBC版本陷阱(附工具链命名解析) 刚接触嵌入式开发的工程师第一次尝试交叉编译时,往往会被各种工具链名称搞得晕头转向。更令人头疼的是,当你好不容易编译出可执行文件,却…...
第三节课总结
一、计算机中的单位1、比特位(bit):一个比特位只能放一个二进制数据,要么0要么12.字节(byte):一个字节 8个比特位1024byte 1KB1024KB 1MB1024MB 1GB1024GB 1T1024TB 1PB3.每一种数据类型都可…...
基本数据类型(小数/浮点数)
在Java中,表示小数的数据类型有两个:float和double。其中float占用4个字节,是单精度浮点数;double占用8个字节,是双精度浮点数。从字节数可以看出,double的存储范围更大。所谓精度,指的是小数点…...
告别一人一链!微信URL Scheme新规详解:如何让同一个链接被千万用户访问
微信URL Scheme新规实战指南:如何设计高并发跳转架构 去年底微信团队对URL Scheme规则的调整,像一场及时雨浇灭了营销活动中的技术焦虑。记得去年双十一前夜,我们团队为了给H5活动页配置小程序跳转链接,不得不连夜生成数百万条独立…...
源码级交付的低代码革命:基于 Spring Boot 的 AI 视频中台二次开发实战
引言:从“项目定制”到“产品化”的跨越之痛 作为一名在安防行业摸爬滚打多年的架构师,我深知行业内的一个悖论:客户想要的是“开箱即用”的成熟产品,而现实场景却要求“千企千面”的深度定制。传统的开发模式下,为了满…...
ARM 架构 JuiceFS 性能优化:基于 MLPerf 的实践与调优妒
Qt是一个跨平台C图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本笔记将重点介绍QSpinBox数值微调组件的常用方法及灵活应用。…...
Dial2硬件传感器适配库:嵌入式固件的契约实现层
1. 项目概述 Dial2HardwareSensors 是一个面向 AhmsVille Dial 2 硬件平台的专用传感器适配层实现库。该库不提供抽象接口定义,而是聚焦于在真实嵌入式硬件上完成传感器驱动的最终落地——即把 AhmsVille Dial2 sensor adapter interfaces (通常为纯虚…...
Windows/Mac双平台实测:Caption滚动字幕软件如何5分钟打造高逼格桌面特效
Windows/Mac双平台实测:Caption滚动字幕软件如何5分钟打造高逼格桌面特效 在数字内容创作领域,视觉冲击力往往决定着作品的传播效果。无论是自媒体博主的视频包装,还是创意工作者的项目展示,动态文字元素总能成为吸引眼球的利器。…...
Ubuntu 24.04 上Ollama的部署、模型管理与服务化实战
1. 为什么选择Ollama搭建本地LLM环境 最近两年,大型语言模型(LLM)的火爆程度有目共睹。但很多开发者遇到一个现实问题:云端API不仅费用高,还存在数据隐私和响应延迟的困扰。这时候Ollama就像及时雨一样出现了——这个不…...
linux驱动调试方法整理
一般我喜欢把linux驱动的调试按矛盾分为几大类:1.驱动工程师和内核/自己的 2.应用工程师和驱动工程师的。其中我们需要掌握linux众多的调试控件才能灵活运用。驱动工程师和内核/自己的1.驱动加载和驱动调试信息使用printk(per_log)打印需求使…...
