乐于分享
好东西不私藏

Spring AI 源码解读:一次对话请求的完整生命线

Spring AI 源码解读:一次对话请求的完整生命线

Spring AI 源码解读 – 第 1 篇:整体架构与核心抽象

一次 AI 对话请求背后的架构设计

📖 开篇引言

当我们写下这行代码时:

Stringanswer= 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
publicinterfaceModel<TReq, TRes> {
    TRes call(TReq request);
}

极简的泛型接口,定义了 “输入请求,返回响应” 这一核心契约。

2.2 三大模型接口

在 Model<TReq, TRes> 之上,Spring AI 扩展出三大模型接口:

// 1. 对话模型
publicinterfaceChatModelextendsModel<Prompt, ChatResponse> {

@Override
    ChatResponse call(Prompt prompt);

// 默认实现:提取第一个生成结果的文本内容
default String call(String message) {
Promptprompt=newPrompt(newUserMessage(message));
return call(prompt).getResult().getOutput().getContent();
    }
}

// 2. 向量嵌入模型
publicinterfaceEmbeddingModelextendsModel<EmbeddingRequest, EmbeddingResponse> {

@Override
    EmbeddingResponse call(EmbeddingRequest request);

// 便捷方法:直接对文本列表做向量化
default List<Double> embed(String text) { ... }
default List<float[]> embed(List<String> texts) { ... }
}

// 3. 图像生成模型
publicinterfaceImageModelextendsModel<ImagePrompt, ImageResponse> {

@Override
    ImageResponse call(ImagePrompt request);
}

接口继承体系

Model<TReq, TRes>
    ├── ChatModel                 对话模型
    │       └── StreamingChatModel    流式对话(扩展)
    ├── EmbeddingModel            向量嵌入模型
    └── ImageModel                图像生成模型

2.3 StreamingChatModel:流式扩展

流式输出是独立的子接口,而非在 ChatModel 中直接定义:

publicinterfaceStreamingChatModelextendsChatModel {

// 返回 Reactor 的 Flux,支持响应式流
    Flux<ChatResponse> stream(Prompt prompt);

// 便捷方法:直接流式输出文本
default Flux<String> stream(String message) {
Promptprompt=newPrompt(newUserMessage(message));
return stream(prompt)
            .map(response -> response.getResult().getOutput().getContent());
    }
}

