Skip to content

Spring AI 从零到精通验收清单 ​

这页用来验收你是否真的把 Spring AI 学到了“能解释原理、能写 demo、能做商业项目、能排查线上问题、能面试回答”的程度。

Spring AI 不是“Java 调一下大模型接口”。真正学懂要能说清楚:

  1. 为什么企业项目需要 Spring AI,而不是直接在 Controller 里写 HTTP 调模型。
  2. ChatClient、ChatModel、Prompt、Advisor、EmbeddingModel、VectorStore、Tool Calling 分别负责什么。
  3. 一次 AI 请求从前端进入后端,到模型返回,中间经过哪些安全、编排、检索、工具、解析和观测步骤。
  4. RAG 为什么要先切分、向量化、保存元数据、权限过滤、召回、重排,再交给模型。
  5. Tool Calling 为什么不能让模型直接决定权限,为什么写操作必须幂等和二次确认。
  6. 结构化输出为什么会失败,生产中如何做校验、重试、降级。
  7. 上线后如何监控 token、耗时、召回质量、幻觉、安全、成本和失败率。

总学习路线 ​

mermaid
flowchart TD
    A["阶段1:理解 Spring AI 解决什么问题"] --> B["阶段2:ChatClient 和 ChatModel 调用链"]
    B --> C["阶段3:Prompt 模板和结构化输出"]
    C --> D["阶段4:Advisor 扩展请求链路"]
    D --> E["阶段5:Embedding 和 VectorStore"]
    E --> F["阶段6:RAG 从入库到问答全过程"]
    F --> G["阶段7:Tool Calling 调用业务能力"]
    G --> H["阶段8:记忆、会话和上下文治理"]
    H --> I["阶段9:生产安全、评估、成本和排查"]
    I --> J["阶段10:面试和商业项目表达"]

这条路线的重点是先建立“后端 AI 编排层”的概念。模型只是能力来源,真正的业务系统还要负责权限、上下文、资料、工具、安全、监控和失败兜底。

阶段1:Spring AI 到底解决什么问题 ​

最原始的模型调用可能这样写:

java
@PostMapping("/chat")
public String chat(@RequestBody ChatRequest request) {
    return httpClient.post("https://model.example.com/chat", request.question());
}

Demo 可以跑,但商业系统会很快失控:

问题不治理的后果
模型供应商接口散落在业务代码里换模型要改很多地方
Prompt 写在各个 Controller不可版本化、不可评估、不可回滚
RAG 检索逻辑分散召回质量难排查,权限过滤容易漏
工具调用没有统一安全边界模型可能调用越权参数或高风险操作
输出没有结构化校验JSON 解析失败,业务流程中断
没有 token 和耗时统计成本失控,慢请求不知道慢在哪里

Spring AI 的定位是把这些能力变成 Spring 风格的工程组件:

mermaid
flowchart TD
    A["业务 Controller"] --> B["AI 编排 Service"]
    B --> C["ChatClient"]
    C --> D["Advisor 链"]
    D --> E["ChatModel"]
    D --> F["VectorStore"]
    D --> G["Tool Callback"]
    E --> H["模型供应商或本地模型"]
    F --> I["向量数据库"]
    G --> J["业务系统"]

如果你只会 ChatClient.prompt().user().call(),只是入门;真正要掌握的是这条链路上每一层为什么存在、出了问题怎么定位。

阶段2:核心对象关系 ​

Spring AI 核心对象可以按“请求、模型、上下文、资料、工具、治理”理解:

对象解决的问题类比
ChatClient面向业务的模型调用入口类似 RestTemplate/WebClient 的 AI 版本
ChatModel底层模型适配真正和模型服务交互
Prompt发送给模型的完整输入系统指令、用户问题、上下文
MessagePrompt 中的消息system、user、assistant、tool
Advisor调用前后增强类似拦截器,补 RAG、记忆、安全规则
EmbeddingModel文本转向量把语义变成可检索向量
VectorStore存储和检索向量RAG 的检索层
Tool Callback暴露 Java 方法给模型模型选择工具,后端执行
ChatResponse模型返回结果包含文本、元数据、token 信息

