Spring AI 概述与核心概念

学习目标

  • 深入理解 Spring AI 的架构设计和核心原理
  • 掌握 Spring AI 的核心接口和抽象层
  • 理解 Spring AI 与其他 AI 框架的区别
  • 掌握 Spring AI 的设计模式和最佳实践
  • 能够基于核心概念设计复杂的 AI 应用

知识结构

Spring AI 核心

架构设计

分层架构

抽象层设计

可移植性

扩展性

核心接口

Model 接口

Client 接口

Prompt 体系

Response 体系

设计模式

策略模式

工厂模式

建造者模式

适配器模式

核心组件

ChatModel

EmbeddingModel

ImageModel

VectorStore

配置体系

自动配置

属性绑定

条件装配

Bean 管理

一、项目场景引入

1.1 企业 AI 应用的挑战

场景:某电商公司的 AI 应用需求

初期需求:
- 使用 OpenAI 构建智能客服
- 快速上线,验证效果

发展阶段:
- 用户量增长,成本上升
- 需要评估其他 AI 服务商
- 考虑使用本地模型降低成本

面临的问题:
1. 不同 AI 服务商的 API 完全不同
2. 切换模型需要重写大量代码
3. 测试环境和生产环境使用不同模型
4. 缺乏统一的监控和管理

传统解决方案的问题:

// 方案 1:直接调用 OpenAI API
public class OpenAIService {
    public String chat(String message) {
        // 硬编码 OpenAI API 调用
        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://api.openai.com/v1/chat/completions"))
            .header("Authorization", "Bearer " + apiKey)
            .POST(HttpRequest.BodyPublishers.ofString(buildRequestBody(message)))
            .build();
        // ... 处理响应
    }
}

// 问题:
// 1. 切换到 Azure 需要完全重写
// 2. 代码与具体实现强耦合
// 3. 难以测试和维护
// 方案 2:自己封装抽象层
public interface AIService {
    String chat(String message);
}

public class OpenAIServiceImpl implements AIService {
    // OpenAI 实现
}

public class AzureAIServiceImpl implements AIService {
    // Azure 实现
}

// 问题:
// 1. 需要自己维护抽象层
// 2. 缺少 Spring 生态集成
// 3. 没有自动配置
// 4. 缺少企业级特性

1.2 Spring AI 的解决方案

统一的抽象层 + Spring 生态集成:

// Spring AI 方式
@Service
public class ChatService {
    
    @Autowired
    private ChatClient chatClient;  // 统一接口
    
    public String chat(String message) {
        return chatClient.call(message);  // 简单调用
    }
}

// 配置文件切换模型
# 开发环境:使用 Ollama 本地模型
spring:
  ai:
    ollama:
      base-url: http://localhost:11434
      chat:
        options:
          model: llama2

# 生产环境:使用 OpenAI
spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        options:
          model: gpt-4

# 代码无需修改!

优势:

  • ✅ 统一的编程接口
  • ✅ 配置化的模型切换
  • ✅ Spring Boot 自动配置
  • ✅ 依赖注入和 AOP 支持
  • ✅ 完善的测试支持

1.3 为什么需要 Spring AI

对比其他方案:

方案 优点 缺点 适用场景
直接调用 API 灵活、直接 代码耦合、难维护 简单原型
LangChain 功能丰富 Python 生态、学习曲线陡 Python 项目
自己封装 完全控制 开发成本高、缺少生态 特殊需求
Spring AI Spring 集成、易用 相对较新 Java 企业应用

Spring AI 的定位:

Spring AI = Spring 生态 + AI 能力
         = Spring Boot 的简单 + AI 的强大
         = 企业级特性 + 开发者友好

二、Spring AI 架构设计

2.1 整体架构

应用层

Spring AI Core

Model 抽象层

OpenAI 实现

Azure 实现

Ollama 实现

其他实现

Spring Boot 自动配置

属性绑定

Bean 管理

条件装配

企业级特性

缓存

监控

重试

限流

架构层次:

  1. 应用层

    • 业务代码
    • 服务层
    • 控制器层
  2. Spring AI Core

    • 核心抽象接口
    • 通用功能实现
    • 工具类和辅助类
  3. Model 实现层

    • 各个 AI 服务商的具体实现
    • 适配器模式
    • 协议转换
  4. Spring Boot 集成层

    • 自动配置
    • 属性管理
    • Bean 生命周期管理

2.2 分层架构详解

第一层:接口抽象层

// 核心接口定义
package org.springframework.ai.model;

/**
 * AI 模型的顶层接口
 * 所有 AI 模型都实现此接口
 */
public interface Model<TRequest extends ModelRequest<?>, TResponse extends ModelResponse<?>> {
    
    /**
     * 调用模型
     * 
     * @param request 请求对象
     * @return 响应对象
     */
    TResponse call(TRequest request);
}

/**
 * 聊天模型接口
 */
public interface ChatModel extends Model<Prompt, ChatResponse> {
    
    /**
     * 同步调用
     */
    @Override
    ChatResponse call(Prompt prompt);
    
    /**
     * 流式调用
     */
    Flux<ChatResponse> stream(Prompt prompt);
}

/**
 * 嵌入模型接口
 */
public interface EmbeddingModel extends Model<EmbeddingRequest, EmbeddingResponse> {
    
    /**
     * 文本向量化
     */
    @Override
    EmbeddingResponse call(EmbeddingRequest request);
    
    /**
     * 获取向量维度
     */
    int dimensions();
}

第二层:Client 封装层

package org.springframework.ai.chat;

/**
 * 聊天客户端
 * 提供更简单的 API
 */
public interface ChatClient {
    
