Spring AI 核心架构拆解

Spring AI 核心架构入门:ChatClient、ChatModel、Prompt、Advisor、ChatResponse 关系说明

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

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

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

每个词好像都认识。

但串不起来。

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

本文关键词:Spring AI、ChatClient、ChatModel、Prompt、Advisor、ChatResponse。

如果你已经跑通了 Spring AI 的 Hello World,但还没搞清楚这些类之间的关系,可以先看这条主链路:

Controller -> ChatClient -> Prompt -> Advisor Chain -> ChatModel -> ChatResponse

一、先看完整链路

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

String response = chatClient.prompt()
    .user("什么是 Spring AI?")
    .call()
    .content();

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

实际背后大概是这条链:

Controller -> ChatClient -> Prompt -> Advisor Chain -> ChatModel -> ChatResponse

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

先用一句话记住:

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

接下来按这条链路拆。


二、ChatClient:业务入口

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

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

比如:

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

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

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

你只要按顺序写:

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

一个最小接口大概这样:

@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

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

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

常见消息有这几类:

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

2. Options

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

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、做轻量处理。

链路可以这样理解:

ChatClient
  -> Advisor 1
  -> Advisor 2
  -> ChatModel
  -> Advisor 2
  -> Advisor 1
  -> 返回结果

SimpleLoggerAdvisor

最容易理解的是日志。

ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(new SimpleLoggerAdvisor())
    .build();

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

MessageChatMemoryAdvisor

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

ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(
        MessageChatMemoryAdvisor.builder(chatMemory).build()
    )
    .build();

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

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

QuestionAnswerAdvisor

RAG 也可以通过 Advisor 接进来。

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 配置是:

spring:
  ai:
    openai:
      api-key: sk-xxx
      chat:
        options:
          model: your-openai-model

DeepSeek 配置是:

spring:
  ai:
    deepseek:
      api-key: sk-xxx
      chat:
        options:
          model: deepseek-v4-flash

业务代码还是这一段:

String response = chatClient.prompt()
    .user("什么是 Spring AI?")
    .call()
    .content();

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

平时写业务,优先用 ChatClient

什么时候看 ChatModel

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

比如直接调底层模型:

Prompt prompt = new Prompt("测试请求");
ChatResponse response = chatModel.call(prompt);

这种场景不多。

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

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


六、ChatResponse:返回结果

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

它可能包含:

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

拿文本:

ChatResponse response = chatClient.prompt()
    .user("什么是 Spring AI?")
    .call()
    .chatResponse();

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

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

String content = chatClient.prompt()
    .user("什么是 Spring AI?")
    .call()
    .content();

看元信息:

ChatResponseMetadata metadata = response.getMetadata();

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

看 finish reason:

String finishReason = response.getResult()
    .getMetadata()
    .getFinishReason();

看 token:

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
想理解底层,再看 ChatModelChatResponse


写在最后

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

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 等实战内容。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