Spring AI 源码解读 - 第 1 篇:整体架构与核心抽象
一次 AI 对话请求背后的架构设计
📖 开篇引言
当我们写下这行代码时:
String answer = chatClient.prompt()
.user("你好,介绍一下 Spring AI")
.call()
.content();
背后发生了什么?Spring AI 是如何将这个简单的调用转化为 AI 模型的 HTTP 请求,又将响应优雅地返回给我们?
本篇将站在全局视角,拆解 Spring AI 的整体架构与核心抽象,为后续的源码解读奠定基础。
一、项目模块结构
1.1 模块总览
Spring AI 采用模块化设计,核心模块如下:
spring-ai/
├── spring-ai-core/ # 核心抽象层(接口定义)
├── spring-ai-client-chat/ # ChatClient 实现
├── spring-ai-ollama/ # Ollama 模型实现
├── spring-ai-openai/ # OpenAI 模型实现
├── spring-ai-azure-openai/ # Azure OpenAI 实现
├── spring-ai-bedrock/ # AWS Bedrock 实现
├── spring-ai-vertex-ai/ # Google Vertex AI 实现
├── spring-ai-zhipuai/ # 智谱 AI 实现
├── spring-ai-redis/ # Redis VectorStore
├── spring-ai-pgvector/ # PgVector VectorStore
└── spring-ai-*-spring-boot-starter/ # 各模型 Starter
模块划分逻辑:
| 模块类型 | 说明 | 代表模块 |
|---|---|---|
| 核心层 | 定义接口和抽象,不含具体实现 | spring-ai-core |
| 客户端层 | ChatClient 高层封装 | spring-ai-client-chat |
| 模型实现层 | 对接具体 AI 服务商 | spring-ai-ollama、spring-ai-openai |
| 存储实现层 | 向量数据库集成 | spring-ai-redis、spring-ai-pgvector |
| 自动装配层 | Spring Boot Starter | spring-ai-ollama-spring-boot-starter |
1.2 依赖关系
Your Application
│
↓ 引入
spring-ai-ollama-spring-boot-starter
│
├──> spring-ai-ollama (Ollama 实现)
│ │
│ └──> spring-ai-core (核心接口)
│
└──> spring-ai-client-chat (ChatClient)
│
└──> spring-ai-core (核心接口)
设计意图:
spring-ai-core只定义接口,不依赖任何具体实现- 各模型实现层依赖
spring-ai-core,实现其接口 - 应用层只需引入对应的 Starter,其余由自动装配完成
二、核心接口体系
2.1 顶层抽象:Model 接口
Spring AI 所有模型能力的顶层接口只有一个:
// org.springframework.ai.model.Model
public interface Model<TReq, TRes> {
TRes call(TReq request);
}
极简的泛型接口,定义了 "输入请求,返回响应" 这一核心契约。
2.2 三大模型接口
在 Model<TReq, TRes> 之上,Spring AI 扩展出三大模型接口:
// 1. 对话模型
public interface ChatModel extends Model<Prompt, ChatResponse> {
@Override
ChatResponse call(Prompt prompt);
// 默认实现:提取第一个生成结果的文本内容
default String call(String message) {
Prompt prompt = new Prompt(new UserMessage(message));
return call(prompt).getResult().getOutput().getContent();
}
}
// 2. 向量嵌入模型
public interface EmbeddingModel extends Model<EmbeddingRequest, EmbeddingResponse> {
@Override
EmbeddingResponse call(EmbeddingRequest request);
// 便捷方法:直接对文本列表做向量化
default List<Double> embed(String text) { ... }
default List<float[]> embed(List<String> texts) { ... }
}
// 3. 图像生成模型
public interface ImageModel extends Model<ImagePrompt, ImageResponse> {
@Override
ImageResponse call(ImagePrompt request);
}
接口继承体系:
Model<TReq, TRes>
├── ChatModel 对话模型
│ └── StreamingChatModel 流式对话(扩展)
├── EmbeddingModel 向量嵌入模型
└── ImageModel 图像生成模型
2.3 StreamingChatModel:流式扩展
流式输出是独立的子接口,而非在 ChatModel 中直接定义:
public interface StreamingChatModel extends ChatModel {
// 返回 Reactor 的 Flux,支持响应式流
Flux<ChatResponse> stream(Prompt prompt);
// 便捷方法:直接流式输出文本
default Flux<String> stream(String message) {
Prompt prompt = new Prompt(new UserMessage(message));
return stream(prompt)
.map(response -> response.getResult().getOutput().getContent());
}
}
为什么单独抽出 StreamingChatModel?
- 并非所有模型都支持流式输出
- 流式和非流式的调用方式差异较大(
ChatResponsevsFlux<ChatResponse>) - 遵循接口隔离原则(ISP):不强迫不需要流式的实现类实现该方法
三、输入输出数据模型
3.1 Prompt:请求封装
public class Prompt implements ModelRequest<List<Message>> {
private final List<Message> messages; // 消息列表(核心)
private final ChatOptions chatOptions; // 可选参数(temperature 等)
// 单条消息的便捷构造
public Prompt(String contents) {
this(new UserMessage(contents));
}
public Prompt(Message message) {
this(List.of(message));
}
public Prompt(List<Message> messages) {
this(messages, null);
}
public Prompt(List<Message> messages, ChatOptions chatOptions) {
this.messages = messages;
this.chatOptions = chatOptions;
}
}
Prompt 的本质:消息列表 + 可选参数的容器。
3.2 Message 接口体系
public interface Message extends Node<String> {
String getContent(); // 消息内容
MessageType getMessageType(); // 消息角色
Map<String, Object> getMetadata(); // 元数据
}
四种消息类型:
public enum MessageType {
USER("user"), // 用户消息
ASSISTANT("assistant"), // 助手消息(AI 回复)
SYSTEM("system"), // 系统提示词
TOOL("tool"); // 工具调用结果
}
对应的实现类:
// 用户消息
public class UserMessage implements Message {
private final String content;
// MessageType = USER
}
// 系统提示词
public class SystemMessage implements Message {
private final String content;
// MessageType = SYSTEM
}
// 助手消息(AI 的历史回复,用于多轮对话)
public class AssistantMessage implements Message {
private final String content;
private final List<ToolCall> toolCalls; // 工具调用信息
// MessageType = ASSISTANT
}
// 工具调用结果
public class ToolResponseMessage implements Message {
private final List<ToolResponse> responses;
// MessageType = TOOL
}
消息类型在多轮对话中的作用:
[SystemMessage] "你是一个专业的 Java 工程师"
[UserMessage] "什么是 Spring AI?"
[AssistantMessage] "Spring AI 是..."
[UserMessage] "它有哪些核心模块?" ← 当前输入
3.3 ChatResponse:响应封装
public class ChatResponse implements ModelResponse<Generation> {
private final List<Generation> generations; // 生成结果列表
private final ChatResponseMetadata metadata; // 元数据(Token 用量等)
// 便捷方法:获取第一个生成结果
public Generation getResult() {
return generations.get(0);
}
}
Generation:单个生成结果:
public class Generation implements ModelResult<AssistantMessage> {
private final AssistantMessage assistantMessage; // AI 回复的消息
private final ChatGenerationMetadata metadata; // 生成元数据
// 获取 AI 回复内容
public AssistantMessage getOutput() {
return assistantMessage;
}
}
完整的调用链:
ChatResponse response = chatModel.call(prompt);
// 获取文本内容的完整路径
String content = response // ChatResponse
.getResult() // Generation
.getOutput() // AssistantMessage
.getContent(); // String
3.4 ChatOptions:参数配置
public interface ChatOptions extends ModelOptions {
String getModel(); // 模型名称
Double getTemperature(); // 温度(创造性)
Double getTopP(); // Top-P 采样
Integer getMaxTokens(); // 最大 Token 数
List<String> getStopSequences(); // 停止词
// ...
}
各模型的 Options 实现:
// Ollama 专属参数
public class OllamaOptions implements ChatOptions {
private String model;
private Double temperature;
private Integer numCtx; // 上下文窗口大小(Ollama 特有)
private String format; // 输出格式(json 等)
// ...
}
// OpenAI 专属参数
public class OpenAiChatOptions implements ChatOptions {
private String model;
private Double temperature;
private String responseFormat; // 响应格式
private Boolean stream;
// ...
}
Options 的优先级:
请求级 Options(Prompt 中指定)
↓ 覆盖
ChatClient 默认 Options(构建时指定)
↓ 覆盖
模型级 Options(application.properties 配置)
四、OllamaChatModel 源码解析
以 OllamaChatModel 为例,深入理解 ChatModel 的实现机制。
4.1 类结构
public class OllamaChatModel implements StreamingChatModel {
private final OllamaApi ollamaApi; // HTTP 客户端
private final OllamaChatOptions defaultOptions; // 默认参数
private final FunctionCallbackContext functionCallbackContext; // 工具调用上下文
@Override
public ChatResponse call(Prompt prompt) {
// 1. 合并 Options(请求级 > 默认级)
OllamaOptions requestOptions = mergeOptions(prompt.getOptions());
// 2. 构建 Ollama API 请求
ChatRequest request = buildRequest(prompt, requestOptions, false);
// 3. 调用 Ollama HTTP API
ChatResponse ollamaResponse = ollamaApi.chat(request);
// 4. 转换为 Spring AI 标准响应
return convertResponse(ollamaResponse, request);
}
@Override
public Flux<ChatResponse> stream(Prompt prompt) {
OllamaOptions requestOptions = mergeOptions(prompt.getOptions());
ChatRequest request = buildRequest(prompt, requestOptions, true); // stream=true
return ollamaApi.streamingChat(request)
.map(chunk -> convertResponse(chunk, request));
}
}
4.2 请求构建:Prompt → Ollama ChatRequest
private ChatRequest buildRequest(Prompt prompt, OllamaOptions options, boolean stream) {
// 转换消息列表
List<ChatRequest.Message> ollamaMessages = prompt.getInstructions()
.stream()
.map(this::toOllamaMessage)
.toList();
return ChatRequest.builder()
.model(options.getModel())
.messages(ollamaMessages)
.stream(stream)
.options(toOllamaOptions(options))
.build();
}
// Spring AI Message → Ollama Message
private ChatRequest.Message toOllamaMessage(Message message) {
String role = switch (message.getMessageType()) {
case USER -> "user";
case ASSISTANT -> "assistant";
case SYSTEM -> "system";
case TOOL -> "tool";
};
return new ChatRequest.Message(role, message.getContent());
}
4.3 响应转换:Ollama Response → ChatResponse
private ChatResponse convertResponse(ChatResponse ollamaResponse, ChatRequest request) {
// 构建 AssistantMessage
AssistantMessage assistantMessage = new AssistantMessage(
ollamaResponse.getMessage().getContent()
);
// 构建 Generation
Generation generation = new Generation(
assistantMessage,
ChatGenerationMetadata.builder()
.finishReason(ollamaResponse.getDoneReason())
.build()
);
// 构建 ChatResponseMetadata(Token 用量)
ChatResponseMetadata metadata = ChatResponseMetadata.builder()
.usage(new DefaultUsage(
ollamaResponse.getPromptEvalCount(), // 输入 Token
ollamaResponse.getEvalCount() // 输出 Token
))
.build();
return new ChatResponse(List.of(generation), metadata);
}
4.4 适配器模式的体现
OllamaChatModel 的本质是一个适配器:
Spring AI 标准 Ollama API
───────────────── ─────────────────
Prompt ──转换──> ChatRequest
└── List<Message> └── List<Message>
├── UserMessage ├── {role:"user", ...}
├── SystemMessage ├── {role:"system", ...}
└── AssistantMessage └── {role:"assistant", ...}
ChatResponse <──转换── ChatResponse
└── Generation └── Message
└── AssistantMessage └── content
为什么需要这层转换?
不同 AI 服务商的 API 格式各不相同:
- Ollama:
{"model":"qwen2.5","messages":[...]} - OpenAI:
{"model":"gpt-4","messages":[...],"stream":false} - 智谱 AI:
{"model":"glm-4","messages":[...],"request_id":"..."}
Spring AI 通过适配器模式,将这些差异屏蔽在实现层,对外暴露统一的 ChatModel 接口。
五、Spring Boot 自动装配机制
5.1 Starter 的自动装配流程
1. 引入 spring-ai-ollama-spring-boot-starter
↓
2. Spring Boot 扫描
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
↓
3. 加载 OllamaAutoConfiguration
↓
4. 读取 application.properties 绑定到 OllamaChatProperties
↓
5. 创建 OllamaApi Bean(HTTP 客户端)
↓
6. 创建 OllamaChatModel Bean(注入容器)
↓
7. 创建 ChatClient.Builder Bean(供业务层使用)
5.2 OllamaAutoConfiguration 源码
@AutoConfiguration
@ConditionalOnClass(OllamaChatModel.class)
@EnableConfigurationProperties({
OllamaConnectionProperties.class,
OllamaChatProperties.class,
OllamaEmbeddingProperties.class
})
public class OllamaAutoConfiguration {
// 创建 HTTP 客户端
@Bean
@ConditionalOnMissingBean
public OllamaApi ollamaApi(OllamaConnectionProperties props) {
return new OllamaApi(props.getBaseUrl());
}
// 创建 ChatModel
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(
prefix = OllamaChatProperties.CONFIG_PREFIX,
name = "enabled",
havingValue = "true",
matchIfMissing = true
)
public OllamaChatModel ollamaChatModel(
OllamaApi ollamaApi,
OllamaChatProperties chatProps) {
OllamaChatOptions options = OllamaChatOptions.builder()
.model(chatProps.getModel())
.temperature(chatProps.getOptions().getTemperature())
.build();
return OllamaChatModel.builder()
.ollamaApi(ollamaApi)
.defaultOptions(options)
.build();
}
// 创建 ChatClient.Builder(供业务层注入)
@Bean
@ConditionalOnMissingBean
public ChatClient.Builder chatClientBuilder(OllamaChatModel chatModel) {
return ChatClient.builder(chatModel);
}
}
5.3 关键注解解析
| 注解 | 作用 |
|---|---|
@AutoConfiguration | 标记为自动配置类,Spring Boot 启动时自动加载 |
@ConditionalOnClass | 类路径中存在指定类时才生效 |
@ConditionalOnMissingBean | 容器中不存在该 Bean 时才创建(允许用户覆盖) |
@ConditionalOnProperty | 配置项满足条件时才生效 |
@EnableConfigurationProperties | 启用 Properties 类的绑定 |
@ConditionalOnMissingBean 的重要性:
// 自动配置创建默认 Bean
@Bean
@ConditionalOnMissingBean
public OllamaChatModel ollamaChatModel(...) { ... }
// 用户可以在自己的配置类中覆盖
@Bean
public OllamaChatModel ollamaChatModel(...) {
// 自定义配置,会替代自动配置的 Bean
return OllamaChatModel.builder()
.ollamaApi(...)
.defaultOptions(OllamaChatOptions.builder()
.model("llama3")
.temperature(0.1)
.build())
.build();
}
5.4 Properties 绑定
@ConfigurationProperties(prefix = "spring.ai.ollama.chat")
public class OllamaChatProperties {
public static final String CONFIG_PREFIX = "spring.ai.ollama.chat";
private boolean enabled = true;
private String model = "mistral";
private OllamaOptions options = new OllamaOptions();
// getters / setters
}
对应的配置文件:
spring.ai.ollama.base-url=http://localhost:11434
spring.ai.ollama.chat.model=qwen2.5:14b
spring.ai.ollama.chat.options.temperature=0.3
spring.ai.ollama.chat.options.num-ctx=4096
六、多模型切换的原理
6.1 为什么换模型只需改配置?
核心在于依赖倒置原则(DIP):
// ❌ 错误:依赖具体实现
@Service
public class ChatService {
@Autowired
private OllamaChatModel chatModel; // 耦合了 Ollama
}
// ✅ 正确:依赖抽象接口
@Service
public class ChatService {
@Autowired
private ChatModel chatModel; // 只依赖接口
}
当业务代码依赖 ChatModel 接口时:
引入 spring-ai-ollama-starter
→ 自动装配 OllamaChatModel
→ 注入到 ChatModel 类型的字段
引入 spring-ai-openai-starter
→ 自动装配 OpenAIChatModel
→ 注入到 ChatModel 类型的字段
业务代码完全不需要修改。
6.2 多模型并存时的处理
如果同时引入多个 Starter,Spring 容器中会存在多个 ChatModel Bean,需要通过 @Qualifier 或 @Primary 区分:
// 方式 1:@Primary 标记主要模型
@Bean
@Primary
public ChatModel primaryChatModel() {
return ollamaChatModel;
}
// 方式 2:@Qualifier 按名称注入
@Autowired
@Qualifier("ollamaChatModel")
private ChatModel ollamaModel;
@Autowired
@Qualifier("openAIChatModel")
private ChatModel openAIModel;
七、一次对话请求的完整链路
7.1 链路全景
用户代码
chatClient.prompt().user("你好").call().content()
│
↓
┌───────────────┐
│ ChatClient │ ← 高层封装,提供流式 API
└───────┬───────┘
│ 构建 Prompt,触发 Advisor 链
↓
┌───────────────┐
│ Advisor Chain │ ← 前置拦截(注入记忆、日志等)
└───────┬───────┘
│
↓
┌───────────────┐
│ ChatModel │ ← 核心接口,发送请求
└───────┬───────┘
│ 转换 Prompt → API 请求
↓
┌───────────────┐
│ OllamaApi │ ← HTTP 客户端
└───────┬───────┘
│ HTTP POST
↓
┌───────────────┐
│ Ollama 服务 │ ← 本地 AI 模型推理
└───────┬───────┘
│ HTTP 响应
↑
┌───────────────┐
│ OllamaApi │ ← 解析响应
└───────┬───────┘
│ 转换 API 响应 → ChatResponse
↑
┌───────────────┐
│ Advisor Chain │ ← 后置拦截(日志、Token 统计等)
└───────┬───────┘
│
↑
┌───────────────┐
│ ChatClient │ ← 提取 content 返回
└───────────────┘
7.2 各层职责
| 层次 | 核心类 | 职责 |
|---|---|---|
| 用户层 | 业务代码 | 调用 ChatClient |
| 封装层 | ChatClient | 提供流式 Builder API,管理 Advisor 链 |
| 拦截层 | Advisor | 前置/后置增强(记忆注入、日志、限流) |
| 模型层 | ChatModel | 定义调用契约,屏蔽模型差异 |
| 适配层 | OllamaChatModel | 转换请求/响应格式,调用 HTTP API |
| 通信层 | OllamaApi | 封装 HTTP 通信细节 |
| 推理层 | Ollama 服务 | 实际的 AI 模型推理 |
八、设计模式总结
8.1 本篇涉及的设计模式
策略模式(Strategy)
ChatModel(策略接口)
├── OllamaChatModel(策略 A)
├── OpenAIChatModel(策略 B)
└── ZhipuAIChatModel(策略 C)
业务代码面向 ChatModel 接口编程,运行时注入具体策略。
适配器模式(Adapter)
目标接口:ChatModel.call(Prompt) → ChatResponse
被适配者:Ollama HTTP API
适配器:OllamaChatModel
OllamaChatModel 将 Spring AI 的标准调用转换为 Ollama 的 HTTP 请求格式。
模板方法模式(Template Method)
AbstractChatModel 定义了调用的骨架流程:
public abstract class AbstractChatModel implements ChatModel {
// 模板方法:定义骨架
public final ChatResponse call(Prompt prompt) {
// 1. 前置处理(通用)
validatePrompt(prompt);
// 2. 实际调用(子类实现)
ChatResponse response = doCall(prompt);
// 3. 后置处理(通用)
return postProcess(response);
}
// 抽象方法:子类实现具体调用
protected abstract ChatResponse doCall(Prompt prompt);
}
建造者模式(Builder)
// ChatClient 的构建
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultSystem("你是一个专业助手")
.defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory))
.build();
// OllamaOptions 的构建
OllamaOptions options = OllamaOptions.builder()
.model("qwen2.5:14b")
.temperature(0.3)
.numCtx(4096)
.build();
工厂模式(Factory)
// AutoConfiguration 中的 @Bean 方法就是工厂方法
@Bean
public OllamaChatModel ollamaChatModel(OllamaApi api, OllamaChatProperties props) {
return OllamaChatModel.builder()
.ollamaApi(api)
.defaultOptions(...)
.build();
}
九、小结
9.1 本篇要点
| 主题 | 核心要点 |
|---|---|
| 模块结构 | 核心层(接口)→ 实现层(适配器)→ Starter(自动装配) |
| 核心接口 | Model<T,R> → ChatModel / EmbeddingModel / ImageModel |
| 数据模型 | Prompt(输入)→ ChatResponse(输出)→ Generation(结果) |
| 实现机制 | 适配器模式:将各模型 API 适配为统一的 ChatModel 接口 |
| 自动装配 | @AutoConfiguration + @ConditionalOnMissingBean 实现可覆盖的默认配置 |
| 多模型切换 | 依赖倒置原则:业务代码依赖接口,Spring 注入具体实现 |
9.2 关键类清单
| 类 / 接口 | 所在模块 | 职责 |
|---|---|---|
Model<TReq, TRes> | spring-ai-core | 顶层模型接口 |
ChatModel | spring-ai-core | 对话模型接口 |
StreamingChatModel | spring-ai-core | 流式对话接口 |
Prompt | spring-ai-core | 请求封装 |
Message | spring-ai-core | 消息接口 |
ChatResponse | spring-ai-core | 响应封装 |
Generation | spring-ai-core | 单次生成结果 |
ChatOptions | spring-ai-core | 参数配置接口 |
OllamaChatModel | spring-ai-ollama | Ollama 适配器 |
OllamaAutoConfiguration | spring-ai-ollama-starter | 自动装配 |
9.3 下一篇预告
第 2 篇:ChatClient 调用链路
ChatModel 是底层接口,而日常开发中我们更多使用的是 ChatClient。下一篇将深入解读:
ChatClient相比ChatModel多了什么?prompt().user().call()这条链式调用背后的执行路径call()和stream()的内部差异ChatClient.Builder的构建过程
需要Spring AI系列学习代码的同学 欢迎关注公众号「AI日撰」,点击菜单「获取源码」获取完整代码(Gitee 仓库)。