Spring AI 核心链路拆解:ChatClient、Prompt、Advisor、ChatModel 到底怎么串起来?

Spring AI 核心架构拆解:ChatClient、ChatModel、Prompt、Advisor 到底是什么?

很多 Java 开发者学 Spring AI,都会卡在同一个地方:

代码能跑,但不知道为什么能跑。

chatClient.prompt().user(...).call().content() 这一行能拿到模型回复。可再往下看,ChatClient、ChatModel、Prompt、Advisor 这些类名全冒出来了。

每个词好像都认识。

但串不起来。

这篇不继续堆 Demo。我们把 Spring AI 的主调用链路拆开,看一次请求是怎么从 Controller 走到模型,再把结果返回来的。


一、先看完整链路

你在 Controller 里可能写过这样的代码:

java 复制代码
String response = chatClient.prompt()
    .user("什么是 Spring AI?")
    .call()
    .content();

表面看,就是发个问题,拿个回答。

实际背后大概是这条链:

text 复制代码
Controller -> ChatClient -> Prompt -> Advisor Chain -> ChatModel -> ChatResponse

这里的代码里看不到 Advisor,因为它通常是在构建 ChatClient 时配置好的。真正调用 .call() 时,请求才会经过这些 Advisor。

先用一句话记住:

ChatClient 是业务入口,Prompt 描述这次请求,Advisor 在调用前后做增强,ChatModel 对接具体模型,最后返回 ChatResponse。

接下来按这条链路拆。


二、ChatClient:业务入口

ChatClient 是业务代码里最常见的入口。

它的价值很简单:让你用链式 API 顺手地调用模型。

比如:

java 复制代码
chatClient.prompt()
    .system("你是一个技术文档助手")
    .user("解释下 Spring Bean 生命周期")
    .call()
    .content();

这里的 .prompt()、.system()、.user()、.call(),都是 ChatClient 给你的调用方式。

刚开始写业务时,你暂时不用管 Prompt 怎么构造、Advisor 怎么执行、ChatModel 怎么调用。

你只要按顺序写:

  • .user():用户问题;
  • .system():可选,系统提示;
  • .call():发起调用;
  • .content():拿文本结果。

一个最小接口大概这样:

java 复制代码
@RestController
public class AiController {

    private final ChatClient chatClient;

    public AiController(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }

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

这就是 ChatClient 的定位:把复杂链路藏起来,让你先把业务接口写出来。


三、Prompt:这次请求长什么样

Prompt 不是"万能容器"。

更准确地说,它是一次模型调用的请求描述。

你可以先把它拆成两块。

1. Messages

一次调用不一定只有用户问题,还可能有系统提示和历史回复。

java 复制代码
Prompt prompt = new Prompt(List.of(
    new SystemMessage("你是一个 Java 专家"),
    new UserMessage("什么是 Spring Bean?")
));

常见消息有这几类:

  • SystemMessage:告诉模型应该怎么回答;
  • UserMessage:用户输入;
  • AssistantMessage:模型历史回复,多轮对话会用到。

2. Options

也就是这次调用的参数,比如模型名、温度、最大输出长度。

java 复制代码
ChatOptions options = ChatOptions.builder()
    .model("deepseek-v4-flash")
    .temperature(0.7)
    .build();

Prompt prompt = new Prompt("解释下 JVM", options);

那历史对话、RAG 文档、工具相关信息在哪?

不一定一开始就在 Prompt 里。

实际运行时,Advisor 可能会在调用前补充这次请求。比如把历史消息补进去,或者把检索到的文档作为上下文交给模型。

所以你可以把 Prompt 理解成一个请求快照:

在某个时刻,它描述了这次要发给模型的内容。

但到达 ChatModel 之前,它可能已经被 Advisor Chain 改过了。


四、Advisor Chain:增强链

Advisor 是这条链路里最灵活的部分。

它有点像拦截器,但不是 Spring MVC 的 HandlerInterceptor。

它做的是模型调用前后的增强。

比如:

