Open AI和Spring AI简介
当OpenAI发布ChatGPT时,它引起了全球的关注。那是语言模型第一次能够生成类似人类的响应。自那时以来,OpenAI又发布了其他几款模型,包括可以根据文本提示生成图像的DALL-E。
Spring AI是一个Java库,提供了一个简单易用的接口,可以与LLM模型进行交互。Spring AI提供了更高级的抽象,可以与Open AI, Azure Open AI, Hugging Face, Google Vertex, Ollama, Amazon Bedrock等各种LLM进行交互。
在本文中,我们将探讨如何使用Spring AI与Open AI进行交互。
首先,我们需要在OpenAI中创建一个账户并获取API密钥。
前往OpenAI平台并创建一个账户。在仪表板中,点击左侧导航菜单中的API Keys,然后创建一个新的API密钥。如果您正在创建一个新账户,您将获得一些免费的额度来使用OpenAI的APIs。 否则,您需要购买额度才能使用OpenAI的APIs。
一旦您拥有API密钥,将环境变量OPENAI_API_KEY设置为API密钥。
export OPENAI_API_KEY=<your-api-key>
创建Spring AI项目让我们使用Spring Initializr创建一个新的Spring Boot项目。
前往Spring Initializr https://start.spring.io/选择Web,并且选择OpenAI starters使用ChatClient与Open AI进行交互Spring AI提供了ChatClient抽象,能够与不同类型的LLM进行交互,而无需与实际的LLM模型耦合。
例如,我们可以使用ChatClient与OpenAI进行如下交互:
@RestControllerclass ChatController { private final ChatClient chatClient; ChatController(ChatClient chatClient) { this.chatClient = chatClient; } @GetMapping("/ai/chat") Map<String, String> chat(@RequestParam String question) { String response = chatClient.call(question); return Map.of("question", question, "answer", response); }}
在上面的代码中,没有任何东西与OpenAI耦合。
我们可以通过在 application.properties 文件中提供 API 密钥和其他参数来配置 ChatClient 以使用OpenAI。
spring.ai.openai.api-key=${OPENAI_API_KEY}spring.ai.openai.chat.model=gpt-3.5-turbospring.ai.openai.chat.temperature=0.7
现在,我们可以运行应用并测试聊天API。首先,启动你的Spring Boot应用程序。然后,你可以使用 Postman 或者任何其他的 API 测试工具来发送 POST 请求到你的服务。记住,你应该在你的请求正文中包含一个消息体,这将使得 ChatClient 能够与 OpenAI 进行交互。你将在响应中看到自由形式的答复。此答复是 OpenAI 模型根据你的消息生成的。
curl --location 'http://localhost:8080/ai/chat?question=Tell%20me%20about%20SpringBoot'//OUTPUT:{ "question":"请介绍下SpringBoot框架", "answer":"Spring Boot是一个开源的基于Java的框架,用于构建和部署独立的、生产就绪的应用程序。它是更大的Spring生态系统的一部分,提供了更简单、更快捷的方式来设置和配置Spring应用程序。Spring Boot消除了手动配置的需要,通过为大多数Spring项目提供默认设置,让开发人员能够快速开始他们的应用程序开发。它还提供了一系列的特性,如内嵌服务器、度量、健康检查和安全性,这些都是预配置的,可以开箱即用。"}
使用提示词模板我们可以使用提示词模板为ChatClient提供一组预定义的提示词。
@RestControllerclass 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); }}@Serviceclass JokeService { private final ChatClient chatClient; JokeService(ChatClient chatClient) { this.chatClient = chatClient; } String getJoke(String subject) { PromptTemplate promptTemplate = new PromptTemplate("告诉我一个关于 {subject} 的笑话""); Prompt prompt = promptTemplate.create(Map.of("subject", subject)); ChatResponse response = chatClient.call(prompt); return response.getResult().getOutput().getContent(); }}
通过使用提示词模板,我们可以隐藏创建提示词的复杂性,并为用户提供一个简单的接口。
在上述示例中,我们创建了代表用户消息的提示词。我们可以使用 SystemMessage 来表示 LLM 在对话中的角色。
@Serviceclass JokeService { private final ChatClient chatClient; JokeService(ChatClient chatClient) { this.chatClient = chatClient; } String getJoke(String subject) { SystemMessage systemMessage = new SystemMessage("你是一个有用又风趣的聊天机器人"); UserMessage userMessage = new UserMessage("告诉我一个关于 " + subject +" 的笑话"); Prompt prompt = new Prompt(List.of(systemMessage, userMessage)); ChatResponse response = chatClient.call(prompt); return response.getResult().getOutput().getContent(); }}
在上述示例中,我们创建了一个系统消息和用户消息,以代表用户和 LLM 之间的对话。通过使用系统消息,我们可以定义角色并向 LLM 提供额外的上下文。
使用输出解析器
在前面的例子中,我们将 LLM 的回应作为字符串获取。我们可以使用输出解析器来解析回应并以所需格式提取所需信息。
目前,Spring AI 提供了以下类型的输出解析器:
BeanOutputParser - 用于解析回应并转换成Java Bean。MapOutputParser - 用于解析回应并转换成Map。ListOutputParser - 用于解析回应并转换成List。
我们创建了一个新的 MovieController 控制器,用来获取某位导演导演的电影列表。
@RestControllerclass MovieController { private final ChatClient chatClient; MovieController(ChatClient chatClient) { this.chatClient = chatClient; } private static final String PROMPT_TEMPLATE = """ What are the best movies directed by {director}? {format} """; //...}
现在,让我们来看一下如何使用 BeanOutputParser 来解析响应并将其转换为 Java Bean。
record DirectorResponse(String director, List<String> movies) {}@RestControllerclass MovieController { //... @GetMapping("/ai/chat/movies") DirectorResponse chat(@RequestParam String director) { var outputParser = new BeanOutputParser<>(DirectorResponse.class); var userPromptTemplate = new PromptTemplate(PROMPT_TEMPLATE); Map<String, Object> model = Map.of("director", director, "format", outputParser.getFormat()); var prompt = userPromptTemplate.create(model); var response = chatClient.call(prompt); return outputParser.parse(response.getResult().getOutput().getContent()); }}
在上述示例中,我们创建了一个名为 DirectorResponse 的 Java Bean,用于表示 LLM 的响应。BeanOutputParser 将解析响应并将其转为 DirectorResponse 对象。
同样,我们可以使用 MapOutputParser 和 ListOutputParser 来解析响应并分别将其转换为 Map 和 List。
@RestControllerclass MovieController { //... @GetMapping("/ai/chat/movies-as-map") Map<String, Object> chatWithMapOutput(@RequestParam String director) { var outputParser = new MapOutputParser(); var userPromptTemplate = new PromptTemplate(PROMPT_TEMPLATE); Map<String, Object> model = Map.of("director", director, "format", outputParser.getFormat()); var prompt = userPromptTemplate.create(model); var response = chatClient.call(prompt); return outputParser.parse(response.getResult().getOutput().getContent()); } @GetMapping("/ai/chat/movies-as-list") List<String> chatWithListOutput(@RequestParam String director) { var outputParser = new ListOutputParser(new DefaultConversionService()); var userPromptTemplate = new PromptTemplate(PROMPT_TEMPLATE); Map<String, Object> model = Map.of("director", director, "format", outputParser.getFormat()); var prompt = userPromptTemplate.create(model); var response = chatClient.call(prompt); return outputParser.parse(response.getResult().getOutput().getContent()); }}
我们可以按照以下方式测试API:
curl --location 'http://localhost:8080/ai/chat/movies?director=Quentin%20Tarantino'//OUTPUT:{"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'//OUTPUT:{"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'//OUTPUT:["Pulp Fiction","Kill Bill: Volume 1","Inglourious Basterds","Django Unchained","Once Upon a Time in Hollywood"]
你需要根据 LLM 的响应以及你希望转换的格式,使用相应的 OutputParser。
结论
在这篇文章中,我们了解了如何使用 Spring AI 与 OpenAI 进行交互。我们创建了Java Bean并使用了BeanOutputParser,MapOutputParser,和ListOutputParser来解析不同的响应类型。通过本文,我们可以了解到如何根据 LLM 的响应和预期的格式选择适合的 OutputParser 。