W7 周级拆解 Day1 · A 级(必须掌握,面试核心)· 约 4h · 学完能讲清"Java 侧如何用 Spring AI 统一多模型、做拦截治理、并稳定拿到结构化对象"
ChatClient 是 Spring AI 面向应用层的统一对话入口,封装了底层各种模型的 ChatModel 实现(如 QwenChatModel、DeepSeekChatModel、AnthropicChatModel)。业务代码只面向 ChatClient 编程,不直接依赖具体厂商 SDK。
UserMessage / SystemMessage / AssistantMessage / Message 多角色;支持 PromptTemplate 占位符渲染。call() 一次拿到完整结果;stream() 返回 Flux<String> 做打字机效果。chatClient.prompt().system(...).user(...).call().content() 流畅 API。.entity(MyClass.class) 直接拿到 Java 对象(见 s7)。ChatModel,真正的网络调用、协议差异仍在 ChatModel 实现里,模型特有参数(如某个厂商的 thinking 开关)仍需透传。② 别把 ChatClient 当单例乱灌状态——它是无状态门面,但传入的 Prompt / Advisor 链要小心线程安全。③ 流式 stream() 返回的 Flux 必须被订阅(subscribe 或 collectList),否则不会发起请求。@Bean
public ChatClient chatClient(ChatModel chatModel) {
return ChatClient.builder(chatModel)
.defaultSystem("你是医疗保险理赔助手,回答需基于知识库")
.defaultAdvisors(new LoggingAdvisor(), new RetryAdvisor())
.build();
}
// 非流式,拿文本
String ans = chatClient.prompt()
.user("特药理赔需要哪些材料?")
.call()
.content();
// 流式
Flux<String> flux = chatClient.prompt()
.user("解释等待期")
.stream()
.content();
// 直接拿结构化对象
ClaimDecision d = chatClient.prompt()
.user(u -> u.text("判断该理赔是否成立:{0}", claimJson))
.call()
.entity(ClaimDecision.class);
| 方法 | 返回 | 用途 |
|---|---|---|
call().content() | String | 非流式取纯文本 |
call().entity(Class) | Java Bean | 结构化输出(见 s7) |
call().response() | ChatResponse | 要拿 token 用量 / 元数据时用 |
stream().content() | Flux<String> | 流式打字机 |
prompt().advisors(a...) | 链式 | 本次调用叠加/覆盖 Advisor |
call().entity(Class) 拿强类型对象而非手写 JSON parse;需要成本统计时 response().getMetadata().getUsage() 取 token 用量(呼应 W4 Trace 的 cost 字段)。call().content() 拿文本、entity(Class) 拿对象、response() 拿 token 用量、stream() 流式。Spring AI 为每个厂商提供了 ChatModel 实现,并通过 Spring Boot 的 spring-ai-starter-model-* 自动装配。只要换配置(或换 bean),ChatClient 背后就换了模型。
# application.yml 仅改 provider 和 key,业务代码不动
spring:
ai:
qwen:
api-key: ${QWEN_KEY}
chat.options.model: qwen-max
# 切换 DeepSeek 时换成 spring.ai.deepseek.*
ChatModel。适合"测试用便宜模型、生产用强模型"。ChatModel,上层按场景选(如简单问答走 Qwen、复杂推理走 DeepSeek、长文摘要走 Claude)。这正是 W7D4 AI Gateway 路由要干的事,Spring AI 在应用层、Gateway 在接入层。ModelRouter 做"按 provider 选 HTTP client + 统一响应结构";Spring AI 是框架级提供 ChatModel 抽象 + starter 自动装配,省去手写协议适配,但抽象层级更高、可控性略低。.with(ChatOptions) 透传,别假设完全等价。② 切换模型后输出风格/JSON 合规性会变,结构化输出成功率要重新压测。③ 统一不等于"一个 prompt 通吃所有模型"——长上下文、工具调用能力因模型而异。Advisor 是 Spring AI 在 ChatClient 调用前后插入的拦截器,概念上完全类比 Servlet Filter / Spring 的 HandlerInterceptor:围绕一次对话请求做横切处理(日志、重试、限流、改写、记忆)。
AdvisedRequest,可改 prompt、加上下文,再 adviseCall/adviseStream 放行到下一环。@Order 或实现 getOrder(),数字小先执行。| Advisor | 作用 | 企业场景 |
|---|---|---|
LoggingAdvisor | 打印请求/响应(可脱敏) | 调试、审计留痕 |
RetryAdvisor / Re2Advisor | 失败重试(指数退避) | 网络抖动、限流 429 |
RateLimitAdvisor | 令牌桶限流 | 保护后端模型配额 |
MessageChatMemoryAdvisor | 注入会话记忆 | 多轮对话(见 W7D2) |
QuestionAnswerAdvisor | 内置 RAG(向量检索注入) | 保险知识库问答 |
ChatMemory + Prompt 改写 | 压缩/改写用户问题 | 长上下文裁剪 |
@Bean
public Advisor retryAdvisor() {
return new RetryAdvisor(3, Duration.ofSeconds(1)); // 最多3次,间隔1s
}
public class AuditAdvisor implements Advisor {
private final MeterRegistry meter;
public AuditAdvisor(MeterRegistry meter){ this.meter = meter; }
@Override public int getOrder(){ return 1; } // 越早就越靠近入口
@Override
public AdvisedResponse adviseCall(AdvisedRequest req, CallAdvisorChain chain) {
long t0 = System.nanoTime();
AdvisedResponse resp = chain.nextAroundCall(req); // 放行
meter.timer("ai.call").record(System.nanoTime()-t0, TimeUnit.NANOSECONDS);
return resp;
}
// adviseStream 类似,注意流式要透传 Flux
}
通过 ChatClient.builder().defaultAdvisors(auditAdvisor, retryAdvisor) 注册全局 Advisor,或在单次调用 .advisors(...) 临时叠加。
getOrder() 定顺序、adviseCall/adviseStream 包裹链,在 builder().defaultAdvisors() 注册。流式的 adviseStream 必须把 Flux 透传下去,不能提前 collect。adviseStream 时务必透传 Flux,若 collectList() 再返回就失去了流式,还可能导致上游异常时拿不到部分结果。② getOrder() 和 @Order 二选一,混用易乱;建议只用 getOrder() 集中管理。③ 多 Advisor 修改同一字段要防互相覆盖(用 AdvisedRequest.mutate() 生成新对象而非改原对象)。Structured Output 让模型返回的内容自动绑定成 Java 对象,底层靠 JSON Schema 约束 + 解析。Spring AI 用 BeanOutputConverter / StructuredOutputConverter 把 Java 类型信息翻译成 JSON Schema 注入 prompt 或 response_format,再把响应解析回对象。
response_format=json + Pydantic 校验(Python 侧)。BeanOutputConverter 等于"Jackson 版 schema 自动生成 + 反序列化"。但语义校验(值是否合理)仍是业务自己的事——框架只保证形,Spring 的 @NotNull / @Min Bean Validation 可补一层。@JsonProperty / @JsonClassDescription),字段没描述、没类型约束,模型更易瞎填。② 复杂嵌套/泛型/Optional 的 schema 生成可能不全,成功率下降,越简单越稳。③ 解析失败不要静默返回 null,要抛异常走重试/降级(呼应 W1 校验重试)。@JsonClassDescription("特药理赔审核结论")
public class ClaimDecision {
@JsonPropertyDescription("理赔是否成立") boolean approved;
@JsonPropertyDescription("拒赔原因,成立时为空") String rejectReason;
@JsonPropertyDescription("风险等级 low/medium/high") String riskLevel;
@JsonPropertyDescription("引用的知识库条款 id 列表") List<String> clauseIds;
@JsonPropertyDescription("无法判断时为 true") boolean shouldRefuse;
}
ClaimDecision d = chatClient.prompt()
.user(u -> u.text("根据以下理赔材料给出结论:\n{claim}", claim))
.call()
.entity(ClaimDecision.class); // 内部用 BeanOutputConverter
// 或显式拿 converter
BeanOutputConverter<ClaimDecision> conv = new BeanOutputConverter<>(ClaimDecision.class);
String schema = conv.getJsonSchema();
Prompt p = new Prompt("按 schema 输出:" + schema + "\n材料:" + claim);
ClaimDecision d2 = conv.convert(chatClient.prompt().user(p.contents()).call().content());
叠加 Bean Validation:validator.validate(d) 校验 @NotNull / 枚举 / 范围,失败则带反馈重试(同 W1 思路)。
entity(Class) + Bean Validation(@NotNull/@Pattern/@Min)+ 失败带反馈重试(把校验错误喂回 prompt)+ 超限降级(默认值/转人工)。把这套封成平台级 Advisor 最省心。@JsonClassDescription + @JsonPropertyDescription 给字段加描述,调用 call().entity(ClaimDecision.class) 直接拿对象;再叠 Bean Validation 做语义校验,失败带反馈重试。| 维度 | W1 自研 Model Adapter(轻量网关) | Spring AI(框架级) |
|---|---|---|
| 抽象层级 | 薄封装:统一 HTTP + 响应结构 | 厚框架:ChatClient/Advisor/记忆/工具/向量全套 |
| 多模型 | 手写 ModelRouter 按 provider 选 client | ChatModel 抽象 + starter 自动装配 |
| 结构化 | 手动 response_format + Pydantic | BeanOutputConverter 自动 schema + 反序列化 |
| 治理 | 自己写限流/重试/日志 | Advisor 链 + 内置 Advisor |
| 可控性 | 高,每行都自己写 | 中,受框架约定约束 |
| 开发速度 | 慢,但灵活 | 快,约定优于配置 |
| 适用 | 需极致定制/自研网关(W7D4) | 业务侧快速搭 AI 能力 |
作为 36 岁、Java + 大数据 + 医疗保险背景的候选人,讲 Spring AI 时要落到企业 AI 平台工程能力,而非"会用 API":
entity(Class) + Bean Validation + 降级,保证下游 Java 系统能稳定消费。api/chatclient.html(ChatClient)、api/advisors.html(Advisor)、api/structured-output.html(Structured Output)三章精读。| 组件 | 一句话作用 | 关键点 |
|---|---|---|
| ChatClient | 统一对话门面,业务只面向它编程 | 多角色 Prompt、流式、Advisor 链、entity() 结构化 |
| Advisor | 对话前后拦截器链,做横切治理 | 类比 Filter,可改 prompt、做日志/重试/限流/改写,order 控序 |
| Structured Output | 把模型输出绑成 Java 对象 | Jackson 反射生成 JSON Schema + 反序列化,需校验兜底 |
spring.ai.*.api-key/model 配置或注入不同 ChatModel bean,业务零改动;运行时可注入多个 ChatModel 做场景路由。