  • 调用前:补历史、查知识库、记录请求;
  • 调用后:记录响应、补 metadata、做轻量处理。

链路可以这样理解:

text 复制代码
ChatClient
  -> Advisor 1
  -> Advisor 2
  -> ChatModel
  -> Advisor 2
  -> Advisor 1
  -> 返回结果

SimpleLoggerAdvisor

最容易理解的是日志。

java 复制代码
ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(new SimpleLoggerAdvisor())
    .build();

调试 Spring AI 调用链路时,它很有用。

MessageChatMemoryAdvisor

如果你想让模型记住前面聊过什么,可以加记忆 Advisor。

java 复制代码
ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(
        MessageChatMemoryAdvisor.builder(chatMemory).build()
    )
    .build();

它会在调用前,把需要的历史消息补进这次请求里。

真实项目里还要注意一件事:多用户场景要区分 conversationId,不然不同用户的上下文可能串在一起。

QuestionAnswerAdvisor

RAG 也可以通过 Advisor 接进来。

java 复制代码
QuestionAnswerAdvisor advisor = QuestionAnswerAdvisor.builder(vectorStore)
    .searchRequest(SearchRequest.builder().topK(5).build())
    .build();

ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(advisor)
    .build();

这类 Advisor 会先去向量库检索相关文档,再把上下文补进请求里,让模型带着资料回答。

注意,QuestionAnswerAdvisor 需要对应的 vector store / advisor 依赖。不是只引入一个聊天模型 starter 就一定有。

Advisor 不要乱塞

Advisor 很强,但不是所有逻辑都往里放。

适合放进去的:

  • 日志和监控;
  • 对话记忆;
  • RAG 上下文增强;
  • 请求改写;
  • 轻量响应处理。

不太适合放进去的:

  • 复杂业务逻辑;
  • 重量级数据处理;
  • 跟模型调用无关的副作用,比如发邮件、写业务库。

记住一句话:

Advisor 是增强这次模型调用,不是替代 Service 层。

顺序也要注意。

多个 Advisor 会按配置顺序处理请求,响应回来时再反向经过。日志放前面,看到的更接近原始请求;日志放后面,看到的更接近最终发给模型的请求。

没有绝对对错,关键是你要知道自己想看哪一层。


五、ChatModel:模型抽象层

很多人会把 ChatModel 理解成"大模型"。

其实不是。

ChatModel 是 Spring AI 对聊天模型的统一抽象。

它不是 OpenAI、DeepSeek、Ollama 这些模型本身,而是 Spring AI 和具体模型实现之间的一层接口。

Spring Boot 会根据你引入的 starter 和配置,创建对应的 ChatModel Bean。

比如从 OpenAI 换到 DeepSeek,业务代码通常不用动。

OpenAI 配置可能是:

yaml 复制代码
spring:
  ai:
    openai:
      api-key: sk-xxx
      chat:
        options:
          model: your-openai-model

DeepSeek 配置可能是:

yaml 复制代码
spring:
  ai:
    deepseek:
      api-key: sk-xxx
      chat:
        options:
          model: deepseek-v4-flash

业务代码还是这一段:

java 复制代码
String response = chatClient.prompt()
    .user("什么是 Spring AI?")
    .call()
    .content();

这就是抽象层的价值:业务代码尽量不绑死具体供应商。

平时写业务,优先用 ChatClient。

什么时候看 ChatModel?

  • 想理解底层怎么对接模型;
  • 要做自定义模型实现;
  • 调试时想绕过 ChatClient 和 Advisor。

比如直接调底层模型:

java 复制代码
Prompt prompt = new Prompt("测试请求");
ChatResponse response = chatModel.call(prompt);

这种场景不多。

大部分时候,你只要记住:

ChatClient 是主要入口,ChatModel 是底层抽象。


六、ChatResponse:返回结果

如果 .content() 是快捷拿文本,那么 ChatResponse 就是完整返回包。

它可能包含:

