Spring AI 概述与核心概念
Spring AI 概述与核心概念
学习目标
- 深入理解 Spring AI 的架构设计和核心原理
- 掌握 Spring AI 的核心接口和抽象层
- 理解 Spring AI 与其他 AI 框架的区别
- 掌握 Spring AI 的设计模式和最佳实践
- 能够基于核心概念设计复杂的 AI 应用
知识结构
一、项目场景引入
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 实现层
- 各个 AI 服务商的具体实现
- 适配器模式
- 协议转换
-
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 接口体系
接口继承关系:
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;
}
}
为什么这样设计?
- 泛型设计:支持不同类型的请求和响应
- 单一方法:简化接口,易于实现
- 默认方法:提供默认实现,减少样板代码
- 类型安全:编译时检查类型
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 的核心概念和架构设计。继续学习后续章节,掌握更多实战技能!
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)