【LLM大模型技术专题】「入门到精通系列教程」基于ai-openai-spring-boot-starter集成开发实战指南
基于ai-openai-spring-boot-starter集成开发实战指南
- 前提介绍
- Spring AI
- 抽象接口实现
- Openai-starter启动器项目Maven依赖
- API密钥
- 配置SpringAI项目的配置
- SpringAI的API
- 多种类型返回结果
- 客户端API
- ChatModel
- ImageModel
- SpeechModel /StreamingSpeechModel
- AudioTranscriptionModel
- EmbeddingModel
- 通过JavaConfig进行注册和建立Bean
- 输出转换器
- 输出转换器类型
- Spring AI聊天补全示例
- Spring AI-PromptTemplate示例
- Spring-AI结构化输出示例
- Spring AI 图像生成示例
为企业做决策的人正在努力理解如何在他们的产品和服务中使用这些智能程序。Spring AI 通过提供众所周知的自动配置和基于客户端 API 的编程方法,帮助 Java 开发人员创建这些智能程序。
前提介绍
大型语言模型(Large Language Models,LLMs),如 GPT-4、Claude、Gemini 和 Mistral,为像 ChatGPT 这样的生成式人工智能应用提供动力。这些大型语言模型已经发展并且数量在增加,同时加速了一类新的智能应用的出现,并使现有的应用更加智能。
Spring AI
Spring AI 项目旨在简化将人工智能功能开发并集成到现有或新的 Spring 应用程序中的过程。通过利用其他模块中众所周知的编码模式,如 JdbcTemplate 或 RestClient,该集成隐藏了处理来自所有受支持供应商(如 OpenAI、微软、亚马逊、谷歌和 Hugging Face)的大语言模型的各种复杂性。
抽象接口实现
Spring AI 提供高级接口(如 ChatModel、ImageModel、VectorStore 等),并将这些接口实现到不同的模块中,而每个模块都为特定的语言模型提供集成点,我们可以根据需要导入这些模块。
Openai-starter启动器项目Maven依赖
实现为 Maven 依赖配置关联的仓库功能:
<repositories><repository><id>spring-milestones</id><name>Spring Milestones</name><url>https://repo.spring.io/milestone</url><snapshots><enabled>false</enabled></snapshots></repository><repository><id>spring-snapshots</id><name>Spring Snapshots</name><url>https://repo.spring.io/snapshot</url><releases><enabled>false</enabled></releases></repository><repository><id>spring-snapshots</id><name>Spring Snapshots</name><url>https://repo.spring.io/snapshot</url><releases><enabled>false</enabled></releases></repository><repository><name>Central Portal Snapshots</name><id>central-portal-snapshots</id><url>https://central.sonatype.com/repository/maven-snapshots/</url><releases><enabled>false</enabled></releases><snapshots><enabled>true</enabled></snapshots></repository>
</repositories>
具体而言,当我们准备运用OpenAI模型时,首先需要完成的关键步骤便是导入相应的依赖项。
Spring AI
支持几乎所有主要的模型提供商,如OpenAI
、微软
、亚马逊
、谷歌
和 Huggingface
。它支持纯聊天式交互以及从文本到图像的转换等,以同步和异步(流 API)方法进行。
<dependencyManagement><dependencies><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-bom</artifactId><version>1.0.0-SNAPSHOT</version><type>pom</type><scope>import</scope></dependency></dependencies>
</dependencyManagement>
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
此外,我们还需要包含 spring-web、spring-webflux 以及可选的 httpclient 依赖项,以创建 Web 组件并处理与后端大型语言模型的 HTTP 连接。
<!-- other dependnecies -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
通过Spring AI
访问的大语言模型的API访问权限。例如,我们正在访问OpenAI的gpt-3.5-turbo
模型,因此我们在OpenAI网站上创建了一个帐户,并在支付少量费用后获得了API密钥。
API密钥
API密钥是一个唯一的编码字符串,用于识别和验证用户或应用程序。可以在:https://platform.openai.com/api-keys,网站进行访问对应的内容。
配置SpringAI项目的配置
Spring AI模块为不同的语言模型定义了单独的键名,我们可以在项目的属性文件中引用这个 API 密钥。
spring.ai.openai.api-key=${OPENAI_API_KEY}
SpringAI的API
SpringAI
是Spring框架生态系统中的新成员,用于开发以生成式人工智能为重点的应用程序,这些应用程序专注于根据输入提示生成新内容,它会向OpenAI进行API调用并返回响应。
多种类型返回结果
Spring AI 提供了一个基于文本的生成式人工智能系统。
- 多返回类型:应用程序以不同格式(字符串、映射、列表、XML、JSON、图像、视频等)响应相关输出。
- 多厂商支持:与大多数著名的生成式人工智能模型集成,包括:OpenAI、Azure Open AI、Bedrock(亚马逊)、Ollama和Vertex AI(谷歌)。
客户端API
以下是在与OpenAI大语言模型交互时使用的重要类/接口列表:
ChatModel
ChatClient(ChatModel)文本交互的核心接口。用于简单请求、基于提示的请求以及需要特定格式(如 JSON、XML 等)响应的请求。
ImageModel
ImageModel它提供了一个用于调用特定供应商图像生成 API 的客户端。当我们使用这个客户端发送一些文本作为请求时,我们会在响应中得到生成图像的URL。
SpeechModel /StreamingSpeechModel
SpeechModel /StreamingSpeechModel它提供了一个客户端,用于调用特定于供应商的文本到语音生成 API(TTS)。当我们使用此客户端发送一些文本作为请求时,我们会在响应中获得语音文件路径。
AudioTranscriptionModel
AudioTranscriptionModel:它提供了一个客户端,用于调用特定供应商的音频转录(语音转文本)模型。目前,仅支持 OpenAI 的“whisper”。
EmbeddingModel
EmbeddingModel:它为调用特定于供应商的向量嵌入模型提供了一个客户端,通常用于在向量存储中进行相似性搜索。
通过JavaConfig进行注册和建立Bean
并使用自动配之类AppConfiguration的bean(ChatClient、ChatModel等)与大语言模型进行交互:
import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.image.ImageClient;
import org.springframework.ai.openai.OpenAiChatClient;
import org.springframework.ai.openai.OpenAiAudioSpeechClient;
import org.springframework.ai.openai.OpenAiImageClient;
import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.ai.openai.api.OpenAiAudioApi;
import org.springframework.ai.openai.api.OpenAiImageApi;
import org.springframework.ai.openai.audio.speech.SpeechClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class AppConfiguration {@BeanSpeechClient speechClient(@Value("${spring.ai.openai.api-key}") String apiKey) {return new OpenAiAudioSpeechClient(new OpenAiAudioApi(apiKey));}@BeanImageClient imageClient(@Value("${spring.ai.openai.api-key}") String apiKey) {return new OpenAiImageClient(new OpenAiImageApi(apiKey));}@BeanChatClient chatClient(@Value("${spring.ai.openai.api-key}") String apiKey) {return new OpenAiChatClient(new OpenAiApi(apiKey));}
}
输出转换器
以纯文本格式与大型语言模型进行交互。应用程序需要非常特定结构的输出(例如固定的 JSON 模式)。在这种情况下,我们可以根据用例使用特定的转换器。
转换器执行两项主要任务:
- 它会指明提示所要求的响应格式与结构,以此保证生成的响应严格遵循该格式。
- 接收到响应之后,它会按照所需格式(例如 Java 实体类、列表或者值的映射)对响应进行解析。
输出转换器类型
- BeanOutputConverter:请求LLM输出的JSON格式,并使用 JSON 将LLM输出转换为特定Java对象。
- ListOutputConverter:以逗号分隔的值列表中的JSON格式请求LLM输出,然后将响应转换为List对象。
- MapOutputConverter:同样也是以JSON格式请求LLM输出,结构应为HashMap类型,然后将响应转换为Map对象。
Spring AI聊天补全示例
让大型语言模型给我们讲一个笑话。提示是一个简单的字符串,并且我们没有为响应格式指定任何内容。
@RestController
public class OpenAiChatController {private final ChatClient chatClient;@Autowiredpublic OpenAiChatController(ChatClient chatClient) {this.chatClient = chatClient;}@GetMapping("/joke/chat")public Map<String, String> tellSimpleJoke() {return Map.of("响应结果", chatClient.call("给我讲个笑话!"));}
}
tellSimpleJoke()
方法借助 ChatClient API 向 LLM 发送一条简单提示 “给我讲个笑话!”。不论响应内容如何,我们都将其存入一个 Map 里,再把这个 Map 作为响应反馈给 API 的使用者。
注意,如果希望可以从模型中流式传输响应,可以调用
chatClient.stream()
方法,如果客户端实现类实现了StreamingChatClient
接口。OpenAiChatClient
类实现了ChatClient
和StreamingChatClient
这两个接口。
Spring AI-PromptTemplate示例
在PromptTemplate
里,字符串中设有可替换标记,这些标记会于运行阶段依据参数进行置换。以与application.properties
中定义相似的提示模板为例,其中的subject
和language
作为模板参数,将在运行时传入并生效。
使用配置相关的配置文件application.properties:
ai.promptTemplate=给我讲一个笑话: {subject} 使用语言: {language}.
PromptTemplate类有助于解析此类提示并将其传递给 ChatClient.call()方法。
以下是添加了详细注释后的代码:```java
// 该注解表明这是一个配置类,Spring会对其进行扫描并将其纳入到Spring的应用上下文管理中
@Configuration
public class AppConfiguration {/*** 创建一个SpeechClient的Bean实例。* SpeechClient用于处理语音相关的操作。* @param apiKey 从配置文件中读取的OpenAI API密钥,通过@Value注解注入* @return 返回一个OpenAiAudioSpeechClient实例,用于与OpenAI的语音API进行交互*/@BeanSpeechClient speechClient(@Value("${spring.ai.openai.api-key}") String apiKey) {// 使用传入的API密钥创建OpenAiAudioApi实例,再用该实例创建OpenAiAudioSpeechClient实例return new OpenAiAudioSpeechClient(new OpenAiAudioApi(apiKey));}/*** 创建一个ImageClient的Bean实例。* ImageClient用于处理图像相关的操作。* @param apiKey 从配置文件中读取的OpenAI API密钥,通过@Value注解注入* @return 返回一个OpenAiImageClient实例,用于与OpenAI的图像API进行交互*/@BeanImageClient imageClient(@Value("${spring.ai.openai.api-key}") String apiKey) {// 使用传入的API密钥创建OpenAiImageApi实例,再用该实例创建OpenAiImageClient实例return new OpenAiImageClient(new OpenAiImageApi(apiKey));}/*** 创建一个ChatClient的Bean实例。* ChatClient用于处理聊天相关的操作。* @param apiKey 从配置文件中读取的OpenAI API密钥,通过@Value注解注入* @return 返回一个OpenAiChatClient实例,用于与OpenAI的聊天API进行交互*/@BeanChatClient chatClient(@Value("${spring.ai.openai.api-key}") String apiKey) {// 使用传入的API密钥创建OpenAiApi实例,再用该实例创建OpenAiChatClient实例return new OpenAiChatClient(new OpenAiApi(apiKey));}
}
Spring-AI结构化输出示例
AI聊天机器人类型的应用程序,简单的基于文本的输出就足够了
在数据密集型智能应用程序中,我们必须以复杂的格式(XML、JSON 等)进行请求和响应,就像我们在使用 Java Beans 的典型 REST API 中所做的那样。
在下面的示例中,我们修改了前面的 API,以 JSON 格式请求和响应,字段采用 JokeResponse 记录类型。
public record JokeResponse(String subject, String language, String joke) {}
使用BeanOutputConverter类,这个类从指定的Java bean中派生JSON格式。之后,它使用其parse()方法将JSON响应转换为 JokeResponse 对象。
```java
/*** 该方法用于以JSON格式获取特定主题和语言的笑话响应。* * @param subject 笑话的主题,通过请求参数传递。* @param language 笑话的语言,通过请求参数传递。* @return 返回一个包含笑话信息的JokeResponse对象。*/
public JokeResponse tellSpecificJokeInJsonFormat(// 从请求参数中获取笑话的主题@RequestParam("subject") String subject,// 从请求参数中获取笑话的语言@RequestParam("language") String language) {// 创建一个BeanOutputConverter实例,用于将响应内容解析为JokeResponse对象BeanOutputConverter<JokeResponse> parser = new BeanOutputConverter<>(JokeResponse.class);// 获取解析所需的格式字符串String format = parser.getFormat();// 使用预定义的JSON提示模板创建一个PromptTemplate实例PromptTemplate pt = new PromptTemplate(jsonPromptTemplate);// 根据传入的主题、语言和解析格式渲染提示信息Prompt renderedPrompt = pt.create(Map.of("subject", subject, "language", language, "format", format));// 调用聊天客户端,传入渲染后的提示信息以获取响应ChatResponse response = chatClient.call(renderedPrompt);// 解析响应结果的内容,将其转换为JokeResponse对象并返回return parser.parse(response.getResult().getOutput().getContent());
}
使用配置相关的配置文件application.properties:
ai.promptTemplate=给我讲一个笑话 {subject} 使用 {language}. {format}
Spring AI 图像生成示例
OpenAI 的图像生成使用 DALL-E 模型。我们向图像客户端提供消息,它会以生成图像的 URL 作为响应。
// 该注解表明这是一个RESTful风格的控制器,用于处理HTTP请求并返回JSON、XML等数据格式的响应
@RestController
public class OpenAiImageController {// 定义一个私有成员变量,用于存储ImageClient实例,该实例用于与OpenAI的图像生成API进行交互private final ImageClient imageClient;/*** 构造函数,用于初始化OpenAiImageController实例。** @param imageClient 用于与OpenAI图像API交互的客户端实例,通过依赖注入的方式传入*/public OpenAiImageController(ImageClient imageClient) {// 将传入的ImageClient实例赋值给类的成员变量this.imageClient = imageClient;}/*** 处理HTTP GET请求,请求路径为"/image-gen",用于生成图像并返回图像的URL。** @param message 用于描述要生成图像的文本信息,通过请求参数传递* @return 重定向到生成图像的URL*/@GetMapping("/image-gen")public String imageGen(@RequestParam String message) {// 构建图像生成的选项ImageOptions options = ImageOptionsBuilder.builder()// 指定使用的模型为dall - e - 3.withModel("dall-e-3")// 设置生成图像的高度为1024像素.withHeight(1024)// 设置生成图像的宽度为1024像素.withWidth(1024)// 完成选项的构建.build();// 创建一个ImagePrompt实例,包含图像生成的描述信息和选项ImagePrompt imagePrompt = new ImagePrompt(message, options);// 调用ImageClient的call方法,传入ImagePrompt实例,发起图像生成请求并获取响应ImageResponse response = imageClient.call(imagePrompt);// 从响应结果中提取生成图像的URLString imageUrl = response.getResult().getOutput().getUrl();// 返回重定向的URL,将用户重定向到生成的图像页面return "redirect:" + imageUrl;}
}
我们可以从我们的应用程序中的 URL 下载图像。对于测试,在浏览器中复制该 URL,它应该会显示生成的图像。
注意,在设置 ImageOptions 的参数时应小心,因为无效参数会引发运行时错误。例如,如果我们请求一个 900×600 大小的图像,那么它将抛出 NonTransientAiException。