一次普通聊天调用:

java
@RestController
public class AiChatController {
    private final ChatClient chatClient;

    public AiChatController(ChatClient.Builder builder) {
        this.chatClient = builder
                .defaultSystem("你是企业内部技术助手,回答要准确、简洁、可执行。")
                .build();
    }

    @GetMapping("/ai/chat")
    public String chat(String question) {
        return chatClient.prompt()
                .user(question)
                .call()
                .content();
    }
}

这段代码看起来简单,但底层发生了几件事:

mermaid
flowchart TD
    A["Controller 接收 question"] --> B["ChatClient 创建请求"]
    B --> C["合并默认 system 和 user message"]
    C --> D["执行 Advisor 前置逻辑"]
    D --> E["调用 ChatModel"]
    E --> F["模型服务生成结果"]
    F --> G["执行 Advisor 后置逻辑"]
    G --> H["返回 content"]

面试时不要只说“调用模型返回字符串”,要说出 Prompt 组装、Advisor 增强、ChatModel 适配和响应解析。

阶段3:ChatClient 调用链为什么要分层 ​

ChatClient 面向业务,ChatModel 面向模型适配。

为什么不让业务直接调 ChatModel?

直接调 ChatModel 的问题使用 ChatClient 的好处
每次都要手工组装 Prompt可以统一默认 system、advisor、options
不好复用 RAG、记忆、安全逻辑Advisor 可以统一增强
业务代码绑定模型细节更容易替换模型
流式、结构化输出、工具调用代码分散API 更清晰

同步调用适合短文本:

java
String answer = chatClient.prompt()
        .system("你是订单客服助手。")
        .user("订单 1001 为什么还没发货?")
        .call()
        .content();

流式调用适合长回答和聊天体验:

java
@GetMapping(value = "/ai/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> stream(String question) {
    return chatClient.prompt()
            .user(question)
            .stream()
            .content();
}

流式输出并不一定减少总耗时,它主要降低首字等待,让用户更早看到内容。如果业务要求完整 JSON 校验,直接流式返回可能不合适,因为你必须等完整结果才能解析和校验。

阶段4:Prompt 和结构化输出 ​

Prompt 不是一句“你是专家”。生产 Prompt 至少包含:

部分作用
角色让模型知道身份和回答风格
任务明确要做什么
上下文提供资料、规则、业务数据
约束禁止编造、必须引用、不能泄露
输出格式JSON、表格、步骤、字段
拒答条件资料不足、权限不足、风险过高

结构化输出 Demo:

java
public record TicketClassifyResult(
        String category,
        String priority,
        String reason
) {
}
java
TicketClassifyResult result = chatClient.prompt()
        .system("""
            你是工单分类助手。
            只能输出 JSON。
            category 只能是 DATA_COLLECT、ASSET_MANAGE、SYSTEM_ERROR。
            priority 只能是 LOW、MEDIUM、HIGH。
            """)
        .user("采集任务连续失败,提示数据库连接超时")
        .call()
        .entity(TicketClassifyResult.class);

为什么结构化输出仍然会失败?

失败原因现象处理
Prompt 没约束字段枚举输出字段乱写加 JSON Schema 或明确枚举
用户输入诱导模型改格式多出解释文本输出校验失败后重试
模型能力不足JSON 缺字段换模型或降低任务复杂度
上下文太长关键格式要求被稀释把格式规则放到更稳定位置

生产做法是:模型输出不能直接信任,必须解析、校验、失败重试或降级。

java
public TicketClassifyResult classify(String text) {
    try {
        TicketClassifyResult result = chatClient.prompt()
                .system(CLASSIFY_PROMPT)
                .user(text)
                .call()
                .entity(TicketClassifyResult.class);
        validateResult(result);
        return result;
    } catch (Exception ex) {
        return new TicketClassifyResult("SYSTEM_ERROR", "MEDIUM", "AI 分类失败,进入人工复核");
    }
}

