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

0 阅读10分钟

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-ollamaspring-ai-openai
存储实现层向量数据库集成spring-ai-redisspring-ai-pgvector
自动装配层Spring Boot Starterspring-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?

  • 并非所有模型都支持流式输出
  • 流式和非流式的调用方式差异较大(ChatResponse vs Flux<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顶层模型接口
ChatModelspring-ai-core对话模型接口
StreamingChatModelspring-ai-core流式对话接口
Promptspring-ai-core请求封装
Messagespring-ai-core消息接口
ChatResponsespring-ai-core响应封装
Generationspring-ai-core单次生成结果
ChatOptionsspring-ai-core参数配置接口
OllamaChatModelspring-ai-ollamaOllama 适配器
OllamaAutoConfigurationspring-ai-ollama-starter自动装配

9.3 下一篇预告

第 2 篇:ChatClient 调用链路

ChatModel 是底层接口,而日常开发中我们更多使用的是 ChatClient。下一篇将深入解读:

  • ChatClient 相比 ChatModel 多了什么?
  • prompt().user().call() 这条链式调用背后的执行路径
  • call()stream() 的内部差异
  • ChatClient.Builder 的构建过程

需要Spring AI系列学习代码的同学 欢迎关注公众号「AI日撰」,点击菜单「获取源码」获取完整代码(Gitee 仓库)。