Spring AI 从入门到实战示例
今天这篇文章带大家深入了解Spring AI,不仅会详细讲解它的核心概念,还会通过具体示例展示如何用简单文本提示与OpenAI的聊天和图像生成API进行交互。
一、获取API密钥
在开始使用Spring AI前,你得先拿到要接入的大语言模型(LLM)的API访问权限。本文以OpenAI的gpt-3.5-turbo
模型为例,你需要在OpenAI官网注册账号,支付一点费用(我们这次花了5美元)后,就能获取API密钥啦。
项目API密钥(以前叫用户API密钥)是一串独一无二的编码字符串,它的作用是识别和验证用户或应用程序的身份。你可以在platform.openai.com/api-keys这个页面找到自己账号的密钥。在“使用情况”页面,还能查看每个API密钥的使用记录。
名称 | 密钥 | 创建时间 | 最后使用时间 | 创建者 | 权限 |
---|---|---|---|---|---|
testkey | sk-… | 2024年5月8日 | 2024年5月9日 | Lokesh Gupta | 全部 |
点击“创建新密钥”就能生成新的密钥。Spring AI模块针对不同的大语言模型定义了不同的密钥名称,没有固定的标准。一般来说,密钥名称遵循SPRING_AI_<LLM>_API_KEY
这种格式。比如OpenAI的密钥名是spring.ai.openai.api-key
,Mistral的则是spring.ai.mistralai.api-key
。
为了安全起见,建议把API密钥添加到机器的环境变量里,然后在应用配置文件中引用它。当然,也可以直接把密钥写在application.properties
文件里。下面这个例子是把API密钥导出到OPENAI_API_KEY
环境变量中:
export OPENAI_API_KEY=[从OpenAI网站复制的密钥]
如果是在Windows系统上,可以在系统属性里添加环境变量:
- 找到“用户变量”区域。
- 新建一个变量,变量名为
OPENAI_API_KEY
,变量值填写从OpenAI获取的密钥。
之后,在项目的属性文件里就能这样引用这个API密钥:
spring.ai.openai.api-key=${OPENAI_API_KEY}
二、配置项目依赖
在Spring Boot应用里,想要支持Spring AI,得添加spring-ai-openai-spring-boot-starter
这个依赖。在pom.xml
文件里添加如下配置:
<properties> <java.version>21</java.version> <spring-ai.version>1.0.0-SNAPSHOT</spring-ai.version> </properties> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-openai-spring-boot-starter</artifactId> </dependency> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>${spring-ai.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
目前Spring AI还在开发阶段,所以要从里程碑仓库拉取依赖。最新版本信息可以去官方GitHub页面查看。在pom.xml
里添加仓库配置:
<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> </repositories>
此外,为了创建Web组件并处理与后端大语言模型的HTTP连接,还需要添加spring-web
、spring-webflux
以及可选的HTTP客户端依赖:
<dependencies> <!-- 其他依赖 --> <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> </dependencies>
三、Spring AI模块有哪些功能?
Spring AI是Spring框架生态系统的新成员,主要用于开发专注于生成式AI的应用程序,这类应用能根据输入的提示生成新内容。它在后端会调用OpenAI的API,然后把响应结果返回给用户。
简单来说,Spring AI提供了一个基于文本的生成式AI系统。用户输入文本,应用程序就会以不同格式(比如字符串、映射、列表、XML、JSON、图像、视频等)给出相关的输出。为了生成这些响应,Spring AI集成了许多主流的生成式AI模型,像OpenAI、Azure Open AI、Bedrock(亚马逊)、Ollama以及Vertex AI(谷歌)的模型都支持。下面详细看看它的一些关键功能。
(一)客户端API
与OpenAI大语言模型交互时,有几个重要的类和接口:
- ChatClient / ChatModel:这是基于文本交互的核心接口。不管是简单请求,还是基于提示的请求,又或是要求特定格式(如JSON、XML)响应的请求,都可以用它来处理。
- ImageModel:专门用来调用特定供应商的图像生成API。用这个客户端发送文本请求后,会在响应里得到生成图像的URL。
- SpeechModel / StreamingSpeechModel:用于调用供应商的文本转语音生成API(TTS-1)。发送文本请求后,能收到语音文件的路径。
- AudioTranscriptionModel:可以调用特定供应商的音频转录(语音转文本)模型,目前只支持OpenAI的
whisper-1
模型。 - EmbeddingModel:用于调用特定供应商的向量嵌入模型,在向量存储中进行相似性搜索时经常会用到它。
下面这段Java代码展示了如何配置这些客户端:
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; // 配置类,用于定义Spring容器中的Bean @Configuration public class AppConfiguration { // 配置SpeechClient Bean @Bean SpeechClient speechClient(@Value("${spring.ai.openai.api-key}") String apiKey) { // 创建OpenAiAudioSpeechClient实例,传入OpenAiAudioApi对象(包含API密钥) return new OpenAiAudioSpeechClient(new OpenAiAudioApi(apiKey)); } // 配置ImageClient Bean @Bean ImageClient imageClient(@Value("${spring.ai.openai.api-key}") String apiKey) { // 创建OpenAiImageClient实例,传入OpenAiImageApi对象(包含API密钥) return new OpenAiImageClient(new OpenAiImageApi(apiKey)); } // 配置ChatClient Bean @Bean ChatClient chatClient(@Value("${spring.ai.openai.api-key}") String apiKey) { // 创建OpenAiChatClient实例,传入OpenAiApi对象(包含API密钥) return new OpenAiChatClient(new OpenAiApi(apiKey)); } }
(二)输出转换器
和大语言模型交互时,并不总是用纯文本格式。有时候,应用程序需要特定结构(比如符合特定JSON模式)的输出。这种情况下,可以根据实际需求选择合适的转换器。
输出转换器主要干两件事:一是在提示中指定想要的响应格式和结构,这样能保证模型生成的响应格式规范;二是收到响应后,把它解析成所需的格式,比如Java Bean、列表或者映射。常见的输出转换器有:
- BeanOutputConverter:它会根据Java Bean的字段生成JSON格式,用这个格式向大语言模型请求输出,然后再把模型的输出通过JSON模式转换成特定的Java Bean实例。
- ListOutputConverter:它会让提示要求大语言模型以逗号分隔值的JSON格式输出,之后再把响应转换成
java.util.List
实例。 - MapOutputConverter:它要求提示大语言模型以JSON格式输出,并且结构是
java.util.HashMap
类型,最后把响应转换成java.util.Map
实例。
(三)检索增强生成(RAG)
如果希望大语言模型根据指定文档里的信息来回答问题,Spring AI提供的文档读取器就能派上用场啦,这在检索增强生成(RAG)场景里特别有用。
Spring AI的文档读取器API支持多种格式,像纯文本、JSON、Apache Tika能处理的格式,还有PDF都没问题。在Spring AI的文档处理功能里,主要涉及这些类和接口:
- Document:用文本形式表示数据和元数据。
- DocumentReader:负责从数据源(比如PDF、Markdown、JSON文件)加载数据,返回一个列表。
- DocumentWriter:把文档持久化存储到向量数据库里。
- DocumentTransformer:对数据进行各种处理,像拆分、拼接等操作。
- Embedding:把数据表示成列表形式,向量数据库会用它来计算用户查询和相关文档的“相似度”。
- ContentFormatter:把文档的文本和元数据转换成适合作为AI提示的文本形式。
- VectorStore:存储嵌入向量,用来进行相似性搜索。
(四)向量存储集成
向量存储(也叫向量数据库)能进行“相似性搜索”,有点像传统关系型数据库里的精确匹配,但更灵活。向量存储可以把文档数据拆分成小片段,在RAG相关的场景里,通常会用ETL管道把文档存储到向量数据库中。
当向量数据库收到一个向量作为查询条件时,它会返回和这个查询向量“相似”的向量。这些返回的向量对应的是文档的小片段,比起直接把整个文档发给大语言模型(这可是个开销很大的操作),发送这些小片段更高效。
Spring AI支持集成多种主流向量存储,像Chroma、Pinecone、Redis、Weavite、Milvus、Azure以及PostgreSQL(PG Vector)都可以。
四、Spring AI聊天完成示例
先来看个最简单的例子,用Spring AI让大语言模型(这里是OpenAI GPT)给我们讲个笑话。这个示例里,提示就是简单的一句话,也不用指定响应格式。
下面这个tellSimpleJoke()
方法用到了前面提到的ChatClient API,它会把“讲个笑话”这个简单提示发给大语言模型,然后把收到的响应放到一个映射里,再返回给API调用者。
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.ai.chat.ChatClient; import java.util.Map; // 定义一个RestController,用于处理HTTP请求 @RestController public class OpenAiChatController { // 注入ChatClient实例 private final ChatClient chatClient; // 通过构造函数注入ChatClient @Autowired public OpenAiChatController(ChatClient chatClient) { this.chatClient = chatClient; } // 处理GET请求,路径为/joke-service/simple @GetMapping("/joke-service/simple") public Map<String, String> tellSimpleJoke() { // 调用ChatClient的call方法发送提示,把响应结果放入Map中返回 return Map.of("generation", chatClient.call("Tell me a joke")); } }
接下来测试一下这个API,在浏览器地址栏输入http://localhost:8080/joke-service/simple
,或者用API测试工具发送请求,得到的响应可能是这样:
{ "generation": "why don't scientists trust atoms?nnBecause they make up everything!" }
如果想实现模型响应的流式输出,只要客户端实现类实现了StreamingChatClient
接口,就可以调用chatClient.stream()
方法。比如OpenAiChatClient
类就同时实现了ChatClient
和StreamingChatClient
接口。
五、Spring AI提示模板示例
提示模板(PromptTemplate)和Java 21里的模板字符串有点像。在提示模板的字符串里,有一些可以替换的占位符,在运行时会根据传入的参数进行替换。比如在application.properties
文件里定义了这样一个提示模板:
app.joke.simple.promptTemplate=Tell me a joke about {subject} in {language}.
这里的{subject}
和{language}
就是模板参数,会在运行时传入具体的值。Spring AI的PromptTemplate
类能解析这样的提示模板,并把它传给ChatClient.call()
方法。
下面这段代码展示了如何使用提示模板:
import org.springframework.ai.chat.ChatClient; import org.springframework.ai.prompt.Prompt; import org.springframework.ai.prompt.PromptTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; // 定义一个RestController,用于处理HTTP请求 @RestController public class OpenAiChatController { // 注入ChatClient实例 private final ChatClient chatClient; // 注入提示模板字符串 private String promptTemplate; // 通过构造函数注入ChatClient和提示模板字符串 @Autowired public OpenAiChatController(ChatClient chatClient, @Value("${app.joke.simple.promptTemplate}") String promptTemplate) { this.chatClient = chatClient; this.promptTemplate = promptTemplate; } // 处理GET请求,路径为/joke-service/simple-with-prompt @GetMapping("/joke-service/simple-with-prompt") public String tellSimpleJokeWithPrompt(@RequestParam("subject") String subject, @RequestParam("language") String language) { // 创建PromptTemplate实例 PromptTemplate pt = new PromptTemplate(promptTemplate); // 根据传入的参数替换模板占位符,生成最终的提示 Prompt renderedPrompt = pt.create(Map.of("subject", subject, "language", language)); // 调用ChatClient的call方法发送提示,获取响应 ChatResponse response = chatClient.call(renderedPrompt); // 返回响应结果中的内容 return response.getResult().getOutput().getContent(); } }
在这段代码里,从API用户那里获取笑话主题和语言作为请求参数。现在来测试一下:在浏览器地址栏输入http://localhost:8080/joke-service/simple-with-prompt?subject=Car&language=Hindi
,或者用API测试工具发送请求,就能得到根据指定主题和语言生成的笑话啦。
六、Spring AI结构化输出示例
对于聊天机器人这类应用,简单的文本输出可能就够用了。但在数据密集型的智能应用里,往往需要像在典型REST API里使用Java Bean那样,以复杂格式(如XML、JSON)进行请求和响应。
下面这个示例对前面的API进行了修改,让它以JSON格式请求和响应,并且定义了一个JokeResponse
记录类型来规范响应字段:
// 定义JokeResponse记录类型,包含笑话主题、语言和笑话内容三个字段 public record JokeResponse(String subject, String language, String joke) { }
这个API用到了BeanOutputConverter
类,它会根据指定的Java Bean导出JSON格式,然后用parse()
方法把JSON响应转换成JokeResponse
对象。
import org.springframework.ai.chat.ChatClient; import org.springframework.ai.prompt.Prompt; import org.springframework.ai.prompt.PromptTemplate; import org.springframework.ai.response.BeanOutputConverter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; // 定义一个RestController,用于处理HTTP请求 @RestController public class OpenAiChatController { // 注入ChatClient实例 private final ChatClient chatClient; // 注入JSON格式提示模板字符串 private String jsonPromptTemplate; // 通过构造函数注入ChatClient和JSON格式提示模板字符串 @Autowired public OpenAiChatController(ChatClient chatClient, @Value("${app.joke.formatted.promptTemplate}") String promptTemplate) { this.chatClient = chatClient; this.jsonPromptTemplate = promptTemplate; } // 处理GET请求,路径为/joke-service/json-with-prompt @GetMapping("/joke-service/json-with-prompt") public JokeResponse tellFormattedJokeWithPrompt( @RequestParam("subject") String subject, @RequestParam("language") String language) { // 创建提示模板对象 PromptTemplate pt = new PromptTemplate(jsonPromptTemplate); // 使用参数渲染提示 Prompt renderedPrompt = pt.create(Map.of("subject", subject, "language", language)); // 创建BeanOutputConverter对象,将响应转换为JokeResponse类型 BeanOutputConverter<JokeResponse> converter = new BeanOutputConverter<>(JokeResponse.class); // 调用ChatClient发送请求并获取响应,同时进行转换 JokeResponse response = chatClient.call(renderedPrompt, converter); return response; } }
在application.properties
文件中,定义了一个JSON格式的提示模板:
app.joke.formatted.promptTemplate=Tell me a joke about {subject} in {language} and return the response in JSON format with keys "subject", "language", "joke".
这个模板要求大语言模型以JSON格式返回笑话,并且指定了JSON对象的键。
现在来测试这个API,在浏览器地址栏输入http://localhost:8080/joke-service/json-with-prompt?subject=Dog&language=English
,或者使用API测试工具发送请求,得到的响应可能是这样:
{ "subject": "Dog", "language": "English", "joke": "Why don't dogs make good dancers? Because they have two left feet!" }
七、Spring AI图像生成示例
接下来看看如何使用Spring AI的ImageClient
接口,调用OpenAI的图像生成API。在这个示例中,我们会发送一个简单的文本提示,让模型生成图像。
首先,在配置类中定义ImageClient
的Bean:
import org.springframework.ai.image.ImageClient; import org.springframework.ai.openai.OpenAiImageClient; import org.springframework.ai.openai.api.OpenAiImageApi; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; // 配置类,用于定义Spring容器中的Bean @Configuration public class AppConfiguration { // 配置ImageClient Bean @Bean ImageClient imageClient(@Value("${spring.ai.openai.api-key}") String apiKey) { // 创建OpenAiImageClient实例,传入OpenAiImageApi对象(包含API密钥) return new OpenAiImageClient(new OpenAiImageApi(apiKey)); } }
然后,创建一个控制器来处理图像生成请求:
import org.springframework.ai.image.ImageClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; // 定义一个RestController,用于处理HTTP请求 @RestController public class OpenAiImageController { // 注入ImageClient实例 private final ImageClient imageClient; // 通过构造函数注入ImageClient @Autowired public OpenAiImageController(ImageClient imageClient) { this.imageClient = imageClient; } // 处理GET请求,路径为/image-service/generate @GetMapping("/image-service/generate") public String generateImage(@RequestParam("prompt") String prompt) { // 调用ImageClient的generate方法生成图像,返回图像URL return imageClient.generate(prompt); } }
在这个示例中,generateImage
方法接收一个文本提示作为参数,调用ImageClient
的generate
方法生成图像,并返回生成图像的URL。
现在来测试这个API,在浏览器地址栏输入http://localhost:8080/image-service/generate?prompt=A beautiful sunset over the ocean
,或者使用API测试工具发送请求,就能得到一个包含生成图像URL的响应。
八、总结
通过这篇文章,我们详细了解了Spring AI的基本概念和使用方法,包括获取API密钥、配置项目依赖、使用Spring AI的各种功能,以及通过多个示例展示了如何与OpenAI的大语言模型和图像生成API进行交互。希望这些内容能帮助大家,如果在实践过程中有任何问题,欢迎在评论区交流讨论!