Spring AI 和 Open AI 入门

Open AI 和 Spring AI 简介

当 OpenAI 发布 ChatGPT 时,它在全球掀起了一场风暴。这是第一次有语言模型能够根据提示生成类似人类的回答。此后,OpenAI 又发布了其他几个模型,包括可以根据文字提示生成图像的 DALL-E

Spring AI 是一个 Java 库,为与 LLM 模型交互提供了一个简单易用的接口。Spring AI 提供了与各种 LLM(如 Open AIAzure Open AIHugging FaceGoogle VertexOllamaAmazon Bedrock 等)交互的高级抽象。

本文将带你了解如何使用 Spring AI 与 Open AI 能进行交互。

首先,需要在 OpenAI 上创建账户并获取 API Key。

  • 访问 OpenAI Platform 并创建账户。
  • 在控制面板中,点击左侧导航菜单中的 API Keys,创建一个新的 API Key。

如果你是新用户,你可能会获得一些免费点数来使用 OpenAI API。否则,你需要购买点数才能使用 OpenAI API。

获得 API KEY 后,把它添加到环境变量 OPENAI_API_KEY 中。

export OPENAI_API_KEY=<your-api-key>

创建 Spring AI 项目

使用 Spring Initializr 创建一个新的 Spring Boot 项目。

使用 ChatClient 与 Open AI 互动

Spring AI 提供了 ChatClient 抽象,用于与不同类型的 LLM 交互,而无需与实际的 LLM 模型耦合。

例如,可以使用 ChatClient 与 OpenAI 进行如下交互:

@RestController
class ChatController {

    private final ChatClient chatClient;

    ChatController(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder.build();
    }

    @GetMapping("/ai/chat")
    Map<String, String> chat(@RequestParam String question) {
        String response = chatClient.prompt().user(question).call().content();
        return Map.of("question", question, "answer", response);
    }
}

在上述代码中,没有任何内容与 OpenAI 关联。

我们可以通过在 application.properties 文件中提供 API Key 和其他参数,将 ChatClient 配置为使用 OpenAI

# 从环境变量中读取 Open API 的 Api Key
spring.ai.openai.api-key=${OPENAI_API_KEY}
spring.ai.openai.chat.model=gpt-3.5-turbo
spring.ai.openai.chat.temperature=0.7

现在,运行应用并测试 chat API:

curl --location 'http://localhost:8080/ai/chat?question=Tell%20me%20about%20SpringBoot'

//输出:
{
  "question":"Tell me about SpringBoot",
  "answer":"Spring Boot is an open-source Java-based framework used for building and 
            deploying stand-alone, production-ready applications. It is a part of the 
            larger Spring ecosystem and provides a simpler and faster way to set up and 
            configure Spring applications.\n\nSpring Boot eliminates the need for 
            manual configuration by providing default settings for most Spring projects, 
            allowing developers to quickly get started with their application development. 
            It also offers a wide range of features, such as embedded servers, metrics, 
            health checks, and security, that are pre-configured and ready to use out of the box."
}

使用 PromptTemplate

我们可以使用 PromptTemplateChatClient 提供一组预定义的提示。

@RestController
class ChatController {

    private final JokeService jokeService;

    ChatController(JokeService jokeService) {
        this.jokeService = jokeService;
    }

    @GetMapping("/ai/chat-with-prompt")
    Map<String,String> chatWithPrompt(@RequestParam String subject) {
        String answer = jokeService.getJoke(subject);
        return Map.of("answer", answer);
    }
}

@Service
class JokeService {
    private final ChatClient chatClient;

    JokeService(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder.build();
    }

    String getJoke(String subject) {
        PromptTemplate promptTemplate = new PromptTemplate("Tell me a joke about {subject}");
        Prompt prompt = promptTemplate.create(Map.of("subject", subject));
        return chatClient.prompt(prompt).call().content();
    }
}

通过使用 PromptTemplate,我们可以隐藏创建提示的复杂性,为用户提供一个简单的接口。

在上例中,我们创建了一个表示用户信息的提示(Prompt)。可以使用 SystemMessage 来表示 LLM 在对话中的角色。

@Service
class JokeService {
    private final ChatClient chatClient;

    JokeService(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder.build();
    }

    String getJoke(String subject) {
        SystemMessage systemMessage = new SystemMessage("You are a helpful and funny chat bot");
        UserMessage userMessage = new UserMessage("Tell me a joke about " + subject);
        Prompt prompt = new Prompt(List.of(systemMessage, userMessage));
        return  chatClient.prompt(prompt).call().content();
    }
}

