版本:Spring AI 2.0.1
目标:把应用层入口讲清楚——如何创建、如何设默认值、如何发一次同步 / 流式请求,以及 Tools、结构化输出在 Client 上的基本挂法。
一次请求在应用侧通常长这样:
业务代码
→ ChatClient(DSL + 默认值 + Advisors + Tools)
→ Advisor 链(记忆 / RAG / 安全 / ToolCalling …)
→ ChatModelCallAdvisor / ChatModelStreamAdvisor
→ ChatModel.call / stream(Prompt)
→ Provider API

本章按这条链路自上而下:先创建与默认值,再 prompt() 组装,再 call() / stream() 取结果,最后补 Tools 与 entity(...) 的入口用法。
4.1 在架构里的位置
业务代码通常只面对 ChatClient。它不是另一套协议:底下流通的仍是 Message / Prompt / ChatResponse。ChatClient 减少的是组装成本,并把 Advisor、Tools 挂到同一条请求链上。
先记住三点:
-
ChatClient本身不保存会话。 多轮要靠 Advisor +ChatMemory+ conversationId 一类上下文。 -
Boot 自动配置提供的是
ChatClient.Builder(prototype),不是单例ChatClient。 每次注入拿到的是新克隆,互不污染。 -
默认往往会挂上
ToolCallingAdvisor。 需要完全自管工具循环时,可用配置关闭自动注册。
4.2 创建 Client:三种常见写法
最快:直接 create
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
ChatClient client = ChatClient.create(chatModel);
适合试验与短脚本;生产更常见的是 Builder 或 Boot 注入。
推荐:Builder 堆默认值再 build
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.openai.OpenAiChatOptions;
ChatClient client = ChatClient.builder(chatModel)
.defaultSystem("你是简洁准确的中文助手")
.defaultAdvisors(new SimpleLoggerAdvisor())
.defaultTools(weatherTools) // 带 @Tool 的 POJO,或 ToolCallback
.defaultOptions(OpenAiChatOptions.builder()
.temperature(0.3)
.build())
.build();
Boot:注入 prototype Builder
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
class AiClientConfig {
@Bean
ChatClient chatClient(ChatClient.Builder builder) {
// builder 已经过自动配置与各类 Customizer
return builder
.defaultSystem("你是简洁准确的中文助手")
.build();
}
}
已有 Client 衍生变体:mutate()
ChatClient visionClient = textClient.mutate()
.defaultOptions(OpenAiChatOptions.builder()
.model("gpt-4o")
.build())
.build();
RequestSpec 也有 mutate(),适合把「这次请求的参数」固化成新的 Client。Builder 还有 clone(),用于构建完成前复制模板。
4.3 默认值 vs 单次请求
这是 ChatClient 最容易踩坑的一层。