    /**
     * 简单调用(字符串输入)
     */
    default String call(String message) {
        return call(new Prompt(message))
            .getResult()
            .getOutput()
            .getContent();
    }
    
    /**
     * 标准调用(Prompt 输入)
     */
    ChatResponse call(Prompt prompt);
    
    /**
     * 流式调用
     */
    Flux<ChatResponse> stream(Prompt prompt);
}

第三层:具体实现层

package org.springframework.ai.openai;

/**
 * OpenAI 聊天模型实现
 */
public class OpenAiChatModel implements ChatModel {
    
    private final OpenAiApi openAiApi;
    private final OpenAiChatOptions defaultOptions;
    
    public OpenAiChatModel(OpenAiApi openAiApi, OpenAiChatOptions options) {
        this.openAiApi = openAiApi;
        this.defaultOptions = options;
    }
    
    @Override
    public ChatResponse call(Prompt prompt) {
        // 1. 转换请求格式
        OpenAiApi.ChatCompletionRequest request = createRequest(prompt);
        
        // 2. 调用 OpenAI API
        OpenAiApi.ChatCompletion completion = openAiApi.chatCompletionEntity(request)
            .getBody();
        
        // 3. 转换响应格式
        return toChatResponse(completion);
    }
    
    @Override
    public Flux<ChatResponse> stream(Prompt prompt) {
        // 流式实现
        OpenAiApi.ChatCompletionRequest request = createRequest(prompt);
        return openAiApi.chatCompletionStream(request)
            .map(this::toChatResponse);
    }
}

2.3 可移植性设计

核心思想:面向接口编程

// 应用代码只依赖接口
@Service
public class AIService {
    
    private final ChatModel chatModel;  // 接口类型
    
    public AIService(ChatModel chatModel) {
        this.chatModel = chatModel;
    }
    
    public String chat(String message) {
        Prompt prompt = new Prompt(message);
        return chatModel.call(prompt)
            .getResult()
            .getOutput()
            .getContent();
    }
}

// 运行时注入具体实现
// OpenAI 实现
@Bean
@ConditionalOnProperty("spring.ai.openai.api-key")
public ChatModel openAiChatModel() {
    return new OpenAiChatModel(...);
}

// Azure 实现
@Bean
@ConditionalOnProperty("spring.ai.azure.openai.api-key")
public ChatModel azureChatModel() {
    return new AzureOpenAiChatModel(...);
}

// Ollama 实现
@Bean
@ConditionalOnProperty("spring.ai.ollama.base-url")
public ChatModel ollamaChatModel() {
    return new OllamaChatModel(...);
}

切换模型的三种方式:

# 方式 1:修改配置文件
spring:
  profiles:
    active: openai  # 或 azure, ollama

---
spring:
  config:
    activate:
      on-profile: openai
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}

---
spring:
  config:
    activate:
      on-profile: azure
  ai:
    azure:
      openai:
        api-key: ${AZURE_API_KEY}
// 方式 2:使用 @Qualifier
@Service
public class MultiModelService {
    
    @Autowired
    @Qualifier("openAiChatModel")
    private ChatModel openAiModel;
    
    @Autowired
    @Qualifier("azureChatModel")
    private ChatModel azureModel;
    
    public String chatWithOpenAI(String message) {
        return openAiModel.call(new Prompt(message))
            .getResult().getOutput().getContent();
    }
    
    public String chatWithAzure(String message) {
        return azureModel.call(new Prompt(message))
            .getResult().getOutput().getContent();
    }
}
// 方式 3:动态选择
@Service
public class DynamicModelService {
    
    private final Map<String, ChatModel> models;
    
    public DynamicModelService(List<ChatModel> modelList) {
        this.models = modelList.stream()
            .collect(Collectors.toMap(
                model -> model.getClass().getSimpleName(),
                model -> model
            ));
    }
    
    public String chat(String modelName, String message) {
        ChatModel model = models.get(modelName);
        if (model == null) {
            throw new IllegalArgumentException("Unknown model: " + modelName);
        }
        return model.call(new Prompt(message))
            .getResult().getOutput().getContent();
    }
}

三、核心接口详解

3.1 Model 接口体系

接口继承关系:

«interface»

Model

+call(request) : response

«interface»

ChatModel

+call(prompt) : ChatResponse

+stream(prompt) : Flux<ChatResponse>

«interface»

EmbeddingModel

+call(request) : EmbeddingResponse

+dimensions() : int

«interface»

ImageModel

+call(request) : ImageResponse

OpenAiChatModel

AzureOpenAiChatModel

OllamaChatModel

OpenAiEmbeddingModel

AzureOpenAiEmbeddingModel

Model 接口源码分析:

package org.springframework.ai.model;

/**
 * AI 模型的顶层抽象
 * 
 * @param <TRequest> 请求类型
 * @param <TResponse> 响应类型
 */
public interface Model<TRequest extends ModelRequest<?>, TResponse extends ModelResponse<?>> {
    
    /**
     * 调用模型
     * 这是所有 AI 模型的统一入口
     * 
     * @param request 模型请求
     * @return 模型响应
     */
    TResponse call(TRequest request);
    
    /**
     * 获取模型选项(可选)
     * 
     * @return 模型配置选项
     */
    default ModelOptions getDefaultOptions() {
        return ModelOptionsUtils.EMPTY_OPTIONS;
    }
}

为什么这样设计?

  1. 泛型设计:支持不同类型的请求和响应
  2. 单一方法:简化接口,易于实现
  3. 默认方法:提供默认实现,减少样板代码
  4. 类型安全:编译时检查类型

3.2 ChatModel 接口

完整定义:

package org.springframework.ai.chat.model;

import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.ChatResponse;
import org.springframework.ai.model.Model;
import reactor.core.publisher.Flux;

/**
 * 聊天模型接口
 * 支持同步和流式两种调用方式
 */
public interface ChatModel extends Model<Prompt, ChatResponse> {
    
    /**
     * 同步调用
     * 等待完整响应后返回
     * 
     * @param prompt 提示词
     * @return 完整的聊天响应
     */
    @Override
    ChatResponse call(Prompt prompt);
    
    /**
     * 流式调用
     * 逐步返回响应内容
     * 
     * @param prompt 提示词
     * @return 响应流
     */
    default Flux<ChatResponse> stream(Prompt prompt) {
        throw new UnsupportedOperationException(
            "Stream not supported for this model"
        );
    }
}

使用示例:

package com.example.springai.service;

import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.ChatResponse;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;

/**
 * ChatModel 使用示例
 */
@Service
public class ChatModelService {
    
    private final ChatModel chatModel;
    
    public ChatModelService(ChatModel chatModel) {
        this.chatModel = chatModel;
    }
    
    /**
     * 同步调用示例
     */
    public String syncChat(String message) {
        // 1. 创建 Prompt
        Prompt prompt = new Prompt(message);
        
        // 2. 调用模型(阻塞等待)
        ChatResponse response = chatModel.call(prompt);
        
        // 3. 提取响应内容
        return response.getResult().getOutput().getContent();
    }
    
    /**
     * 流式调用示例
     */
    public Flux<String> streamChat(String message) {
        // 1. 创建 Prompt
        Prompt prompt = new Prompt(message);
        
        // 2. 流式调用
        Flux<ChatResponse> responseStream = chatModel.stream(prompt);
        
        // 3. 提取内容流
        return responseStream
            .map(response -> response.getResult().getOutput().getContent());
    }
    
    /**
     * 流式调用 - 实时输出
     */
    public void streamChatWithCallback(String message, Consumer<String> callback) {
        Prompt prompt = new Prompt(message);
        
        chatModel.stream(prompt)
            .map(response -> response.getResult().getOutput().getContent())
            .subscribe(
                callback::accept,  // 每次接收到内容时调用
                error -> System.err.println("Error: " + error),
                () -> System.out.println("Stream completed")
            );
    }
}

3.3 EmbeddingModel 接口

接口定义:

package org.springframework.ai.embedding;

import org.springframework.ai.model.Model;

/**
 * 嵌入模型接口
 * 用于将文本转换为向量
 */
public interface EmbeddingModel extends Model<EmbeddingRequest, EmbeddingResponse> {
    
    /**
     * 文本向量化
     * 
     * @param request 嵌入请求
     * @return 嵌入响应(包含向量)
     */
    @Override
    EmbeddingResponse call(EmbeddingRequest request);
    
    /**
     * 获取向量维度
     * 
     * @return 向量维度数
     */
    int dimensions();
    
    /**
     * 便捷方法:单个文本向量化
     * 
     * @param text 文本
     * @return 向量
     */
    default List<Double> embed(String text) {
        EmbeddingRequest request = new EmbeddingRequest(List.of(text), null);
        EmbeddingResponse response = call(request);
        return response.getResults().get(0).getOutput();
    }
    
    /**
     * 便捷方法:批量文本向量化
     * 
     * @param texts 文本列表
     * @return 向量列表
     */
    default List<List<Double>> embed(List<String> texts) {
        EmbeddingRequest request = new EmbeddingRequest(texts, null);
        EmbeddingResponse response = call(request);
        return response.getResults().stream()
            .map(Embedding::getOutput)
            .collect(Collectors.toList());
    }
}

使用示例:

package com.example.springai.service;

import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.embedding.EmbeddingRequest;
import org.springframework.ai.embedding.EmbeddingResponse;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * 嵌入模型使用示例
 */
@Service
public class EmbeddingService {
    
    private final EmbeddingModel embeddingModel;
    
    public EmbeddingService(EmbeddingModel embeddingModel) {
        this.embeddingModel = embeddingModel;
    }
    
    /**
     * 单个文本向量化
     */
    public List<Double> embedText(String text) {
        return embeddingModel.embed(text);
    }
    
    /**
     * 批量文本向量化
     */
    public List<List<Double>> embedTexts(List<String> texts) {
        return embeddingModel.embed(texts);
    }
    
    /**
     * 计算文本相似度
     */
    public double calculateSimilarity(String text1, String text2) {
        List<Double> vector1 = embedText(text1);
        List<Double> vector2 = embedText(text2);
        
        return cosineSimilarity(vector1, vector2);
    }
    
    /**
     * 余弦相似度计算
     */
    private double cosineSimilarity(List<Double> v1, List<Double> v2) {
        double dotProduct = 0.0;
        double norm1 = 0.0;
        double norm2 = 0.0;
        
        for (int i = 0; i < v1.size(); i++) {
            dotProduct += v1.get(i) * v2.get(i);
            norm1 += Math.pow(v1.get(i), 2);
            norm2 += Math.pow(v2.get(i), 2);
        }
        
        return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
    }
    
    /**
     * 获取向量维度
     */
    public int getDimensions() {
        return embeddingModel.dimensions();
    }
}

3.4 Prompt 体系

Prompt 类结构:

package org.springframework.ai.chat.prompt;

import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.model.ModelRequest;

import java.util.List;

/**
 * 提示词类
 * 封装发送给 AI 模型的消息和选项
 */
public class Prompt implements ModelRequest<List<Message>> {
    
    private final List<Message> messages;
    private final ChatOptions options;
    