阶段5:Advisor 是什么 ​

Advisor 可以理解为 AI 请求链路上的拦截器。它可以在模型调用前后加入通用能力,例如:

  1. 加入历史对话记忆。
  2. 根据问题检索知识库。
  3. 加入安全提示和拒答规则。
  4. 记录日志、token、耗时。
  5. 对输出做后处理。
mermaid
flowchart TD
    A["用户问题"] --> B["ChatClient"]
    B --> C["Advisor1:安全规则"]
    C --> D["Advisor2:RAG 检索"]
    D --> E["Advisor3:会话记忆"]
    E --> F["ChatModel"]
    F --> G["模型响应"]
    G --> H["Advisor 后处理和日志"]

为什么 Advisor 重要?

如果没有 Advisor,RAG、记忆、安全、日志都要写进每个业务方法。代码重复只是表面问题,更大的问题是安全策略和日志指标不一致,出了线上问题无法统一排查。

阶段6:Embedding 和 VectorStore ​

Embedding 是把文本转换成向量,让系统能用数学距离近似表达语义相似。

mermaid
flowchart TD
    A["文本:数据库连接超时"] --> B["EmbeddingModel"]
    B --> C["向量:[0.12, -0.31, ...]"]
    C --> D["VectorStore"]

为什么入库和查询必须使用同一个 Embedding 模型?

因为向量距离只在同一个向量空间里有意义。文档用模型 A 转向量,问题用模型 B 转向量,距离比较就像用两把不同刻度的尺子量同一个物体,结果不可信。

入库 Demo:

java
@Service
public class DocIngestService {
    private final VectorStore vectorStore;

    public DocIngestService(VectorStore vectorStore) {
        this.vectorStore = vectorStore;
    }

    public void ingest(Long docId, String title, List<String> chunks) {
        List<Document> documents = chunks.stream()
                .map(chunk -> new Document(chunk, Map.of(
                        "docId", docId,
                        "title", title,
                        "status", "active",
                        "tenantId", "hospital-a",
                        "source", "/docs/" + docId
                )))
                .toList();
        vectorStore.add(documents);
    }
}

元数据不是可有可无。没有 tenantId、status、source、docId,你就无法做权限过滤、删除同步、引用来源和问题排查。

阶段7:RAG 全过程 ​

RAG 分成离线入库和在线问答两条链路。

mermaid
flowchart TD
    A["原始文档"] --> B["解析和清洗"]
    B --> C["脱敏和去重"]
    C --> D["按标题、段落、语义切分 Chunk"]
    D --> E["补元数据:来源、版本、权限、租户"]
    E --> F["Embedding 向量化"]
    F --> G["写入 VectorStore"]
mermaid
flowchart TD
    A["用户问题"] --> B["鉴权和租户识别"]
    B --> C["问题改写和关键词提取"]
    C --> D["问题向量化"]
    D --> E["按权限过滤检索 VectorStore"]
    E --> F["召回 TopK Chunk"]
    F --> G["Rerank 和阈值判断"]
    G --> H["组装带引用的 Prompt"]
    H --> I["模型基于资料回答"]
    I --> J["引用校验和日志记录"]

RAG 查询 Demo:

java
@RestController
public class RagController {
    private final ChatClient chatClient;

    public RagController(ChatClient.Builder builder, VectorStore vectorStore) {
        this.chatClient = builder
                .defaultSystem("""
                    你是企业知识库助手。
                    必须优先依据检索资料回答。
                    如果资料不足,明确回答“当前资料不足”。
                    回答末尾列出引用来源。
                    """)
                .defaultAdvisors(QuestionAnswerAdvisor.builder(vectorStore).build())
                .build();
    }

    @GetMapping("/ai/rag")
    public String ask(String question) {
        return chatClient.prompt()
                .user(question)
                .call()
                .content();
    }
}

