1. 概述
Spring Framework通过Spring AI项目正式启用了AI生成提示的强大功能。在本教程中,我们将详细介绍Spring Boot应用程序中的生成式AI集成,并熟悉基本的AI概念。
我们还将了解Spring AI如何与模型交互,并创建一个应用程序来展示其功能。
2. Spring AI主要概念
在开始之前,让我们先回顾一下一些关键领域的术语和概念。
Spring AI最初专注于处理语言输入和生成语言输出的模型,该项目背后的想法是为开发人员提供一个抽象接口,这是将生成式AI API作为独立组件纳入应用程序的基础。
其中一个抽象是接口AiClient,它有两个基本实现,OpenAI和Azure OpenAI:
public interface AiClient {
default String generate(String message);
AiResponse generate(Prompt prompt);
}
AiClient提供了两种生成函数供选择,简化版的generate(String message)使用String作为输入和输出,可以避免Promt和AiResponse类带来的额外复杂性。
现在让我们仔细看看它们的区别。
2.1 高级Prompt和AiResponse
在AI领域,prompt是指提供给AI的文本信息,由上下文和问题组成,模型使用该信息生成答案。
从Spring AI项目角度来看,Prompt是参数化的Message列表:
public class Prompt {
private final List<Message> messages;
// constructors and utility methods
}
public interface Message {
String getContent();
Map<String, Object> getProperties();
MessageType getMessageType();
}
Prompt使开发人员能够更好地控制文本输入,一个很好的例子是提示模板,它由预定义的文本和一组占位符构成,然后我们可以使用传递给Message构造函数的Map<String, Object>值来填充它们:
Tell me a {adjective} joke about {content}.
Message接口还包含有关AI模型可以处理的消息类别的高级信息。例如,OpenAI实现区分对话角色,通过MessageType进行有效映射。在其他模型中,它可以反映消息格式或其他一些自定义属性。有关更多详细信息,请参阅官方文档:
public class AiResponse {
private final List<Generation> generations;
// getters and setters
}
public class Generation {
private final String text;
private Map<String, Object> info;
}
AiResponse由Generation对象列表组成,每个对象都包含相应提示的输出。此外,Generation对象还提供AI响应的元数据信息。
然而,由于Spring AI项目仍处于测试阶段,因此并非所有功能都已完成并记录在案。我们可以通过GitHub仓库上的问题来跟踪进展。
3. 开始使用Spring AI项目
首先,AiClient需要API密钥才能与OpenAI平台进行所有通信。为此,我们将在API Keys页面上创建一个令牌。
Spring AI项目定义了配置属性spring.ai.openai.api-key,我们可以在application.yml文件中进行设置:
spring:
ai:
openai.api-key: ${OPEN_AI_KEY}
下一步是配置依赖仓库。Spring AI项目在Spring Milestone 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>1.0.0-M1</version>
</dependency>
请记住,Spring AI项目正在积极发展,因此请查看官方GitHub页面以获取最新版本。
现在让我们将这个概念付诸实践。
4. Spring AI的实际应用
为了演示,我们来编写一个简单的REST API。它由两个端点组成,可以返回我们想要的任何主题和类型的诗歌:
- /ai/cathaiku:将实现基本的generate()方法并返回有关猫的Haiku纯字符串值
- /ai/poetry?theme=&genre=:将演示PromtTemplate和AiResponse类的功能
4.1 在Spring Boot应用中注入AiClient
为了简单起见,我们从cathaiku端点开始。使用@RestController注解,我们将设置PoetryController并添加GET方法映射:
@RestController
@RequestMapping("ai")
public class PoetryController {
private final PoetryService poetryService;
// constructor
@GetMapping("/cathaiku")
public ResponseEntity<String> generateHaiku(){
return ResponseEntity.ok(poetryService.getCatHaiku());
}
}
接下来,按照DDD的概念,服务层将定义所有域逻辑。要调用generate()方法,我们需要做的就是将AiClient注入PoetryService。现在我们可以定义字符串提示,我们将在其中指定生成俳句的请求:
@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;
// constructor
@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契约的最佳解决方案。
此外,一直用相同的旧提示查询ChatGPT也没有多大价值。所以我们的下一步是添加参数化值:主题和类型,这时PromtTemplate就能发挥最大作用了。
4.2 使用PromptTemplate进行可配置查询
从本质上讲,PromptTemplate的工作原理与StringBuilder和字典的组合非常相似。与/cathaiku端点类似,我们首先定义提示的基本字符串。相比之下,这次我们将定义使用实际值填充的占位符:
String promptString = """
Write me {genre} poetry about {theme}
""";
PromptTemplate promptTemplate = new PromptTemplate(promptString);
promptTemplate.add("genre", genre);
promptTemplate.add("theme", theme);
接下来,我们可能想要标准化端点输出。为此,我们将引入简单的记录类PoetryDto,它将包含诗歌标题、名称和流派:
public record PoetryDto (String title, String poetry, String genre, String theme){}
下一步是在BeanOutputParser类中注册PoetryDto;它提供序列化和反序列化OpenAI API输出的功能。
然后我们将这个解析器提供给PromtTemplate,从现在开始,我们的消息将被序列化到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. 错误处理
Spring AI项目使用OpenAiHttpException类对OpenAPI错误进行了抽象,遗憾的是,它没有为每种错误类型提供单独的类映射。不过,得益于这种抽象,我们可以在一个处理程序中使用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响应包含错误,我们将处理它:
{
"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. 总结
在本文中,我们熟悉了Spring AI项目及其在REST API环境中的功能。尽管在撰写本文时,Spring AI Starter仍处于积极开发中,并且可以通过快照版本访问,但它为将生成式AI集成到Spring Boot应用程序中提供了可靠的接口。
在本文中,我们介绍了与Spring AI的基本和高级集成,包括AiClient的底层工作原理。作为概念验证,我们实现了一个生成诗歌的基本REST应用程序。除了生成端点的基本示例外,我们还提供了一个使用高级Spring AI功能的示例:PromtTemplate、AiResponse和BeanOutputParser。此外,我们还实现了错误处理功能。
Post Directory