    /**
     * 构造函数 1:简单文本
     */
    public Prompt(String contents) {
        this(new UserMessage(contents));
    }
    
    /**
     * 构造函数 2:单个消息
     */
    public Prompt(Message message) {
        this(List.of(message));
    }
    
    /**
     * 构造函数 3:消息列表
     */
    public Prompt(List<Message> messages) {
        this(messages, null);
    }
    
    /**
     * 构造函数 4:消息 + 选项
     */
    public Prompt(List<Message> messages, ChatOptions options) {
        this.messages = messages;
        this.options = options;
    }
    
    @Override
    public List<Message> getInstructions() {
        return this.messages;
    }
    
    public ChatOptions getOptions() {
        return this.options;
    }
}

Message 类型:

package org.springframework.ai.chat.messages;

/**
 * 消息基类
 */
public interface Message {
    
    /**
     * 获取消息内容
     */
    String getContent();
    
    /**
     * 获取消息类型
     */
    MessageType getMessageType();
    
    /**
     * 获取元数据
     */
    Map<String, Object> getMetadata();
}

/**
 * 用户消息
 */
public class UserMessage extends AbstractMessage {
    
    public UserMessage(String content) {
        super(MessageType.USER, content);
    }
}

/**
 * 系统消息
 */
public class SystemMessage extends AbstractMessage {
    
    public SystemMessage(String content) {
        super(MessageType.SYSTEM, content);
    }
}

/**
 * 助手消息
 */
public class AssistantMessage extends AbstractMessage {
    
    public AssistantMessage(String content) {
        super(MessageType.ASSISTANT, content);
    }
}

Prompt 使用示例:

package com.example.springai.service;

import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.chat.messages.*;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * Prompt 使用示例
 */
@Service
public class PromptService {
    
    private final ChatClient chatClient;
    
    public PromptService(ChatClient chatClient) {
        this.chatClient = chatClient;
    }
    
    /**
     * 示例 1:简单 Prompt
     */
    public String simplePrompt() {
        Prompt prompt = new Prompt("你好");
        return chatClient.call(prompt)
            .getResult().getOutput().getContent();
    }
    
    /**
     * 示例 2:带系统消息的 Prompt
     */
    public String promptWithSystem() {
        List<Message> messages = List.of(
            new SystemMessage("你是一个专业的 Java 技术专家"),
            new UserMessage("什么是 Spring Boot?")
        );
        
        Prompt prompt = new Prompt(messages);
        return chatClient.call(prompt)
            .getResult().getOutput().getContent();
    }
    
    /**
     * 示例 3:多轮对话 Prompt
     */
    public String multiTurnPrompt() {
        List<Message> messages = List.of(
            new SystemMessage("你是一个友好的助手"),
            new UserMessage("你好"),
            new AssistantMessage("你好!有什么可以帮助你的?"),
            new UserMessage("介绍一下 Spring AI")
        );
        
        Prompt prompt = new Prompt(messages);
        return chatClient.call(prompt)
            .getResult().getOutput().getContent();
    }
    
    /**
     * 示例 4:带选项的 Prompt
     */
    public String promptWithOptions() {
        // 创建消息
        Message message = new UserMessage("写一首诗");
        
        // 创建选项
        OpenAiChatOptions options = OpenAiChatOptions.builder()
            .withModel("gpt-4")
            .withTemperature(1.5)  // 高创造性
            .withMaxTokens(500)
            .build();
        
        // 创建 Prompt
        Prompt prompt = new Prompt(List.of(message), options);
        
        return chatClient.call(prompt)
            .getResult().getOutput().getContent();
    }
}

3.5 Response 体系

ChatResponse 结构:

package org.springframework.ai.chat;

import org.springframework.ai.model.ModelResponse;

import java.util.List;

/**
 * 聊天响应
 */
public class ChatResponse implements ModelResponse<Generation> {
    
    private final List<Generation> generations;
    private final ChatResponseMetadata metadata;
    
    public ChatResponse(List<Generation> generations) {
        this(generations, ChatResponseMetadata.NULL);
    }
    
    public ChatResponse(List<Generation> generations, ChatResponseMetadata metadata) {
        this.generations = generations;
        this.metadata = metadata;
    }
    
    /**
     * 获取所有生成结果
     */
    @Override
    public List<Generation> getResults() {
        return this.generations;
    }
    
    /**
     * 获取第一个结果(最常用)
     */
    @Override
    public Generation getResult() {
        return this.generations.get(0);
    }
    
    /**
     * 获取元数据
     */
    @Override
    public ChatResponseMetadata getMetadata() {
        return this.metadata;
    }
}

Generation 类:

package org.springframework.ai.chat;

/**
 * 单个生成结果
 */
public class Generation implements ModelResult<AssistantMessage> {
    
    private final AssistantMessage assistantMessage;
    private final ChatGenerationMetadata metadata;
    
    public Generation(String text) {
        this(new AssistantMessage(text));
    }
    
    public Generation(AssistantMessage assistantMessage) {
        this(assistantMessage, ChatGenerationMetadata.NULL);
    }
    
    public Generation(AssistantMessage assistantMessage, ChatGenerationMetadata metadata) {
        this.assistantMessage = assistantMessage;
        this.metadata = metadata;
    }
    
    /**
     * 获取输出消息
     */
    @Override
    public AssistantMessage getOutput() {
        return this.assistantMessage;
    }
    
    /**
     * 获取元数据
     */
    @Override
    public ChatGenerationMetadata getMetadata() {
        return this.metadata;
    }
}

元数据类:

package org.springframework.ai.chat;

/**
 * 响应元数据
 */
public interface ChatResponseMetadata {
    
