今天这篇文章带大家深入了解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密钥的使用记录。

名称密钥创建时间最后使用时间创建者权限
testkeysk-…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系统上,可以在系统属性里添加环境变量:

  1. 找到“用户变量”区域。
  2. 新建一个变量,变量名为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-webspring-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类就同时实现了ChatClientStreamingChatClient接口。

五、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方法接收一个文本提示作为参数,调用ImageClientgenerate方法生成图像,并返回生成图像的URL。

现在来测试这个API,在浏览器地址栏输入http://localhost:8080/image-service/generate?prompt=A beautiful sunset over the ocean,或者使用API测试工具发送请求,就能得到一个包含生成图像URL的响应。

八、总结

通过这篇文章,我们详细了解了Spring AI的基本概念和使用方法,包括获取API密钥、配置项目依赖、使用Spring AI的各种功能,以及通过多个示例展示了如何与OpenAI的大语言模型和图像生成API进行交互。希望这些内容能帮助大家,如果在实践过程中有任何问题,欢迎在评论区交流讨论!