  • 模型返回的文本;
  • 模型信息;
  • finish reason;
  • token 使用量。

拿文本:

java 复制代码
ChatResponse response = chatClient.prompt()
    .user("什么是 Spring AI?")
    .call()
    .chatResponse();

String content = response.getResult()
    .getOutput()
    .getText();

只要文本时,直接这样就行:

java 复制代码
String content = chatClient.prompt()
    .user("什么是 Spring AI?")
    .call()
    .content();

看元信息:

java 复制代码
ChatResponseMetadata metadata = response.getMetadata();

System.out.println("模型: " + metadata.getModel());
System.out.println("响应 ID: " + metadata.getId());

看 finish reason:

java 复制代码
String finishReason = response.getResult()
    .getMetadata()
    .getFinishReason();

看 token:

java 复制代码
Usage usage = response.getMetadata().getUsage();

if (usage != null) {
    System.out.println("输入 token: " + usage.getPromptTokens());
    System.out.println("输出 token: " + usage.getCompletionTokens());
    System.out.println("总计: " + usage.getTotalTokens());
}

注意,不是所有模型、所有调用方式都会返回完整 usage。做日志和成本统计时,先判空。

大部分业务接口只用 .content() 就够了。

但如果你要做成本统计、监控埋点、调用分析,就要看 ChatResponse。


七、什么时候用谁?

简单整理一下:

场景 优先看什么 原因
写业务代码 ChatClient 链式 API 顺手,封装了底层细节
只要文本结果 .call().content() 最简单,业务代码最常用
需要对话记忆 MessageChatMemoryAdvisor 自动补历史消息
需要 RAG 增强 QuestionAnswerAdvisor 检索文档,补上下文
需要记录日志 SimpleLoggerAdvisor 调试请求和响应
需要 token 统计 ChatResponse / metadata 看模型是否返回 usage
理解底层机制 ChatModel 看 Spring AI 怎么对接模型
自定义模型实现 ChatModel 接口 对接 Spring AI 还不支持的模型

一句话:

写业务,优先用 ChatClient。

需要增强,配置 Advisor。

想理解底层,再看 ChatModel 和 ChatResponse。


写在最后

这篇把 Spring AI 的主链路拆了一遍:

text 复制代码
Controller -> ChatClient -> Prompt -> Advisor Chain -> ChatModel -> ChatResponse

你只要先记住这几个角色:

  • ChatClient:发起调用;
  • Prompt:描述请求;
  • Advisor:前后增强;
  • ChatModel:对接模型;
  • ChatResponse:承接结果。

以后再看 .call().content(),就不会只看到一行代码了。

你会知道它背后经过了哪些层,每一层大概在干什么。

如果你已经跑通了 Spring AI 的 Hello World,建议回头再看一遍这篇,会更清楚。

因为对话记忆、RAG、日志增强这些能力,基本都会绕到它。


后续会继续更新 Spring AI、RAG、Memory、Tool Calling、MCP 等实战内容。完整系列也会同步整理在公众号「AI Agent 实战有术」。

相关推荐
行百里er7 小时前
Redis 完结篇——Redis 热点问题在 Spring Boot 项目中实战
redis·后端
高频因子挖掘机8 小时前
只取最近 60 个交易日,却每次拉全历史:量化脚本变慢的原因与改法
后端·github·api
孟健9 小时前
从5000亿Token到83%字节匹配:AI逆向工程与重构的工程闭环实践
后端·架构·ai编程
码事漫谈10 小时前
丢得静悄悄,谈一谈 Agent 的记忆是怎么没的
后端
迪飞特科技10 小时前
智慧物业平台实战:Spring Boot 整合小程序实现报修巡检费用全流程
spring boot·后端·小程序
考虑考虑10 小时前
@JsonInclude注解使用
spring boot·后端·spring
IT_陈寒10 小时前
Vite的这个问题坑了我三天,原来如此解决!
前端·人工智能·后端
网易云信10 小时前
大模型能上岗做销售吗?我们让 12 款国产模型交了 420 份作业
前端·后端
CCblandy10 小时前
HTTP 长连接 3 类故障实测:[WinError 10053]、99 个 TIME_WAIT、69 次多余握手,4 种对策怎么选
后端·python·网络协议·http·云原生·性能优化·架构
蜗牛互联网10 小时前
Python Agent工具调用的白名单、参数校验与审批门禁
java·服务器·前端·人工智能·后端·python