    /**
     * 获取 Token 使用情况
     */
    Usage getUsage();
    
    /**
     * 获取速率限制信息
     */
    RateLimit getRateLimit();
    
    /**
     * 获取其他元数据
     */
    <T> T get(String key);
}

/**
 * Token 使用情况
 */
public interface Usage {
    
    /**
     * 输入 Token 数
     */
    Long getPromptTokens();
    
    /**
     * 输出 Token 数
     */
    Long getGenerationTokens();
    
    /**
     * 总 Token 数
     */
    Long getTotalTokens();
}

/**
 * 速率限制信息
 */
public interface RateLimit {
    
    /**
     * 请求限制
     */
    Long getRequestsLimit();
    
    /**
     * 剩余请求数
     */
    Long getRequestsRemaining();
    
    /**
     * Token 限制
     */
    Long getTokensLimit();
    
    /**
     * 剩余 Token 数
     */
    Long getTokensRemaining();
}

Response 使用示例:

package com.example.springai.service;

import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.chat.ChatResponse;
import org.springframework.ai.chat.Generation;
import org.springframework.ai.chat.metadata.Usage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;

/**
 * Response 使用示例
 */
@Slf4j
@Service
public class ResponseService {
    
    private final ChatClient chatClient;
    
    public ResponseService(ChatClient chatClient) {
        this.chatClient = chatClient;
    }
    
    /**
     * 完整的响应处理
     */
    public void handleResponse(String message) {
        // 1. 调用 AI
        Prompt prompt = new Prompt(message);
        ChatResponse response = chatClient.call(prompt);
        
        // 2. 获取生成结果
        Generation generation = response.getResult();
        String content = generation.getOutput().getContent();
        log.info("AI 回复: {}", content);
        
        // 3. 获取元数据
        var metadata = response.getMetadata();
        
        // 4. 获取 Token 使用情况
        Usage usage = metadata.getUsage();
        log.info("Token 使用 - 输入: {}, 输出: {}, 总计: {}",
            usage.getPromptTokens(),
            usage.getGenerationTokens(),
            usage.getTotalTokens()
        );
        
        // 5. 获取结束原因
        String finishReason = generation.getMetadata().getFinishReason();
        log.info("结束原因: {}", finishReason);
        
        // 6. 计算成本(假设 gpt-3.5-turbo 价格)
        double inputCost = usage.getPromptTokens() * 0.0015 / 1000;
        double outputCost = usage.getGenerationTokens() * 0.002 / 1000;
        double totalCost = inputCost + outputCost;
        log.info("预估成本: ${}", String.format("%.6f", totalCost));
    }
    
    /**
     * 处理多个生成结果
     */
    public void handleMultipleGenerations(String message) {
        Prompt prompt = new Prompt(message);
        ChatResponse response = chatClient.call(prompt);
        
        // 遍历所有生成结果
        for (int i = 0; i < response.getResults().size(); i++) {
            Generation generation = response.getResults().get(i);
            String content = generation.getOutput().getContent();
            log.info("结果 {}: {}", i + 1, content);
        }
    }
}

四、设计模式应用

4.1 策略模式

应用场景: 不同的 AI 模型实现

// 策略接口
public interface ChatModel {
    ChatResponse call(Prompt prompt);
}

// 具体策略 1
public class OpenAiChatModel implements ChatModel {
    @Override
    public ChatResponse call(Prompt prompt) {
        // OpenAI 实现
    }
}

// 具体策略 2
public class AzureOpenAiChatModel implements ChatModel {
    @Override
    public ChatResponse call(Prompt prompt) {
        // Azure 实现
    }
}

// 上下文
@Service
public class ChatService {
    private final ChatModel chatModel;  // 策略
    
    public ChatService(ChatModel chatModel) {
        this.chatModel = chatModel;
    }
    
    public String chat(String message) {
        return chatModel.call(new Prompt(message))
            .getResult().getOutput().getContent();
    }
}

4.2 建造者模式

应用场景: 构建复杂的选项对象

// 建造者模式
OpenAiChatOptions options = OpenAiChatOptions.builder()
    .withModel("gpt-4")
    .withTemperature(0.7)
    .withMaxTokens(1000)
    .withTopP(0.9)
    .withFrequencyPenalty(0.0)
    .withPresencePenalty(0.0)
    .build();

// 实现
public class OpenAiChatOptions {
    
    private String model;
    private Double temperature;
    private Integer maxTokens;
    // ... 其他字段
    
    public static Builder builder() {
        return new Builder();
    }
    
    public static class Builder {
        private String model;
        private Double temperature;
        private Integer maxTokens;
        
        public Builder withModel(String model) {
            this.model = model;
            return this;
        }
        
        public Builder withTemperature(Double temperature) {
            this.temperature = temperature;
            return this;
        }
        
        public Builder withMaxTokens(Integer maxTokens) {
            this.maxTokens = maxTokens;
            return this;
        }
        
        public OpenAiChatOptions build() {
            return new OpenAiChatOptions(this);
        }
    }
}

4.3 适配器模式

应用场景: 适配不同 AI 服务商的 API

// 目标接口(Spring AI 标准)
public interface ChatModel {
    ChatResponse call(Prompt prompt);
}

// 被适配者(OpenAI API)
public class OpenAiApi {
    public ChatCompletion createChatCompletion(ChatCompletionRequest request) {
        // OpenAI 原生 API 调用
    }
}

// 适配器
public class OpenAiChatModel implements ChatModel {
    
    private final OpenAiApi openAiApi;
    
