大模型——Spring AI 简介
大模型——Spring AI 简介
1、概览
Spring 通过 Spring AI 项目全面支持了基于生成式人工智能(Generative AI)的提示功能。本文将带你深入探讨如何在 Spring Boot 应用中成功集成生成式 AI 模型,并展示 Spring AI 如何与外部模型实现无缝交互。
2、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)$ 的接口设计,在代码实现上更加简洁直观。该接口仅需将字符串类型的数据传递给方法即可完成操作,并省去了调用 Prompt 和 AiResponse 类时通常需要处理的一些复杂逻辑细节。
2.1、高级的 Prompt 和 AiResponse
在人工智能领域中,“提示”(Prompt)被定义为供人工智能处理的信息文本;它由上下文信息与具体问题构成,并被设计以生成响应;从Spring的人工智能解决方案角度来看,“Prompt”被视为一个可参数化的消息集合。
public class Prompt {
private final List<Message> messages;
// 构造函数和其他方法
}
public interface Message {
String getContent();
Map<String, Object> getProperties();
MessageType getMessageType();
}
提示 提供了开发人员对文本输入进行灵活操控的能力。
提示模板 是一个绝佳的例子;它基于预先设定好的文本片段和占位符位置构建。
接着,在传递给 Message 构造函数时,则会将 Map<String, Object> 的值填入这些位置。
Tell me a {adjective} joke about {content}.
Message 接口还包括AI模型支持的消息高级信息。例如,在_OpensAI_中是通过区分会话角色实现的,并使用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(提示)的输出信息。
目前来看, Spring AI 项目仍在测试阶段中, 并未完全实现所有功能以及进行详细文档化. 建议查看该项目的 GitHub仓库 的最新动态
3、Spring 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 项目通过提供丰富的核心组件库集成到项目的开发流程中。
因此,需要添加 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>
随后就可以直接引入该技术栈
<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 实践
目前我们正在开发一个简单的 REST API 作为演示工具。该服务主要包含两个核心组件,并能够根据需求提供各种主题与流派的诗歌内容。
/ai/cathaiku:开发一个基础的generate()方法以生成关于猫的俳句(简单的字符串)。/ai/poetry?theme={{theme}}&genre={{genre}}:展示PromtTemplate和AiResponse这两个功能模块的基本功能。
4.1、在 Spring Boot 中注入 AiClient
基于简化的考虑,在_cathaiku_端点处启动服务。借助@@RestController注解实现对该Controller的配置,并在其中注册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相关的逻辑。通过注入AiClient实例实现功能调用。现在需要先定义字符串Prompt,并将其注入到实例化后的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;
// 构造函数
@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 配置查询
从本质上讲,PromptTemplate的工作模式类似于StringBuilder和dictionary的结合体。
类似于 /cathau 端点类型,在于先声明基础字符串变量名的基础字符串值。
String promptString = """
Write me {genre} poetry about {theme}
""";
PromptTemplate promptTemplate = new PromptTemplate(promptString);
promptTemplate.add("genre", genre);
promptTemplate.add("theme", theme);
随后对输出数据实施标准化处理工作。为了实现这一目标,请引入一个简单的Record类—— PoetryDto类。该类将包含以下三个字段:诗歌标题、诗歌内容以及其所属的艺术流派
public record PoetryDto (String title, String poetry, String genre, String theme){}
下一步步骤是用于在 BeanOutputParser 类中注册 PoetryDto 类型;该DTO支持对 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 处理
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、总结
本文阐述了Spring AI项目的各个方面及其在REST API领域的功能实现,并提供了可靠的技术接口以支持生成式AI在Spring Boot应用中的集成。撰写本文时,_spring-ai-starter_仍在积极开发中(建议访问快照版本)。
本文阐述了与Spring AI的基本集成及高级集成方法,并结合具体案例分析了Spring AI的关键高级功能:PromtTemplate、AiResponse以及BeanOutputParser同时涵盖异常处理机制。
