Introduction to Spring AI – Spring人工智能简介

最后修改: 2024年 1月 7日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Spring Framework officially enabled the power of AI generative prompts with the Spring AI project. This article aims to provide a robust introduction to the generative AI integration in the Spring Boot applications. Within the tutorial, we’ll familiarize ourselves with the essential AI concepts.

Spring Framework 通过 Spring AI 项目正式启用了人工智能生成提示的功能。本文旨在为 Spring Boot 应用程序中的人工智能生成集成提供一个强大的介绍。在本教程中,我们将熟悉基本的人工智能概念

Also, we will gain an understanding of how Spring AI interacts with the models and create an application to demonstrate its capabilities.

此外,我们还将了解 Spring AI 如何与模型互动,并创建一个应用程序来演示其功能。

2. Spring AI Main Concepts

2.Spring AI 的主要概念

Before we start, let’s review some key domain terms and concepts.

在开始之前,让我们回顾一下一些关键的领域术语和概念。

Spring AI initially focused on models designed to handle language input and generate language output. The idea behind the project was to provide developers with an abstract interface, the foundation for enabling generative AI APIs into the application as an isolated component.

Spring AI 最初侧重于旨在处理语言输入和生成语言输出的模型。该项目背后的理念是为开发人员提供一个抽象接口,为将生成式人工智能 API 作为独立组件纳入应用程序奠定基础。

One such abstraction is the interface AiClient, which has two basic implementations  OpenAI and Azure OpenAI.

其中一个抽象是接口 AiClient,它有两个基本实现 OpenAI 和 Azure OpenAI。

public interface AiClient {
    default String generate(String message);
    AiResponse generate(Prompt prompt);
}

AiClient provides two options for the generative function. The simplified one – generate(String message) – uses String as input and output, and it could be used to avoid the extra complexity of Promt and AiResponse classes.

AiClient为生成函数提供了两个选项。简化的选项 – generate(String message) – 使用 String 作为输入和输出,可用于避免 PromtAiResponse 类的额外复杂性。

Now, let’s take a closer look at their difference.

现在,让我们来仔细看看它们的区别。

2.1. Advanced Prompt and AiResponse

2.1.高级提示AiResponse</em

In the AI domain, prompt refers to a text message provided to AI. It consists of the context and question, and that model is used for the answer generation.

在人工智能领域,提示是指提供给人工智能的文本信息。它由上下文和问题组成,该模型用于生成答案。

From the Spring AI project perspective, the Prompt is a list of parametrized Messages. 

从 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 enables developers to have more control over the text input. A good example is the prompt templates, constructed with a predefined text and set of placeholders. Then, we may populate them with the Map<String, Object> values passed to the Message constructor.

Prompt 使开发人员能够对文本输入进行更多控制。提示模板就是一个很好的例子,它使用预定义文本和占位符集构建。然后,我们可以使用传递给 Message 构造函数的 Map<String, Object> 值填充它们。

Tell me a {adjective} joke about {content}.

The Message interface also holds advanced information about the categories of messages that an AI model can process. For example, OpenAI implementation distinguishes between conversational roles, effectively mapped by the MessageType. In the case of other models, it could reflect the message format or some other custom properties. For more details, please refer to the official documentation.

Message 接口还包含有关人工智能模型可处理的消息类别的高级信息。例如,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;
}

The AiResponse consists of the list of Generation objects, each holding output from the corresponding prompt. In addition, the Generation object provides metadata information of the AI response.

AiResponseGeneration 对象列表组成,每个对象都包含来自相应提示符的输出。此外,Generation对象还提供了人工智能响应的元数据信息。

However, while the Spring AI project is still in beta, not all features are finished and documented. Please follow the progress with the issues on the GitHub repository.

不过,由于 Spring AI 项目仍处于测试阶段,并非所有功能都已完成并记录在案。请关注GitHub 代码库中问题的进展情况。

3. Getting Started with the Spring AI Project

