此版本仍在开发中,尚不被认为是稳定的。对于最新的快照版本,请使用 Spring AI 1.0.1! |
聊天客户端 API
这ChatClient
提供流畅的 API 来与 AI 模型进行通信。
它支持同步和流式编程模型。
请参阅本文档底部的实现说明,该说明与命令式编程模型和响应式编程模型的组合使用有关 |
Fluent API 具有用于构建 Prompt 的组成部分的方法,这些部分作为输入传递给 AI 模型。
这Prompt
包含指导 AI 模型输出和行为的指导文本。从 API 的角度来看,提示由消息集合组成。
人工智能模型处理两种主要类型的消息:用户消息(用户直接输入)和系统消息(系统生成的用于指导对话)。
这些消息通常包含占位符,这些占位符在运行时根据用户输入进行替换,以自定义 AI 模型对用户输入的响应。
还可以指定提示选项,例如要使用的 AI 模型的名称以及控制生成输出的随机性或创造力的温度设置。
创建 ChatClient
使用自动配置的 ChatClient.Builder
在最简单的用例中,Spring AI 提供 Spring Boot 自动配置,创建原型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()
方法将 AI 模型的响应作为String
.
使用多个聊天模型
在多种情况下,您可能需要在单个应用程序中使用多个聊天模型:
-
对不同类型的任务使用不同的模型(例如,用于复杂推理的强大模型和用于更简单任务的更快、更便宜的模型)
-
在一个模型服务不可用时实现回退机制
-
A/B 测试不同的型号或配置
-
为用户提供根据喜好选择的型号
-
组合专用模型(一个用于代码生成,另一个用于创意内容等)
默认情况下,Spring AI 会自动配置单个ChatClient.Builder
豆。
但是,您可能需要在应用程序中使用多个聊天模型。
以下是处理这种情况的方法:
在所有情况下,都需要禁用ChatClient.Builder
通过设置属性自动配置spring.ai.chat.client.enabled=false
.
这允许您创建多个ChatClient
实例。
具有单个模型类型的多个 ChatClient
本节介绍一个常见用例,您需要创建多个 ChatClient 实例,这些实例都使用相同的基础模型类型,但配置不同。
// Create ChatClient instances programmatically
ChatModel myChatModel = ... // already autoconfigured by Spring Boot
ChatClient chatClient = ChatClient.create(myChatModel);
// Or use the builder for more control
ChatClient.Builder builder = ChatClient.builder(myChatModel);
ChatClient customChatClient = builder
.defaultSystemPrompt("You are a helpful assistant.")
.build();
不同模型类型的 ChatClients
使用多个 AI 模型时,可以定义单独的ChatClient
每个模型的 bean:
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);
}
}
然后,您可以使用@Qualifier
注解:
@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;
// Model selection
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");
}
// Use the selected chat client
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 {
private static final Logger logger = LoggerFactory.getLogger(MultiModelService.class);
@Autowired
private OpenAiChatModel baseChatModel;
@Autowired
private OpenAiApi baseOpenAiApi;
public void multiClientFlow() {
try {
// Derive a new OpenAiApi for Groq (Llama3)
OpenAiApi groqApi = baseOpenAiApi.mutate()
.baseUrl("https://api.groq.com/openai")
.apiKey(System.getenv("GROQ_API_KEY"))
.build();
// Derive a new OpenAiApi for OpenAI GPT-4
OpenAiApi gpt4Api = baseOpenAiApi.mutate()
.baseUrl("https://api.openai.com")
.apiKey(System.getenv("OPENAI_API_KEY"))
.build();
// Derive a new OpenAiChatModel for Groq
OpenAiChatModel groqModel = baseChatModel.mutate()
.openAiApi(groqApi)
.defaultOptions(OpenAiChatOptions.builder().model("llama3-70b-8192").temperature(0.5).build())
.build();
// Derive a new OpenAiChatModel for GPT-4
OpenAiChatModel gpt4Model = baseChatModel.mutate()
.openAiApi(gpt4Api)
.defaultOptions(OpenAiChatOptions.builder().model("gpt-4").temperature(0.7).build())
.build();
// Simple prompt for both models
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
Fluent API 允许您使用重载的prompt
启动 Fluent API 的方法:
-
prompt()
:这种没有参数的方法可让您开始使用 Fluent API,允许您构建用户、系统和提示的其他部分。 -
prompt(Prompt prompt)
:此方法接受Prompt
参数,让你在Prompt
使用提示的非流畅 API 创建的实例。 -
prompt(String content)
:这是一种类似于前面重载的便捷方法。它采用用户的文本内容。
ChatClient 响应
这ChatClient
API 提供了多种使用流畅的 API 格式化 AI 模型响应的方法。
返回 ChatResponse
来自 AI 模型的响应是由类型定义的丰富结构ChatResponse
. 它包括有关响应生成方式的元数据,还可以包含多个响应,称为 Generation s,每个响应都有自己的元数据。元数据包括用于创建响应的Tokens数(每个Tokens大约占单词的 3/4)。此信息很重要,因为托管 AI 模型根据每个请求使用的Tokens数收费。
返回ChatResponse
包含元数据的对象如下所示,方法是调用chatResponse()
之后call()
方法。
ChatResponse chatResponse = chatClient.prompt()
.user("Tell me a joke")
.call()
.chatResponse();
返回实体
您经常希望返回从返回的String
. 这entity()
方法提供了此功能。
例如,给定 Java 记录:
record ActorFilms(String actor, List<String> movies) {}
您可以使用entity()
方法,如下图所示:
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> chatResponse()
.
将来,我们将提供一种方便的方法,让您返回带有响应式stream()
方法。
同时,应使用结构化输出转换器显式转换聚合响应,如下所示。
这还演示了 fluent 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);
提示模板
这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 构建器链(例如,通过.user() ,.system() ).
它不会影响顾问内部使用的模板,例如QuestionAnswerAdvisor ,它们具有自己的模板自定义机制(请参阅自定义顾问模板)。 |
如果您更愿意使用不同的模板引擎,则可以提供TemplateRenderer
直接连接到 ChatClient 的接口。您也可以继续使用默认值StTemplateRenderer
,但具有自定义配置。
例如,默认情况下,模板变量由语法标识。
如果计划在提示中包含 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();
call() 返回值
指定call()
方法ChatClient
,响应类型有几个不同的选项。
-
String content()
:返回响应的字符串内容 -
ChatResponse chatResponse()
:返回ChatResponse
包含多代以及有关响应的元数据的对象,例如创建响应使用了多少Tokens。 -
ChatClientResponse chatClientResponse()
:返回一个ChatClientResponse
包含ChatResponse
对象和 ChatClient 执行上下文,使您可以访问执行顾问期间使用的其他数据(例如,在 RAG 流程中检索到的相关文档)。 -
ResponseEntity<?> responseEntity()
:返回一个ResponseEntity
包含完整的 HTTP 响应,包括状态代码、标头和正文。 当您需要访问响应的低级 HTTP 详细信息时,这非常有用。 -
entity()
返回 Java 类型-
entity(ParameterizedTypeReference<T> type)
:用于返回Collection
实体类型。 -
entity(Class<T> type)
:用于返回特定实体类型。 -
entity(StructuredOutputConverter<T> structuredOutputConverter)
:用于指定StructuredOutputConverter
将String
设置为实体类型。
-
您还可以调用stream()
方法而不是call()
.
调用call() 方法实际上不会触发 AI 模型执行。相反,它只指示 Spring AI 是使用同步调用还是流式调用。实际的 AI 模型调用发生在content() ,chatResponse() 和responseEntity() 被调用。 |
stream() 返回值
指定stream()
方法ChatClient
,响应类型有几个选项:
-
Flux<String> content()
:返回一个Flux
AI 模型生成的字符串。 -
Flux<ChatResponse> chatResponse()
:返回一个Flux
的ChatResponse
对象,其中包含有关响应的其他元数据。 -
Flux<ChatClientResponse> chatClientResponse()
:返回一个Flux
的ChatClientResponse
包含ChatResponse
对象和 ChatClient 执行上下文,使您可以访问执行顾问期间使用的其他数据(例如,在 RAG 流程中检索到的相关文档)。
消息元数据
ChatClient 支持向用户和系统消息添加元数据。元数据提供有关 AI 模型或下游处理可以使用的消息的其他上下文和信息。
向用户消息添加元数据
您可以使用metadata()
方法:
// Adding individual metadata key-value pairs
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();
// Adding multiple metadata entries at once
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();
将元数据添加到系统消息
同样,您可以向系统消息添加元数据:
// Adding metadata to system messages
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 构建器级别配置默认元数据:
@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 验证元数据以确保数据完整性:
-
元数据键不能为空或空
-
元数据值不能为空
-
传递 Map 时,键和值都不能包含 null 元素
// This will throw an IllegalArgumentException
chatClient.prompt()
.user(u -> u.text("Hello")
.metadata(null, "value")) // Invalid: null key
.call()
.content();
// This will also throw an IllegalArgumentException
chatClient.prompt()
.user(u -> u.text("Hello")
.metadata("key", null)) // Invalid: null value
.call()
.content();
使用默认值
创建ChatClient
在@Configuration
class 简化了运行时代码。
通过设置默认值,您只需在调用时指定用户文本ChatClient
,无需为运行时代码路径中的每个请求设置系统文本。
默认系统文本
在下面的示例中,我们将配置系统文本以始终以海盗的声音回复。
为了避免在运行时代码中重复系统文本,我们将创建一个ChatClient
实例中的@Configuration
类。
@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, 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。 -
defaultFunction(String name, String description, java.util.function.Function<I, O> function)
:这name
用于引用用户文本中的函数。 这description
解释函数的用途,并帮助 AI 模型选择正确的函数以获得准确的响应。 这function
argument 是一个 Java 函数实例,模型将在必要时执行它。 -
defaultFunctions(String… functionNames)
:在应用程序上下文中定义的 'java.util.Function' 的 bean 名称。 -
defaultUser(String text)
,defaultUser(Resource text)
,defaultUser(Consumer<UserSpec> userSpecConsumer)
:这些方法允许您定义用户文本。 这Consumer<UserSpec>
允许您使用 lambda 来指定用户文本和任何默认参数。 -
defaultAdvisors(Advisor… advisor)
:顾问允许修改用于创建Prompt
. 这QuestionAnswerAdvisor
实现使Retrieval Augmented Generation
通过在提示中附加与用户文本相关的上下文信息。 -
defaultAdvisors(Consumer<AdvisorSpec> advisorSpecConsumer)
:此方法允许您定义Consumer
要使用AdvisorSpec
.顾问可以修改用于创建最终Prompt
. 这Consumer<AdvisorSpec>
允许您指定要添加顾问的 lambda,例如QuestionAnswerAdvisor
,支持Retrieval Augmented Generation
通过根据用户文本在提示中附加相关上下文信息。
您可以在运行时使用相应的方法覆盖这些默认值,而无需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 API 提供了一种灵活而强大的方式来拦截、修改和增强 Spring 应用程序中 AI 驱动的交互。
使用用户文本调用 AI 模型时,一种常见的模式是使用上下文数据附加或增强提示。
此上下文数据可以是不同的类型。常见类型包括:
-
您自己的数据:这是 AI 模型尚未训练的数据。 即使模型看到了类似的数据,附加的上下文数据在生成响应时也会优先。
-
对话历史记录:聊天模型的 API 是无状态的。 如果你告诉人工智能模型你的名字,它不会在后续的交互中记住它。 对话历史记录必须随每个请求一起发送,以确保在生成响应时考虑以前的交互。
ChatClient 中的顾问配置
ChatClient 流畅的 API 提供了一个AdvisorSpec
用于配置顾问的接口。
该接口提供了添加参数、一次设置多个参数以及将一个或多个顾问添加到链的方法。
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
将首先执行,将对话历史添加到提示中。
然后,QuestionAnswerAdvisor
将根据用户的问题和添加的对话历史记录执行搜索,从而可能提供更相关的结果。
检索增强生成
请参阅检索增强生成指南。
Logging
这SimpleLoggerAdvisor
是一个顾问,它记录了request
和response
数据ChatClient
.
这对于调试和监控 AI 交互非常有用。
Spring AI 支持 LLM 和向量存储交互的可观测性。有关更多信息,请参阅可观测性指南。 |
要启用日志记录,请添加SimpleLoggerAdvisor
创建 ChatClient 时添加到顾问链。建议将其添加到链的末尾:
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
);
这使您可以根据自己的特定需求定制记录的信息。
在生产环境中记录敏感信息时要小心。 |
聊天记忆
界面ChatMemory
表示聊天对话内存的存储。
它提供了向对话添加消息、从对话中检索消息以及清除对话历史记录的方法。
目前有一个内置实现:MessageWindowChatMemory
.
MessageWindowChatMemory
是一种聊天内存实现,用于维护一个消息窗口,最大可达指定的最大大小(默认:20 条消息)。
当消息数超过此限制时,将逐出较旧的消息,但系统消息将保留。
如果添加新的系统消息,则会从内存中删除所有以前的系统消息。
这可确保最新的上下文始终可用于对话,同时保持内存使用量有限。
这MessageWindowChatMemory
由ChatMemoryRepository
抽象,为聊天对话内存提供存储实现。
有几种可用的实现,包括InMemoryChatMemoryRepository
,JdbcChatMemoryRepository
,CassandraChatMemoryRepository
和Neo4jChatMemoryRepository
.
有关更多详细信息和使用示例,请参阅聊天内存文档。
实施说明
命令式编程模型和响应式编程模型的组合使用ChatClient
是 API 的一个独特方面。
通常,应用程序要么是响应式的,要么是命令式的,但不能同时是两者。
-
自定义 Model 实现的 HTTP 客户端交互时,必须同时配置 RestClient 和 WebClient。
由于 Spring Boot 3.4 中的一个错误,必须设置“spring.http.client.factory=jdk”属性。 否则,默认情况下,它设置为“反应器”,这会破坏某些 AI 工作流程,例如 ImageModel。 |
-
流式处理仅支持通过响应式堆栈。 出于这个原因,命令式应用程序必须包含响应式堆栈(例如 spring-boot-starter-webflux)。
-
非流式处理仅通过 Servlet 堆栈提供支持。 出于这个原因,响应式应用程序必须包含 Servlet 堆栈(例如 spring-boot-starter-web),并期望某些调用被阻塞。
-
工具调用势在必行,导致工作流程受阻。 这也会导致部分/中断的千分尺观测(例如,ChatClient 跨度和工具调用跨度未连接,因此第一个跨度仍然不完整)。
-
内置顾问对标准调用执行阻止作,对流式调用执行非阻塞作。 用于顾问流式调用的 Reactor 调度程序可以通过每个顾问类上的构建器进行配置。