    @Override
    public ChatResponse call(Prompt prompt) {
        // 1. 转换请求格式(Spring AI → OpenAI)
        ChatCompletionRequest request = adaptRequest(prompt);
        
        // 2. 调用 OpenAI API
        ChatCompletion completion = openAiApi.createChatCompletion(request);
        
        // 3. 转换响应格式(OpenAI → Spring AI)
        return adaptResponse(completion);
    }
    
    private ChatCompletionRequest adaptRequest(Prompt prompt) {
        // 格式转换逻辑
    }
    
    private ChatResponse adaptResponse(ChatCompletion completion) {
        // 格式转换逻辑
    }
}

4.4 工厂模式

应用场景: 创建不同类型的 Message

// 工厂类
public class MessageFactory {
    
    public static Message createMessage(MessageType type, String content) {
        return switch (type) {
            case USER -> new UserMessage(content);
            case SYSTEM -> new SystemMessage(content);
            case ASSISTANT -> new AssistantMessage(content);
            default -> throw new IllegalArgumentException("Unknown message type: " + type);
        };
    }
    
    public static List<Message> createConversation(String systemPrompt, String... userMessages) {
        List<Message> messages = new ArrayList<>();
        messages.add(new SystemMessage(systemPrompt));
        
        for (String userMessage : userMessages) {
            messages.add(new UserMessage(userMessage));
        }
        
        return messages;
    }
}

// 使用
List<Message> messages = MessageFactory.createConversation(
    "你是一个专业的助手",
    "你好",
    "介绍一下 Spring AI"
);

五、Spring Boot 自动配置

5.1 自动配置原理

配置类示例:

package org.springframework.ai.autoconfigure.openai;

import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;

/**
 * OpenAI 自动配置类
 */
@AutoConfiguration
@ConditionalOnClass(OpenAiApi.class)
@EnableConfigurationProperties({
    OpenAiConnectionProperties.class,
    OpenAiChatProperties.class
})
public class OpenAiAutoConfiguration {
    
    /**
     * 创建 OpenAI API Bean
     */
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = "spring.ai.openai", name = "api-key")
    public OpenAiApi openAiApi(OpenAiConnectionProperties properties) {
        return new OpenAiApi(
            properties.getBaseUrl(),
            properties.getApiKey()
        );
    }
    
    /**
     * 创建 ChatModel Bean
     */
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = "spring.ai.openai", name = "api-key")
    public OpenAiChatModel openAiChatModel(
            OpenAiApi openAiApi,
            OpenAiChatProperties chatProperties) {
        
        return new OpenAiChatModel(
            openAiApi,
            chatProperties.getOptions()
        );
    }
}

属性配置类:

package org.springframework.ai.autoconfigure.openai;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * OpenAI 连接属性
 */
@ConfigurationProperties(prefix = "spring.ai.openai")
public class OpenAiConnectionProperties {
    
    /**
     * API Key
     */
    private String apiKey;
    
    /**
     * Base URL
     */
    private String baseUrl = "https://api.openai.com";
    
    // Getters and Setters
}

/**
 * OpenAI 聊天属性
 */
@ConfigurationProperties(prefix = "spring.ai.openai.chat")
public class OpenAiChatProperties {
    
    /**
     * 是否启用
     */
    private boolean enabled = true;
    
    /**
     * 模型选项
     */
    private OpenAiChatOptions options = OpenAiChatOptions.builder()
        .withModel("gpt-3.5-turbo")
        .withTemperature(0.7)
        .build();
    
    // Getters and Setters
}

5.2 条件装配

常用条件注解:

// 1. 类路径条件
@ConditionalOnClass(OpenAiApi.class)
// 当类路径中存在 OpenAiApi 类时生效

// 2. Bean 条件
@ConditionalOnMissingBean(ChatModel.class)
// 当容器中不存在 ChatModel Bean 时生效

// 3. 属性条件
@ConditionalOnProperty(prefix = "spring.ai.openai", name = "api-key")
// 当配置了 spring.ai.openai.api-key 时生效

// 4. 表达式条件
@ConditionalOnExpression("${spring.ai.openai.enabled:true}")
// 当表达式为 true 时生效

自定义条件:

package com.example.springai.config;

import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

/**
 * 自定义条件:检查 API Key 是否有效
 */
public class OnValidApiKeyCondition extends SpringBootCondition {
    
    @Override
    public ConditionOutcome getMatchOutcome(
            ConditionContext context,
            AnnotatedTypeMetadata metadata) {
        
        String apiKey = context.getEnvironment()
            .getProperty("spring.ai.openai.api-key");
        
        if (apiKey == null || apiKey.isBlank()) {
            return ConditionOutcome.noMatch("API Key is not configured");
        }
        
        if (!apiKey.startsWith("sk-")) {
            return ConditionOutcome.noMatch("API Key format is invalid");
        }
        
        return ConditionOutcome.match("API Key is valid");
    }
}

// 使用
@Configuration
@Conditional(OnValidApiKeyCondition.class)
public class OpenAiConfig {
    // ...
}

5.3 多模型配置

配置多个模型:

spring:
  ai:
    # OpenAI 配置
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        options:
          model: gpt-3.5-turbo
          temperature: 0.7
    
    # Azure OpenAI 配置
    azure:
      openai:
        api-key: ${AZURE_OPENAI_API_KEY}
        endpoint: ${AZURE_OPENAI_ENDPOINT}
        chat:
          options:
            deployment-name: gpt-35-turbo
            temperature: 0.7
    
    # Ollama 配置
    ollama:
      base-url: http://localhost:11434
      chat:
        options:
          model: llama2
          temperature: 0.7

配置类:

package com.example.springai.config;

import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

/**
 * 多模型配置
 */
@Configuration
public class MultiModelConfig {
    
