聊天客户端 API
ChatClient 提供了一个流式 API(Fluent API),用于与 AI 模型进行交互。它同时支持 同步 和 流式 编程模型。
流式 API 提供了一系列方法,用于构建传递给 AI 模型的 Prompt 的各个组成部分。Prompt 包含指导 AI 模型输出和行为的指令文本。从 API 的角度来看,Prompt 由一组消息(messages)组成。
AI 模型主要处理两类消息:
- 用户消息(user messages):来自用户的直接输入
- 系统消息(system messages):由系统生成,用于引导对话
这些消息通常包含占位符(placeholders),在运行时根据用户输入进行替换,从而定制 AI 模型对用户输入的响应。
此外,还可以指定一些 Prompt 选项,例如使用的 AI 模型名称,以及用于控制生成输出随机性或创造性的温度(temperature)设置。
创建 ChatClient
ChatClient 是通过 ChatClient.Builder 对象创建的。你可以为任何 ChatModel 的 Spring Boot 自动配置获取一个自动配置的 ChatClient.Builder 实例,或者通过编程方式创建一个实例。
使用自动配置的 ChatClient.Builder
在最简单的使用场景中,Spring AI 提供了 Spring Boot 自动配置,为你创建一个原型(prototype)的 ChatClient.Builder Bean,方便你注入到自己的类中。下面是一个简单示例,展示如何获取针对用户请求的字符串(String)响应。
@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,但在应用中你可能需要同时使用多个聊天模型。处理该场景的方法如下:
在所有情况下,需要通过设置属性 spring.ai.chat.client.enabled=false 来禁用 ChatClient.Builder 的自动配置,这样你才能手动创建多个 ChatClient 实例。
使用单一模型类型的多个 ChatClient
本节介绍一种常见场景:需要创建多个 ChatClient 实例,这些实例使用相同的底层模型类型,但配置各不相同。
// 通过编程方式创建 ChatClient 实例
ChatModel myChatModel = ... // 已由 Spring Boot 自动配置
ChatClient chatClient = ChatClient.create(myChatModel);
// 或使用 Builder 进行更多自定义
ChatClient.Builder builder = ChatClient.builder(myChatModel);
ChatClient customChatClient = builder
.defaultSystemPrompt("You are a helpful assistant.")
.build();
使用不同模型类型的多个 ChatClient
当应用中需要使用多个 AI 模型时,可以为每个模型定义单独的 ChatClient Bean:
@Configuration
public class ChatClientConfig {
@Bean
public ChatClient openAiChatClient(OpenAiChatModel chatModel) {
return ChatClient.create(chatModel);
}
@Bean
public ChatClient anthropicChatClient(AnthropicChatModel chatModel) {
return ChatClient.create(chatModel);
}
}
然后,可以在应用组件中通过 @Qualifier 注解注入对应 Bean:
@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");
}
// 使用选定的 ChatClient
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 端点
OpenAiApi 和 OpenAiChatModel 提供了 mutate() 方法,可以基于现有实例创建不同属性的变体。这在需要同时使用多个 OpenAI 兼容 API 时非常有用:
@Service
public class MultiModelService {
@Autowired
private OpenAiChatModel baseChatModel;
@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();
// 为两个模型准备相同的 Prompt
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),通过重载的 prompt 方法启动流式 API:
prompt():无参方法,用于启动流式 API,允许你逐步构建用户消息、系统消息及提示的其他部分。prompt(Prompt prompt):接受一个Prompt对象作为参数,允许你传入通过Prompt的非流式 API 创建的实例。prompt(String content):便捷方法,与上一个重载类似,直接传入用户的文本内容。
ChatClient 响应
ChatClient API 提供了多种方式,通过流式 API 对 AI 模型的响应进行格式化。
返回 ChatResponse
AI 模型的响应是由 ChatResponse 类型定义的丰富结构。它不仅包含响应生成的元数据,还可能包含多个响应,即所谓的 Generations,每个响应都有自己的元数据。元数据中包括用于生成该响应的 token 数量(每个 token 大约相当于 3/4 个单词)。这些信息非常重要,因为托管的 AI 模型通常按每次请求使用的 token 数量收费。
下面是一个示例,通过在 call() 方法之后调用 chatResponse() 来返回包含元数据的 ChatResponse 对象:
ChatResponse chatResponse = chatClient.prompt()
.user("Tell me a joke")
.call()
.chatResponse();
返回实体(Entity)
通常情况下,你可能希望将返回的字符串(String)映射为一个实体类。entity() 方法提供了这种功能。
例如,给定如下 Java record:
record ActorFilms(String actor, List<String> movies) {}
你可以使用 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>>() {});
原生结构化输出
随着越来越多的 AI 模型原生支持结构化输出,你可以通过在调用 ChatClient 时使用 AdvisorParams.ENABLE_NATIVE_STRUCTURED_OUTPUT 参数来利用这一功能。你可以在 ChatClient.Builder 上使用 defaultAdvisors() 方法为所有调用全局设置此参数,或者像下面这样针对单次调用设置:
ActorFilms actorFilms = chatClient.prompt()
.advisors(AdvisorParams.ENABLE_NATIVE_STRUCTURED_OUTPUT)
.user("Generate the filmography for a random actor.")
.call()
.entity(ActorFilms.class);
流式响应(Streaming Responses)
stream() 方法允许你以异步方式获取响应,如下所示:
Flux<String> output = chatClient.prompt()
.user("Tell me a joke")
.stream()
.content();
你也可以使用 Flux<ChatResponse> chatResponse() 方法来流式获取 ChatResponse。
未来,我们将提供一个便捷方法,使你可以在使用响应式 stream() 方法时直接返回 Java 实体类。在此之前,你需要使用 Structured Output Converter 来显式地转换聚合后的响应,如下示例所示。这也展示了在流式 API 中使用参数的方式,后续文档中会有更详细的说明:
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);
这里的流程是:先通过 stream() 异步获取文本内容,然后将所有流式内容聚合,再通过 BeanOutputConverter 转换为指定的实体列表。
Prompt 模板(Prompt Templates)
ChatClient 的 Fluent API 允许你将 用户文本 和 系统文本 作为模板提供,并在运行时用变量替换这些模板。
例如:
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,适用于不需要模板处理的情况。
.templateRenderer() 在 ChatClient 上直接配置的 TemplateRenderer 仅对在 ChatClient 构建链中定义的 prompt 内容生效(例如通过 .user() 或 .system()),不会影响 Advisors(如 QuestionAnswerAdvisor)内部使用的模板,这些 Advisors 有自己的模板定制机制(参见 自定义 Advisor 模板)。如果你想使用不同的模板引擎,可以直接为 ChatClient 提供 TemplateRenderer 接口的自定义实现,也可以继续使用默认的 StTemplateRenderer,但进行自定义配置。
例如,默认情况下,模板变量使用 {} 语法。如果你的 prompt 中需要包含 JSON 内容,为避免与 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();
这样,模板引擎将在运行时用 composer 对应的值替换模板内容。
call() 返回值
在对 ChatClient 调用 call() 方法后,你可以选择多种不同的响应类型:
String content():返回响应的字符串内容。ChatResponse chatResponse():返回ChatResponse对象,其中包含多条生成内容(Generations)及响应的元数据,例如生成响应所使用的 token 数量。ChatClientResponse chatClientResponse():返回ChatClientResponse对象,其中包含ChatResponse对象和 ChatClient 执行上下文,让你在执行 Advisors 时访问额外数据(例如 RAG 流程中检索到的相关文档)。entity():将响应转换为 Java 类型。entity(ParameterizedTypeReference<T> type):用于返回实体类型的集合。entity(Class<T> type):用于返回特定的实体类型。entity(StructuredOutputConverter<T> structuredOutputConverter):指定一个StructuredOutputConverter实例,将字符串转换为实体类型。
responseEntity():同时返回完整的 AI 模型响应(含元数据和生成内容)和结构化输出实体。responseEntity(Class<T> type):返回包含完整ChatResponse对象和特定实体类型的ResponseEntity。responseEntity(ParameterizedTypeReference<T> type):返回包含完整ChatResponse对象和实体类型集合的ResponseEntity。responseEntity(StructuredOutputConverter<T> structuredOutputConverter):返回包含完整ChatResponse对象及使用指定StructuredOutputConverter转换的实体类型的ResponseEntity。
你也可以调用 stream() 方法替代 call(),以获取异步流式响应。
call() 方法并不会立即触发 AI 模型执行。它只是指示 Spring AI 使用同步调用还是流式调用。实际的 AI 模型调用发生在你调用 content()、chatResponse()、responseEntity() 等方法时。stream() 返回值
在对 ChatClient 调用 stream() 方法后,你可以选择几种不同的响应类型:
Flux<String> content():返回一个Flux,流式输出 AI 模型生成的字符串内容。Flux<ChatResponse> chatResponse():返回一个Flux,流式输出ChatResponse对象,其中包含响应的附加元数据。Flux<ChatClientResponse> chatClientResponse():返回一个Flux,流式输出ChatClientResponse对象,其中包含ChatResponse对象和ChatClient执行上下文,让你在执行 Advisors 时访问额外数据(例如 RAG 流程中检索到的相关文档)。
消息元数据(Message Metadata)
ChatClient 支持向 用户消息 和 系统消息 添加元数据。元数据为消息提供了额外的上下文信息,可供 AI 模型或后续处理使用。
为用户消息添加元数据
你可以使用 metadata() 方法向用户消息添加元数据:
// 添加单个元数据键值对
String response = chatClient.prompt()
.user(u -> u.text("天气怎么样?")
.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("天气怎么样?")
.metadata(userMetadata))
.call()
.content();
以上示例展示了如何为用户消息附加单个或多个元数据,以便 AI 模型或后续处理使用这些额外的上下文信息。
为系统消息添加元数据
同样,你也可以向系统消息添加元数据:
// 向系统消息添加元数据
String response = chatClient.prompt()
.system(s -> s.text("你是一个乐于助人的助手。")
.metadata("version", "1.0")
.metadata("model", "gpt-4"))
.user("讲一个笑话给我听")
.call()
.content();
这样可以为系统消息附加额外的上下文信息,例如版本号或模型类型,以便 AI 模型在生成回复时使用。
默认元数据支持
你还可以在 ChatClient 构建器层面配置默认元数据:
@Configuration
class Config {
@Bean
ChatClient chatClient(ChatClient.Builder builder) {
return builder
.defaultSystem(s -> s.text("你是一个乐于助人的助手")
.metadata("assistantType", "general")
.metadata("version", "1.0"))
.defaultUser(u -> u.text("默认用户上下文")
.metadata("sessionId", "default-session"))
.build();
}
}
这样配置后,每条系统消息和用户消息都会自动附加这些默认元数据,无需每次手动设置。
元数据验证
ChatClient 会对元数据进行验证以确保数据完整性:
- 元数据键(key)不能为空或
null - 元数据值(value)不能为
null - 当传入 Map 时,键和值都不能包含
null元素
示例:
// 这会抛出 IllegalArgumentException
chatClient.prompt()
.user(u -> u.text("Hello")
.metadata(null, "value")) // 无效:key 为 null
.call()
.content();
// 这也会抛出 IllegalArgumentException
chatClient.prompt()
.user(u -> u.text("Hello")
.metadata("key", null)) // 无效:value 为 null
.call()
.content();
这样可以防止无效的元数据被发送给 AI 模型,从而保证数据的可靠性。
访问元数据
元数据会包含在生成的 UserMessage 和 SystemMessage 对象中,并且可以通过消息的 getMetadata() 方法访问。这在处理 Advisors 中的消息或检查对话历史时尤其有用。
使用默认值
在 @Configuration 类中创建带有默认系统文本的 ChatClient 可以简化运行时代码。通过设置默认值,你在调用 ChatClient 时只需指定 用户文本,无需在每次运行时请求中都设置 系统文本。
默认系统文本
在下面的示例中,我们将配置 系统文本,使其始终以海盗的口吻回复。为了避免在运行时代码中重复系统文本,我们将在 @Configuration 类中创建一个 ChatClient 实例。
@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 来调用它:
@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!"}
带参数的默认系统文本
在下面的示例中,我们将在 系统文本 中使用占位符,在运行时而非设计时指定回答的语气。
@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();
}
}
@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,
@RequestParam 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 层级,你可以指定默认的 Prompt 配置。
defaultOptions(ChatOptions chatOptions):传入通用选项(ChatOptions类中定义的可移植选项)或模型特定选项,例如OpenAiChatOptions中的选项。有关模型特定ChatOptions实现的详细信息,请参阅 JavaDocs。defaultFunction(String name, String description, java.util.function.Function<I, O> function):name用于在用户文本中引用该函数,description用于说明函数目的,帮助 AI 模型选择正确函数以生成准确响应。function参数是一个 Java 函数实例,模型在需要时会执行该函数。defaultFunctions(String… functionNames):在应用上下文中定义的java.util.FunctionBean 的名称列表。defaultUser(String text)、defaultUser(Resource text)、defaultUser(Consumer<UserSpec> userSpecConsumer):用于定义默认用户文本。通过Consumer<UserSpec>,你可以使用 lambda 指定用户文本及其默认参数。defaultAdvisors(Advisor… advisor):Advisors 可以修改用于创建 Prompt 的数据。例如,QuestionAnswerAdvisor实现支持Retrieval Augmented Generation(RAG)模式,通过在 Prompt 中追加与用户文本相关的上下文信息。defaultAdvisors(Consumer<AdvisorSpec> advisorSpecConsumer):此方法允许你定义一个Consumer来使用AdvisorSpec配置多个 advisor。Advisor 可以修改用于创建最终Prompt的数据。Consumer<AdvisorSpec>允许你通过 lambda 添加各种 advisor,例如QuestionAnswerAdvisor,它通过基于用户输入追加相关上下文信息来支持检索增强生成(RAG)。
你可以在运行时使用对应的方法覆盖这些默认值(无需 default 前缀):
options(ChatOptions chatOptions)function(String name, String description, java.util.function.Function<I, O> function)functions(String… functionNames)user(String text)、user(Resource text)、user(Consumer<UserSpec> userSpecConsumer)advisors(Advisor… advisor)advisors(Consumer<AdvisorSpec> advisorSpecConsumer)
顾问(Advisors)
Advisors API 提供了一种灵活且强大的方式,用于在 Spring 应用中拦截、修改和增强 AI 驱动的交互。
调用 AI 模型并提供用户文本时,一个常见的模式是向 Prompt 中追加或增强上下文数据。
这些上下文数据可以有不同类型,常见类型包括:
- 自有数据:指 AI 模型未经过训练的数据。即使模型之前见过类似数据,追加的上下文数据在生成响应时仍具有优先权。
- 对话历史:聊天模型的 API 是无状态的。如果你告诉 AI 模型你的名字,它不会在后续交互中记住它。为了确保生成响应时考虑到之前的交互,对话历史必须随每次请求一起发送。
ChatClient 中的顾问配置
ChatClient 的 fluent API 提供了一个 AdvisorSpec 接口用于配置 Advisors。该接口提供了添加参数、一次设置多个参数,以及向链中添加一个或多个 Advisors 的方法。
interface AdvisorSpec {
AdvisorSpec param(String k, Object v);
AdvisorSpec params(Map<String, Object> p);
AdvisorSpec advisors(Advisor... advisors);
AdvisorSpec advisors(List<Advisor> advisors);
}
ChatClient.builder(chatModel)
.build()
.prompt()
.advisors(
MessageChatMemoryAdvisor.builder(chatMemory).build(),
QuestionAnswerAdvisor.builder(vectorStore).build()
)
.user(userText)
.call()
.content();
在此配置中,MessageChatMemoryAdvisor 会首先执行,将对话历史添加到 Prompt 中。随后,QuestionAnswerAdvisor 会基于用户的问题以及添加的对话历史执行搜索,从而提供更相关的结果。
了解更多关于 Question Answer Advisor 的内容。
检索增强生成(Retrieval Augmented Generation, RAG)
请参考《增强检索生成(Retrieval Augmented Generation)》指南。
日志记录(Logging)
SimpleLoggerAdvisor 是一个顾问(Advisor),用于记录 ChatClient 的请求和响应数据,这对于调试和监控 AI 交互非常有用。
要启用日志记录,请在创建 ChatClient 时将 SimpleLoggerAdvisor 添加到顾问链中,建议将其放在链的末尾:
ChatResponse response = ChatClient.create(chatModel).prompt()
.advisors(new SimpleLoggerAdvisor())
.user("Tell me a joke?")
.call()
.chatResponse();
要查看日志,请将顾问包的日志级别设置为 DEBUG:
logging.level.org.springframework.ai.chat.client.advisor=DEBUG
将其添加到 application.properties 或 application.yaml 文件中。
你还可以通过以下构造函数自定义记录 AdvisedRequest 和 ChatResponse 中的数据:
SimpleLoggerAdvisor(
Function<ChatClientRequest, String> requestToString,
Function<ChatResponse, String> responseToString,
int order
)
示例用法:
SimpleLoggerAdvisor customLogger = new SimpleLoggerAdvisor(
request -> "Custom request: " + request.prompt().getUserMessage(),
response -> "Custom response: " + response.getResult(),
0
);
这使你可以根据具体需求定制日志记录的信息。
聊天记忆(Chat Memory)
ChatMemory 接口表示聊天对话的记忆存储。它提供了向对话中添加消息、从对话中检索消息以及清除对话历史的相关方法。
目前内置的实现有一个:MessageWindowChatMemory。
MessageWindowChatMemory 是一种聊天记忆实现,它维护一个消息窗口,最多保存指定数量的消息(默认:20 条)。当消息数量超过此上限时,旧消息会被逐出,但系统消息会被保留。如果添加了新的系统消息,之前所有的 系统消息 将从记忆中移除。这确保了对话始终可以访问最新的上下文,同时保持内存使用量有限。
MessageWindowChatMemory 基于 ChatMemoryRepository 抽象,该抽象提供了聊天记忆的存储实现。可用的实现包括:InMemoryChatMemoryRepository、JdbcChatMemoryRepository、CassandraChatMemoryRepository 和 Neo4jChatMemoryRepository。
更多详情和使用示例,请参见《Chat Memory》文档。
实现说明
ChatClient 中命令式(imperative)和响应式(reactive)编程模型的结合使用是 API 的一个独特之处。通常应用程序会选择其中一种,但不同时使用。
- 自定义模型实现的 HTTP 客户端交互时,需要同时配置
RestClient和WebClient。
spring.http.client.factory=jdk。否则默认值为 reactor,会导致某些 AI 工作流(如 ImageModel)出错。- 流式(streaming)仅支持响应式栈(Reactive stack)。因此,命令式应用必须引入响应式栈(如
spring-boot-starter-webflux)。 - 非流式(non-streaming)仅支持 Servlet 栈。响应式应用必须引入 Servlet 栈(如
spring-boot-starter-web),并且某些调用可能会阻塞。 - 工具调用(tool calling)是命令式的,因此会导致阻塞的工作流。这也会造成 Micrometer 监控观测的不完整或中断(例如 ChatClient 的跨度和工具调用的跨度未连接,第一个跨度会因此保持不完整)。
- 内置顾问(advisors)在标准调用中执行阻塞操作,而在流式调用中执行非阻塞操作。用于顾问流式调用的 Reactor Scheduler 可以通过每个顾问类的 Builder 进行配置。