3.Spring人工智能项目入门

First of all, AiClient requires the API key for all communications with the OpenAI platform. For that, we will create a token on the API Keys page.

首先,AiClient 与 OpenAI 平台的所有通信都需要 API 密钥。为此,我们将在 API 密钥页面上创建一个令牌

Spring AI project defines configuration property: spring.ai.openai.api-key. We may set it up in the application.yml file.

Spring AI 项目定义了配置属性:spring.ai.openai.api-key。我们可以在 application.yml 文件中设置它。

spring:
  ai:
    openai.api-key: ${OPEN_AI_KEY}

The next step would be configuring a dependency repository. The Spring AI project provides artifacts in the Spring Milestone Repository.

下一步是配置依赖库。Spring AI 项目在 Spring 里程碑资源库中提供了工件。

Therefore, we need to add the repository definition:

因此,我们需要添加存储库定义:

<repositories>
    <repository>
        <id>spring-snapshots</id>
        <name>Spring Snapshots</name>
        <url>https://repo.spring.io/snapshot</url>
        <releases>
            <enabled>false</enabled>
        </releases>
    </repository>
</repositories>

After that, we are ready to import open-ai-spring-boot-starter:

之后,我们就可以导入 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>

Please keep in mind that the Spring AI project is actively evolving, so check the official GitHub page for the latest version

请注意,Spring AI 项目正在积极发展,因此请查看 官方 GitHub 页面,了解最新版本。

That’s all! Now, let’s put the concept into practice.

就是这样!现在,让我们将这一理念付诸实践。

4. Spring AI in Action

4.Spring人工智能行动

Now, we will write a simple REST API for demonstration purposes. It will consist of two endpoints that return poetry on whatever theme and genre we’d like:

现在,我们将编写一个简单的 REST API 用于演示。它将由两个端点组成,按照我们想要的主题和流派返回诗歌:

  • /ai/cathaiku — will implement the basic generate() method and return a plain string value with Haiku about cats;
  • /ai/poetry?theme={{theme}}&genre={{genre}} — will demonstrate capabilities of PromtTemplate and AiResponse classes;

4.1. Injecting AiClient in Spring Boot Application

4.1.在 Spring Boot 应用程序中注入 AiClient</em

To keep things simple, let’s start with the cat haiku endpoint. With @RestController annotation, we will set up PoetryController and add GET method mapping:

为了简单起见,让我们从猫俳句端点开始。通过 @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());
    }
}

Next, following the DDD concept, the service layer would define all domain logic. All we need to do to call the generate() method is inject the AiClient into the PoetryService. Now, we may define the String prompt, where we will specify our request to generate the Haiku.

接下来,按照 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);
    }
}

The endpoint is up and ready to receive the requests. The response will contain a plain string:

端点已启动并准备接收请求。响应将包含一个纯字符串:

Cat prowls in the night,
Whiskers twitch with keen delight,
Silent hunter's might.

It looks good so far; however, the current solution has a few pitfalls. The response of plain string isn’t the best solution for REST contracts in the first place.

到目前为止,它看起来还不错;但是,当前的解决方案存在一些缺陷。首先,纯字符串响应并不是 REST 合同的最佳解决方案。

Furthermore, there is not this much value in querying ChatGPT with the same old prompt all the time. So, our next step would be to add the parametrized values: theme and genre. That’s when PromtTemplate could serve us the best!

此外,一直使用相同的提示来查询 ChatGPT 并没有太大的价值。因此,我们的下一步将是添加参数值:主题和流派。这时,PromtTemplate 将为我们提供最好的服务!

4.2. Configurable Queries With PromptTemplate

4.2.使用 PromptTemplate 配置查询

In its nature, PromptTemplate works quite similarly to a combination of StringBuilder and dictionary. Similarly to /cathaiku endpoint, we will first define the base string for the prompt. Moreover, this time, we will define the placeholders populated with actual values by their names:

