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引入
XML
<!-- 仓库定义 -->
<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>
XML
<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信息进行填写
XML
spring:
ai:
openai:
api-key: 123
base-url: https://api.openai.com
- 快速对话ChatClient
java
@Slf4j
@RestController
@RequestMapping("/chat")
public class ChatController {
@Autowired
private ChatClient chatClient;
@GetMapping("/demo")
public String chat(String prompt){
return chatClient.call(prompt);
}
}
运行结果:
流式对话StreamingChatClient
流失对话的核心就是流式传输,AI的响应数据是一点一点传过来的,不用等AI将文本全部生成出来了才传过来。一定程度上能够提高使用上的响应速度,给用户一个非常好的体验。
java
@Slf4j
@RestController
@RequestMapping("/chat")
public class ChatController {
@Autowired
private 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的计费规则,你的消息队列越长,单次问询需要的费用就会越高,因此我们需要对这个消息列表的长度进行限制。
java
@Slf4j
@RestController
@RequestMapping("/chat")
public class ChatController {
@Autowired
private ChatClient chatClient;
// 历史消息列表
static List<Message> historyMessage = new ArrayList<>();
// 历史消息列表的最大长度
static int maxLen = 10;
@GetMapping("/context")
public String context(String prompt) {
// 用户输入的文本是UserMessage
historyMessage.add(new UserMessage(prompt));
// 发给AI前对历史消息对列的长度进行检查
if(historyMessage.size() > maxLen){
historyMessage = historyMessage.subList(historyMessage.size()-maxLen-1,historyMessage.size());
}
// 获取AssistantMessage
ChatResponse 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时始终在历史消息列表中。
java
@Slf4j
@RestController
@RequestMapping("/chat")
public class ChatController {
@Autowired
private 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) {
// 用户输入的文本是UserMessage
historyMessage.add(new UserMessage(prompt));
// 发给AI前对历史消息对列的长度进行检查
if(historyMessage.size() > maxLen){
historyMessage = historyMessage.subList(historyMessage.size()-maxLen-1,historyMessage.size());
}
// 获取AssistantMessage
ChatResponse chatResponse = chatClient.call(new Prompt(historyMessage));
AssistantMessage assistantMessage = chatResponse.getResult().getOutput();
// 将AI回复的消息放到历史消息列表中
historyMessage.add(assistantMessage);
return assistantMessage.getContent();
}
}
运行结果:
模板语法PromptTemplate
Spring AI为我们提供了提示词模板,允许我们通过一些模板,快速地动态生成提示词并发起提问
java
@Slf4j
@RestController
@RequestMapping("/prompts")
public class PromptController {
@Autowired
private 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对象。
java
@Slf4j
@RestController
@RequestMapping("/parser")
public class ParserController {
@Autowired
private 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
java
@Slf4j
@RestController
@RequestMapping("/image")
public class ImageController {
@Autowired
private 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();
// 图片url
String 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是否需要图片;
- 如果需要图片,调用绘图模型获取绘图结果;
- 如果不需要图片,直接调用对话模型;'
java
@Slf4j
@RestController
@RequestMapping("/judge")
public class JudgeByAiController {
@Autowired
private ChatClient chatClient;
@Autowired
private 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();
// 图片url
String url = results.get(0).getOutput().getUrl();
return String.format("<img src='%s' alt='%s'>",url,prompt);
}
}
java
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国产替代浪潮即将到来