为什么单独抽出 StreamingChatModel?

  • • 并非所有模型都支持流式输出
  • • 流式和非流式的调用方式差异较大(ChatResponse vs Flux<ChatResponse>
  • • 遵循接口隔离原则(ISP):不强迫不需要流式的实现类实现该方法

三、输入输出数据模型

3.1 Prompt:请求封装

publicclassPromptimplementsModelRequest<List<Message>> {

privatefinal List<Message> messages;      // 消息列表(核心)
privatefinal ChatOptions chatOptions;     // 可选参数(temperature 等)

// 单条消息的便捷构造
publicPrompt(String contents) {
this(newUserMessage(contents));
    }

publicPrompt(Message message) {
this(List.of(message));
    }

publicPrompt(List<Message> messages) {
this(messages, null);
    }

publicPrompt(List<Message> messages, ChatOptions chatOptions) {
this.messages = messages;
this.chatOptions = chatOptions;
    }
}

Prompt 的本质:消息列表 + 可选参数的容器。

3.2 Message 接口体系

publicinterfaceMessageextendsNode<String> {

    String getContent();           // 消息内容
    MessageType getMessageType();  // 消息角色
    Map<String, Object> getMetadata()// 元数据
}

四种消息类型

publicenumMessageType {
    USER("user"),        // 用户消息
    ASSISTANT("assistant"), // 助手消息(AI 回复)
    SYSTEM("system"),    // 系统提示词
    TOOL("tool");        // 工具调用结果
}

对应的实现类

// 用户消息
publicclassUserMessageimplementsMessage {
privatefinal String content;
// MessageType = USER
}

// 系统提示词
publicclassSystemMessageimplementsMessage {
privatefinal String content;
// MessageType = SYSTEM
}

// 助手消息(AI 的历史回复,用于多轮对话)
publicclassAssistantMessageimplementsMessage {
privatefinal String content;
privatefinal List<ToolCall> toolCalls; // 工具调用信息
// MessageType = ASSISTANT
}

// 工具调用结果
publicclassToolResponseMessageimplementsMessage {
privatefinal List<ToolResponse> responses;
// MessageType = TOOL
}

消息类型在多轮对话中的作用

[SystemMessage]   "你是一个专业的 Java 工程师"
[UserMessage]     "什么是 Spring AI?"
[AssistantMessage] "Spring AI 是..."
[UserMessage]     "它有哪些核心模块?"   ← 当前输入

3.3 ChatResponse:响应封装

publicclassChatResponseimplementsModelResponse<Generation> {

privatefinal List<Generation> generations;      // 生成结果列表
privatefinal ChatResponseMetadata metadata;     // 元数据(Token 用量等)

// 便捷方法:获取第一个生成结果
public Generation getResult() {
return generations.get(0);
    }
}

Generation:单个生成结果

publicclassGenerationimplementsModelResult<AssistantMessage> {

privatefinal AssistantMessage assistantMessage;  // AI 回复的消息
privatefinal ChatGenerationMetadata metadata;    // 生成元数据

// 获取 AI 回复内容
public AssistantMessage getOutput() {
return assistantMessage;
    }
}

完整的调用链

ChatResponseresponse= chatModel.call(prompt);

// 获取文本内容的完整路径
Stringcontent= response          // ChatResponse
    .getResult()                   // Generation
    .getOutput()                   // AssistantMessage
    .getContent();                 // String

3.4 ChatOptions:参数配置

publicinterfaceChatOptionsextendsModelOptions {

    String getModel();             // 模型名称
    Double getTemperature();       // 温度(创造性)
    Double getTopP();              // Top-P 采样
    Integer getMaxTokens();        // 最大 Token 数
    List<String> getStopSequences()// 停止词
// ...
}

各模型的 Options 实现

// Ollama 专属参数
publicclassOllamaOptionsimplementsChatOptions {
private String model;
private Double temperature;
private Integer numCtx;        // 上下文窗口大小(Ollama 特有)
private String format;         // 输出格式(json 等)
// ...
}

// OpenAI 专属参数
publicclassOpenAiChatOptionsimplementsChatOptions {
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 类结构

publicclassOllamaChatModelimplementsStreamingChatModel {

privatefinal OllamaApi ollamaApi;           // HTTP 客户端
privatefinal OllamaChatOptions defaultOptions; // 默认参数
privatefinal FunctionCallbackContext functionCallbackContext; // 工具调用上下文

@Override
public ChatResponse call(Prompt prompt) {
// 1. 合并 Options(请求级 > 默认级)
OllamaOptionsrequestOptions= mergeOptions(prompt.getOptions());

// 2. 构建 Ollama API 请求
ChatRequestrequest= buildRequest(prompt, requestOptions, false);

// 3. 调用 Ollama HTTP API
ChatResponseollamaResponse= ollamaApi.chat(request);

// 4. 转换为 Spring AI 标准响应
return convertResponse(ollamaResponse, request);
    }

@Override
public Flux<ChatResponse> stream(Prompt prompt) {
OllamaOptionsrequestOptions= mergeOptions(prompt.getOptions());
ChatRequestrequest= 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) {
Stringrole=switch (message.getMessageType()) {
case USER      -> "user";
case ASSISTANT -> "assistant";
case SYSTEM    -> "system";
case TOOL      -> "tool";
    };

returnnewChatRequest.Message(role, message.getContent());
}

4.3 响应转换:Ollama Response → ChatResponse

private ChatResponse convertResponse(ChatResponse ollamaResponse, ChatRequest request) {

// 构建 AssistantMessage
AssistantMessageassistantMessage=newAssistantMessage(
        ollamaResponse.getMessage().getContent()
    );

// 构建 Generation
Generationgeneration=newGeneration(
        assistantMessage,
        ChatGenerationMetadata.builder()
            .finishReason(ollamaResponse.getDoneReason())
            .build()
    );

// 构建 ChatResponseMetadata(Token 用量)
ChatResponseMetadatametadata= ChatResponseMetadata.builder()
        .usage(newDefaultUsage(
            ollamaResponse.getPromptEvalCount(),   // 输入 Token
            ollamaResponse.getEvalCount()           // 输出 Token
        ))
        .build();

returnnewChatResponse(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
})

publicclassOllamaAutoConfiguration {

// 创建 HTTP 客户端
@Bean
@ConditionalOnMissingBean
public OllamaApi ollamaApi(OllamaConnectionProperties props) {
returnnewOllamaApi(props.getBaseUrl());
    }

// 创建 ChatModel
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(
        prefix = OllamaChatProperties.CONFIG_PREFIX,
        name = "enabled",
        havingValue = "true",
        matchIfMissing = true
    )

public OllamaChatModel ollamaChatModel(
            OllamaApi ollamaApi,
            OllamaChatProperties chatProps)
 {

OllamaChatOptionsoptions= 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")
publicclassOllamaChatProperties {

publicstaticfinalStringCONFIG_PREFIX="spring.ai.ollama.chat";

privatebooleanenabled=true;
privateStringmodel="mistral";
privateOllamaOptionsoptions=newOllamaOptions();

// 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
publicclassChatService {
@Autowired
private OllamaChatModel chatModel;  // 耦合了 Ollama
}

// ✅ 正确:依赖抽象接口
@Service
publicclassChatService {
@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 定义了调用的骨架流程:

publicabstractclassAbstractChatModelimplementsChatModel {

// 模板方法:定义骨架
publicfinal ChatResponse call(Prompt prompt) {
// 1. 前置处理(通用)
        validatePrompt(prompt);

// 2. 实际调用(子类实现)
ChatResponseresponse= doCall(prompt);

// 3. 后置处理(通用)
return postProcess(response);
    }

// 抽象方法:子类实现具体调用
protectedabstract ChatResponse doCall(Prompt prompt);
}

建造者模式(Builder)

// ChatClient 的构建
ChatClientchatClient= ChatClient.builder(chatModel)
    .defaultSystem("你是一个专业助手")
    .defaultAdvisors(newMessageChatMemoryAdvisor(chatMemory))
    .build();

// OllamaOptions 的构建
OllamaOptionsoptions= 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 仓库)。