Spring AI聊天补全组件排行榜:ChatClient与Advisor
全面掌控 Spring AI 聊天补全:ChatClient、PromptTemplate、Advisor 实战精解
为什么必须理解 ChatClient 的运作机制?
上一章我们跑通了第一个 Spring AI 应用,但只是 "Hello World" 级别。生产环境落地时,你离不开:系统角色设定、提示词模板化、Token 用量监控、请求响应拦截……这些能力 Spring AI 早已封装好。本文将从底层到高阶,彻底拆解 ChatClient 的全貌。
核心概念一览
在 Spring AI 中,一次对话请求从发起到返回,经历如下链路:
你的代码 → ChatClient → [Advisor链] → ChatModel → LLM API → ChatResponse → 你的代码
组件职责地图:
| 组件 | 职责 | 类比 |
|---|---|---|
ChatClient | 面向开发者的流畅 API 入口 | 餐厅前台 |
ChatModel | 底层与 LLM 交互的模型接口 | 后厨大厨 |
Prompt | 封装消息列表 + 选项的请求对象 | 菜单 + 口味备注 |
Message | 单条消息(用户/系统/助手/工具) | 菜单上的一道菜 |
Advisor | 请求/响应的拦截处理链 | 服务员、质检员 |
第一节:底层接口设计剖析
1.1 通用 Model 接口
Spring AI 定义了一套通用接口,所有 AI 模型都遵循同一契约:
public interface Model
直白地说:无论 GPT、Claude 还是 Qwen,调用方式统一为 call(请求) → 返回 响应。Spring 的核心优雅就在这里——统一抽象,屏蔽底层差异。
ModelRequest 负责请求(指令 + 选项),ModelResponse 负责响应(结果 + 元数据)。而 ModelResult 内部拆分为 output(AI 生成的内容)和 metadata(生成过程的元数据)。
1.2 ChatModel:专为对话设计的 Model
public interface ChatModel extends Model
ChatModel 将请求类型特化为 Prompt,响应类型特化为 ChatResponse。每个模型实现(例如 OllamaChatModel)都携带自己的默认选项。
第二节:Message 的四种角色定位
一次完整对话中,消息分为四种角色:
| 类型 | 英文 | 作用 | 场景 |
|---|---|---|---|
| 用户消息 | USER | 人说的 | "帮我写个排序算法" |
| 助手消息 | ASSISTANT | AI 回的 | 生成的代码/回答 |
| 系统消息 | SYSTEM | 给 AI 的"人设" | "你是一个 Java 专家" |
| 工具消息 | TOOL | 函数调用结果 | 查询天气后的返回数据 |
关键洞察:系统消息(SystemMessage)是控制 AI 行为的"隐形遥控器"。默认的系统消息 "You're a helpful assistant." 过于宽泛,生产环境务必自定义!
Prompt prompt = new Prompt(List.of(new SystemMessage("You are a Java programming expert."),new UserMessage(input)));
️ 第三节:ChatOptions 参数调优实战
大模型暴露了许多可调参数。Spring AI 通过 ChatOptions 开放这些能力:
public interface ChatOptions extends ModelOptions {String getModel(); // 模型名称Double getFrequencyPenalty();// 频率惩罚(防重复)Integer getMaxTokens();// 最大输出 token 数Double getPresencePenalty(); // 主题惩罚(防跑题)List
3.1 全局默认配置(application.yaml)
spring:ai:ollama:chat:options:model: qwen3:0.6btemperature: 0.7 # 0=保守,1=放飞
3.2 请求级覆盖(代码运行时动态调整)
Prompt prompt = new Prompt(input, OllamaChatOptions.builder().temperature(0.1) // 本次要求严谨.stop(List.of("Observation:")).build());
第四节:Token 用量监控,防止账单超支
调用云模型按 Token 计费,拿到响应后第一件事就是检查用量!
ChatResponse response = chatClient.prompt().user(input).call().chatResponse();// AI 回复的文本String content = response.getResult().getOutput().getText();// Token 用量详情Usage usage = response.getMetadata().getUsage();int promptTokens = usage.getPromptTokens();// 输入消耗int completionTokens = usage.getCompletionTokens();// 输出消耗long totalTokens = usage.getTotalTokens(); // 总计
ChatResponseMetadata 还包含哪些数据?
RateLimit:限流信息(剩余调用次数、重置时间)Usage:Token 消耗明细PromptMetadata:提示词过滤元数据
️ 第五节:ChatClient 链式 API 实战演练
ChatClient 是 Spring AI 给开发者的"糖衣炮弹",链式调用极度丝滑:
5.1 最简调用方式
String content = chatClient.prompt().system("You are a master chef.") // 设定角色.user("How to cook fish?")// 用户提问.call()// 执行调用.content();// 提取文本结果
5.2 直接传入现有 Prompt 对象
Prompt prompt = new Prompt("hello");String content = chatClient.prompt(prompt).call().content();
5.3 ChatClient.Builder 预置默认值
ChatClient chatClient = builder.defaultSystem("你是一个专业的技术翻译")// 默认系统消息.defaultAdvisors(loggingAdvisor) // 默认顾问.defaultOptions(options) // 默认选项.build();
Builder 方法速查表:
| 方法 | 作用 |
|---|---|
defaultUser | 预设默认用户消息 |
defaultSystem | 预设默认系统消息 |
defaultTools | 预设可用工具 |
defaultOptions | 预设聊天选项 |
defaultAdvisors | 预设顾问链 |
第六节:PromptTemplate 提示词模板——告别硬编码拼接
6.1 模板的价值
设想一个烹饪建议服务,用户只输入 "鱼",但发给 AI 的提示词应该是:"如何在 5 分钟内做一道美味的鱼?" 这种"用户输入 + 固定模板"的模式,用 PromptTemplate 最合适。
6.2 模板文件 + 变量替换
src/main/resources/prompts/cooking.st:
How to cook {dish} in 5 minutes?
Java 代码:
private Resource promptResource;public String chat(String dish) {Prompt prompt = new PromptTemplate(promptResource).create(Map.of("dish", dish));return chatClient.prompt(prompt).call().content();}
6.3 模板 + 系统消息组合技
Message userMessage = new PromptTemplate(promptResource).createMessage(Map.of("dish", dish));Prompt prompt = new Prompt(List.of(new SystemMessage("You are a master chef."),userMessage));
6.4 Builder 模式 + 默认值
PromptTemplate template = PromptTemplate.builder().resource(promptResource).variables(Map.of("value", "hello"))// 默认值.build();// 运行时覆盖Prompt prompt = template.create(Map.of("value", "world"));
6.5 自定义模板引擎
默认使用 StringTemplate({var} 语法),如果希望切换,可以自行实现:
public interface TemplateRenderer extends BiFunction
️ 第七节:Advisor 顾问链——Spring AI 的"中间件"
这是 Spring AI 最具架构感的设计!Advisor 类似 Spring 的 AOP 拦截器,在请求到达模型前后插入通用逻辑。
7.1 核心接口
public interface CallAdvisor extends Advisor {ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain);}
执行原理:多个 Advisor 组成链条,每个 Advisor 可以:
- 修改请求(
ChatClientRequest) - 调用下一个 Advisor(
chain.nextCall()) - 修改响应(
ChatClientResponse) - 直接返回,短路后续执行
7.2 写个日志顾问,调试利器
public class LoggingAdvisor implements CallAdvisor {private static final Logger LOGGER = LoggerFactory.getLogger("ChatClient.debugger");public ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) {debug(request);// 记录请求var response = chain.nextCall(request); // 继续执行debug(response); // 记录响应return response;}public int getOrder() {return Ordered.LOWEST_PRECEDENCE;// 放在最后执行}}
启用日志(application.yaml):
logging:level:ChatClient.debugger: DEBUG
7.3 修改请求:重写用户文本
public class RewriteUserTextAdvisor implements CallAdvisor {public ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) {if (request.context().containsKey("updated_user_text")) {String updatedText = (String) request.context().get("updated_user_text");// 使用 mutate 构建新的不可变请求var mutatedRequest = request.mutate().prompt(request.prompt().augmentUserMessage(updatedText)).build();return chain.nextCall(mutatedRequest);}return chain.nextCall(request);}}
REST 控制器里使用:
public class RewriteController {private final ChatClient chatClient;public RewriteController(ChatClient.Builder builder) {this.chatClient = builder.defaultAdvisors(new RewriteUserTextAdvisor()).build();}public String rewrite( Request req) {return chatClient.prompt().user(req.input()).advisors(spec -> spec.param("updated_user_text", StringUtils.trimToEmpty(req.updatedInput()))).call().content();}}
7.4 两种启用方式
| 方式 | 代码 | 生效范围 |
|---|---|---|
| 全局默认 | builder.defaultAdvisors(advisor) | 该 Builder 创建的所有 ChatClient |
| 单次请求 | .prompt().advisors(advisor).call() | 仅当前请求 |
7.5 顾问顺序的重要性
Advisor 继承自 Ordered,数字越小越先执行。Spring AI 内置顾问有固定顺序,自定义顾问建议使用大于 0 的值,避免冲突。
第八节:递归顾问——Spring AI 1.1 的王炸特性
Spring AI 1.1 之前,顾问链每个 Advisor 只能执行一次。1.1 引入了递归顾问,让链的一部分可以重复执行——这就为复杂 Agent 工作流提供了可能。
8.1 核心:copy 方法
CallAdvisorChain.copy(this) 可以复制当前 Advisor 之后的剩余链条,生成一个子链反复调用。
8.2 实战:让 AI 讲三次笑话
public class TellJokeAdvisor implements CallAdvisor {public ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) {var responses = new ArrayList
调用效果:输入 "programmer",返回 3 个关于程序员的笑话!
总结:一张图看懂 Spring AI 聊天补全
┌─────────────────────────────────────────────────────────────┐│ChatClient││.prompt() → .system()/.user() → .call()/.stream() │└────────────────────┬────────────────────────────────────────┘ │┌────────────────────▼────────────────────────────────────────┐│Advisor Chain ││[LoggingAdvisor] → [RewriteAdvisor] → [TellJokeAdvisor] ││ 记录日志修改请求递归执行│└────────────────────┬────────────────────────────────────────┘ │┌────────────────────▼────────────────────────────────────────┐│ChatModel.call(Prompt)││┌─────────────────────────────────────────────┐│││Prompt││││├─ SystemMessage (人设/指令) ││││├─ UserMessage (用户问题)││││└─ ChatOptions (temperature/model等) │││└─────────────────────────────────────────────┘│└────────────────────┬────────────────────────────────────────┘ │ HTTP┌────────────────────▼────────────────────────────────────────┐│LLM API │└────────────────────┬────────────────────────────────────────┘ │┌────────────────────▼────────────────────────────────────────┐│ ChatResponse ││├─ Generation → AssistantMessage (文本)││├─ Usage (Prompt/Completion/Total Tokens)││└─ RateLimit (限流信息)│└─────────────────────────────────────────────────────────────┘
互动时间
你在 Spring AI 实战中踩过哪些坑?
- A:Prompt 模板管理混乱
- B:Advisor 顺序理不清,调试到崩溃
- C:Token 用量监控缺失,月底账单爆炸
- D:其他,评论区见
