Spring AI 从零到生产级掌握
Spring AI 不能只学成“用 ChatClient 调一下模型”。真正的 Java 商业 AI 应用要能解释:Spring Boot 如何自动装配模型客户端,ChatClient 一次调用包含哪些步骤,Prompt 为什么要模板化,结构化输出为什么必须校验,Embedding 和 VectorStore 如何支撑 RAG,Advisor 链如何把记忆、RAG、工具调用、观测串起来,Tool Calling 为什么必须由后端执行和审计。
一句话建立主线:
Spring AI 是 Spring 生态里的 AI 应用工程化层,用 Spring Bean、自动配置、客户端、Advisor、VectorStore 和 Tool Calling 把大模型能力接入业务系统。
如果你想检查自己是否真的从零基础学懂到能面试、能落地、能排查,按 Spring AI 从零到精通验收清单 逐项验收。
学习目标
学完这一页,你要能做到:
- 解释 Spring AI 和 Spring Boot、AI 基础能力的关系。
- 解释
ChatModel、ChatClient、Prompt、ChatResponse的职责。 - 解释同步调用、流式调用、结构化输出各适合什么场景。
- 解释 Advisor 链为什么重要,Advisor 顺序为什么会影响结果。
- 解释 Chat Memory、RAG Advisor、Tool Calling Advisor 如何组合。
- 解释 EmbeddingModel、VectorStore、Document、SearchRequest 的关系。
- 解释 Spring AI RAG 从文档入库到问题回答的完整过程。
- 解释 Tool Calling 为什么不是让模型直接访问业务 API。
- 解释生产环境里的权限、限流、审计、成本、评估、观测和回滚。
- 设计一个企业知识库、采集异常助手、资产问答助手或业务工具 Agent。
学习路线
flowchart TD
A["Spring Boot 基础"] --> B["AI 基础<br/>Token/Prompt/RAG"]
B --> C["ChatClient<br/>模型调用入口"]
C --> D["Prompt 和结构化输出"]
D --> E["Embedding 和 VectorStore"]
E --> F["RAG Advisor"]
F --> G["Tool Calling"]
G --> H["Memory 和 Advisor 链"]
H --> I["安全、观测、成本和评估"]Spring AI 是工程框架,不是模型训练框架。它解决的是“Java 后端如何稳定、可治理地调用模型和 AI 能力”。
第一步:Spring AI 在系统里的位置
不用 Spring AI,也可以直接用 HTTP 调模型:
// 伪代码:手写 HTTP 调模型
HttpRequest request = buildModelRequest(prompt);
HttpResponse response = httpClient.send(request);小 Demo 可以这样写,但项目变复杂会遇到:
| 问题 | 后果 |
|---|---|
| 模型调用到处散落 | 难切换模型、难统一超时和日志 |
| Prompt 写在代码里 | 版本不可控,难灰度和回滚 |
| RAG 链路手写 | 文档切分、检索、重排、引用难维护 |
| 工具调用无规范 | 权限、参数、审计风险大 |
| 没有观测 | 不知道 Token、耗时、失败率、成本 |
Spring AI 的作用是把这些能力抽象成 Spring 风格组件:
flowchart TD
A["Controller"] --> B["Service"]
B --> C["ChatClient"]
C --> D["Advisor 链"]
D --> E["ChatModel"]
D --> F["VectorStore"]
D --> G["ToolCallback"]
E --> H["模型服务"]
F --> I["向量数据库"]
G --> J["业务系统"]第二步:核心对象关系
| 对象 | 作用 | 类比理解 |
|---|---|---|
ChatModel | 底层聊天模型能力 | 模型驱动 |
ChatClient | 面向应用的流式 API | 推荐业务入口 |
Prompt | 模型输入,包含消息和参数 | 请求体 |
Message | system/user/assistant/tool 消息 | 对话上下文 |
ChatResponse | 模型响应和元数据 | 响应对象 |
Advisor | 调用前后增强 | AI 版拦截器 |
EmbeddingModel | 文本转向量 | 语义编码器 |
VectorStore | 向量存储和检索 | RAG 检索层 |
ToolCallback | 可被模型请求的工具 | 后端能力描述 |
一次最小调用:
@Service
public class AiChatService {
private final ChatClient chatClient;
public AiChatService(ChatClient.Builder builder) {
this.chatClient = builder
.defaultSystem("你是一个耐心的 Java 技术文档助手。")
.build();
}
public String chat(String question) {
return chatClient.prompt()
.user(question)
.call()
.content();
}
}这个 Demo 只解决“能问答”。生产还要补权限、限流、RAG、结构化输出、日志和评估。
第三步:ChatClient 调用全过程
flowchart TD
A["业务代码调用 ChatClient"] --> B["组装 system/user 消息"]
B --> C["合并默认参数和本次参数"]
C --> D["执行 Advisor 链前置逻辑"]
D --> E["调用 ChatModel"]
E --> F["模型返回响应"]
F --> G["执行 Advisor 链后置逻辑"]
G --> H["解析 content/entity/ChatResponse"]ChatClient 比直接调用 ChatModel 更适合业务代码,因为它提供:
- 流式 fluent API。
- 默认 system prompt。
- 默认 Advisor。
- 运行时 Advisor。
- 工具调用自动链路。
- 结构化输出。
- 更好的扩展点。
同步调用
适合分类、抽取、短问答:
String answer = chatClient.prompt()
.user("用一句话解释 RAG")
.call()
.content();流式调用
适合聊天、长答案、前端打字机效果:
Flux<String> stream = chatClient.prompt()
.user("详细解释 Spring AI 的 RAG 流程")
.stream()
.content();流式不是模型更快完成,而是边生成边返回,降低用户等待感。
第四步:Prompt 和结构化输出
Prompt 在商业系统里必须模板化。
错误做法:
String prompt = "帮我分析:" + userInput;问题:
- 没有角色。
- 没有边界。
- 没有输出格式。
- 不确定时可能编造。
- 难做版本管理。
更好的 Prompt:
String result = chatClient.prompt()
.system("""
你是医疗数据采集平台的异常分析助手。
只能根据给定上下文回答。
如果无法判断,请回答:需要人工排查。
输出必须是 JSON。
""")
.user("""
异常日志:
{log}
请输出:
{
"reason": "原因",
"suggestion": "处理建议",
"riskLevel": "LOW/MEDIUM/HIGH"
}
""")
.call()
.content();结构化输出要映射成对象:
public record ErrorAnalysis(
String reason,
String suggestion,
String riskLevel
) {
}ErrorAnalysis analysis = chatClient.prompt()
.user(promptText)
.call()
.entity(ErrorAnalysis.class);为什么还要校验:
| 风险 | 例子 |
|---|---|
| 字段缺失 | 没有 riskLevel |
| 类型错误 | 数字输出成文字 |
| 枚举越界 | 输出 VERY_HIGH |
| 内容不可信 | 编造原因 |
生产上应继续用 Bean Validation 或业务规则校验。
第五步:Advisor 链
Advisor 可以理解成 AI 调用链上的拦截器。它可以在模型调用前后修改请求、补充上下文、加入记忆、做 RAG、处理工具调用、记录观测数据。
flowchart TD
A["ChatClient 请求"] --> B["Memory Advisor"]
B --> C["RAG Advisor"]
C --> D["Tool Calling Advisor"]
D --> E["ChatModel"]
E --> F["响应返回 Advisor 链"]Advisor 顺序很重要。先加记忆还是先 RAG,会影响 Prompt 里上下文如何组织;工具调用 Advisor 在循环中也会影响多轮工具结果。
常见 Advisor:
| Advisor | 作用 |
|---|---|
MessageChatMemoryAdvisor | 把历史对话加入上下文 |
VectorStoreChatMemoryAdvisor | 从向量库取长期记忆 |
QuestionAnswerAdvisor | 简单 RAG,向量检索后补资料 |
RetrievalAugmentationAdvisor | 模块化 RAG 流程 |
ToolCallingAdvisor | 处理工具调用循环 |
SafeGuardAdvisor | 内容安全防护 |
示例:记忆 + RAG。
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(chatMemory).build(),
QuestionAnswerAdvisor.builder(vectorStore).build()
)
.build();调用时带会话 ID:
String answer = chatClient.prompt()
.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId))
.user(question)
.call()
.content();第六步:Memory 不是万能上下文
Memory 用来保存对话上下文,但不能无限保存。
常见策略:
| 策略 | 说明 |
|---|---|
| 窗口记忆 | 只保留最近 N 轮 |
| 摘要记忆 | 把历史压缩成摘要 |
| 向量记忆 | 按语义检索相关历史 |
| 外部存储 | Redis、数据库等 |
为什么不能所有历史都塞进 Prompt:
- Token 成本高。
- 延迟高。
- 超过上下文窗口。
- 历史噪声影响回答。
- 可能带入过期或敏感信息。
生产建议:
- 会话型助手保留最近窗口。
- 客服场景按工单 ID 隔离。
- 多租户场景 Memory 必须带租户和用户过滤。
- 高风险工具调用不要只依赖历史记忆,必须查业务系统当前状态。
第七步:Embedding 和 VectorStore
RAG 的基础是 Embedding 和 VectorStore。
文档入库:
flowchart TD
A["读取文档"] --> B["清洗文本"]
B --> C["切分 chunk"]
C --> D["补 metadata<br/>租户、部门、来源、版本"]
D --> E["EmbeddingModel 生成向量"]
E --> F["VectorStore 写入"]问答检索:
flowchart TD
A["用户问题"] --> B["EmbeddingModel 问题向量化"]
B --> C["VectorStore similaritySearch"]
C --> D["返回相关 Document"]
D --> E["拼接到 Prompt"]
E --> F["模型基于资料回答"]Document 示例:
Document doc = new Document(
"热点 key 过期可能导致缓存击穿,应使用互斥锁或逻辑过期。",
Map.of(
"tenantId", "t1",
"department", "backend",
"source", "redis-guide.md"
)
);
vectorStore.add(List.of(doc));检索时要过滤权限:
SearchRequest request = SearchRequest.builder()
.query(question)
.topK(5)
.filterExpression("tenantId == 't1' && department == 'backend'")
.build();
List<Document> docs = vectorStore.similaritySearch(request);不做 metadata 权限过滤,企业知识库很容易越权泄露资料。
第八步:Spring AI RAG
最简单 RAG 可以用 QuestionAnswerAdvisor。
String answer = ChatClient.builder(chatModel)
.build()
.prompt()
.advisors(QuestionAnswerAdvisor.builder(vectorStore).build())
.user(question)
.call()
.content();完整 RAG 不只是检索:
flowchart TD
A["用户问题"] --> B["问题改写"]
B --> C["权限过滤"]
C --> D["向量检索"]
D --> E["关键词检索"]
E --> F["合并召回"]
F --> G["重排 rerank"]
G --> H["引用片段拼接"]
H --> I["模型生成答案"]
I --> J["引用校验和拒答"]RetrievalAugmentationAdvisor 更适合模块化 RAG:你可以控制查询转换、检索、过滤、文档拼接等步骤。
RAG 常见问题:
| 问题 | 原因 | 处理 |
|---|---|---|
| 找不到答案 | 切片差、Embedding 不适配 | 优化切分、混合检索 |
| 答非所问 | 召回错、重排差 | rerank、评估集 |
| 泄露资料 | 没有 metadata 过滤 | 检索前带权限条件 |
| 编造引用 | Prompt 和校验不足 | 引用 ID 校验 |
| 成本高 | 塞太多 chunk | 限制 topK、摘要、重排 |
第九步:Tool Calling
Tool Calling 不是让模型直接访问数据库或业务 API。
正确理解:
- 应用把工具定义告诉模型。
- 模型决定是否请求调用工具,并给出参数。
- Spring AI / 应用侧执行工具。
- 工具结果返回给模型。
- 模型继续生成最终回答。
flowchart TD
A["用户问题"] --> B["ChatClient 发送工具定义"]
B --> C["模型请求调用工具"]
C --> D["ToolCallingAdvisor 接收请求"]
D --> E["应用执行 Java 工具"]
E --> F["工具结果返回模型"]
F --> G["模型生成最终回答"]工具 Demo:
public class AssetTools {
private final AssetService assetService;
public AssetTools(AssetService assetService) {
this.assetService = assetService;
}
@Tool(description = "根据资产编号查询资产状态")
public AssetStatus getAssetStatus(AssetStatusRequest request) {
return assetService.getStatus(request.assetCode());
}
}调用:
String answer = chatClient.prompt()
.user("帮我查询资产 A-1001 当前状态")
.tools(new AssetTools(assetService))
.call()
.content();安全重点:
| 必做 | 为什么 |
|---|---|
| 后端校验用户权限 | 模型不能决定用户能看什么 |
| 参数校验 | 防止错误调用和注入 |
| 幂等号 | 防止重复创建、重复发送 |
| 高风险二次确认 | 防止误删、误改、误发 |
| 审计日志 | 追踪模型请求了什么工具 |
高风险工具不要直接开放:
- 删除数据。
- 修改价格。
- 改权限。
- 发外部通知。
- 执行 SQL。
- 触发支付或退款。
第十步:模型适配和配置
Spring AI 支持多种模型提供方和 OpenAI 兼容接口。工程上要把模型当成可替换资源。
配置通常包括:
| 配置 | 作用 |
|---|---|
| base-url | 模型服务地址 |
| api-key | 访问密钥 |
| model | 模型名称 |
| temperature | 随机性 |
| max-tokens | 输出上限 |
| timeout | 超时时间 |
建议:
- 密钥放环境变量或配置中心,不放代码。
- 不同场景使用不同模型。
- 简单分类用小模型,复杂问答用强模型。
- 统一封装模型路由,避免业务代码写死模型名。
第十一步:生产化治理
AI 接口上线要有这些能力:
| 能力 | 说明 |
|---|---|
| 鉴权 | 用户是否能使用 AI 功能 |
| 数据权限 | RAG 检索只查有权资料 |
| 限流 | 按用户、租户、场景限制调用 |
| 成本统计 | Token、模型、请求次数 |
| 超时和重试 | 防止模型接口拖垮业务 |
| 缓存 | FAQ 和相似问题缓存 |
| 评估集 | 改 Prompt/模型/RAG 后回归测试 |
| 审计 | 记录输入、召回资料、工具调用、输出 |
| 脱敏 | 敏感数据不进模型或日志 |
| 回滚 | Prompt、模型、知识库版本可回退 |
观测指标:
flowchart TD
A["AI 请求"] --> B["记录模型名"]
A --> C["记录 Token"]
A --> D["记录耗时"]
A --> E["记录 RAG 命中"]
A --> F["记录工具调用"]
A --> G["记录错误码"]Spring AI 建立在 Spring 观测体系上,可以对 ChatClient、ChatModel、EmbeddingModel、VectorStore 等组件做指标和链路追踪。
第十二步:商业场景
企业知识库
文档上传 -> 切分 -> Embedding -> VectorStore -> QuestionAnswerAdvisor -> 带引用回答关键:
- 文档 metadata 必须包含租户、部门、密级、版本。
- 检索必须带权限过滤。
- 回答必须带引用来源。
- 无资料时拒答。
医疗数据采集异常助手
采集异常日志 -> Prompt 模板 -> RAG 查排查手册 -> 模型生成原因和建议 -> 人工确认关键:
- 模型只给建议,不直接修改采集任务。
- 高风险操作需要人工确认。
- 日志脱敏。
资产平台业务 Agent
用户问题 -> Tool Calling 查询资产/采集批次/字典 -> 汇总回答关键:
- 查询工具可开放。
- 写操作要二次确认。
- 工具调用带审计和幂等。
生产排查流程
模型没按预期回答
flowchart TD
A["回答异常"] --> B["检查 Prompt"]
B --> C["检查模型参数"]
C --> D["检查 Advisor 顺序"]
D --> E["检查 RAG 召回资料"]
E --> F["检查结构化输出校验"]
F --> G["补评估用例"]RAG 找不到资料
flowchart TD
A["RAG 无答案"] --> B["文档是否入库"]
B --> C["chunk 是否合理"]
C --> D["Embedding 是否成功"]
D --> E["metadata 过滤是否过严"]
E --> F["topK 是否太小"]
F --> G["是否需要混合检索和重排"]工具调用出错
flowchart TD
A["工具调用出错"] --> B["模型是否请求正确工具"]
B --> C["参数 schema 是否清楚"]
C --> D["后端参数校验是否通过"]
D --> E["用户权限是否足够"]
E --> F["业务接口是否成功"]
F --> G["审计日志是否完整"]常见坑
| 坑 | 后果 | 正确做法 |
|---|---|---|
| 只用 ChatClient 不做权限 | AI 越权回答 | Controller 和 RAG 检索都做权限 |
| Prompt 到处硬编码 | 无法灰度和回滚 | 模板化、版本化 |
| RAG 不做引用 | 错了无法追踪 | 返回引用来源 |
| VectorStore 不存 metadata | 无法按租户/部门过滤 | 入库时写完整元数据 |
| Tool 直接改业务状态 | 误操作真实系统 | 二次确认、幂等、审计 |
| 所有场景同一个大模型 | 成本高 | 模型分层路由 |
| 不记录 Token | 成本失控 | 指标和账单监控 |
| 不做评估 | 改动后不知道效果 | 建评估集和回归流程 |
最小完整 Demo:知识库问答
@Service
public class KnowledgeQaService {
private final ChatClient chatClient;
public KnowledgeQaService(ChatClient.Builder builder, VectorStore vectorStore) {
this.chatClient = builder
.defaultSystem("""
你是企业知识库助手。
只能基于检索到的资料回答。
如果资料中没有依据,请回答:资料中未找到依据。
""")
.defaultAdvisors(QuestionAnswerAdvisor.builder(vectorStore).build())
.build();
}
public String answer(String question, String tenantId) {
return chatClient.prompt()
.advisors(a -> a.param("tenantId", tenantId))
.user(question)
.call()
.content();
}
}真实项目还要把 tenantId 转成 VectorStore 的过滤条件,确保只能检索当前用户有权访问的文档。
面试标准回答
Spring AI 是什么
Spring AI 是 Spring 生态的 AI 应用工程化框架,用 Spring Boot 自动配置、Bean、ChatClient、EmbeddingModel、VectorStore、Advisor 和 Tool Calling,把大模型、RAG、工具调用和观测能力接入 Java 后端业务系统。它不是训练大模型的框架,而是让模型能力在业务系统里可维护、可治理、可替换。
ChatClient 调用链怎么说
业务代码通过 ChatClient 组装 system/user 消息、模型参数和 Advisor。请求先经过 Advisor 链,例如记忆、RAG、工具调用、安全和观测,再调用底层 ChatModel。模型返回后,Advisor 可以继续处理响应,最后业务代码拿到文本、结构化对象或完整 ChatResponse。
Spring AI 怎么做 RAG
文档先解析、清洗、切分成 chunk,调用 EmbeddingModel 生成向量后写入 VectorStore,同时保存租户、部门、来源、版本等 metadata。用户提问时,问题也会向量化,再按权限过滤做相似度检索,把召回资料拼进 Prompt 交给模型回答。Spring AI 可以用 QuestionAnswerAdvisor 做简单 RAG,也可以用 RetrievalAugmentationAdvisor 做模块化 RAG。
Tool Calling 安全怎么保证
模型只能请求工具调用和生成参数,真正执行工具的是应用后端。后端必须校验用户权限、参数合法性、业务状态和幂等性,高风险操作要二次确认,并记录审计日志。不能让模型直接访问数据库、执行任意 SQL 或绕过业务权限。
关联知识点
- Spring AI 学习总览
- Spring AI 从零到精通验收清单
- Spring AI 商业生产场景
- 快速入门
- ChatClient
- Prompt 与结构化输出
- RAG 知识库
- Tool Calling
- 生产化治理
- AI 从零到商业生产级掌握
- RAG 流程
- 工具调用
- AI 评估
- AI 安全
本章小结
Spring AI 的核心不是“Java 调模型”,而是把 AI 应用链路放进 Spring 工程体系:ChatClient 负责模型调用入口,Advisor 链负责记忆、RAG、工具调用和增强,Embedding 与 VectorStore 支撑知识检索,Tool Calling 把模型和业务能力连接起来,生产治理负责权限、安全、观测、成本、评估和回滚。只有把这些串起来,才能从 Demo 走到可上线的商业 AI 应用。