    /**
     * OpenAI 模型(主要)
     */
    @Bean
    @Primary
    @Qualifier("openai")
    @ConditionalOnProperty("spring.ai.openai.api-key")
    public ChatModel openAiChatModel(OpenAiChatModel model) {
        return model;
    }
    
    /**
     * Azure 模型(备用)
     */
    @Bean
    @Qualifier("azure")
    @ConditionalOnProperty("spring.ai.azure.openai.api-key")
    public ChatModel azureChatModel(AzureOpenAiChatModel model) {
        return model;
    }
    
    /**
     * Ollama 模型(开发环境)
     */
    @Bean
    @Qualifier("ollama")
    @ConditionalOnProperty("spring.ai.ollama.base-url")
    public ChatModel ollamaChatModel(OllamaChatModel model) {
        return model;
    }
}

使用多个模型:

package com.example.springai.service;

import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

/**
 * 多模型服务
 */
@Service
public class MultiModelService {
    
    private final ChatModel openAiModel;
    private final ChatModel azureModel;
    private final ChatModel ollamaModel;
    
    public MultiModelService(
            @Qualifier("openai") ChatModel openAiModel,
            @Qualifier("azure") ChatModel azureModel,
            @Qualifier("ollama") ChatModel ollamaModel) {
        this.openAiModel = openAiModel;
        this.azureModel = azureModel;
        this.ollamaModel = ollamaModel;
    }
    
    /**
     * 使用 OpenAI
     */
    public String chatWithOpenAI(String message) {
        return openAiModel.call(new Prompt(message))
            .getResult().getOutput().getContent();
    }
    
    /**
     * 使用 Azure(备用)
     */
    public String chatWithAzure(String message) {
        return azureModel.call(new Prompt(message))
            .getResult().getOutput().getContent();
    }
    
    /**
     * 使用 Ollama(开发环境)
     */
    public String chatWithOllama(String message) {
        return ollamaModel.call(new Prompt(message))
            .getResult().getOutput().getContent();
    }
    
    /**
     * 智能路由:根据消息长度选择模型
     */
    public String chatWithSmartRouting(String message) {
        if (message.length() < 100) {
            // 短消息使用便宜的模型
            return chatWithOllama(message);
        } else if (message.length() < 500) {
            // 中等长度使用 gpt-3.5
            return chatWithOpenAI(message);
        } else {
            // 长消息使用 Azure(更稳定)
            return chatWithAzure(message);
        }
    }
}

六、实战应用场景

6.1 场景一:构建可切换的 AI 服务

需求: 开发环境使用本地模型,生产环境使用云端模型

实现:

package com.example.springai.service;

import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.stereotype.Service;

/**
 * 环境感知的 AI 服务
 */
@Service
public class EnvironmentAwareChatService {
    
    private final ChatModel chatModel;
    
    public EnvironmentAwareChatService(ChatModel chatModel) {
        this.chatModel = chatModel;
    }
    
    public String chat(String message) {
        return chatModel.call(new Prompt(message))
            .getResult().getOutput().getContent();
    }
}

配置:

# application-dev.yml(开发环境)
spring:
  ai:
    ollama:
      base-url: http://localhost:11434
      chat:
        options:
          model: llama2

# application-prod.yml(生产环境)
spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        options:
          model: gpt-4

6.2 场景二:实现模型降级策略

需求: 主模型失败时自动切换到备用模型

实现:

package com.example.springai.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

/**
 * 带降级的 AI 服务
 */
@Slf4j
@Service
public class FallbackChatService {
    
    private final ChatModel primaryModel;
    private final ChatModel fallbackModel;
    
    public FallbackChatService(
            @Qualifier("openai") ChatModel primaryModel,
            @Qualifier("azure") ChatModel fallbackModel) {
        this.primaryModel = primaryModel;
        this.fallbackModel = fallbackModel;
    }
    
    public String chat(String message) {
        try {
            // 尝试使用主模型
            log.info("使用主模型");
            return primaryModel.call(new Prompt(message))
                .getResult().getOutput().getContent();
                
        } catch (Exception e) {
            // 主模型失败,使用备用模型
            log.warn("主模型失败,切换到备用模型", e);
            
            try {
                return fallbackModel.call(new Prompt(message))
                    .getResult().getOutput().getContent();
                    
            } catch (Exception ex) {
                log.error("备用模型也失败", ex);
                throw new RuntimeException("所有模型都不可用", ex);
            }
        }
    }
}

6.3 场景三:实现模型性能监控

需求: 监控不同模型的性能和成本

实现:

package com.example.springai.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.stereotype.Service;

import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;

/**
 * 带监控的 AI 服务
 */
@Slf4j
@Service
public class MonitoredChatService {
    
    private final ChatModel chatModel;
    private final Map<String, ModelMetrics> metricsMap = new ConcurrentHashMap<>();
    
    public MonitoredChatService(ChatModel chatModel) {
        this.chatModel = chatModel;
    }
    
    public String chat(String message) {
        String modelName = chatModel.getClass().getSimpleName();
        long startTime = System.currentTimeMillis();
        
        try {
            // 调用模型
            Prompt prompt = new Prompt(message);
            ChatResponse response = chatModel.call(prompt);
            
            // 记录指标
            long duration = System.currentTimeMillis() - startTime;
            recordMetrics(modelName, response, duration, true);
            
            return response.getResult().getOutput().getContent();
            
        } catch (Exception e) {
            // 记录失败
            long duration = System.currentTimeMillis() - startTime;
            recordMetrics(modelName, null, duration, false);
            throw e;
        }
    }
    