真实商业项目还要加权限过滤。不能全库召回后再让模型“不要泄露”,因为模型不是权限系统。

java
public String askWithPermission(String question, CurrentUser user) {
    SearchRequest searchRequest = SearchRequest.builder()
            .query(question)
            .topK(8)
            .filterExpression("tenantId == '" + user.tenantId() + "' && status == 'active'")
            .build();

    List<Document> docs = vectorStore.similaritySearch(searchRequest);
    String context = buildContext(docs);

    return chatClient.prompt()
            .system("只能根据资料回答,不能编造。")
            .user("资料:\n" + context + "\n\n问题:" + question)
            .call()
            .content();
}

如果权限过滤放在模型回答阶段,召回结果里可能已经包含用户无权看到的内容。即使模型最后不输出,也可能被 Prompt 注入诱导泄露。

阶段8:Tool Calling 全过程 ​

Tool Calling 的核心不是“模型执行 Java 方法”,而是模型提出工具调用意图,后端负责校验和执行。

mermaid
flowchart TD
    A["用户问题"] --> B["ChatClient 携带工具定义"]
    B --> C["模型判断需要调用哪个工具"]
    C --> D["模型返回工具名和参数"]
    D --> E["Spring AI 映射到 Java 方法"]
    E --> F["后端做鉴权、参数校验、幂等和审计"]
    F --> G["调用业务服务"]
    G --> H["工具结果返回模型"]
    H --> I["模型生成最终回答"]

工具定义 Demo:

java
@Service
public class AssetTools {
    private final AssetService assetService;
    private final CurrentUserService currentUserService;

    public AssetTools(AssetService assetService,
                      CurrentUserService currentUserService) {
        this.assetService = assetService;
        this.currentUserService = currentUserService;
    }

    @Tool(description = "根据资产编号查询当前用户有权限访问的资产状态")
    public AssetStatusResult queryAssetStatus(String assetCode) {
        CurrentUser user = currentUserService.current();
        if (!user.hasAuthority("asset:read")) {
            throw new AccessDeniedException("无权查询资产");
        }
        return assetService.queryStatusWithDataScope(assetCode, user);
    }
}

调用 Demo:

java
@RestController
public class AssetAiController {
    private final ChatClient chatClient;

    public AssetAiController(ChatClient.Builder builder, AssetTools assetTools) {
        this.chatClient = builder
                .defaultSystem("你是资产管理助手。查询资产必须通过工具,不允许编造资产状态。")
                .defaultTools(assetTools)
                .build();
    }

    @GetMapping("/ai/assets")
    public String ask(String question) {
        return chatClient.prompt()
                .user(question)
                .call()
                .content();
    }
}

高风险写操作不能直接开放:

工具类型风险要求
查询资产泄露数据鉴权、数据权限、脱敏
创建工单重复创建幂等号、参数校验
发送短信资费和骚扰频率限制、模板白名单
删除数据不可逆通常不直接给模型调用
改权限极高风险禁止或人工审批

写操作安全 Demo:

java
@Tool(description = "创建采集异常工单,必须由用户确认后才能执行")
public CreateTicketResult createCollectErrorTicket(CreateTicketCommand command) {
    CurrentUser user = currentUserService.current();
    permissionService.check(user, "ticket:create");
    idempotentService.check(command.requestId());
    validateTicket(command);
    return ticketService.create(command, user);
}

模型可以提出“建议创建工单”,但是否真的创建,必须由后端权限、参数、幂等和用户确认决定。

阶段9:记忆和上下文治理 ​

AI 记忆不是把所有聊天记录无限塞给模型。上下文越长,成本越高,延迟越高,噪声越多,也更容易泄露。

记忆类型说明风险
短期对话历史当前会话的前几轮消息token 膨胀
摘要记忆把历史压缩成摘要摘要错误会污染后续回答
用户偏好常用语言、输出习惯不能保存敏感偏好
业务状态当前订单、资产、任务必须来自后端可信数据

