1、概览 {#1概览}
Spring 通过 Spring AI 项目正式启用了 AI(人工智能)生成提示功能。本文将带你了解如何在 Spring Boot 应用中集成生成式 AI,以及 Spring AI 如何与模型互动。
2、Spring AI 的主要概念 {#2spring-ai-的主要概念}
首先回顾一下一些关键的领域术语和概念。
Spring AI 最初专注于处理语言输入和生成语言输出的模型。该项目的理念是为开发人员提供一个抽象接口,为将生成式 AI 作为独立组件纳入应用奠定基础。
接口 AiClient
就是这样一个抽象,它有两个基本实现:OpenAI
和 Azure OpenAI
。
public interface AiClient {
default String generate(String message);
AiResponse generate(Prompt prompt);
}
AiClient
为生成函数提供了两个选项。简化版生成函数:generate(String message)
,使用 String
作为输入和输出,可以避免使用 Prompt
和 AiResponse
类的额外复杂性。
2.1、高级的 Prompt 和 AiResponse {#21高级的-prompt-和-airesponse}
在 AI 领域,Prompt(提示)是指提供给 AI 的文本信息。它由上下文和问题组成,该模型用于生成答案。 从 Spring AI 项目的角度来看,Prompt
是一个参数化 Message
列表。
public class Prompt {
private final List<Message> messages;
// 构造函数和其他方法
}
public interface Message {
String getContent();
Map<String, Object> getProperties();
MessageType getMessageType();
}
Prompt
使开发人员能够对文本输入进行更多控制。Prompt 模板就是一个很好的例子,它使用预定义文本和占位符集构建。然后,可以使用传递给 Message
构造函数的 Map<String, Object>
值填充它们。
Tell me a {adjective} joke about {content}.
Message
接口还包含有关 AI 模型可处理的消息(Message)类别的高级信息。例如,OpenAI 实现区分会话角色,通过 MessageType
进行映射。对于其他模型,它可以反映消息格式或其他一些自定义属性。更多详情,请参阅 官方文档。
public class AiResponse {
private final List<Generation> generations;
// Get 和 Set
}
public class Generation {
private final String text;
private Map<String, Object> info;
}
AiResponse
由 Generation
(生成)对象列表组成,每个对象都包含来自相应 Prompt
(提示)的输出。此外,Generation
对象还提供 AI 响应的元数据信息。
不过,由于 Spring AI 项目仍处于测试阶段,并非所有功能都已完成并文档化。请关注 GitHub repository 中的 issues 进展。
3、Spring AI 入门 {#3spring-ai-入门}
首先,AiClient
与 OpenAI 平台的所有通信都需要 API Key。为此,可以在 "API Keys" 页面上创建一个 Token。
Spring AI 项目定义了配置属性:spring.ai.openai.api-key
。
可以在 application.yml
文件中进行设置。
spring:
ai:
openai.api-key: ${OPEN_AI_KEY}
下一步是配置依赖。Spring AI 项目在 Spring 里程碑库(Spring Milestone Repository)中提供了组件。
因此,需要添加 repository
定义:
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
之后,就可以导入 open-ai-spring-boot-starter:
<dependency>
<groupId>org.springframework.experimental.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>0.7.1-SNAPSHOT</version>
</dependency>
Spring AI 项目正在积极发展,你可以查看 官方 GitHub 页面 了解最新版本。
4、Spring AI 实践 {#4spring-ai-实践}
现在,编写一个简单的 REST API 用于演示。它由两个端点组成,可以返回想要的任何主题和流派的诗歌:
/ai/cathaiku
:实现基本的generate()
方法,并返回一个关于猫的的俳句(简单的字符串)。/ai/poetry?theme={{theme}}&genre={{genre}}
:演示PromtTemplate
和AiResponse
类的功能。
4.1、在 Spring Boot 中注入 AiClient {#41在-spring-boot-中注入-aiclient}
为了简单起见,从 cathaiku 端点开始。通过 @RestController
注解,设置 PoetryController
并添加 GET
方法映射:
@RestController
@RequestMapping("ai")
public class PoetryController {
private final PoetryService poetryService;
// 构造函数
@GetMapping("/cathaiku")
public ResponseEntity<String> generateHaiku(){
return ResponseEntity.ok(poetryService.getCatHaiku());
}
}
接下来,按照 DDD 概念,Service 层将定义所有 domain 逻辑。要调用 generate()
方法,只需将 AiClient
注入到 PoetryService
中。现在,可以定义字符串 Prompt,并在其中指定生成俳句的请求。
@Service
public class PoetryServiceImpl implements PoetryService {
public static final String WRITE_ME_HAIKU_ABOUT_CAT = """
Write me Haiku about cat,
haiku should start with the word cat obligatory""";
private final AiClient aiClient;
// 构造函数
@Override
public String getCatHaiku() {
return aiClient.generate(WRITE_ME_HAIKU_ABOUT_CAT);
}
}
启动端点,处理请求。响应包含一个简单的字符串:
Cat prowls in the night,
Whiskers twitch with keen delight,
Silent hunter's might.
目前看来效果不错,但目前的解决方案存在一些缺陷。首先,纯字符串响应并不是 REST 标准的最佳解决方案。
用固定的 Prompt 来查询 ChatGPT 并没有太大的价值。因此,下一步是添加参数值:theme
和 genre
。这就是 PromptTemplate
的用武之地。
4.2、使用 PromptTemplate 配置查询 {#42使用-prompttemplate-配置查询}
就其本质而言,PromptTemplate
的工作方式与 StringBuilder
和 dictionary
的组合非常相似。
与 /cathaiku
端点类似,首先定义 Prompt 的基本字符串,然后再定义用实际值填充的占位符名称:
String promptString = """
Write me {genre} poetry about {theme}
""";
PromptTemplate promptTemplate = new PromptTemplate(promptString);
promptTemplate.add("genre", genre);
promptTemplate.add("theme", theme);
接下来,对端点的输出进行标准化。为此,引入简单的 Record 类 - PoetryDto
,其中将包含诗歌标题(title)、名称(poetry)和流派(theme):
public record PoetryDto (String title, String poetry, String genre, String theme){}
下一步是在 BeanOutputParser
类中注册 PoetryDto
;它提供了序列化和反序列化 OpenAI API 输出的功能。
然后,把该解析器(Parser)提供给 promtTemple
,现在,消息(Message)将被序列化为 DTO 对象。
最后,生成函数如下:
@Override
public PoetryDto getPoetryByGenreAndTheme(String genre, String theme) {
BeanOutputParser<PoetryDto> poetryDtoBeanOutputParser = new BeanOutputParser<>(PoetryDto.class);
String promptString = """
Write me {genre} poetry about {theme}
{format}
""";
PromptTemplate promptTemplate = new PromptTemplate(promptString);
promptTemplate.add("genre", genre);
promptTemplate.add("theme", theme);
promptTemplate.add("format", poetryDtoBeanOutputParser.getFormat());
promptTemplate.setOutputParser(poetryDtoBeanOutputParser);
AiResponse response = aiClient.generate(promptTemplate.create());
return poetryDtoBeanOutputParser.parse(response.getGeneration().getText());
}
现在,客户收到的响应看起来要好得多,更重要的是,它符合 REST API 标准和最佳实践:
{
"title": "Dancing Flames",
"poetry": "In the depths of night, flames dance with grace,
Their golden tongues lick the air with fiery embrace.
A symphony of warmth, a mesmerizing sight,
In their flickering glow, shadows take flight.
Oh, flames so vibrant, so full of life,
Burning with passion, banishing all strife.
They consume with ardor, yet do not destroy,
A paradox of power, a delicate ploy.
They whisper secrets, untold and untamed,
Their radiant hues, a kaleidoscope unnamed.
In their gentle crackling, stories unfold,
Of ancient tales and legends untold.
Flames ignite the heart, awakening desire,
They fuel the soul, setting it on fire.
With every flicker, they kindle a spark,
Guiding us through the darkness, lighting up the dark.
So let us gather 'round, bask in their warm embrace,
For in the realm of flames, magic finds its place.
In their ethereal dance, we find solace and release,
And in their eternal glow, our spirits find peace.",
"genre": "Liric",
"theme": "Flames"
}
5、Error 处理 {#5error-处理}
Spring AI 项目通过 OpenAiHttpException
类提供了 OpenAPI Error 的抽象。遗憾的是,它没有为每种 Error 类型提供单独的类映射。不过,得益于这种抽象,我们可以在一个 Handler 中使用 RestControllerAdvice
处理所有异常。
下面的代码使用了 Spring 6 的 ProblemDetail
标准。如果你还不熟悉该标准,请查阅 中文文档。
@RestControllerAdvice
public class ExceptionTranslator extends ResponseEntityExceptionHandler {
public static final String OPEN_AI_CLIENT_RAISED_EXCEPTION = "Open AI client raised exception";
@ExceptionHandler(OpenAiHttpException.class)
ProblemDetail handleOpenAiHttpException(OpenAiHttpException ex) {
HttpStatus status = Optional
.ofNullable(HttpStatus.resolve(ex.statusCode))
.orElse(HttpStatus.BAD_REQUEST);
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(status, ex.getMessage());
problemDetail.setTitle(OPEN_AI_CLIENT_RAISED_EXCEPTION);
return problemDetail;
}
}
现在,如果 OpenAPI 响应包含错误,该 Advice 就会进行处理。下面是一个响应示例:
{
"type": "about:blank",
"title": "Open AI client raised exception",
"status": 401,
"detail": "Incorrect API key provided: sk-XG6GW***************************************wlmi.
You can find your API key at https://platform.openai.com/account/api-keys.",
"instance": "/ai/cathaiku"
}
可能出现的异常状态的完整列表请参见 官方文档页面。
6、总结 {#6总结}
本文介绍了 Spring AI 项目及其在 REST API 方面的功能,它为生成式 AI 集成到 Spring Boot 应用中提供了一个可靠的接口。在撰写本文时,spring-ai-starter 仍在积极开发中(可以访问快照版本)。
本文介绍了与 Spring AI 的基本集成和高级集成,包括 AiClient
的底层原理,还通过实际案例介绍了 Spring AI 高级功能的示例:PromtTemplate
、AiResponse
和 BeanOutputParser
以及异常处理。
Ref:https://www.baeldung.com/spring-ai