    private void recordMetrics(String modelName, ChatResponse response, 
                              long duration, boolean success) {
        ModelMetrics metrics = metricsMap.computeIfAbsent(
            modelName, k -> new ModelMetrics()
        );
        
        metrics.totalRequests++;
        if (success) {
            metrics.successRequests++;
            metrics.totalDuration += duration;
            
            if (response != null && response.getMetadata() != null) {
                var usage = response.getMetadata().getUsage();
                if (usage != null) {
                    metrics.totalTokens += usage.getTotalTokens();
                }
            }
        } else {
            metrics.failedRequests++;
        }
        
        log.info("模型指标 - {}: 总请求={}, 成功={}, 失败={}, 平均耗时={}ms, 总Token={}",
            modelName,
            metrics.totalRequests,
            metrics.successRequests,
            metrics.failedRequests,
            metrics.getAverageDuration(),
            metrics.totalTokens
        );
    }
    
    public Map<String, ModelMetrics> getMetrics() {
        return new HashMap<>(metricsMap);
    }
    
    public static class ModelMetrics {
        public long totalRequests = 0;
        public long successRequests = 0;
        public long failedRequests = 0;
        public long totalDuration = 0;
        public long totalTokens = 0;
        
        public double getAverageDuration() {
            return successRequests > 0 ? 
                (double) totalDuration / successRequests : 0;
        }
        
        public double getSuccessRate() {
            return totalRequests > 0 ? 
                (double) successRequests / totalRequests * 100 : 0;
        }
    }
}

七、最佳实践

7.1 接口设计原则

1. 依赖接口而非实现

// ✓ 正确
@Service
public class ChatService {
    private final ChatModel chatModel;  // 接口
}

// ✗ 错误
@Service
public class ChatService {
    private final OpenAiChatModel chatModel;  // 具体实现
}

2. 使用构造函数注入

// ✓ 正确
@Service
public class ChatService {
    private final ChatModel chatModel;
    
    public ChatService(ChatModel chatModel) {
        this.chatModel = chatModel;
    }
}

// ✗ 错误
@Service
public class ChatService {
    @Autowired
    private ChatModel chatModel;  // 字段注入
}

3. 提供默认实现

@Bean
@ConditionalOnMissingBean
public ChatModel defaultChatModel() {
    // 提供默认实现
    return new OllamaChatModel(...);
}

7.2 配置管理最佳实践

1. 使用环境变量

spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}  # 环境变量

2. 分环境配置

# application.yml
spring:
  profiles:
    active: ${SPRING_PROFILES_ACTIVE:dev}

---
# application-dev.yml
spring:
  ai:
    ollama:
      base-url: http://localhost:11434

---
# application-prod.yml
spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}

3. 配置验证

@ConfigurationProperties(prefix = "spring.ai.openai")
@Validated
public class OpenAiProperties {
    
    @NotBlank(message = "API Key must not be blank")
    private String apiKey;
    
    @Min(value = 0, message = "Temperature must be >= 0")
    @Max(value = 2, message = "Temperature must be <= 2")
    private Double temperature = 0.7;
}

7.3 错误处理最佳实践

1. 统一异常处理

@RestControllerAdvice
public class AIExceptionHandler {
    
    @ExceptionHandler(AIException.class)
    public ResponseEntity<ErrorResponse> handleAIException(AIException e) {
        return ResponseEntity
            .status(HttpStatus.INTERNAL_SERVER_ERROR)
            .body(new ErrorResponse(e.getMessage()));
    }
}

2. 重试机制

@Service
public class RetryableChatService {
    
    @Retryable(
        maxAttempts = 3,
        backoff = @Backoff(delay = 2000)
    )
    public String chat(String message) {
        return chatModel.call(new Prompt(message))
            .getResult().getOutput().getContent();
    }
}

3. 超时控制

@Service
public class TimeoutChatService {
    
    @Timeout(value = 30, unit = TimeUnit.SECONDS)
    public String chat(String message) {
        return chatModel.call(new Prompt(message))
            .getResult().getOutput().getContent();
    }
}

八、常见问题

Q1: 如何选择合适的抽象层级?

A: 根据需求选择:

  • 简单应用:直接使用 ChatClient
  • 标准应用:使用 ChatModel
  • 复杂应用:自定义封装

Q2: 如何实现模型热切换?

A: 使用配置中心:

@Service
@RefreshScope  // 支持配置刷新
public class DynamicChatService {
    
    @Value("${ai.model.type}")
    private String modelType;
    
    private final Map<String, ChatModel> models;
    
    public String chat(String message) {
        ChatModel model = models.get(modelType);
        return model.call(new Prompt(message))
            .getResult().getOutput().getContent();
    }
}

Q3: 如何测试 AI 应用?

A: 使用 Mock:

@SpringBootTest
class ChatServiceTest {
    
    @MockBean
    private ChatModel chatModel;
    
    @Autowired
    private ChatService chatService;
    
    @Test
    void testChat() {
        // Mock 响应
        ChatResponse mockResponse = new ChatResponse(
            List.of(new Generation("Hello"))
        );
        when(chatModel.call(any())).thenReturn(mockResponse);
        
        // 测试
        String result = chatService.chat("Hi");
        assertEquals("Hello", result);
    }
}

十、学习检查清单

  • 理解 Spring AI 的架构设计
  • 掌握核心接口和抽象层
  • 了解设计模式的应用
  • 掌握自动配置原理
  • 能够配置多个模型
  • 能够实现模型切换
  • 了解最佳实践

十一、扩展阅读

总结

通过本章学习,你已经深入理解了 Spring AI 的核心概念和架构设计。继续学习后续章节,掌握更多实战技能!

Logo

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

更多推荐