就其本质而言,PromptTemplate 的工作原理与 StringBuilder 和 dictionary 的组合非常相似。/cathaiku 端点类似,我们将首先定义提示的基本字符串。此外,这次我们将通过名称来定义填充实际值的占位符:

String promptString = """
    Write me {genre} poetry about {theme}
    """;
PromptTemplate promptTemplate = new PromptTemplate(promptString);
promptTemplate.add("genre", genre);
promptTemplate.add("theme", theme);

Next, what we may want to do is to standardize the endpoint output. For that, we will introduce the simple record classPoetryDto, which will contain poetry title, name, and genre:

接下来,我们可能要做的是使端点输出标准化。为此,我们将引入 简单记录类PoetryDto,其中将包含诗歌标题、名称和流派:

public record PoetryDto (String title, String poetry, String genre, String theme){}

A further step would be registering PoetryDto in the BeanOutputParser class; it provides functionality to serialize and deserialize OpenAI API output.

下一步是在 BeanOutputParser 类中注册 PoetryDto;它提供了序列化和反序列化 OpenAI API 输出的功能。

Then, we will provide this parser to the promtTemple, and from now on, our messages will be serialized into the DTO objects.

然后,我们将向 promtTemple 提供该解析器,从现在起,我们的消息将被序列化为 DTO 对象。

In the end, our generative function would look like this:

最后,我们的生成函数会是这样的:

@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());
}

The response our client would receive now looks much better, and more importantly, it fits into the REST API standards and best practices:

我们的客户现在收到的响应看起来要好得多,更重要的是,它符合 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 Handling

5. 错误处理

Spring AI project provides an abstraction over OpenAPI errors with the OpenAiHttpException class. Unfortunately, it does not provide individual mapping of classes per error type. However, thanks to such abstraction, we may handle all exceptions with RestControllerAdvice in one handler.

Spring AI 项目通过 OpenAiHttpException 类提供了 OpenAPI 错误的抽象。遗憾的是,它没有为每种错误类型提供单独的类映射。不过,由于有了这种抽象,我们可以在一个处理程序中使用 RestControllerAdvice 处理所有异常。

The code below uses the ProblemDetail standard of the Spring 6 Framework. If you are unfamiliar with it, please check the official documentation.

下面的代码使用了 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;
    }
}

Now, if the OpenAPI response contains errors, we will handle it. Here is an example of the response:

现在,如果 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"
}

The complete list of possible exception statuses is on the official documentation page.

官方文档页面上有可能出现的异常状态的完整列表。

6. Conclusion

6.结论

In this article, we familiarized ourselves with the Spring AI Project and its capabilities in the context of REST APIs. Despite the fact that at the time this article was written, spring-ai-starter remained in active development and was accessible in a snapshot version. It provided a reliable interface for generative AI integration into the Spring Boot application.

在本文中,我们熟悉了 Spring AI 项目及其在 REST API 方面的功能。尽管在撰写本文时,-ai-starter 仍在积极开发中,并且可以访问快照版本。它为生成式人工智能集成到 Spring Boot 应用程序中提供了一个可靠的接口。

In the context of this article, we covered both basic and advanced integrations with Spring AI, including how the AiClient works under the hood. As the proof of concept, we implemented a basic REST application that generates poetry. Along with a basic example of a generative endpoint, we provided a sample using advanced Spring AI features: PromtTemplate, AiResponse, and BeanOutputParser. In addition, we implemented the error handling functionality.

在本文中,我们介绍了与 Spring AI 的基本集成和高级集成,包括 AiClient 如何在引擎盖下工作。作为概念验证,我们实现了一个生成诗歌的基本 REST 应用程序。除了生成端点的基本示例外,我们还提供了一个使用高级 Spring AI 功能的示例:PromtTemplate、AiResponse 和BeanOutputParser此外,我们还实现了错误处理功能。

The complete examples are available over on GitHub.

在 GitHub 上提供了完整的示例。