上下文裁剪原则:

  1. 只放回答当前问题必要的信息。
  2. 敏感字段先脱敏。
  3. 多轮历史保留最近关键轮次。
  4. RAG Chunk 要有来源和权限。
  5. 工具返回值要裁剪,不要整对象塞给模型。

阶段10:生产治理 ​

上线后不能只看“能不能回答”,要看完整指标:

指标为什么重要
TTFT首 token 等待,影响用户体感
总耗时完整回答时间
输入/输出 token成本和延迟核心来源
RAG 召回命中率决定是否找到资料
引用命中率决定回答是否有依据
JSON 解析成功率决定业务流程是否稳定
工具调用成功率决定 Agent 是否可靠
权限拦截次数发现越权尝试
fallback 次数发现模型或下游不可用
单用户/单租户成本防止成本失控

日志建议记录:

text
requestId
userId / tenantId
scenario
promptVersion
modelName
knowledgeBaseVersion
retrievedChunkIds
toolNames
inputTokens / outputTokens
latencyMs
status
errorCode

不要记录完整 Prompt、完整 RAG 原文、完整工具返回值和密钥。日志系统通常有更多人能访问,完整记录反而扩大泄露面。

阶段11:生产排查 ​

AI 请求慢 ​

mermaid
flowchart TD
    A["AI 请求慢"] --> B["看是首 token 慢还是总耗时慢"]
    B --> C["入口排队和限流"]
    C --> D["RAG 检索耗时"]
    D --> E["Rerank 耗时"]
    E --> F["工具调用耗时"]
    F --> G["Prompt token 是否过长"]
    G --> H["模型生成速度和供应商状态"]

RAG 答错 ​

mermaid
flowchart TD
    A["RAG 答错"] --> B["正确文档是否已入库"]
    B --> C["Chunk 是否切分合理"]
    C --> D["元数据和权限过滤是否正确"]
    D --> E["TopK 是否召回正确片段"]
    E --> F["Rerank 是否把正确片段排前"]
    F --> G["Prompt 是否要求基于资料回答"]
    G --> H["答案引用是否支持结论"]

工具调用异常 ​

mermaid
flowchart TD
    A["工具调用异常"] --> B["模型是否选错工具"]
    B --> C["参数 schema 是否清晰"]
    C --> D["参数校验是否失败"]
    D --> E["权限或数据范围是否拒绝"]
    E --> F["下游业务接口是否超时"]
    F --> G["写操作幂等状态是否已成功"]

成本突然升高 ​

mermaid
flowchart TD
    A["AI 成本升高"] --> B["请求量是否上升"]
    B --> C["输入 token 是否变长"]
    C --> D["RAG TopK 是否过大"]
    D --> E["历史消息是否无限追加"]
    E --> F["模型是否切到更贵版本"]
    F --> G["失败重试是否放大流量"]

阶段12:商业场景验收 ​

场景1:医疗数据采集异常助手 ​

目标:用户输入采集任务失败日志,AI 给出排查建议。

链路:

mermaid
flowchart TD
    A["用户提交异常日志"] --> B["脱敏和截断"]
    B --> C["识别错误码和系统模块"]
    C --> D["RAG 检索采集规范和历史故障"]
    D --> E["模型生成排查步骤"]
    E --> F["返回建议和引用来源"]
    F --> G["记录反馈用于评估"]

必须注意:

  1. 日志里可能有 IP、账号、Token,要脱敏。
  2. 建议必须引用知识库或历史故障。
  3. 高风险操作例如重启、删除数据,只能建议人工确认。

场景2:数据资产问答 ​

目标:用户问“某字段口径是什么”“某资产归属哪个系统”。

要求:

  1. RAG 检索前必须按租户、医院、部门、密级过滤。
  2. 回答必须带来源。
  3. 用户无权访问的资产不能被召回。
  4. 模型不能自己编造资产状态,状态必须来自工具或数据库。

场景3:智能工单分流 ​

