跳到主要内容

Spring AI核心架构解析

用Spring AI写代码很简单,几行就能跑起来。但如果你想用好它,或者遇到问题能快速定位,就得搞清楚底层的架构设计。

这篇文章会带你深入Spring AI的核心组件,理解它们之间的关系和协作方式。

整体架构鸟瞰

先从宏观视角看看Spring AI的分层设计:

Spring AI 核心架构总览
Spring AI 核心架构总览

Spring AI采用了经典的分层架构,核心思想是面向接口编程。你的业务代码只需要和ChatClient或ChatModel打交道,完全不用关心底层对接的是哪家模型。

核心设计思想

Spring AI 的核心是面向接口编程ChatModel 是统一的模型抽象接口,ChatClient 是更高级的封装入口,业务代码与具体的模型实现完全解耦——换模型只需要换依赖,代码基本不用改。

ChatModel:模型统一抽象

ChatModel是Spring AI最核心的接口,它定义了与对话模型交互的标准方式。

接口设计

来看看它的接口定义:

public interface ChatModel extends Model<Prompt, ChatResponse>, StreamingChatModel {

// 最简单的调用方式:传入字符串,返回字符串
default String call(String message) {
Prompt prompt = new Prompt(new UserMessage(message));
Generation generation = call(prompt).getResult();
return (generation != null) ? generation.getOutput().getText() : "";
}

// 传入多条消息
default String call(Message... messages) {
Prompt prompt = new Prompt(Arrays.asList(messages));
Generation generation = call(prompt).getResult();
return (generation != null) ? generation.getOutput().getText() : "";
}

// 核心方法:传入Prompt,返回完整响应
@Override
ChatResponse call(Prompt prompt);

// 流式调用
default Flux<ChatResponse> stream(Prompt prompt) {
throw new UnsupportedOperationException("streaming is not supported");
}
}

注意到它继承了两个接口:

  • Model<Prompt, ChatResponse>:定义了基本的call方法
  • StreamingChatModel:定义了stream方法,支持流式输出

为什么这样设计?

这种设计带来的好处是解耦。假设今天用阿里云的通义千问,明天要换成OpenAI的GPT-4,你只需要换一个依赖包,业务代码一行不改:

// 业务代码完全不关心用的是哪个模型
@Service
public class ProductService {

private final ChatModel chatModel; // 只依赖接口

public String generateDescription(String productName) {
return chatModel.call("为商品'" + productName + "'写一段吸引人的描述");
}
}

ChatClient:更友好的门面

ChatModel虽然功能完整,但用起来稍显繁琐。Spring AI又封装了一个ChatClient,提供了更流畅的API。

ChatClient vs ChatModel

打个比方:ChatModel像是JDBC,功能强大但用起来啰嗦;ChatClient像是Spring Data JPA,简洁优雅,大多数场景用它就够了。

ChatClient vs ChatModel

日常开发中优先使用 ChatClient,它提供了更简洁的链式 API,内置了对 Advisor、默认参数的支持。只有当你需要精细控制底层请求(比如自定义 Prompt 构建),才有必要直接使用 ChatModel

看看两种方式的代码对比:

// 使用ChatModel
Prompt prompt = new Prompt(
List.of(
new SystemMessage("你是订单分析助手"),
new UserMessage("分析这个订单的状态")
),
DashScopeChatOptions.builder().withModel("qwen-plus").build()
);
ChatResponse response = chatModel.call(prompt);
String result = response.getResult().getOutput().getText();

// 使用ChatClient - 链式调用,清爽多了
String result = chatClient.prompt()
.system("你是订单分析助手")
.user("分析这个订单的状态")
.options(DashScopeChatOptions.builder().withModel("qwen-plus").build())
.call()
.content();

ChatClient的构建方式

ChatClient通过Builder模式构建,可以预设各种默认配置:

@Bean
public ChatClient chatClient(ChatModel chatModel) {
return ChatClient.builder(chatModel)
// 默认系统提示词
.defaultSystem("你是一个专业的电商客服")
// 默认模型参数
.defaultOptions(
DashScopeChatOptions.builder()
.withTemperature(0.7)
.build()
)
// 默认Advisor(拦截器)
.defaultAdvisors(
new SimpleLoggerAdvisor()
)
.build();
}

这些默认配置在每次调用时都会生效,但也可以在调用时覆盖。

Prompt:请求的载体

Prompt是发给大模型的请求对象,包含了对话内容和调用参数。

Prompt的结构

public class Prompt implements ModelRequest<List<Message>> {

// 对话内容:历史消息 + 当前消息
private final List<Message> messages;

// 调用参数:模型名称、温度值等
@Nullable
private ChatOptions chatOptions;
}

一个典型的Prompt长这样:

Prompt 对象的组成结构
Prompt 对象的组成结构

Message的类型

Message表示对话中的一条消息,根据角色不同有四种类型:

类型说明使用场景
SystemMessage系统设定定义AI的角色、行为准则
UserMessage用户输入用户的问题或指令
AssistantMessageAI回复模型之前的回答
ToolResponseMessage工具返回工具调用的结果

在多轮对话中,Message列表会包含完整的对话历史,这就是大模型"记住"上下文的方式。

Message 的四种类型
类型说明使用场景
SystemMessage系统设定定义AI的角色、行为准则
UserMessage用户输入用户的问题或指令
AssistantMessageAI回复模型之前的回答
ToolResponseMessage工具返回工具调用的结果

多轮对话中,Message 列表需要包含完整的对话历史,大模型才能"理解"上下文。

ChatOptions:参数配置

ChatOptions用来设置调用大模型时的各种参数,不同模型厂商支持的参数有差异。

通用参数

// 使用通用的ChatOptions
ChatOptions options = ChatOptions.builder()
.model("qwen-plus") // 模型名称
.temperature(0.7) // 温度:越高越有创意
.maxTokens(2000) // 最大生成token数
.topP(0.9) // 核采样概率
.build();

厂商特有参数

如果要用某个厂商的特殊参数,需要用对应的Options实现类:

// 阿里云DashScope特有参数
DashScopeChatOptions options = DashScopeChatOptions.builder()
.withModel("qwen-plus")
.withTemperature(0.7)
.withEnableSearch(true) // 开启联网搜索
.withResultFormat("text") // 结果格式
.build();

参数优先级

参数可以在多个地方设置,优先级从高到低是:

参数优先级规则
  1. 单次调用时指定 - chatClient.prompt().options(xxx) — 最高优先级
  2. ChatClient构建时的默认值 - ChatClient.builder().defaultOptions(xxx)
  3. 配置文件 - spring.ai.xxx.chat.options.xxx
  4. 框架默认值 — 最低优先级

高优先级的配置会覆盖低优先级,灵活地利用这个机制可以让你的配置更整洁。

ChatOptions 参数优先级
ChatOptions 参数优先级

ChatResponse:响应解析

大模型返回的结果封装在ChatResponse中,结构稍微有点复杂。

响应结构

// 简化后的响应结构
public class ChatResponse {

private ChatResponseMetadata metadata; // 元数据
private List<Generation> results; // 生成结果(可能有多个)

public Generation getResult() {
return results.get(0); // 获取第一个结果
}
}

public class Generation {
private AssistantMessage output; // AI回复内容
private ChatGenerationMetadata metadata; // 生成元数据
}

常用的取值方式

ChatResponse response = chatModel.call(prompt);

// 获取文本内容 - 最常用
String text = response.getResult().getOutput().getText();

// 获取完整的消息对象
AssistantMessage message = response.getResult().getOutput();

// 获取token使用情况
Usage usage = response.getMetadata().getUsage();
System.out.println("输入token: " + usage.getPromptTokens());
System.out.println("输出token: " + usage.getCompletionTokens());
System.out.println("总计token: " + usage.getTotalTokens());

如果用ChatClient,取值更简单:

// 直接拿到文本
String content = chatClient.prompt("你好").call().content();

// 拿到完整响应(需要时)
ChatResponse response = chatClient.prompt("你好").call().chatResponse();

自动配置机制

Spring AI大量使用了Spring Boot的自动配置特性,让集成变得非常简单。

以阿里云DashScope为例,当你引入spring-ai-alibaba-starter-dashscope依赖后,会自动发生以下事情:

DashScope 自动配置装配流程
DashScope 自动配置装配流程

这一切都是自动完成的,你只需要配置API Key就行了。

自动配置机制

Spring AI 大量借助 Spring Boot AutoConfiguration,引入 starter 依赖并配置 API Key 后,ChatModel Bean 会自动注册到容器,可直接注入使用,无需任何手动配置。

自定义Bean

如果默认配置不满足需求,可以自己定义Bean覆盖:

@Configuration
public class CustomAiConfig {

@Bean
public ChatModel customChatModel(DashScopeApi dashScopeApi) {
// 自定义ChatModel配置
return new DashScopeChatModel(dashScopeApi,
DashScopeChatOptions.builder()
.withModel("qwen-max")
.withTemperature(0.5)
.build());
}
}

调用流程全景

最后,我们把整个调用流程串起来看:

Spring AI 一次完整调用链路
Spring AI 一次完整调用链路

小结

这篇文章梳理了Spring AI的核心架构:

  • ChatModel是模型抽象层,定义了统一的交互接口
  • ChatClient是更高级的封装,提供流畅的链式API
  • Prompt承载请求信息,包含Message列表和ChatOptions
  • ChatResponse是响应对象,通过getResult().getOutput().getText()获取文本

理解了这些核心概念,你就能更好地使用Spring AI,也能在遇到问题时快速定位根因。

下一篇,我们来聊聊流式输出的实现原理,看看那个Flux到底是怎么回事。

🎁优惠