赞
踩
目录
4.1、在 Spring Boot 中注入 AiClient
Spring 通过 Spring AI 项目正式启用了 AI(人工智能)生成提示功能。本文将带你了解如何在 Spring Boot 应用中集成生成式 AI,以及 Spring 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
类的额外复杂性。
在 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 进展。
github.com/spring-projects/spring-ai
首先,AiClient
与 OpenAI 平台的所有通信都需要 API Key。为此,可以在 Openai 的“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 页面 了解最新版本。
现在,编写一个简单的 REST API 用于演示。它由两个端点组成,可以返回想要的任何主题和流派的诗歌:
/ai/cathaiku
:实现基本的 generate()
方法,并返回一个关于猫的的俳句(简单的字符串)。
/ai/poetry?theme={{theme}}&genre={{genre}}
:演示 PromtTemplate
和 AiResponse
类的功能。
为了简单起见,从 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
的用武之地。
就其本质而言,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"
- }

Spring AI 项目通过 OpenAiHttpException
类提供了 OpenAPI Error 的抽象。遗憾的是,它没有为每种 Error 类型提供单独的类映射。不过,得益于这种抽象,我们可以在一个 Handler 中使用 RestControllerAdvice
处理所有异常。
下面的代码使用了 Spring 6 的 ProblemDetail
标准。如果你还不熟悉该标准,请查阅 中文文档(springdoc.cn)。
- @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"
- }
可能出现的异常状态的完整列表请参见 官方文档页面。
本文介绍了 Spring AI 项目及其在 REST API 方面的功能,它为生成式 AI 集成到 Spring Boot 应用中提供了一个可靠的接口。在撰写本文时,spring-ai-starter 仍在积极开发中(可以访问快照版本)。
本文介绍了与 Spring AI 的基本集成和高级集成,包括 AiClient
的底层原理,还通过实际案例介绍了 Spring AI 高级功能的示例:PromtTemplate
、AiResponse
和 BeanOutputParser
以及异常处理。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。