如上,我们创建了 SystemMessageUserMessage 来表示用户与 LLM 之间的对话。通过使用 SystemMessage,我们可以定义角色并为 LLM 提供额外的上下文。

使用 OutputConverter

在前面的示例中,从 LLM 得到的响应是字符串。我们可以使用 OutputConverter 来解析响应,并以所需格式提取所需信息。

目前,Spring AI 提供以下类型的 OutputConverter

  • BeanOutputConverter - 解析响应并转换为 Java Bean。
  • MapOutputConverter - 解析响应并转换为 Map。
  • ListOutputConverter - 解析响应并转换为 List。

创建一个名为 MovieController 的新 Controller,以获取导演执导的电影列表。

@RestController
class MovieController {
    private final ChatClient chatClient;

    MovieController(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder.build();
    }

    private static final String PROMPT_TEMPLATE = """
            What are the best movies directed by {director}?
                    
            {format}
            """;
    //...
}

现在,来看看如何使用 BeanOutputConverter 解析响应并将其转换为 Java Bean。

record DirectorResponse(String director, List<String> movies) {}

@RestController
class MovieController {
    //...

    @GetMapping("/ai/chat/movies")
    DirectorResponse chat(@RequestParam String director) {
        var outputConverter = new BeanOutputConverter<>(DirectorResponse.class);
        var userPromptTemplate = new PromptTemplate(PROMPT_TEMPLATE);
        Map<String, Object> model = Map.of("director", director, "format", outputConverter.getFormat());
        var prompt = userPromptTemplate.create(model);
        var response = chatClient.prompt(prompt).call().content();
        return outputConverter.convert(response);
    }
}

如上,创建了一个名为 DirectorResponse 的 Java Bean 来表示来自 LLM 的响应。BeanOutputConverter 将解析响应并将其转换为 DirectorResponse 对象。

同样,我们可以使用 MapOutputConverterListOutputConverter 来解析响应,并分别将其转换为 MapList

@RestController
class MovieController {
    //...

    @GetMapping("/ai/chat/movies-as-map")
    Map<String, Object> chatWithMapOutput(@RequestParam String director) {
        var outputConverter = new MapOutputConverter();
        var userPromptTemplate = new PromptTemplate(PROMPT_TEMPLATE);
        Map<String, Object> model = Map.of("director", director, "format", outputConverter.getFormat());
        var prompt = userPromptTemplate.create(model);
        var response = chatClient.prompt(prompt).call().content();
        return outputConverter.convert(response);
    }

    @GetMapping("/ai/chat/movies-as-list")
    List<String> chatWithListOutput(@RequestParam String director) {
        var outputConverter = new ListOutputConverter(new DefaultConversionService());
        var userPromptTemplate = new PromptTemplate(PROMPT_TEMPLATE);
        Map<String, Object> model = Map.of("director", director, "format", outputConverter.getFormat());
        var prompt = userPromptTemplate.create(model);
        var response = chatClient.prompt(prompt).call().content();
        return outputConverter.convert(response);
    }
}

对 API 进行测试,如下:

curl --location 'http://localhost:8080/ai/chat/movies?director=Quentin%20Tarantino'

//输出:
{"director":"Quentin Tarantino","movies":["Pulp Fiction","Inglourious Basterds","Django Unchained","Kill Bill: Volume 1","Kill Bill: Volume 2"]}

curl --location 'http://localhost:8080/ai/chat/movies-as-map?director=Quentin%20Tarantino'

//输出:
{"best_movies":[{"title":"Pulp Fiction","year":1994},{"title":"Inglourious Basterds","year":2009},{"title":"Kill Bill: Volume 1","year":2003},{"title":"Kill Bill: Volume 2","year":2004},{"title":"Django Unchained","year":2012}]}

curl --location 'http://localhost:8080/ai/chat/movies-as-list?director=Quentin%20Tarantino'

//输出:
["Pulp Fiction","Kill Bill: Volume 1","Inglourious Basterds","Django Unchained","Once Upon a Time in Hollywood"]

你可以根据 LLM 的响应以及你想要转换成的格式使用适当的 OutputConverte

总结

本文介绍了如何使用 Spring AI 与 OpenAI 交互,详细介绍了如何使用 ChatClient 与 Open AI 互动、如何使用 PromptTemplateChatClient 提供一组预定义的提示以及如何使用 OutputConverter 解析响应为所需要的格式。


Ref:https://www.sivalabs.in/getting-started-with-spring-ai-and-open-ai/#google_vignette