ChatClient 是 Spring AI 框架中用于与大型语言模型(LLM)进行交互的核心接口,它提供了与聊天模型通信的标准化方式。通过 ChatClient,你可以向模型发送提示(Prompts)并接收生成的回复。
ChatClient 通过 Fluent API 与 AI 模型交互,同时支持同步和流式(Stream)编程模型。
Fluent API 提供了构建提示词(Prompt)组件的方法,这些组件将作为输入参数传递给 AI 模型。提示词包含指导 AI 模型输出内容和行为的指令文本。
从 API 角度来看,一个完整的提示词(Prompt)由一组消息集合构成,Prompt 部分源码:
public class Prompt implements ModelRequest<List<Message>> { private final List<Message> messages; @Nullable private ChatOptions chatOptions; //.... }
注意,AI 模型主要处理两类消息:
用户消息:也称“用户提示词”,直接来自用户的输入。
系统消息:也称“系统提示词”,由开发系统的人员提前设置,用于引导对话走向、角色定义、输出限制等等。例如,开发一个高考分数查询 AI 机器人,那么系统提示词就会给出限制,仅能查询分数,不能处理非高考相关的内容。
提示词中的这些消息中通常包含占位符(如 ${content}),会在运行时根据实际用户输入内容进行动态替换,从而定制 AI 模型对用户输入的响应内容。
此外,你还可以指定一些提示词选项,如要使用的 AI 模型名称,以及控制生成输出随机性 / 创造性的 temperature(温度)、top-k 参数。
ChatClient 需通过 ChatClient.Builder 对象来创建。你可以获取 Spring Boot 自动配置的 ChatModel 对应的 ChatClient.Builder 实例,也可以通过编程方式自行构建。
在最简单的使用场景中,Spring AI 通过 Spring Boot 自动配置生成 ChatClient.Builder Bean。注意,该 Bean 是 Prototype 类型的,每次请求时都会创建一个新的 ChatClient.Builder Bean 实例,可直接注入类中使用。
以下是获取用户简单请求 String 响应的基础示例:
package com.hxstrive.springai.springai_demo1.controller; import org.springframework.ai.chat.client.ChatClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController class MyController { private final ChatClient chatClient; // 自动注入,自动配置的 ChatClient.Builder public MyController(ChatClient.Builder chatClientBuilder) { System.out.println("chatClientBuilder = " + chatClientBuilder); // chatClientBuilder = org.springframework.ai.chat.client.DefaultChatClientBuilder@73113098 this.chatClient = chatClientBuilder.build(); } @GetMapping("/ai") String generation(String userInput) { return this.chatClient.prompt() .user(userInput) // 用户提示词 .call() .content(); } }
上述代码采用同步方式调用大模型,其中,userInput 为用户输入的消息内容。call() 方法向 AI 模型发送请求, content() 方法以 String 形式返回模型响应。运行效果:
什么是多聊天模型协作?可以理解为,我们在处理一个业务时,需要多种类型模型配合完成,而不是一种类型模型完成。例如,我们要对用户上传的资料进行数据提取,而资料分为很多种(有合同、发票等等),此时,我们可以选择擅长分类的模型进行分类(即使是同一种分类模型,也存在大小,如 8B、32B 等等),选择多模态模型进行数据提取。
什么是多模态模型?多模态模型指能够同时处理多种类型数据(如图像、文本、音频、视频等)并理解它们之间关联的人工智能模型。与传统单模态模型(如仅处理文本的 GPT)不同,多模态模型可以整合不同模态的信息,实现更复杂的任务。代表性模型有:
GPT-4V:OpenAI 的多模态版本,支持图像输入
Llama-3:Meta 的多模态大模型
Gemini Pro Vision:Google 的多模态模型
CLIP(OpenAI):连接图像和文本的基础模型
Stable Diffusion:文本到图像生成模型
在单一应用中需使用多个聊天模型的典型场景包括:
不同任务类型选用不同模型(如复杂推理用高性能模型,更准确;简单任务用快速经济型模型,节约成本)
主模型服务不可用时启用备用机制
不同模型或配置的 A/B 测试。什么是 A/B 测试?A/B 测试(A/B Testing)是一种通过对比不同版本方案的效果,来确定最优解的实验方法。在大模型中,通过对比不同模型配置、提示词设计或输出策略的效果,优化模型性能和业务目标的实验方法,最终选出最优模型。
根据用户偏好提供可选的模型,如下图:
上图是,FastGPT 中,AI 对话节点,用户就可以选择自己喜欢的模型。
组合专用模型(如代码生成与创意内容分别使用不同模型)
注意,Spring AI 默认自动配置单个 ChatClient.Builder Bean,但应用中可能需要使用多个聊天模型。处理方法如下:
spring.ai.chat.client.enabled=false
通过上述配置, 禁用 Spring Boot 自动配置 ChatClient.Builder。此时,允许用户手动创建多个 ChatClient 实例。
如果需创建多个使用相同底层模型类型,但配置不同的 ChatClient 实例。可以这样做:
// ChatModel 已由 Spring Boot 自动配置完成,注入即可使用 ChatModel myChatModel = ...; // 以编程式创建 ChatClient 实例 ChatClient chatClient = ChatClient.create(myChatModel); // 或使用 Builder 实现更精细控制,允许设置更多参数 ChatClient.Builder builder = ChatClient.builder(myChatModel); ChatClient customChatClient = builder .defaultSystemPrompt("You are a helpful assistant.") .build();
使用多个 AI 模型时,可为每个模型定义独立的 ChatClient Bean,如下:
(1)创建配置类,创建两个 ChatClient:
import org.springframework.ai.chat.ChatClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class ChatClientConfig { @Bean public ChatClient openAiChatClient(OpenAiChatModel chatModel) { return ChatClient.create(chatModel); } @Bean public ChatClient anthropicChatClient(AnthropicChatModel chatModel) { return ChatClient.create(chatModel); } }
(2)通过 @Qualifier 注解,将这些 Bean 注入应用组件:
@Configuration public class ChatClientExample { @Bean public CommandLineRunner cli( @Qualifier("openAiChatClient") ChatClient openAiChatClient, @Qualifier("anthropicChatClient") ChatClient anthropicChatClient) { return args -> { var scanner = new Scanner(System.in); ChatClient chat; // 模型选择 System.out.println("\nSelect your AI model:"); System.out.println("1. OpenAI"); System.out.println("2. Anthropic"); System.out.print("Enter your choice (1 or 2): "); String choice = scanner.nextLine().trim(); if (choice.equals("1")) { chat = openAiChatClient; System.out.println("Using OpenAI model"); } else { chat = anthropicChatClient; System.out.println("Using Anthropic model"); } // 使用选定的聊天客户端 System.out.print("\nEnter your question: "); String input = scanner.nextLine(); String response = chat.prompt(input).call().content(); System.out.println("ASSISTANT: " + response); scanner.close(); }; } }
OpenAiApi 与 OpenAiChatModel 类提供的 mutate() 方法,该方法返回一个预先使用当前配置填充的生成器,以便进行修改。支持基于现有实例创建不同属性的变体,特别适用于需对接多个 OpenAI 兼容 API 的场景。
mutate() 方法定义如下:
/** * Returns a builder pre-populated with the current configuration for mutation. */ public Builder mutate() { return new Builder(this); }
Builder 部分代码如下:
public static final class Builder { // Copy constructor for mutate() public Builder(OpenAiChatModel model) { this.openAiApi = model.openAiApi; this.defaultOptions = model.defaultOptions; this.toolCallingManager = model.toolCallingManager; this.toolExecutionEligibilityPredicate = model.toolExecutionEligibilityPredicate; this.retryTemplate = model.retryTemplate; this.observationRegistry = model.observationRegistry; } //... }
简单示例:
@Service public class MultiModelService { private static final Logger logger = LoggerFactory.getLogger(MultiModelService.class); // 基础 OpenAiChatModel @Autowired private OpenAiChatModel baseChatModel; // 基础 OpenAiApi @Autowired private OpenAiApi baseOpenAiApi; public void multiClientFlow() { try { // 为Groq(Llama3)派生一个新的 OpenAiApi OpenAiApi groqApi = baseOpenAiApi.mutate() .baseUrl("https://api.groq.com/openai") .apiKey(System.getenv("GROQ_API_KEY")) .build(); // 为OpenAI GPT-4 派生一个新的 OpenAiApi OpenAiApi gpt4Api = baseOpenAiApi.mutate() .baseUrl("https://api.openai.com") .apiKey(System.getenv("OPENAI_API_KEY")) .build(); // 为 Groq 派生一个新的 OpenAiChatModel OpenAiChatModel groqModel = baseChatModel.mutate() .openAiApi(groqApi) .defaultOptions(OpenAiChatOptions.builder().model("llama3-70b-8192").temperature(0.5).build()) .build(); // 为 GPT-4 派生一个新的 OpenAiChatModel OpenAiChatModel gpt4Model = baseChatModel.mutate() .openAiApi(gpt4Api) .defaultOptions(OpenAiChatOptions.builder().model("gpt-4").temperature(0.7).build()) .build(); // 适用于两种模型的简单提示词 String prompt = "What is the capital of France?"; // 分别调用不同的模型 String groqResponse = ChatClient.builder(groqModel).build().prompt(prompt).call().content(); String gpt4Response = ChatClient.builder(gpt4Model).build().prompt(prompt).call().content(); logger.info("Groq (Llama3) response: {}", groqResponse); logger.info("OpenAI GPT-4 response: {}", gpt4Response); } catch (Exception e) { logger.error("Error in multi-client flow", e); } } }
创建 MyController 控制器,提供 /ai 接口,接收 userInput 用户输入信息,如果用户要询问编程相关问题,则使用 gpt-3.5-turbo-16k 模型进行回答,否则使用默认的模型进行回答。如下:
package com.hxstrive.springai.chatclient2.controller; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController class MyController { @Autowired private ChatModel chatModel; @GetMapping(value = "/ai",produces = "text/html; charset=UTF-8") String generation(String userInput) { // 以编程式创建 ChatClient 实例 ChatClient chatClient = ChatClient.create(chatModel); String type = chatClient.prompt( "用户输入内容 `" + userInput + "` 是关于编程相关问题,返回 A001;否则,返回 A000。").call().content(); System.out.println("type = " + type); if("A001".equals(type)) { // 或使用 Builder 实现更精细控制 ChatClient.Builder builder = ChatClient.builder(chatModel); ChatClient customChatClient = builder .defaultSystem("你是一名软件工程师,根据用户提问回答问题,并且给出简单的代码示例。") .defaultOptions(ChatOptions.builder().model("gpt-3.5-turbo-16k").temperature(0.7D).build()) .build(); return type + "<br/>" + customChatClient.prompt(userInput).call().content(); } else { return type + "<br/>" + chatClient.prompt(userInput).call().content(); } } }
重启应用,访问 /ai 接口,效果如下图:
什么是 Fluent 风格?Fluent 风格(Fluent Interface)是一种面向对象的编程风格,它通过方法链(Method Chaining) 的方式让代码更加简洁易读。这种风格允许你在同一个对象上连续调用多个方法,而不需要每次都显式引用对象本身。例如:
String result = new StringBuilder() .append("Hello") .append(" ") .append("World") .toString();
Fluent 风格的每个方法返回当前对象实例,从而支持链式调用。且代码看起来像自然语言句子,逻辑更加清晰,无需创建中间对象来存储结果。
ChatClient Fluent 式 API 通过重载 prompt() 方法提供三种提示词创建方式:
prompt():无参方法启动 Fluent 式 API,支持逐步构建用户消息、系统消息等提示词组件。
prompt(Prompt prompt):接收 Prompt 参数,支持通过非 Fluent 式 API 构建的 Prompt 实例。
prompt(String content):便捷方法,接收用户文本内容,功能类似前项重载。
prompt() 方法源码如下:
// DefaultChatClientRequestSpec 用于构建和配置聊天客户端(ChatClient)的请求参数。 // 它属于 Spring AI 的流式 API(Fluent API)设计的一部分,主要作用是为开发者提供 // 一种链式调用的方式,以声明式语法定义 AI 模型的交互请求。 public ChatClient.ChatClientRequestSpec prompt() { return new DefaultChatClientRequestSpec(this.defaultChatClientRequest); } // 实际调用了 prompt(Prompt prompt),帮我们构建了一个 Prompt对象 public ChatClient.ChatClientRequestSpec prompt(String content) { Assert.hasText(content, "content cannot be null or empty"); return this.prompt(new Prompt(content)); } public ChatClient.ChatClientRequestSpec prompt(Prompt prompt) { Assert.notNull(prompt, "prompt cannot be null"); DefaultChatClientRequestSpec spec = new DefaultChatClientRequestSpec(this.defaultChatClientRequest); if (prompt.getOptions() != null) { spec.options(prompt.getOptions()); } if (prompt.getInstructions() != null) { spec.messages(prompt.getInstructions()); } return spec; }
简单示例,使用 prompt() 返回一个 ChatClient.ChatClientRequestSpec,通过链式调用构建请求参数,如系统提示词、用户提示词、温度、模型等等参数。如下:
@GetMapping(value = "/ai") public String generation(String userInput) { ChatClient chatClient = ChatClient.create(chatModel); return chatClient.prompt().system("你是编码助手,仅回答编码相关的内容。") .user(userInput) .options(ChatOptions.builder().temperature(0.6D).model("gpt-3.5-turbo-16k").build()) .call() .content(); }
ChatClient API 运用 Fluent 式接口,为 AI 模型的响应提供了多种格式化的途径。具体而言,它能够以不同的形式来呈现 AI 模型给出的响应内容,方便用户根据自身需求获取和处理这些响应。
例如,它可以返回 ChatResponse 元数据,让用户了解响应生成的详细情况;支持多响应,以 Generations 数组形式提供每个响应独立的元数据;还能进行 Token 统计,按照约 3/4 单词计为 1 个 Token 的规则,为计费等场景提供依据。又比如在实际代码中,通过在 call () 方法后调用 chatResponse (),可以返回包含元数据的 ChatResponse 对象,从而实现对 AI 模型响应的一种格式化输出。
AI 模型返回的 ChatResponse 是一个包含了丰富信息的结构化响应:
元数据:响应生成详情
多响应支持:Generation 数组,每个 Generation 包含独立元数据
Token 统计:1 个 Token 约等于 3/4 个单词 ,可用来计算费用,作为计费依据
ChatResponse 部分源码如下:
public class ChatResponse implements ModelResponse<Generation> { // 元数据 private final ChatResponseMetadata chatResponseMetadata; // 多响应支持 /** * List of generated messages returned by the AI provider. */ private final List<Generation> generations; //... }
下面我们通过 Debug 调试,查看 Generations 内部数据情况,如下图:
继续查看 ChatResponseMetadata 内部数据,如下图:
下面是在 call() 方法后调用 chatResponse() 方法,返回包含元数据的 ChatResponse 对象的示例:
ChatResponse chatResponse = chatClient.prompt() .user("Tell me a joke") .call() .chatResponse(); // 返回 ChatResponse
通常需要将返回的 String 映射为实体类,entity() 方法正提供此功能。
例如:给定一个 Java record:
record ActorFilms(String actor, List<String> movies) {}
什么是 Java record?
Java record 这是 Java 14(预览版)及后续版本引入的一种特殊类,其主要功能是作为不可变数据的透明载体。上面的代码定义了一个名为 ActorFilms 的记录,它包含以下内容:
一个名为 actor 的 String 类型字段。
一个名为 movies 的 List<String> 类型字段。
记录(record)的所有字段都是 final 的,一旦实例化,其状态就不能再改变。编译器会自动生成以下方法:
构造函数
equals()、hashCode()方法
toString()方法
访问器方法( 如 actor() 和 movies() )
和普通类相比,记录(record)的语法更加简洁,这样可以减少样板代码。
下面是 Record 的简单示例:
// 需要使用 jdk16+ public class RecordDemo1 { // 定义一个记录类型 record ActorFilms(String actor, List<String> movies) {} public static void main(String[] args) { ActorFilms actorFilms = new ActorFilms("张无忌", List.of("倚天屠龙记", "九阴真经")); System.out.println(actorFilms); //ActorFilms[actor=张无忌, movies=[倚天屠龙记, 九阴真经]] System.out.println(actorFilms.actor); //张无忌 System.out.println(actorFilms.movies); //[倚天屠龙记, 九阴真经] System.out.println(actorFilms.actor()); //张无忌 System.out.println(actorFilms.movies()); //[倚天屠龙记, 九阴真经] } }更多关于 Record 知识,请查阅 Java 文档。
如下所示,通过 entity() 方法可轻松将 AI 模型输出映射至该 record 类:
ActorFilms actorFilms = chatClient.prompt() .user("Generate the filmography for a random actor.") .call() .entity(ActorFilms.class);
另提供 entity 重载方法 entity(ParameterizedTypeReference<T> type),支持泛型集合等复杂类型指定:
List<ActorFilms> actorFilms = chatClient.prompt() .user("Generate the filmography of 5 movies for Tom Hanks and Bill Murray.") .call() .entity(new ParameterizedTypeReference<List<ActorFilms>>() {});
stream() 方法支持异步获取响应,示例如下:
Flux<String> output = chatClient.prompt() .user("Tell me a joke") .stream() .content();
也可通过 chatResponse() 方法以 Flux<ChatResponse> 流式获取响应。
未来版本将提供便捷方法支持响应式流直接返回 Java 实体。当前版本需通过结构化输出转换器(Structured Output Converter)显式聚合响应,如下所示。这也演示了 Fluent API 中参数的使用:
package com.hxstrive.springai.springai_openai.chatclient4.controller; import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONWriter; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.converter.BeanOutputConverter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.ParameterizedTypeReference; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; import java.util.List; import java.util.stream.Collectors; @RestController class MyController { @Autowired private ChatModel chatModel; // 定义名为 ActorFilms 的记录 record ActorsFilms(String actor, List<String> movies) {} @GetMapping(value = "/ai") public String generation(String userInput) { ChatClient chatClient = ChatClient.create(chatModel); // var 是在 Java 10 及以后的版本中,引入了一个新的特性,即局部变量类型推断 // 编译器会自动推断出 converter 的类型是 BeanOutputConverter // BeanOutputConverter 是一个自定义转换器, // 用于将 AI 返回的文本响应转换为 Java 对象列表(List<ActorFilms>) var converter = new BeanOutputConverter<>(new ParameterizedTypeReference<List<ActorsFilms>>() {}); // 构建一个 AI 请求, Flux<String> flux = chatClient.prompt() .user(u -> u.text(""" Generate the filmography for a random actor. {format} """) // 上面的 {format} 会被替换为 converter.getFormat() 返回的内容(如 "JSON") .param("format", converter.getFormat()) ) .stream() // 流式响应 .content(); // 使用 block() 将异步流转换为同步列表(阻塞操作) // 然后,将列表中的所有字符串连接成一个完整文本 String content = flux.collectList().block().stream().collect(Collectors.joining()); // 调用转换器将文本内容解析为List<ActorFilms> List<ActorsFilms> actorFilms = converter.convert(content); return JSONObject.toJSONString(actorFilms, JSONWriter.Feature.PrettyFormat); } }
上述代码,需要额外添加 fastjson2 的依赖:
<!-- fastjson2 --> <dependency> <groupId>com.alibaba.fastjson2</groupId> <artifactId>fastjson2</artifactId> <version>2.0.34</version> </dependency>
运行示例,访问 http://localhost:8080/ai 地址,输出如下图:
ChatClient Fluent 式 API 支持提供含变量的用户/系统消息模板,运行时进行替换:
String answer = ChatClient.create(chatModel).prompt() .user(u -> u // {composer} 是模板占位符 .text("Tell me the names of 5 movies whose soundtrack was composed by {composer}") .param("composer", "John Williams")) // 参数 .call() .content();
ChatClient 内部使用 PromptTemplate 类处理用户/系统文本,依赖 TemplateRenderer 实现运行时变量替换。Spring AI 默认采用基于 Terence Parr 开发的 StringTemplate 引擎的 StTemplateRenderer 实现。
Spring AI 还提供 NoOpTemplateRenderer,用于无需模板处理的场景。源码如下:
package org.springframework.ai.template; import java.util.Map; import org.springframework.util.Assert; public class NoOpTemplateRenderer implements TemplateRenderer { public NoOpTemplateRenderer() { } public String apply(String template, Map<String, Object> variables) { Assert.hasText(template, "template cannot be null or empty"); Assert.notNull(variables, "variables cannot be null"); Assert.noNullElements(variables.keySet(), "variables keys cannot be null"); return template; } }
注意:通过 .templateRenderer() 在 ChatClient 上直接配置的 TemplateRenderer 仅作用于构建链(Builder Chain)中直接定义的提示内容(如 .user() / .system())。它不会影响 Advisor(如 QuestionAnswerAdvisor)内部使用的模板 — 这些模板有独立的定制机制。
如需改用其他模板引擎,可直接向 ChatClient 提供 TemplateRenderer 接口的自定义实现。也可保留默认 StTemplateRenderer 但进行自定义配置。
例如,默认模板变量采用 {} 语法。若提示词中包含 JSON,建议改用 <> 等分隔符避免冲突。示例如下:
String answer = ChatClient.create(chatModel).prompt() .user(u -> u .text("Tell me the names of 5 movies whose soundtrack was composed by <composer>") .param("composer", "John Williams")) .templateRenderer(StTemplateRenderer.builder().startDelimiterToken('<').endDelimiterToken('>').build()) .call() .content();
ChatClient 的 call() 方法调用后,响应类型有以下几种处理选项:
String content():返回响应的字符串内容
ChatResponse chatResponse():返回包含多组生成结果及元数据(如消耗的 Token 数)的 ChatResponse 对象。
ChatClientResponse chatClientResponse():返回包含 ChatResponse 对象和 ChatClient 执行上下文的 ChatClientResponse 对象,可访问 Advisor 执行期间的附加数据(如 RAG 流程中检索的相关文档)。
entity() 返回 Java 类型
entity(ParameterizedTypeReference<T> type):用于返回实体类型的集合(Collection)。
entity(Class<T> type):用于返回特定实体类型。
entity(StructuredOutputConverter<T> structuredOutputConverter):通过 StructuredOutputConverter 将 String 转为实体类型。
也可使用 stream() 方法替代 call()。
ChatClient 的 stream() 方法调用后,响应类型有以下处理选项:
Flux<String> content():返回 AI 模型生成字符串的 Flux 流。
Flux<ChatResponse> chatResponse():返回包含响应元数据的 ChatResponse 对象 Flux 流。
Flux<ChatClientResponse> chatClientResponse():返回包含 ChatResponse 对象和 ChatClient 执行上下文的 ChatClientResponse 对象 Flux 流,可访问 Advisor 执行期间的附加数据(如 RAG 流程检索的相关文档)。
在 @Configuration 类中为 ChatClient 配置默认 system(系统提示词)消息可简化运行时代码。预设默认值后,调用时仅需指定 user 消息(用户提示词),无需每次请求重复设置系统消息。
以下示例将系统消息配置为始终以海盗口吻回复。为避免在运行时代码中重复系统消息,我们将在 @Configuration 类中创建 ChatClient 实例。
package com.hxstrive.springai.springai_openai.chatclient5.config; import org.springframework.ai.chat.client.ChatClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class Config { @Bean public ChatClient chatClient(ChatClient.Builder builder) { return builder // 默认的系统提示词 .defaultSystem("你是一个友好的聊天机器人,能以海盗的口吻回答问题") .build(); } }
并通过 @RestController 调用:
package com.hxstrive.springai.springai_openai.chatclient5.controller; import org.springframework.ai.chat.client.ChatClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.Map; @RestController public class AIController { private final ChatClient chatClient; // 注入 ChatClient AIController(ChatClient chatClient) { this.chatClient = chatClient; } @GetMapping("/ai/simple") public Map<String, String> completion( @RequestParam(value = "message", defaultValue = "给我讲个笑话") String message) { return Map.of("completion", this.chatClient.prompt().user(message).call().content()); } }
使用浏览器访问 http://localhost:8080/ai/simple,效果如下:
以下示例将在系统消息中使用占位符,以便在运行时(而非设计时)指定回复语气。
(1)创建 Config 配置类
package com.hxstrive.springai.springai_openai.chatclient6.config; import org.springframework.ai.chat.client.ChatClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class Config { @Bean public ChatClient chatClient(ChatClient.Builder builder) { return builder // 设置默认系统提示词,并且使用 {voice} 占位符 .defaultSystem("你是一个友好的聊天机器人,能用{voice}回答问题。") .build(); } }
(2)创建 AIController,提供一个 /ai/simple 接口,用于聊天:
package com.hxstrive.springai.springai_openai.chatclient6.controller; import org.springframework.ai.chat.client.ChatClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.Map; import java.util.Objects; @RestController public class AIController { private final ChatClient chatClient; // 注入 ChatClient AIController(ChatClient chatClient) { this.chatClient = chatClient; } @GetMapping("/ai/simple") public Map<String, String> completion(@RequestParam(value = "message", defaultValue = "给我讲个笑话") String message, @RequestParam("voice") String voice) { return Map.of("completion", Objects.requireNonNull(this.chatClient.prompt() .system(sp -> sp.param("voice", voice)) .user(message) .call() .content())); } }
使用浏览器访问 http://localhost:8080/ai/simple?voice=幽默的,效果如下:
可在 ChatClient.Builder 层级指定默认提示词配置。方法如下:
为聊天客户端设定默认的配置选项。借助传入 ChatOptions 类型的对象,能够对聊天过程中的通用参数进行配置。若要使用特定模型的专属选项,像 OpenAI 模型的特殊设置,可传入 OpenAiChatOptions 这类继承自 ChatOptions 的具体实现类。要了解各模型支持的详细选项,可查看对应 ChatOptions 实现类的 JavaDoc 文档。
该方法的功能是注册一个默认的工具函数,这个函数可在对话里被调用。
name:这是函数的名称,在后续调用时会用到。
description:用于描述函数的功能,帮助模型理解函数的用途。
function:是一个 Java 函数式接口,它定义了函数的输入参数类型 I 和返回值类型 O。
此方法允许批量注册已存在的工具函数。通过传入函数名称数组,能将多个已定义的函数设置为默认可用的函数。需要注意的是,这些函数必须事先已经被注册过。
该方法用于设置默认的用户输入文本。当发起聊天请求时,如果没有特别指定用户输入,就会使用这个默认文本。
这是设置默认用户输入的另一种方式,它支持从资源文件中读取用户输入内容。Resource 对象可用于加载类路径下的文件或者外部资源。
此方法提供了更灵活的方式来配置用户输入。通过传入一个 UserSpec 的消费者函数,可以对用户输入的各种属性进行定制,比如设置输入的格式、添加元数据等。
该方法用于注册默认的顾问(Advisor)列表。顾问能够对聊天交互进行拦截和处理,例如记录日志、转换输出格式等。可以同时传入多个 Advisor 实例。
Advisor 机制支持修改用于创建 Prompt 的数据。QuestionAnswerAdvisor 实现通过追加与用户消息相关的上下文信息,启用检索增强生成(RAG)模式。
该方法允许通过 Consumer 使用 AdvisorSpec 配置多个 Advisor。Advisor 可修改最终 Prompt 的生成数据。Consumer<AdvisorSpec> 支持以 lambda 形式添加 Advisor(如 QuestionAnswerAdvisor),该 Advisor 基于用户消息追加相关上下文信息,实现检索增强生成(RAG)模式。
注意,还可以通过不带 default 前缀的对应方法在运行时覆盖这些默认配置。方法如下:
该方法对聊天客户端的全局配置选项进行设置。ChatOptions 类涵盖了众多配置项,像超时时间、重试策略、认证信息以及日志级别等都包含在内。一旦调用该方法设置了选项,这些选项就会应用于后续所有的聊天交互。
用于向聊天客户端注册一个可调用的函数。其中:
name 是函数的标识符,在后续的交互里会用到。
description 对函数的功能和用途进行了说明,聊天模型会依据这个说明来决定是否调用该函数。
function 是一个 Java 函数式接口,它定义了函数的具体实现逻辑,包括输入参数和返回值。
该方法用于激活特定的函数。通过传入已注册函数的名称数组,能够限制在当前对话中可以使用的函数范围。这一功能很实用,比如在特定的业务场景下,只允许调用部分函数。
直接将字符串形式的用户消息发送给聊天模型。
可以发送文件资源,例如 JSON、XML 等格式的文件内容。
通过 UserSpec 接口,能够更灵活地构建用户输入,比如添加元数据、设置参考资料等。
该方法用于注册一个或多个 Advisor 组件。Advisor 可以对聊天交互进行拦截和增强,例如添加日志记录、实现访问控制、进行数据转换等功能。多个 Advisor 会按照注册的顺序依次执行。
此方法提供了一种流式 API 风格的方式来注册 Advisor。通过 AdvisorSpec 接口,能够更便捷地对多个 Advisor 进行链式配置,让代码的可读性更强。
ChatMemory 接口定义了聊天对话记忆的存储机制,提供添加消息、检索消息及清空对话历史的方法。
当前内置实现为:MessageWindowChatMemory。
MessageWindowChatMemory 是聊天记忆实现,维护最多指定数量(默认 20 条)的消息窗口。当消息超出限制时,旧消息会被移除(系统消息除外)。若添加新系统消息,则清除所有旧系统消息,确保始终保留最新上下文的同时控制内存占用。
MessageWindowChatMemory 基于 ChatMemoryRepository 抽象层实现,该抽象层提供多种聊天记忆存储方案,包括:
InMemoryChatMemoryRepository(内存存储)
JdbcChatMemoryRepository(JDBC 关系型数据库存储)
CassandraChatMemoryRepository(Cassandra 存储)
Neo4jChatMemoryRepository(Neo4j 图数据库存储)
更详细的聊天记忆功能后续将详细介绍。
ChatClient API 的独特之处在于融合了命令式与响应式编程模型。通常应用会选择一种使用,而不是两者都使用:
定制 Model 实现的 HTTP 客户端交互时,需同时配置 RestClient 和 WebClient。注意:由于 Spring Boot 3.4 的缺陷,必须设置 spring.http.client.factory=jdk 属性。默认值“reactor”会导致 ImageModel 等 AI 工作流异常。
流式传输仅通过响应式技术栈支持,因此命令式应用需引入响应式依赖,例如:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency>
非流式传输仅通过 Servlet 技术栈支持。因此响应式应用需引入 Servlet 依赖,例如:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
并且部分调用将出现阻塞。
工具调用采用命令式设计,会导致工作流阻塞。这将产生不完整的 Micrometer 观测数据。例如,ChatClient 的跨度(可能指相关操作的范围或持续时间等概念)和工具调用的跨度没有关联,正因如此,ChatClient 的跨度会保持不完整状态。在相关操作中,ChatClient 的操作范围或持续时间等方面,与工具调用的相应方面没有建立连接,导致 ChatClient 的这部分状态处于不完整,可能影响相关功能或数据分析等。
系统中内置的 Advisor 组件,在处理标准调用时,会采用阻塞操作方式,即调用过程中会暂停程序的其他操作,直到该调用完成。而在处理流式调用时,Advisor 则会执行非阻塞操作,允许程序在流式调用进行的同时,继续执行其他任务。另外,针对 Advisor 的流式调用所使用的 Reactor Scheduler(一种用于管理异步操作调度的工具),开发人员可以通过各个 Advisor 类的 Builder(一种用于创建对象的设计模式,可方便地进行对象属性配置)来对其进行相关配置,以满足不同的业务需求。例如,开发人员可以根据实际业务场景,通过 Builder 来调整 Reactor Scheduler 的线程池大小、调度策略等参数,使得流式调用在异步执行过程中能更高效地利用系统资源。
参考页面:https://docs.spring.io/spring-ai/reference/api/chatclient.html