Spring AI 开发指南:ChatClient 基础、原理与实战案例
ChatClient是 Spring AI 与大语言模型交互的统一入口,通过流式 DSL 设计,兼顾简洁性与扩展性,支持多模型、可观测性、模板、历史上下文、结构化输出等高级特性,适用于从简单问答到复杂 Agent 系统的各类场景。
ChatClient 是一个用于与大语言模型通信的 API 接口,支持同步和流式编程模型,同时它也是 Spring AI 一切的起源。总体通过 ChatClientRequestSpec、CallResponseSpec 和 StreamResponseSpec 三个子接口定义来串联构造一个完整的聊天客户端。
returns
returns
returns
returns
uses via Consumer
uses via Consumer
uses via Consumer
builds
<<interface>>
ChatClient
+static create(ChatModel, ObservationRegistry, ChatClientObservationConvention, AdvisorObservationConvention) : : ChatClient
+static builder(ChatModel, ObservationRegistry, ChatClientObservationConvention, AdvisorObservationConvention) : : Builder
+prompt() : : ChatClientRequestSpec
+prompt(String) : : ChatClientRequestSpec
+prompt(Prompt) : : ChatClientRequestSpec
+mutate() : : Builder
<<interface>>
ChatClientRequestSpec
+mutate() : : Builder
+system(String) : : ChatClientRequestSpec
+system(Resource, Charset) : : ChatClientRequestSpec
+system(Consumer<PromptSystemSpec>) : : ChatClientRequestSpec
+user(String) : : ChatClientRequestSpec
+user(Consumer<PromptUserSpec>) : : ChatClientRequestSpec
+messages(Message[]) : : ChatClientRequestSpec
+advisors(Advisor[]) : : ChatClientRequestSpec
+tools(Object[]) : : ChatClientRequestSpec
+toolCallbacks(ToolCallback[]) : : ChatClientRequestSpec
+options(ChatOptions) : : ChatClientRequestSpec
+templateRenderer(TemplateRenderer) : : ChatClientRequestSpec
+call() : : CallResponseSpec
+stream() : : StreamResponseSpec
<<interface>>
CallResponseSpec
+entity(Class<T>) : : T
+entity(ParameterizedTypeReference<T>) : : T
+entity(StructuredOutputConverter<T>) : : T
+content() : : String
+chatResponse() : : ChatResponse
+chatClientResponse() : : ChatClientResponse
+responseEntity(Class<T>) : : ResponseEntity<ChatResponse, T>
<<interface>>
StreamResponseSpec
+content() : : Flux<String>
+chatResponse() : : Flux<ChatResponse>
+chatClientResponse() : : Flux<ChatClientResponse>
<<interface>>
PromptUserSpec
+text(String) : : PromptUserSpec
+text(Resource, Charset) : : PromptUserSpec
+param(String, Object) : : PromptUserSpec
+params(Map<String, Object>) : : PromptUserSpec
+media(MimeType, URL) : : PromptUserSpec
+media(Media[]) : : PromptUserSpec
+metadata(String, Object) : : PromptUserSpec
<<interface>>
PromptSystemSpec
+text(String) : : PromptSystemSpec
+text(Resource, Charset) : : PromptSystemSpec
+param(String, Object) : : PromptSystemSpec
+metadata(String, Object) : : PromptSystemSpec
<<interface>>
AdvisorSpec
+advisors(Advisor[]) : : AdvisorSpec
+params(Map<String, Object>) : : AdvisorSpec
+param(String, Object) : : AdvisorSpec
<<interface>>
Builder
+defaultSystem(String) : : Builder
+defaultUser(String) : : Builder
+defaultTools(Object[]) : : Builder
+defaultToolCallbacks(ToolCallback[]) : : Builder
+defaultAdvisors(Advisor[]) : : Builder
+defaultTemplateRenderer(TemplateRenderer) : : Builder
+clone() : : Builder
+build() : : ChatClient
1. 快速开始
Spring Boot 提供自动配置 Spring AI ChatClient,通过 ChatClient.Builder 装配一个开箱即用的 ChatClient 实例,如下代码所示,注入 ChatClient 后通过 user 方法设置用户消息内容,然后通过 call 方法向大语言模型发送请求,最后使用 content 方法将大语言模型的响应以字符串的形式返回。
java
@RestController
@RequestMapping("/chat/client")
public class ChatClientController {
private final ChatClient chatClient;
public ChatClientController(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
@GetMapping("/call")
public String call() {
return chatClient
.prompt()
.user("我是提示词")
.call()
.content();
}
}
请求到响应的流程如下所示:
用户代码: chatClient.prompt()
构造 Prompt: system/user/messages
模板渲染: 变量替换
Advisor 拦截: before()
调用 ChatModel: 生成响应
Advisor 拦截: after()
响应处理: content/entity/chatResponse
返回结果
2. 核心方法
ChatClient 接口有三类重要的方法定义,第一通过定义 create 和 builder 两个静态方法来引导初始构建对象,第二通过定义三个重载的 prompt 来引导完成构建对象,第三通过定义一个 mutate 方法引导基于当前配置创建新对象(实现配置的复用与修改)。
<<interface>>
ChatClient
+static create(ChatModel, ObservationRegistry, ChatClientObservationConvention, AdvisorObservationConvention) : : ChatClient
+static builder(ChatModel, ObservationRegistry, ChatClientObservationConvention, AdvisorObservationConvention) : : Builder
+prompt() : : ChatClientRequestSpec
+prompt(String) : : ChatClientRequestSpec
+prompt(Prompt) : : ChatClientRequestSpec
+mutate() : : Builder
2.1 构造
ChatClient 接口提供 create 和 builder 两种构造方式,从源码来看 create 最终也是调到了 builder 方法,最终 builder 通过 org.springframework.ai.chat.client.DefaultChatClientBuilder 构造一个 org.springframework.ai.chat.client.DefaultChatClient 对象提供服务。
java
static ChatClient create(ChatModel chatModel, ObservationRegistry observationRegistry,
@Nullable ChatClientObservationConvention chatClientObservationConvention,
@Nullable AdvisorObservationConvention advisorObservationConvention) {
Assert.notNull(chatModel, "chatModel cannot be null");
Assert.notNull(observationRegistry, "observationRegistry cannot be null");
return builder(chatModel, observationRegistry, chatClientObservationConvention, advisorObservationConvention)
.build();
}
static Builder builder(ChatModel chatModel, ObservationRegistry observationRegistry,
@Nullable ChatClientObservationConvention chatClientObservationConvention,
@Nullable AdvisorObservationConvention advisorObservationConvention) {
Assert.notNull(chatModel, "chatModel cannot be null");
Assert.notNull(observationRegistry, "observationRegistry cannot be null");
return new DefaultChatClientBuilder(chatModel, observationRegistry, chatClientObservationConvention,
advisorObservationConvention);
}
默认情况下 Spring AI 会通过 ChatClient.Builder 自动配置一个 ChatClient 以供使用,但是我们可以通过配置属性 spring.ai.chat.client.enabled=false 来禁用 ChatClient.Builder 自动配置。
2.1.1 不同模型类型的 ChatClient
我们对模型的需求是多样性的,对不同类型的任务使用不同的模型(例如,对于复杂推理使用强大的模型,对于简单任务使用更快、更便宜的模型),所以就存在装配不同模型类型 ChatClient 的需求。
java
@Configuration
public class ChatClientConfig {
@Bean
public ChatClient openAiChatClient(OpenAiChatModel chatModel) {
return ChatClient.create(chatModel);
}
@Bean
public ChatClient anthropicChatClient(DashScopeChatModel dashScopeChatModel) {
return ChatClient.create(chatModel);
}
}
也可以在使用的时候根据当时需求通过 create 方法构建 ChatClient
java
@RestController
@RequestMapping("/chat/client")
public class ChatClientController {
@Autowired
private ChatModel chatModel;
@GetMapping("/call")
public String call() {
ChatClient chatClient = ChatClient.create(chatModel);
return chatClient
.prompt()
.user("我是提示词")
.call()
.content();
}
}
2.1.2 可观测性
ChatClient 提供的构造方法中可以传入 ObservationRegistry 和 ChatClientObservationConvention 这两个对象的属性,通过对象名称不难看出这两个都是辅助用于可观测性的配置属性。从 spring ai 依赖管理中可以看到引入了 Micrometer 组件,所以上述两个属性配置都是用于集成 Micrometer 的观测(Observation)机制。
ObservationRegistry
- 作用:全局的观测注册表,是 Micrometer Observation 的核心组件。
- 职责:
- 管理所有 Observation 实例的生命周期。
- 将观测数据(如指标、日志、追踪 span)发送给后端(如 Prometheus、Zipkin、OpenTelemetry)。
- 决定是否启用观测、应用哪些 ObservationConvention。
- 如果不提供 ChatClient 会使用
ObservationRegistry.NOOP(即不记录任何观测数据)。
ChatClientObservationConvention
- 作用:定义 ChatClient 执行过程中生成的命名、标签(low-cardinality tags)等行为的约定。
- 职责:
- 自定义观测名称,实现
getName方法(例如ai.chat→myapp.llm.call)。 - 添加自定义上下文标签,如模型名称、用户 ID、请求类型等。
- 控制哪些信息被作为指标标签暴露(需注意避免高基数标签
getHighCardinalityKeyValues)。
- 自定义观测名称,实现
- 如果不提供,使用默认约定(
DefaultChatClientObservationConvention)。
| 属性 | 作用 | 控制什么 |
|---|---|---|
| ObservationRegistry | 观测的运行时注册中心 | 是否记录、如何导出数据 |
| ChatClientObservationConvention | 观测的命名与标签约定 | 观测叫什么、带什么标签 |
| 是否必需 | 否(默认 NOOP) | 否(默认 Default) |
| 类比 | 日志系统(如 Logback) | 日志格式模板(如 logback.xml) |
示例
- 自定义 ChatClientObservationConvention
java
public class CustomChatObservationConvention implements ChatClientObservationConvention {
@Override
public String getName() {
return "ai.demo.llm.chat";
}
@Override
public String getContextualName(ChatClientObservationContext context) {
return "Call to LLM: " + context.getName();
}
@Override
public KeyValues getLowCardinalityKeyValues(ChatClientObservationContext context) {
return context.getAllKeyValues();
}
@Override
public boolean supportsContext(Observation.Context context) {
return ChatClientObservationConvention.super.supportsContext(context);
}
}
- 引入 Observation
可以通过ChatClient#create或者ChatClent#builder方法引入自定义的 Observation,同时可以使用 spring 自动装配的ObservationRegistry无需手动构造。
java
@RestController
@RequestMapping("/chat/client")
public class ChatClientController {
@Autowired
private ChatModel chatModel;
@Autowired
private ObservationRegistry observationRegistry;
@GetMapping("/call")
public String call() {
ChatClient chatClient = ChatClient.create(
chatModel, observationRegistry, new CustomChatObservationConvention(), null);
return chatClient
.prompt()
.user("我是提示词")
.call()
.content();
}
}
- 启用 Micrometer 日志
yaml
logging:
level:
io.micrometer.observation: TRACE
2.2 提示引导
ChatClient 提供无参、字符串 prompt 入参和 Prompt 对象入参三种重载的 prompt 方法创建提示,并返回 ChatClientRequestSpec 接口提供配置其它的属性,也可以直接再通过 call() 或 stream() 执行。
prompt(): 此无参数方法直接返回ChatClientRequestSpec,使其进一步构建用户、系统和提示的其他部分。prompt(Prompt prompt): 此方法接受一个org.springframework.ai.chat.prompt.Prompt对象,可以通过个性化的方法创建一个 Prompt 对象来给 ChatClient 提供用户或者助手聊天记录、提示词,以及进一步控制大语言模型的温度等参数。prompt(String content): 提供一个简单字符串方法,其底层直接调用了 Prompt 类的构造器new Prompt(content),直接将用户的文本内容传输给大语言模型。
java
@RestController
@RequestMapping("/chat/client")
public class ChatClientController {
@Autowired
private ChatModel chatModel;
@GetMapping("/call")
public String call() {
ChatClient chatClient = ChatClient.create(chatModel);
// 1、prompt()
String message = chatClient
.prompt()
.user("我是提示词")
.call()
.content();
// 2、prompt(String prompt)
String message = chatClient
.prompt("我是提示词")
.call()
.content();
// 3、prompt(Prompt prompt)
ChatOptions chatOptions = ChatOptions.builder()
.temperature(0.9)
.maxTokens(1024)
.build();
Prompt prompt = new Prompt("我是提示词", chatOptions);
String message = chatClient
.prompt(prompt)
.call()
.content();
}
}
2.3 变体
ChatClient 提供 mutate 方法,该方法定义返回一个 Builder,所以实现允许基于当前配置创建新实例,进而达到配置的复用与修改目的。mutate对比builder有如下差异:
- builder:从零构建全新 ChatClient(如切换底层模型);
- mutate:基于现有 ChatClient微调配置(如仅修改温度参数)。
java
@RestController
@RequestMapping("/chat/client")
public class ChatClientController {
@Autowired
private ChatModel chatModel;
@GetMapping("/call")
public String call() {
// 构建 ChatClient
ChatClient chatClient = ChatClient.builder(chatModel).build();
// 定义大语言模型参数配置
ChatOptions chatOptions = ChatOptions.builder()
.temperature(0.9).maxTokens(1024).build();
// 基于 chatClient,追加大语言模型参数配置,生成新对象
ChatClient chatClientByOptions = chatClient
.mutate().defaultOptions(chatOptions).build();
return chatClientByOptions.prompt("这是提示词").call().content();
}
}
3. 请求构造器
ChatClient 无论通过哪种 prompt 方法都会返回一个 ChatClientRequestSpec 接口来完成一个 ChatClient 的请求构造。使用 ChatClientRequestSpec 可以进一步构造系统提示词、用户提示词、历史聊天记录、工具相关、大语言模型参数、Advisor 拦截和模板引擎等。
<<interface>>
ChatClientRequestSpec
+mutate() : : Builder
+system(String) : : ChatClientRequestSpec
+system(Resource, Charset) : : ChatClientRequestSpec
+system(Consumer<PromptSystemSpec>) : : ChatClientRequestSpec
+user(String) : : ChatClientRequestSpec
+user(Consumer<PromptUserSpec>) : : ChatClientRequestSpec
+messages(Message[]) : : ChatClientRequestSpec
+advisors(Advisor[]) : : ChatClientRequestSpec
+tools(Object[]) : : ChatClientRequestSpec
+toolCallbacks(ToolCallback[]) : : ChatClientRequestSpec
+options(ChatOptions) : : ChatClientRequestSpec
+templateRenderer(TemplateRenderer) : : ChatClientRequestSpec
+call() : : CallResponseSpec
+stream() : : StreamResponseSpec
3.1 提示词
3.1.1 基础提示配置(system/user)
ChatClientRequestSpec 接口提供了 system 和 user 两个配置提示词的入口,并对其重载了多个方法实现以应对不同配置提示词的需求。
java
ChatClientRequestSpec system(String text);
ChatClientRequestSpec system(Resource textResource, Charset charset);
ChatClientRequestSpec system(Resource text);
ChatClientRequestSpec system(Consumer<PromptSystemSpec> consumer);
ChatClientRequestSpec user(String text);
ChatClientRequestSpec user(Resource text, Charset charset);
ChatClientRequestSpec user(Resource text);
ChatClientRequestSpec user(Consumer<PromptUserSpec> consumer);
从源码定义来看 system 和 user 的前三个方法都是一样的参数定义,只有最后一个略有不同,system 方法提供的是 PromptSystemSpec 接口入参,user 方法则提供的是 PromptUserSpec 接口入参。从前三个方法来看都是直接通过字符串的形式来配置一个提示,如下:
java
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource resource = resolver.getResource("");
chatClient.prompt().system("我是提示词").call().content();
chatClient.prompt().system(resource).call().content();
chatClient.prompt().system(resource, Charset.defaultCharset()).call().content();
chatClient.prompt().user("我是提示词").call().content();
chatClient.prompt().user(resource).call().content();
chatClient.prompt().user(resource, Charset.defaultCharset()).call().content();
3.1.2 高级提示定制(PromptSystemSpec/PromptUserSpec)
PromptSystemSpec 和 PromptUserSpec 这两个接口则可以实现各个性化的配置需求,其都提供了通用的提示配置、参数填充配置和元数据配置,详情可以查看源码。
java
String content = chatClient.prompt().user(u -> {
u.text("你用{style}风格,写一篇300字的童话故事");
u.param("style", "天真浪漫");
}).call().content();
3.2 模板引擎
ChatClient 允许将用户和系统文本作为模板提供,其中变量在运行时被替换。其使用 PromptTemplate 类处理用户和系统文本,并根据给定的 TemplateRenderer 实现,在运行时用提供的值替换变量。默认情况下,Spring AI 使用 StTemplateRenderer 实现,该实现基于 Terence Parr 开发的开源 StringTemplate 引擎。同时 Spring AI 还提供了一个 NoOpTemplateRenderer,用于不需要模板处理的情况。
java
String content = chatClient.prompt().user(u -> {
u.text("你用{style}风格,写一篇300字的童话故事");
u.param("style", "天真浪漫");
}).call().content();
如果想要使用自己个性化的方式来处理变量,可以通过实现 org.springframework.ai.template.TemplateRenderer 接口实现自定义的模板引擎,并在构造 ChatClient 的时候通过 ChatClientRequestSpec 接口提供的 templateRenderer 方法注入。
同时你也可以基于现有的 StTemplateRenderer 实现来自定义完成自定义配置。例如,默认情况下,模板变量由 {} 语法标识。如果您打算在提示中包含 JSON,您可能需要使用不同的语法来避免与 JSON 语法冲突。那么就可以使用 < 和 > 分隔符。
java
String content = chatClient.prompt()
.user(u -> {
u.text("你用{style}风格,写一篇300字的童话故事");
u.param("style", "天真浪漫");})
.templateRenderer(StTemplateRenderer.builder()
.startDelimiterToken('<')
.endDelimiterToken('>')
.build())
.call()
.content();
3.3 历史消息
ChatClientRequestSpec 接口除了提供 system 和 user 方法来传递提示以外,还提供了 message 方法来配置各类想要传递给大语言模型的消息,从 org.springframework.ai.chat.messages.Message 接口实现来看可以传递用户历史消息、助手历史消息、工具返回消息和系统消息等,各类实现来看已经支持了各种想要传递的消息类型且支持批量消息。
java
ChatClientRequestSpec messages(Message... messages);
ChatClientRequestSpec messages(List<Message> messages);
4. 响应处理
ChatClient 提供阻塞式和流式两种应答响应模式分别通过 ChatClientRequestSpec 接口的 call 方法和 stream 方法来引导,阻塞式响应返回 CallResponseSpec,流式响应返回 StreamResponseSpec 如下代码所示。
java
CallResponseSpec call();
StreamResponseSpec stream();
4.1 阻塞式响应
在 ChatClient 上指定 call() 方法后响应的 CallResponseSpec 接口的定义。调用 call 方法实际上不会触发大语言模型执行,它只是告诉 Spring AI 使用阻塞式的方式调用。实际的大语言模型调用发生在 content、chatResponse 和 responseEntity 方法被调用时。
String content(): 直接以字符串的形式返回大语言模型响应的内容。ChatResponse chatResponse(): 返回ChatResponse对象,该对象包含多个生成以及有关响应的元数据,例如用于创建响应的令牌数量。ChatClientResponse chatClientResponse(): 返回一个ChatClientResponse对象,该对象包含ChatResponse对象和 ChatClient 执行上下文,使您可以访问 Advisor 执行期间使用的额外数据(例如,RAG 流中检索到的相关文档)。entity()返回 Java 类型entity(ParameterizedTypeReference<T> type): 用于返回实体类型的 Collection。entity(Class<T> type): 用于返回特定实体类型。entity(StructuredOutputConverter<T> structuredOutputConverter): 用于指定StructuredOutputConverter的实例,将 String 转换为实体类型。
responseEntity()返回 ChatResponse 和 Java 类型。当您需要在一次调用中同时访问完整的 AI 模型响应(带元数据和生成)和结构化输出实体时,这非常有用。responseEntity(Class<T> type): 用于返回包含完整 ChatResponse 对象和特定实体类型的 ResponseEntity。responseEntity(ParameterizedTypeReference<T> type): 用于返回包含完整 ChatResponse 对象和实体类型 Collection 的 ResponseEntity。responseEntity(StructuredOutputConverter<T> structuredOutputConverter): 用于返回包含完整 ChatResponse 对象和使用指定 StructuredOutputConverter 转换的实体的 ResponseEntity。
4.2 流式响应
在 ChatClient 上指定 stream() 方法后响应的 CallResponseSpec 接口的定义
Flux<String> content(): 返回 AI 模型正在生成的字符串的 Flux。Flux<ChatResponse> chatResponse(): 返回ChatResponse对象的 Flux,该对象包含有关响应的其他元数据。Flux<ChatClientResponse> chatClientResponse(): 返回ChatClientResponse对象的 Flux,该对象包含ChatResponse对象和 ChatClient 执行上下文,使您可以访问 Advisor 执行期间使用的额外数据(例如,RAG 流中检索到的相关文档)。
5. 消息元数据
我们在 PromptSystemSpec 构造系统提示和 PromptUserSpec 构造用户提示这两个接口的定义中都看见了 metadata 的影子,甚至在 ChatResponse 响应接口中也看到其影子。这里的 metadata 元数据是 ChatClient 为了支持添加到用户和系统消息中,从而实现提供额外的上下文和消息,进而交给大语言模型或者响应处理。
用户提示添加元数据
java
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();
系统提示添加元数据
java
String response = chatClient.prompt()
.system(s -> s.text("我是系统提示词")
.metadata("version", "1.0")
.metadata("model", "gpt-4"))
.user("我是用户提示词")
.call()
.content();
使用元数据
元数据信息放在生成的 UserMessage 和 SystemMessage 对象中,可以通过消息的 getMetadata 方法访问。可用在 Advisor 中以实现各种前后拦截。
java
@Component
public class DemoAdvisor implements BaseAdvisor {
@Override
public int getOrder() {
return 0;
}
@Override
public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) {
// 读取用户提示添加的元数据信息
chatClientRequest.prompt().getUserMessage().getMetadata();
return chatClientRequest;
}
@Override
public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {
return chatClientResponse;
}
}
一键三连,让我的信心像气球一样膨胀!