Spring AI 参考文档 - 聊天客户端 API
聊天客户端 API
ChatClient 提供了一套流畅的 API(Fluent API),用于与 AI 模型进行通信。它同时支持同步和流式编程模型。
请参阅本文档末尾的实现说明,了解 ChatClient 中命令式编程模型和响应式编程模型结合使用的相关内容。
流畅 API 提供了构建传递给 AI 模型的 Prompt(提示词)各个组成部分的方法。Prompt 包含指导 AI 模型输出和行为的指令性文本。从 API 的角度来看,提示词由一系列消息(Message)组成。
AI 模型处理两种主要类型的消息:用户消息(User Message),即来自用户的直接输入;以及系统消息(System Message),由系统生成以引导对话。
这些消息通常包含占位符,在运行时根据用户输入进行替换,以根据用户输入定制 AI 模型的响应。
还可以指定提示词选项(Prompt Options),例如要使用的 AI 模型名称,以及控制生成输出随机性或创造性的温度(Temperature)设置。
创建 ChatClient
ChatClient 使用 ChatClient.Builder 对象创建。您可以为任何 ChatModel Spring Boot 自动配置获取一个自动配置的 ChatClient.Builder 实例,也可以手动创建。
使用自动配置的 ChatClient.Builder
在最简单的使用场景中,Spring AI 提供了 Spring Boot 自动配置,为您创建了一个原型(Prototype)作用域的 ChatClient.Builder Bean,您可以将其注入到您的类中。以下是一个检索简单用户请求的 String 响应的示例:
java
@RestController
class MyController {
private final ChatClient chatClient;
public MyController(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
@GetMapping("/ai")
String generation(String userInput) {
return this.chatClient.prompt()
.user(userInput)
.call()
.content();
}
}
在这个简单示例中,用户输入设置了用户消息的内容。call() 方法向 AI 模型发送请求,content() 方法以 String 形式返回 AI 模型的响应。
使用多个聊天模型
在某些场景下,您可能需要在单个应用程序中使用多个聊天模型:
- 为不同类型的任务使用不同的模型(例如,复杂推理使用强大的模型,简单任务使用更快、更便宜的模型)
- 当一个模型服务不可用时实现故障转移机制
- 对不同模型或配置进行 A/B 测试
- 根据用户偏好为用户提供模型选择
- 组合专用模型(一个用于代码生成,另一个用于创意内容等)
默认情况下,Spring AI 自动配置单个 ChatClient.Builder Bean。但是,您可能需要在应用程序中使用多个聊天模型。以下是如何处理这种情况的方法:
使用单一模型类型的多个 ChatClient
本节介绍一个常见用例:您需要创建多个 ChatClient 实例,它们都使用相同的底层模型类型,但具有不同的配置。您可以使用自动配置的 ChatClient.Builder,因为它是原型作用域的,意味着每个注入点都会创建一个新实例:
java
@Configuration
class ChatClientConfig {
@Bean
ChatClient defaultChatClient(ChatClient.Builder builder) {
return builder.build();
}
@Bean
ChatClient customChatClient(ChatClient.Builder builder) {
return builder.defaultSystem("You are a helpful assistant.").build();
}
}
用于不同模型类型的 ChatClient
当使用多个 AI 模型时,您可能会想使用 ChatClient.create(chatModel) 或 ChatClient.builder(chatModel) 来定义单独的 ChatClient Bean。但是,这样做会绕过自动配置的 ChatClient.Builder,这意味着可观测性(Observability)和 ChatClientBuilderCustomizer Bean 将被忽略。
为了保留可观测性和自定义配置,您应该注入 ChatClientBuilderConfigurer 来创建自定义构建器。ChatClientBuilderConfigurer 会应用所有已注册的 ChatClientBuilderCustomizer Bean,并连接可观测性功能,与自动配置内部所做的操作一致。
当应用程序上下文中存在多个 ChatModel Bean 时,Spring 无法在没有歧义的情况下解析自动配置的 ChatClient.Builder Bean 的 ChatModel 依赖。为了解决这个问题,您可以将您的某个 ChatClient Bean 标记为 @Primary。如果您正在手动定义 ChatModel Bean,您可能还需要将某个 ChatModel Bean 标记为 @Primary,或者定义您自己的 ChatClient.Builder Bean 来覆盖自动配置的 Bean。
java
import io.micrometer.observation.ObservationRegistry;
import org.springframework.ai.anthropic.AnthropicChatModel;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.ToolCallingAdvisor;
import org.springframework.ai.chat.client.advisor.observation.AdvisorObservationConvention;
import org.springframework.ai.chat.client.observation.ChatClientObservationConvention;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.model.chat.client.autoconfigure.ChatClientBuilderConfigurer;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@Configuration
public class ChatClientConfig {
@Bean
@Primary
public ChatClient openAiChatClient(OpenAiChatModel chatModel, ChatClientBuilderConfigurer configurer,
ObjectProvider<ObservationRegistry> observationRegistry,
ObjectProvider<ChatClientObservationConvention> chatClientObservationConvention,
ObjectProvider<AdvisorObservationConvention> advisorObservationConvention,
ObjectProvider<ToolCallingAdvisor.Builder<?>> toolCallingAdvisorBuilder) {
return buildChatClient(chatModel, configurer, observationRegistry,
chatClientObservationConvention, advisorObservationConvention, toolCallingAdvisorBuilder);
}
@Bean
public ChatClient anthropicChatClient(AnthropicChatModel chatModel, ChatClientBuilderConfigurer configurer,
ObjectProvider<ObservationRegistry> observationRegistry,
ObjectProvider<ChatClientObservationConvention> chatClientObservationConvention,
ObjectProvider<AdvisorObservationConvention> advisorObservationConvention,
ObjectProvider<ToolCallingAdvisor.Builder<?>> toolCallingAdvisorBuilder) {
return buildChatClient(chatModel, configurer, observationRegistry,
chatClientObservationConvention, advisorObservationConvention, toolCallingAdvisorBuilder);
}
private ChatClient buildChatClient(ChatModel chatModel, ChatClientBuilderConfigurer configurer,
ObjectProvider<ObservationRegistry> observationRegistry,
ObjectProvider<ChatClientObservationConvention> chatClientObservationConvention,
ObjectProvider<AdvisorObservationConvention> advisorObservationConvention,
ObjectProvider<ToolCallingAdvisor.Builder<?>> toolCallingAdvisorBuilder) {
ChatClient.Builder builder = ChatClient.builder(chatModel,
observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP),
chatClientObservationConvention.getIfUnique(),
advisorObservationConvention.getIfUnique(),
toolCallingAdvisorBuilder.getIfAvailable());
return configurer.configure(builder).build();
}
}
然后,您可以使用 @Qualifier 注解将这些 Bean 注入到您的应用程序组件中:
java
@Configuration
public class ChatClientExample {
@Bean
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();
};
}
}
多个兼容 OpenAI 的 API 端点
您可以使用它们的构建器创建多个 OpenAiChatModel 实例,以连接到不同的兼容 OpenAI 的 API。当您需要与多个提供商合作时,这特别有用。
java
@Service
public class MultiModelService {
private static final Logger logger = LoggerFactory.getLogger(MultiModelService.class);
public void multiClientFlow() {
try {
// 为 Groq (Llama3) 创建新的 OpenAiChatModel
OpenAiChatModel groqModel = OpenAiChatModel.builder()
.options(OpenAiChatOptions.builder()
.baseUrl("https://api.groq.com/openai/v1")
.apiKey(System.getenv("GROQ_API_KEY"))
.model("llama3-70b-8192")
.temperature(0.5)
.build())
.build();
// 为 GPT-4 创建新的 OpenAiChatModel
OpenAiChatModel gpt4Model = OpenAiChatModel.builder()
.options(OpenAiChatOptions.builder()
.baseUrl("https://api.openai.com")
.apiKey(System.getenv("OPENAI_API_KEY"))
.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);
}
}
}
ChatClient 流畅 API
ChatClient 流畅 API 允许您通过重载的 prompt 方法以三种不同的方式创建提示词,从而启动流畅 API:
prompt():此方法不带参数,让您开始使用流畅 API,允许您构建用户、系统以及提示词的其他部分。prompt(Prompt prompt):此方法接受一个Prompt参数,让您传入一个使用 Prompt 的非流畅 API 创建的Prompt实例。prompt(String content):这是一个便利方法,类似于前面的重载。它接受用户的文本内容。
ChatClient 响应
ChatClient API 提供了几种使用流畅 API 格式化 AI 模型响应的方法。
返回 ChatResponse
AI 模型的响应是一个由 ChatResponse 类型定义的丰富结构。它包含有关响应生成方式的元数据,并且还可以包含多个响应(称为 Generations,生成结果),每个响应都有自己的元数据。元数据包括用于创建响应的令牌数量(每个令牌大约相当于 3/4 个单词)。这些信息很重要,因为托管的 AI 模型根据每个请求使用的令牌数量收费。
以下示例通过调用 call() 方法后调用 chatResponse() 来返回包含元数据的 ChatResponse 对象。
java
ChatResponse chatResponse = chatClient.prompt()
.user("Tell me a joke")
.call()
.chatResponse();
返回实体(Entity)
您通常希望返回一个从返回的 String 映射而来的实体类。entity() 方法提供了此功能。
例如,给定 Java 记录(Record):
java
record ActorFilms(String actor, List<String> movies) {}
您可以使用 entity() 方法轻松地将 AI 模型的输出映射到此记录,如下所示:
java
ActorFilms actorFilms = chatClient.prompt()
.user("Generate the filmography for a random actor.")
.call()
.entity(ActorFilms.class);
还有一个重载的 entity 方法,签名是 entity(ParameterizedTypeReference<T> type),允许您指定类型,例如泛型 List:
java
List<ActorFilms> actorFilms = chatClient.prompt()
.user("Generate the filmography of 5 movies for Tom Hanks and Bill Murray.")
.call()
.entity(new ParameterizedTypeReference<List<ActorFilms>>() {});
EntityParamSpec
所有三个 entity() 重载方法都接受一个可选的 Consumer<EntityParamSpec>,它启用两种独立的行为:
提供商原生结构化输出(Provider-Native Structured Output)
默认情况下,JSON Schema 作为文本指令附加到用户消息中。当设置 useProviderStructuredOutput() 时,Schema 会直接作为结构化约束传递给 AI 提供商 API(相当于设置 AdvisorParams.ENABLE_NATIVE_STRUCTURED_OUTPUT)。这要求底层模型支持 StructuredOutputChatOptions。
java
ActorFilms actorFilms = chatClient.prompt()
.user("Generate the filmography for a random actor.")
.call()
.entity(ActorFilms.class, spec -> spec.useProviderStructuredOutput());
注意 :原生结构化输出默认未启用,因为不同模型和提供商的支持程度差异很大。值得注意的限制包括:带有推理/思考模式的 Ollama 模型(可能返回纯文本而不是 JSON)以及 OpenAI 不支持顶级数组 Schema。有关详细信息和解决方法,请参阅已知限制。
您仍然可以通过顾问参数(Advisor Parameter)为所有调用全局设置此功能:
java
ActorFilms actorFilms = chatClient.prompt()
.advisors(AdvisorParams.ENABLE_NATIVE_STRUCTURED_OUTPUT)
.user("Generate the filmography for a random actor.")
.call()
.entity(ActorFilms.class);
带重试的 Schema 验证(Schema Validation with Retry)
validateSchema() 在返回之前验证模型的 JSON 响应是否符合实体 Schema。如果验证失败,错误消息会附加到用户提示词中,并重试模型,最多重试 maxRepeatAttempts 次(默认:3)。这在内部使用了 StructuredOutputValidationAdvisor。
java
ActorFilms actorFilms = chatClient.prompt()
.user("Generate the filmography for a random actor.")
.call()
.entity(ActorFilms.class, spec -> spec.validateSchema());
两个选项可以组合使用:
java
ActorFilms actorFilms = chatClient.prompt()
.user("Generate the filmography for a random actor.")
.call()
.entity(ActorFilms.class, spec -> spec
.useProviderStructuredOutput()
.validateSchema());
注意 :当
validateSchema()激活时,不支持流式(Streaming)。
流式响应(Streaming Responses)
stream() 方法允许您获取异步响应,如下所示:
java
Flux<String> output = chatClient.prompt()
.user("Tell me a joke")
.stream()
.content();
您还可以使用 Flux<ChatResponse> chatResponse() 方法流式传输 ChatResponse。
将来,我们将提供一个便利方法,允许您使用响应式 stream() 方法返回 Java 实体。与此同时,您应该使用**结构化输出转换器(Structured Output Converter)**来显式转换聚合响应,如下所示。这也演示了流畅 API 中参数的使用,将在本文档的后续部分更详细地讨论。
java
var converter = new BeanOutputConverter<>(new ParameterizedTypeReference<List<ActorsFilms>>() {});
Flux<String> flux = this.chatClient.prompt()
.user(u -> u.text("""
Generate the filmography for a random actor.
{format}
""")
.param("format", this.converter.getFormat()))
.stream()
.content();
String content = this.flux.collectList().block().stream().collect(Collectors.joining());
List<ActorsFilms> actorFilms = this.converter.convert(this.content);
提示词模板(Prompt Templates)
ChatClient 流畅 API 允许您将用户和系统文本作为模板提供,其中的变量在运行时被替换。
java
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"))
.call()
.content();
在内部,ChatClient 使用 PromptTemplate 类来处理用户和系统文本,并依赖给定的 TemplateRenderer 实现,在运行时将变量替换为提供的值。默认情况下,Spring AI 使用 StTemplateRenderer 实现,它基于 Terence Parr 开发的开源 StringTemplate 引擎。
Spring AI 还提供了一个 NoOpTemplateRenderer,用于不需要模板处理的情况。
注意 :直接在 ChatClient 上配置的
TemplateRenderer(通过.templateRenderer())仅适用于 ChatClient 构建器链中直接定义的提示词内容(例如,通过.user()、.system())。它不会影响 Advisor(如QuestionAnswerAdvisor)内部使用的模板,这些 Advisor 有自己的模板定制机制(请参阅自定义 Advisor 模板)。
如果您想使用不同的模板引擎,您可以直接为 ChatClient 提供 TemplateRenderer 接口的自定义实现。您也可以继续使用默认的 StTemplateRenderer,但使用自定义配置。
例如,默认情况下,模板变量由 {} 语法标识。如果您计划在提示词中包含 JSON,您可能希望使用不同的语法以避免与 JSON 语法冲突。例如,您可以使用 < 和 > 分隔符。
java
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();
call() 返回值
在 ChatClient 上指定 call() 方法后,响应类型有几个不同的选项:
String content():返回响应的 String 内容ChatResponse chatResponse():返回包含多个生成结果以及响应元数据(例如用于创建响应的令牌数量)的ChatResponse对象ChatClientResponse chatClientResponse():返回包含ChatResponse对象和 ChatClient 执行上下文的ChatClientResponse对象,让您可以访问 Advisor 执行期间使用的额外数据(例如,RAG 流程中检索到的相关文档)entity()返回 Java 类型entity(ParameterizedTypeReference<T> type):用于返回实体类型的集合entity(Class<T> type):用于返回特定实体类型entity(StructuredOutputConverter<T> structuredOutputConverter):用于指定StructuredOutputConverter实例以将 String 转换为实体类型entity(ParameterizedTypeReference<T> type, Consumer<EntityParamSpec> spec):与上述类似,带有可选的EntityParamSpec配置entity(Class<T> type, Consumer<EntityParamSpec> spec):与上述类似,带有可选的EntityParamSpec配置entity(StructuredOutputConverter<T> converter, Consumer<EntityParamSpec> spec):与上述类似,带有可选的EntityParamSpec配置
responseEntity()同时返回ChatResponse和 Java 类型。当您需要在单次调用中同时访问完整的 AI 模型响应(包含元数据和生成结果)和结构化输出实体时,这非常有用responseEntity(Class<T> type):用于返回包含完整ChatResponse对象和特定实体类型的ResponseEntityresponseEntity(Class<T> type, Consumer<EntityParamSpec> spec):与上述类似,带有可选的EntityParamSpec配置responseEntity(ParameterizedTypeReference<T> type):用于返回包含完整ChatResponse对象和实体类型集合的ResponseEntityresponseEntity(ParameterizedTypeReference<T> type, Consumer<EntityParamSpec> spec):与上述类似,带有可选的EntityParamSpec配置responseEntity(StructuredOutputConverter<T> structuredOutputConverter):用于返回包含完整ChatResponse对象和使用指定StructuredOutputConverter转换的实体的ResponseEntityresponseEntity(StructuredOutputConverter<T> converter, Consumer<EntityParamSpec> spec):与上述类似,带有可选的EntityParamSpec配置
您也可以调用 stream() 方法而不是 call()。
重要 :调用
call()方法实际上并不会触发 AI 模型执行。相反,它只是指示 Spring AI 是使用同步调用还是流式调用。实际的 AI 模型调用发生在调用content()、chatResponse()和responseEntity()等方法时。
stream() 返回值
在 ChatClient 上指定 stream() 方法后,响应类型有几个选项:
Flux<String> content():返回 AI 模型生成的字符串的 FluxFlux<ChatResponse> chatResponse():返回包含响应额外元数据的ChatResponse对象的 FluxFlux<ChatClientResponse> chatClientResponse():返回包含ChatResponse对象和 ChatClient 执行上下文的ChatClientResponse对象的 Flux,让您可以访问 Advisor 执行期间使用的额外数据(例如,RAG 流程中检索到的相关文档)
消息元数据(Message Metadata)
ChatClient 支持为用户和系统消息添加元数据。元数据为消息提供了额外的上下文和信息,可供 AI 模型或下游处理使用。
为用户消息添加元数据
您可以使用 metadata() 方法为用户消息添加元数据:
java
// 添加单个元数据键值对
String response = chatClient.prompt()
.user(u -> u.text("What's the weather like?")
.metadata("messageId", "msg-123")
.metadata("userId", "user-456")
.metadata("priority", "high"))
.call()
.content();
// 一次添加多个元数据条目
Map<String, Object> userMetadata = Map.of(
"messageId", "msg-123",
"userId", "user-456",
"timestamp", System.currentTimeMillis()
);
String response = chatClient.prompt()
.user(u -> u.text("What's the weather like?")
.metadata(userMetadata))
.call()
.content();
为系统消息添加元数据
类似地,您可以为系统消息添加元数据:
java
// 为系统消息添加元数据
String response = chatClient.prompt()
.system(s -> s.text("You are a helpful assistant.")
.metadata("version", "1.0")
.metadata("model", "gpt-4"))
.user("Tell me a joke")
.call()
.content();
默认元数据支持
您还可以在 ChatClient 构建器级别配置默认元数据:
java
@Configuration
class Config {
@Bean
ChatClient chatClient(ChatClient.Builder builder) {
return builder
.defaultSystem(s -> s.text("You are a helpful assistant")
.metadata("assistantType", "general")
.metadata("version", "1.0"))
.defaultUser(u -> u.text("Default user context")
.metadata("sessionId", "default-session"))
.build();
}
}
元数据验证
ChatClient 会验证元数据以确保数据完整性:
- 元数据键不能为 null 或空
- 元数据值不能为 null
- 当传递 Map 时,键和值都不能包含 null 元素
java
// 这将抛出 IllegalArgumentException
chatClient.prompt()
.user(u -> u.text("Hello")
.metadata(null, "value")) // 无效:键为 null
.call()
.content();
// 这也将抛出 IllegalArgumentException
chatClient.prompt()
.user(u -> u.text("Hello")
.metadata("key", null)) // 无效:值为 null
.call()
.content();
访问元数据
元数据包含在生成的 UserMessage 和 SystemMessage 对象中,可以通过消息的 getMetadata() 方法访问。这在 Advisor 中处理消息或检查对话历史时特别有用。
使用默认值(Defaults)
在 @Configuration 类中使用默认系统文本创建 ChatClient 可以简化运行时代码。通过设置默认值,您在调用 ChatClient 时只需要指定用户文本,无需在运行时代码路径中为每个请求设置系统文本。
默认系统文本
在以下示例中,我们将配置系统文本始终以海盗的声音回复。为了避免在运行时代码中重复系统文本,我们将在 @Configuration 类中创建一个 ChatClient 实例。
java
@Configuration
class Config {
@Bean
ChatClient chatClient(ChatClient.Builder builder) {
return builder.defaultSystem("You are a friendly chat bot that answers question in the voice of a Pirate")
.build();
}
}
以及一个 @RestController 来调用它:
java
@RestController
class AIController {
private final ChatClient chatClient;
AIController(ChatClient chatClient) {
this.chatClient = chatClient;
}
@GetMapping("/ai/simple")
public Map<String, String> completion(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) {
return Map.of("completion", this.chatClient.prompt().user(message).call().content());
}
}
通过 curl 调用应用程序端点时,结果是:
❯ curl localhost:8080/ai/simple
{"completion":"Why did the pirate go to the comedy club? To hear some arrr-rated jokes! Arrr, matey!"}
带参数的默认系统文本
在以下示例中,我们将在系统文本中使用占位符,以便在运行时而不是设计时指定回复的声音。
java
@Configuration
class Config {
@Bean
ChatClient chatClient(ChatClient.Builder builder) {
return builder.defaultSystem("You are a friendly chat bot that answers question in the voice of a {voice}")
.build();
}
}
java
@RestController
class AIController {
private final ChatClient chatClient;
AIController(ChatClient chatClient) {
this.chatClient = chatClient;
}
@GetMapping("/ai")
Map<String, String> completion(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message, String voice) {
return Map.of("completion",
this.chatClient.prompt()
.system(sp -> sp.param("voice", voice))
.user(message)
.call()
.content());
}
}
通过 httpie 调用应用程序端点时,结果是:
http localhost:8080/ai voice=='Robert DeNiro'
{
"completion": "You talkin' to me? Okay, here's a joke for ya: Why couldn't the bicycle stand up by itself? Because it was two tired! Classic, right?"
}
其他默认值
在 ChatClient.Builder 级别,您可以指定默认的提示词配置。
defaultOptions(ChatOptions chatOptions):传入ChatOptions类中定义的可移植选项,或特定于模型的选项(如OpenAiChatOptions)。有关特定于模型的ChatOptions实现的更多信息,请参阅 JavaDocs。defaultTools(Object... tools):注册一个或多个默认工具,这些工具将可用于每个请求。接受ToolCallback、ToolCallbackProvider或带有@Tool注解方法的 POJO 的异构混合。defaultToolContext(Map<String, Object> toolContext):为工具执行设置默认上下文。defaultSystem(String text)、defaultSystem(Resource text)、defaultSystem(Consumer<PromptSystemSpec> systemSpecConsumer):这些方法允许您定义默认系统文本。defaultUser(String text)、defaultUser(Resource text)、defaultUser(Consumer<UserSpec> userSpecConsumer):这些方法允许您定义用户文本。Consumer<UserSpec>允许您使用 lambda 指定用户文本和任何默认参数。defaultTemplateRenderer(TemplateRenderer templateRenderer):为提示词模板设置默认的TemplateRenderer。defaultAdvisors(Advisor... advisor)、defaultAdvisors(List<Advisor> advisors):Advisor 允许修改用于创建 Prompt 的数据。QuestionAnswerAdvisor实现通过将与用户文本相关的上下文信息附加到提示词来实现检索增强生成(Retrieval Augmented Generation)模式。defaultAdvisors(Consumer<AdvisorSpec> advisorSpecConsumer):此方法允许您定义一个 Consumer 来使用AdvisorSpec配置多个 Advisor。Advisor 可以修改用于创建最终 Prompt 的数据。Consumer<AdvisorSpec>允许您指定一个 lambda 来添加 Advisor,例如QuestionAnswerAdvisor,它通过根据用户文本附加相关上下文信息来支持检索增强生成。
您可以在运行时使用相应的不带 default 前缀的方法覆盖这些默认值。
options(ChatOptions.Builder optionsCustomizer)tools(Object... tools)toolContext(Map<String, Object> toolContext)messages(Message... messages)、messages(List<Message> messages)system(String text)、system(Resource text)、system(Consumer<PromptSystemSpec> systemSpecConsumer)user(String text)、user(Resource text)、user(Consumer<UserSpec> userSpecConsumer)templateRenderer(TemplateRenderer templateRenderer)advisors(Advisor... advisor)、advisors(List<Advisor> advisors)advisors(Consumer<AdvisorSpec> advisorSpecConsumer)
聊天客户端选项合并(Chat Client Options Merging)
变更 ChatClient
您可以使用 mutate() 方法从现有 ChatClient 复制设置来创建新的 ChatClient(或 ChatClientRequestSpec):
- 在
ChatClient上:Builder mutate()返回一个使用客户端默认设置初始化的ChatClient.Builder。 - 在
ChatClientRequestSpec上:Builder mutate()返回一个使用请求当前设置初始化的ChatClient.Builder。
这对于创建派生客户端或请求而无需重新定义所有选项非常有用。
Advisor
Advisor API 提供了一种灵活而强大的方式来拦截、修改和增强 Spring 应用程序中的 AI 驱动交互。
使用用户文本调用 AI 模型时的一个常见模式是附加或增强提示词与上下文数据。
这些上下文数据可以是不同类型。常见类型包括:
- 您自己的数据:这是 AI 模型尚未训练过的数据。即使模型见过类似数据,附加的上下文数据在生成响应时也具有优先权。
- 对话历史:聊天模型的 API 是无状态的。如果您告诉 AI 模型您的名字,它不会在后续交互中记住。必须随每个请求发送对话历史,以确保在生成响应时考虑之前的交互。
ChatClient 中的 Advisor 配置
ChatClient 流畅 API 提供了一个 AdvisorSpec 接口用于配置 Advisor。此接口提供了添加参数、一次设置多个参数以及向链中添加一个或多个 Advisor 的方法。
java
interface AdvisorSpec {
AdvisorSpec param(String k, Object v);
AdvisorSpec params(Map<String, Object> p);
AdvisorSpec advisors(Advisor... advisors);
AdvisorSpec advisors(List<Advisor> advisors);
}
重要:Advisor 添加到链中的顺序至关重要,因为它决定了它们的执行顺序。每个 Advisor 都会以某种方式修改提示词或上下文,一个 Advisor 所做的更改会传递给链中的下一个 Advisor。
java
ChatClient.builder(chatModel)
.build()
.prompt()
.advisors(a -> a
.advisors(
MessageChatMemoryAdvisor.builder(chatMemory).build(),
QuestionAnswerAdvisor.builder(vectorStore).build()
)
.param(ChatMemory.CONVERSATION_ID, conversationId))
.user(userText)
.call()
.content();
在此配置中,MessageChatMemoryAdvisor 将首先执行,将对话历史添加到提示词中。然后,QuestionAnswerAdvisor 将根据用户的问题和添加的对话历史执行搜索,可能提供更相关的结果。
重要 :
ChatMemory.CONVERSATION_ID必须在每次使用内存 Advisor 的调用中通过.param()提供。如果省略,将在运行时抛出IllegalArgumentException。
检索增强生成(Retrieval Augmented Generation)
请参阅检索增强生成指南。
日志记录(Logging)
SimpleLoggerAdvisor 是一个记录 ChatClient 请求和响应数据的 Advisor。这对于调试和监控 AI 交互非常有用。
注意 :Spring AI 支持 LLM 和向量存储交互的可观测性。请参阅可观测性指南以获取更多信息。
要启用日志记录,请在创建 ChatClient 时将 SimpleLoggerAdvisor 添加到 Advisor 链中。建议将其添加到链的末尾:
java
ChatResponse response = ChatClient.create(chatModel).prompt()
.advisors(new SimpleLoggerAdvisor())
.user("Tell me a joke?")
.call()
.chatResponse();
要查看日志,请将 Advisor 包的日志记录级别设置为 DEBUG:
properties
logging.level.org.springframework.ai.chat.client.advisor=DEBUG
将此添加到您的 application.properties 或 application.yaml 文件中。
您可以使用以下构造函数自定义记录 AdvisedRequest 和 ChatResponse 的哪些数据:
java
SimpleLoggerAdvisor(
Function<ChatClientRequest, String> requestToString,
Function<ChatResponse, String> responseToString,
int order
)
使用示例:
java
SimpleLoggerAdvisor customLogger = new SimpleLoggerAdvisor(
request -> "Custom request: " + request.prompt().getUserMessage(),
response -> "Custom response: " + response.getResult(),
0
);
这允许您根据自己的特定需求定制记录的信息。
警告:在生产环境中记录敏感信息时要小心。
工具调用(Tool Calling)
ChatClient 总是在 Advisor 链中自动注册一个 ToolCallingAdvisor,除非显式禁用了自动注册(见下文)。这确保即使调用时没有配置静态工具,由另一个 Advisor 在运行时动态注入的工具也能被正确处理。
java
String response = ChatClient.builder(chatModel)
.build()
.prompt("What day is tomorrow?")
.tools(new DateTimeTools()) // ToolCallingAdvisor 自动注册
.call()
.content();
ToolCallingAdvisor 默认以 Ordered.HIGHEST_PRECEDENCE + 300 的顺序注册。
禁用自动注册
全局禁用(所有调用)
设置 spring.ai.chat.client.tool-calling.enabled=false 以禁用从自动配置的 ChatClient 进行的每次调用的自动注册:
properties
spring.ai.chat.client.tool-calling.enabled=false
当设置此属性时,工具仍然作为定义发送给 AI 模型,但响应中的工具调用不会自动执行。使用用户控制的工具执行来自己驱动循环。
每次调用禁用
您可以使用 AdvisorParams.toolCallingAdvisorAutoRegister(false) 禁用单次调用的自动注册:
java
chatClient.prompt("What day is tomorrow?")
.tools(new DateTimeTools())
.advisors(AdvisorParams.toolCallingAdvisorAutoRegister(false))
.call()
.content();
或者您可以提供自己的 ToolCallingAdvisor(或任何其他实现 ToolAdvisor 的 Advisor)------在这种情况下,自动注册会被自动抑制,因为链中已经包含了一个 ToolAdvisor。
您可以通过 .advisors() 传入自定义 Advisor:
java
chatClient.prompt("What day is tomorrow?")
.tools(new DateTimeTools())
.advisors(customAdvisor) // 自动注册被抑制
.call()
.content();
自定义默认的 ToolCallingAdvisor
spring.ai.chat.client.tool-calling.* 属性控制自动配置的 ToolCallingAdvisor:
| 属性 | 默认值 | 描述 |
|---|---|---|
spring.ai.chat.client.tool-calling.enabled |
true |
设置为 false 以禁用所有调用的 ToolCallingAdvisor 自动注册。工具仍然发送给模型,但工具调用不会自动执行。 |
spring.ai.chat.client.tool-calling.advisor-order |
Ordered.HIGHEST_PRECEDENCE + 300 |
自动注册的 ToolCallingAdvisor 在 Advisor 链中的位置。 |
Spring Boot:Advisor 顺序属性
调整自动注册 Advisor 的最简单方法是通过 spring.ai.chat.client.tool-calling.advisor-order 属性。它控制 ToolCallingAdvisor 插入到 Advisor 链中的位置:
properties
spring.ai.chat.client.tool-calling.advisor-order=0
该值必须低于 任何应在工具调用循环内运行的 Advisor(即在每次迭代中重复执行)的顺序,并且高于 任何应在循环外运行的 Advisor(即每个用户请求仅执行一次)。默认值为 ToolCallingAdvisor.DEFAULT_ORDER。
Spring Boot:用户控制的工具执行
要观察工具调用循环的每次迭代------例如,将中间块转发到 UI------请在每次调用时禁用自动注册的 Advisor,并使用 AdvisorParams.toolCallingAdvisorAutoRegister(false) 自行驱动循环。请参阅用户控制的工具执行------与 ChatClient 一起使用获取完整示例。
Spring Boot:自定义 ToolCallingAdvisor.Builder Bean
为了更深入的自定义------例如,提供自定义的 ToolCallingManager------声明一个 ToolCallingAdvisor.Builder<?> Bean。Spring Boot 在自动配置的 Bean 上使用了 @ConditionalOnMissingBean,这意味着您的 Bean 将优先:
java
@Bean
ToolCallingAdvisor.Builder<?> toolCallingAdvisorBuilder(ToolCallingManager myToolCallingManager) {
return ToolCallingAdvisor.builder()
.toolCallingManager(myToolCallingManager)
.advisorOrder(Ordered.LOWEST_PRECEDENCE);
}
然后,ChatClient.Builder Bean 将自动与此自定义构建器连接。
不使用 Spring Boot
当不使用 Spring Boot 自动配置时,直接将预配置的 ToolCallingAdvisor.Builder 传递给 ChatClient.builder():
java
ToolCallingManager customManager = ToolCallingManager.builder()
// 自定义解析器、异常处理器等
.build();
ChatClient chatClient = ChatClient
.builder(chatModel, observationRegistry, null, null,
ToolCallingAdvisor.builder().toolCallingManager(customManager))
.build();
// 每个注册了工具的提示词都将在自动注册的 Advisor 中使用 customManager
chatClient.prompt("What day is tomorrow?")
.tools(new DateTimeTools())
.call()
.content();
ToolAdvisor 标记接口
ToolAdvisor 是一个标记接口,用于向 ChatClient 发出信号,表明 Advisor 链已经处理了工具执行。在自定义 Advisor 上实现此接口可以防止自动注册第二个 ToolCallingAdvisor。
有关更多详细信息,请参阅 Advisor 控制的工具执行。
聊天内存(Chat Memory)
ChatMemory 接口表示聊天对话内存的存储。它提供了向对话添加消息、从对话检索消息以及清除对话历史的方法。
目前有一个内置实现:MessageWindowChatMemory。
MessageWindowChatMemory 是一种聊天内存实现,它维护一个消息窗口,最大大小可配置(默认:20 条消息)。当消息数量超过此限制时,较旧的消息会被淘汰,但系统消息会被保留。如果添加了新的系统消息,所有先前的系统消息都会从内存中移除。这确保了对话的最新上下文始终可用,同时保持内存使用有界。
MessageWindowChatMemory 由 ChatMemoryRepository 抽象支持,该抽象为聊天对话内存提供了存储实现。有几种可用的实现,包括 InMemoryChatMemoryRepository、JdbcChatMemoryRepository、CassandraChatMemoryRepository、Neo4jChatMemoryRepository、MongoChatMemoryRepository 和 RedisChatMemoryRepository。
重要 :所有内存 Advisor 都需要
ChatMemory.CONVERSATION_ID参数。它必须在每次调用中通过.advisors(a → a.param(ChatMemory.CONVERSATION_ID, conversationId))提供。如果省略,将抛出IllegalArgumentException。没有默认的对话 ID。
有关更多详细信息和用法示例,请参阅聊天内存文档。
MemoryAdvisor 标记接口
MemoryAdvisor 是一个由 BaseChatMemoryAdvisor 扩展的标记接口。DefaultChatClient 使用此标记在自动注册逻辑中检测下游的内存 Advisor。
不扩展 BaseChatMemoryAdvisor 但应参与此检测的自定义内存 Advisor 应实现 MemoryAdvisor。
实现说明
ChatClient 中命令式编程模型和响应式编程模型的结合使用是 API 的一个独特方面。通常,应用程序要么是响应式的,要么是命令式的,但不会两者都是。
-
当自定义模型实现的 HTTP 客户端交互时,必须同时配置
RestClient和WebClient。 -
流式(Streaming)仅通过响应式堆栈支持。因此,命令式应用程序必须包含响应式堆栈(例如
spring-boot-starter-webflux)。 -
非流式(Non-streaming)仅通过 Servlet 堆栈支持。因此,响应式应用程序必须包含 Servlet 堆栈(例如
spring-boot-starter-web),并且需要预期某些调用会阻塞。 -
工具调用是命令式的,这会导致阻塞工作流。这也会导致 Micrometer 观测部分/中断(例如,ChatClient Span 和工具调用 Span 没有连接,第一个 Span 因此保持不完整)。
-
内置 Advisor 对标准调用执行阻塞操作,对流式调用执行非阻塞操作。用于 Advisor 流式调用的 Reactor Scheduler 可以通过每个 Advisor 类上的 Builder 进行配置。