目标:根据用户描述自动分类、定优先级、建议处理人。

要求:

  1. 使用结构化输出。
  2. 分类结果要校验枚举。
  3. 高优先级工单要有人复核。
  4. 分类 Prompt 要版本化,变更后跑评估集。

阶段13:常见坑 ​

坑后果正确做法
前端直接调模型绕过鉴权、限流、审计统一走后端 AI 编排层
Prompt 写死在代码各处难评估、难回滚Prompt 版本管理
RAG 只存文本和向量无法权限过滤、引用、删除同步保存完整元数据
全库检索后让模型保密可能越权泄露检索前按权限过滤
工具相信模型传的 userId越权访问当前用户必须来自后端登录态
写工具无幂等号重复创建、重复发送幂等、二次确认、审计
完整工具结果给模型泄露敏感字段,成本高字段裁剪和脱敏
不做评估集改 Prompt 靠感觉固定样例回归
日志记录完整 Prompt扩大敏感数据泄露面记录元数据和脱敏摘要

阶段14:面试标准回答 ​

问:Spring AI 是什么? ​

标准回答:

Spring AI 是 Spring 生态里的 AI 应用工程化框架。它用 Spring Bean、自动配置、ChatClient、Prompt、Advisor、EmbeddingModel、VectorStore 和 Tool Calling,把模型调用、RAG、结构化输出、工具调用和可观测治理接入 Java 后端系统。它不是训练大模型的框架,而是让企业应用更稳定地使用大模型能力。

问:Spring AI 一次请求链路怎么走? ​

标准回答:

请求先进入 Controller,做鉴权、租户、限流和参数校验;业务层通过 ChatClient 组装 Prompt;Advisor 链按需补充安全规则、会话记忆、RAG 检索资料或工具定义;底层 ChatModel 调用模型服务;模型返回后做结构化解析、引用校验、日志、token 和耗时记录,最后返回前端。生产中还要处理超时、重试、降级和安全审计。

问:Spring AI 怎么做 RAG? ​

标准回答:

离线阶段把文档解析、清洗、脱敏、切分 Chunk,补充来源、版本、租户、权限等元数据,用 EmbeddingModel 向量化后写入 VectorStore。在线阶段用户提问后先做身份和权限过滤,再把问题向量化,在用户有权访问的资料中召回 TopK,必要时 Rerank,把资料拼进 Prompt,由模型基于资料回答,并返回引用来源。

问:Tool Calling 为什么必须后端鉴权? ​

标准回答:

模型不是权限系统,它只能根据文本推断调用意图,不能可信判断用户身份和数据范围。用户可能通过自然语言诱导模型传入别人的 userId、tenantId 或资产编号。真正的用户身份必须来自后端登录态,工具方法执行前必须做参数校验、接口权限、数据权限、风险判断和审计,写操作还要幂等和二次确认。

最终验收题 ​

能答清楚这些问题,才算真的学懂:

  1. 为什么 Spring AI 不是训练框架,而是应用工程化框架?
  2. ChatClient 和 ChatModel 为什么要分层?
  3. Advisor 和普通业务 Service 的职责边界是什么?
  4. 为什么 RAG 入库必须保存元数据?
  5. 为什么文档和问题必须使用同一个 Embedding 模型?
  6. RAG 答错时你怎么区分是检索错还是生成错?
  7. Tool Calling 中模型和后端分别负责什么?
  8. 为什么写操作工具要幂等和二次确认?
  9. 结构化输出解析失败怎么处理?
  10. AI 请求变慢、成本升高、越权风险分别怎么排查?

关联知识点跳转 ​

本章小结 ​

Spring AI 的学习核心不是记 API,而是理解 AI 请求在后端如何被编排。ChatClient 负责统一调用入口,Advisor 负责链路增强,RAG 负责把企业知识带给模型,Tool Calling 负责连接业务能力,生产治理负责安全、成本、质量和排查。只有把这些环节串起来,才能从“会调模型”升级到“能做生产级 AI 应用”。