| 层 | 典型 API | 作用范围 |
|---|---|---|
Builder default* | defaultSystem、defaultAdvisors、defaultTools、defaultOptions、defaultToolContext | build() 后对该 Client 所有请求生效 |
| RequestSpec | .system / .user、.advisors、.tools、.options、.call / .stream | 只影响这一次;多数情况单次优先 |
合并时务必记住一条硬规则:
请求级
tools(...)会整组覆盖该次请求的defaultTools,不是追加合并。
因此:
-
真正「几乎每次都该有」的工具,才挂
defaultTools -
「仅某接口需要」的工具,放在该次
.tools(...),并想清楚是否还要带上默认那一组(需要的话要显式一并传入)
4.4 发起一次请求:prompt()
三种入口:
import java.util.List;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
// 1) 空起手,再链式组装
String a = client.prompt()
.system("用中文简要回答")
.user("用一句话解释依赖注入")
.call()
.content();
// 2) 整段当作 user 文本
String b = client.prompt("用一句话解释依赖注入")
.call()
.content();
// 3) 直接喂入底层 Prompt(与 ChatModel 互通)
String c = client.prompt(new Prompt(List.of(
new SystemMessage("用中文简要回答"),
new UserMessage("用一句话解释依赖注入")
)))
.call()
.content();
在 RequestSpec 上,常见能力包括:
| 能力 | 说明 |
|---|---|
system / user | 字符串、Resource,或 Consumer 精细配置 |
messages / options | 补充消息列表与本次选项 |
advisors | 追加 Advisor,并可通过 param 传入上下文 |
tools / toolContext | 注册工具与工具侧上下文 |
templateRenderer | 自定义模板渲染 |
call() / stream() | 同步或流式终止 |
system / user 细粒度写法
模板参数、metadata、Media 都可以在 Consumer 里完成:
import org.springframework.util.MimeTypeUtils;
String text = chatClient.prompt()
.system(s -> s.text("你是 {role} 助手").param("role", "技术"))
.user(u -> u
.text("你好,{name},请总结 {topic}")
.param("name", "Tom")
.param("topic", "ChatClient")
.media(MimeTypeUtils.IMAGE_PNG, pngResource)) // 可选
.call()
.content();
模板渲染发生在变成真正 Message 之前;渲染完成后再进入 Prompt。
挂 Advisor(含记忆场景的典型写法)
import org.springframework.ai.chat.memory.ChatMemory;
String reply = chatClient.prompt()
.user(userInput)
.advisors(a -> a
.advisors(messageChatMemoryAdvisor)
.param(ChatMemory.CONVERSATION_ID, conversationId))
.call()
.content();
这里只需建立印象:多轮不是 ChatClient 内置状态,而是 Advisor + 上下文键。记忆策略与实现细节不属于本章展开范围。
4.5 同步返回:call() → CallResponseSpec
最常用:直接取文本。
String text = chatClient.prompt()
.user(question)
.call()
.content();
也可以取更完整的结构:
| 方法 | 得到什么 |
|---|---|
content() | 最终助手文本(最常用) |
chatResponse() | ChatResponse(Generation、Usage、hasToolCalls 等) |
chatClientResponse() | 带 Advisor 上下文的包装,调试链路时更有用 |
entity(...) | 反序列化为 Java 类型(见下节) |
responseEntity(...) | 同时要元数据 + 强类型对象 |
取完整响应示例:
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.metadata.Usage;
ChatResponse resp = chatClient.prompt()
.system("用中文简要回答")
.user(question)
.call()
.chatResponse();
String text = resp.getResult().getOutput().getText();
Usage usage = resp.getMetadata().getUsage();
boolean needTool = resp.hasToolCalls();
4.6 结构化输出入口:entity(...)
当业务要的是对象而不是散文时,用 entity:
record Answer(String summary, java.util.List<String> bullets) {}
Answer answer = chatClient.prompt()
.user("总结 Spring AI ChatClient:给一句摘要,并列 3 个要点")
.call()
.entity(Answer.class);
需要更强约束时,用 EntityParamSpec 两个开关(彼此独立):
Answer answer = chatClient.prompt()
.user("总结 Spring AI ChatClient:给一句摘要,并列 3 个要点")
.call()
.entity(Answer.class, spec -> spec
.useProviderStructuredOutput() // 供应商 API 级 Schema;默认不启用
.validateSchema()); // 校验失败可带错误反馈重试;不支持 streaming
要点:
-
默认偏提示侧 + Converter;
useProviderStructuredOutput()需要模型 / 供应商支持,不支持时可能静默回退 -
validateSchema()只适合同步路径 -
OpenAI Structured Outputs 通常不接受顶层 JSON 数组 schema;
List<T>+ 原生 structured 容易失败,可包一层容器类型 -
既要 Usage / finishReason,又要强类型:用
responseEntity(...)
选型与失败重试的完整展开,属于结构化输出专题;本章只要会在 Client 上正确取实体即可。
4.7 流式返回:stream() → StreamResponseSpec
import reactor.core.publisher.Flux;
Flux<String> tokens = chatClient.prompt()
.user(question)
.stream()
.content();
// 例如写入 SSE:tokens.subscribe(...)
| 方法 | 适用 |
|---|---|
content() | 打字机体验,逐段文本 |
chatResponse() | 需要增量 metadata 或工具相关帧 |
注意:
-
结构化输出不要指望流式路径上的
validateSchema;同步call().entity(...),或在完整文本上自行解析更稳妥 -
工程需引入 WebFlux(或自行用
SseEmitter桥接);与「会不会流式」是两件事
4.8 Tools:统一走 tools(Object...)
2.0 起,请求级与默认级都收敛到 tools(...) / defaultTools(...)。旧的 toolCallbacks(...) 已标记废弃,迁移时一并改掉。
tools(Object...) 会按元素类型分发:
| 元素类型 | 行为 |
|---|---|
ToolCallback | 直接注册 |
ToolCallbackProvider | 注册 Provider,请求时再解析 |
| 数组 / Collection | 展开后继续按同样规则处理 |
| 其他对象 | 当作带 @Tool 的 POJO;若没有任何 @Tool 方法,会抛异常 |
最小示例:
import java.util.Map;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
class DateTimeTools {
@Tool(description = "获取当前日期(ISO)")
String today() {
return java.time.LocalDate.now().toString();
}
@Tool(description = "按城市查询天气(示例)")
String weather(@ToolParam(description = "城市名") String city) {
return "{\"city\":\"" + city + "\",\"tempC\":28}";
}
}
String reply = chatClient.prompt()
.user("今天几号?北京天气怎么样?")
.tools(new DateTimeTools())
.toolContext(Map.of("tenantId", "acme"))
.call()
.content();
toolContext 传的是工具执行时可取的上下文(租户、用户 ID 等),与模型可见的 JSON 参数是分开的。
再强调覆盖规则:
// 若 Client 上 defaultTools(A),而本次只写 tools(B),
// 则这一次只有 B,不会自动带上 A。
chatClient.prompt()
.tools(onlyForThisApiTools)
.user(question)
.call()
.content();

