Skip to content

Spring AI 从零到精通验收清单

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

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

  1. 为什么企业项目需要 Spring AI,而不是直接在 Controller 里写 HTTP 调模型。
  2. ChatClientChatModelPromptAdvisorEmbeddingModelVectorStoreTool 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);
    }
}

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

阶段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、自动配置、ChatClientPromptAdvisorEmbeddingModelVectorStore 和 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. ChatClientChatModel 为什么要分层?
  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 应用”。