边界可以记成三句话:
-
ChatClient:编排入口(组装请求、挂 Advisor / Tools、发起 call/stream)
-
Tools:提供可执行能力,结果回灌给模型
-
RAG:通过 Advisor 注入检索上下文,不是
ChatClient内置检索器
本章把 Tools 在 Client 上的挂法讲清楚即可;工具契约、Advisor 链细节、RAG 流水线各自有专章空间,不必在 Client DSL 里一次写完。
4.9 Builder Customizer(Boot)
容器里可以有多个 ChatClientBuilderCustomizer,按 @Order / Ordered 依次作用到 prototype Builder 上。旧的 ChatClientCustomizer 仍可能被收集,但 2.0 更推荐前者。
import org.springframework.ai.chat.client.ChatClientBuilderCustomizer;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
@Configuration
class ChatClientCustomizers {
@Bean
ChatClientBuilderCustomizer loggingAndPersona() {
return builder -> builder
.defaultSystem("你是企业内部助手")
.defaultAdvisors(new SimpleLoggerAdvisor());
}
@Bean
@Order(100)
ChatClientBuilderCustomizer defaultTools(DateTimeTools tools) {
return builder -> builder.defaultTools(tools);
}
}
因为每次注入的 Builder 都是新克隆,某个 @Bean ChatClient 方法里继续改,不会直接污染其他注入点;但仍需注意 Customizer 自身是否持有可变共享状态。
若要完全自管 Tool 循环,可关闭 ToolCalling 自动注册。观测侧若打开 prompt / completion 日志,可能记录敏感内容,生产环境需谨慎。
4.10 常见坑
| 现象 | 先查什么 |
|---|---|
注入不到 ChatClient | Boot 是否提供的是 ChatClient.Builder;是否自己 @Bean 了 Client |
| 某次请求「默认工具没了」 | 是否写了请求级 tools(...)(整组覆盖) |
| Options / temperature 不生效 | 单次 .options(...) 是否写了,是否 .build();是否被更外层覆盖 |
entity 反序列化失败 | 提示是否要求 JSON;是否需要 validateSchema;流式路径不要用校验开关 |
| 多轮「没记忆」 | Client 不存会话;是否挂了 Memory Advisor,以及 conversationId 是否传对 |
| 流式接口 406 / 类型错误 | 是否引入 WebFlux,返回类型是否为 Flux |
4.11 本章小结
到这里应能:
-
用
create/builder/ Boot 注入正确创建ChatClient -
区分 Builder 默认值 与 单次 RequestSpec,尤其是
tools的整组覆盖规则 -
用
prompt()组装 system / user / advisors / tools,并完成call().content()与stream().content() -
会用
entity(...)取对象,知道两个 Spec 开关的基本含义 -
理解 ChatClient 只做编排入口;Tool / RAG / 记忆都是挂上去的能力,不是另一套协议
掌握 Client 之后,读日志、改默认值、排「工具偶发消失」会轻松很多;需要更薄的调用或手工工具循环时,再下沉到 ChatModel。
98



被折叠的 条评论
为什么被折叠?



