深度剖析Spring AI源码(二):Model抽象层 - “驯服”天下AI的“紧箍咒”
上一章我们鸟瞰了Spring AI的宏伟蓝图,今天,我们要深入这座大厦的基石——
Model
抽象层。如果说Spring AI是连接Java与AI世界的桥梁,那么Model
接口就是这座桥最核心的承重结构。它定义了一套“普通话”,让我们的Java代码可以和来自五湖四海(OpenAI, Azure, Bedrock…)的AI模型们无障碍沟通。准备好了吗?让我们一起看看这套“普通-AI-话”是如何设计的。
设计目标:以不变应万变
AI领域日新月异,新的模型层出不穷,API的变更也如家常便饭。如果我们的应用代码与某个具体的模型(比如OpenAI的GPT-4)深度绑定,那么当你想切换到Anthropic的Claude,或者想“白嫖”一下本地部署的Ollama时,将面临一场伤筋动骨的重构灾难。
Spring AI的设计哲学,正是我们早已烂熟于心的面向接口编程。它在变幻莫测的AI模型和我们稳健的业务代码之间,建立了一道坚固的抽象层。这道“防火墙”将AI模型善变的“脾气”隔离开来,让你的业务逻辑能够稳如泰山,静观其变。
核心接口剖析:AI世界的“四梁八柱”
在spring-ai-model
模块中,定义了几个关键的核心接口,它们共同构成了与AI模型进行标准化对话的“四梁八柱”。
1. Model<Q, R>
: 抽象的“祖师爷”
这是所有模型接口的“根”,是整个抽象体系的“万恶之源”(开个玩笑)。它用最纯粹的形式定义了AI模型的核心交互模式。
// spring-ai-model/src/main/java/org/springframework/ai/model/Model.java
public interface Model<TReq extends ModelRequest<?>, TRes extends ModelResponse<?>> {
TRes call(TReq request);
}
它运用Java最经典的泛型,优雅地解决了“输入什么,输出什么”这个宇宙终极问题之一。大道至简,一个call
方法便定义了请求-响应的完整交互。
TReq
: 代表请求类型,它必须是ModelRequest
的子类。TRes
: 代表响应类型,它必须是ModelResponse
的子类。
2. ChatModel
& StreamingChatModel
: 对话的“双子星”
ChatModel
是你与AI进行聊天式对话的主力接口,它专为对话场景而设计。
// spring-ai-model/src/main/java/org/springframework/ai/model/chat/ChatModel.java
public interface ChatModel extends Model<Prompt, ChatResponse> {
// ... 默认方法
}
它直接继承自Model
接口,并具体化了泛型:明确了在“聊天”这个场景下,输入是Prompt
(提示),输出是ChatResponse
(聊天回复)。
而它的“兄弟”StreamingChatModel
则提供了更酷、用户体验更佳的玩法:
// spring-ai-model/src/main/java/org/springframework/ai/model/chat/StreamingChatModel.java
public interface StreamingChatModel extends StreamingModel<Prompt, ChatResponse> {
// ... 默认方法
}
// spring-ai-model/src/main/java/org/springframework/ai/model/StreamingModel.java
public interface StreamingModel<TReq extends ModelRequest<?>, TResChunk extends ModelResponse<?>> {
Flux<TResChunk> stream(TReq request);
}
如果说ChatModel
是你问一句,AI完整地回答一句,如同传统的HTTP请求-响应模式;那么StreamingChatModel
则像是实时视频流,AI的回答会一个字一个字地“流”向你,极大地提升了交互的实时感。这背后,正是响应式编程的魔力在驱动。
3. EmbeddingModel
: AI的“翻译官”和“度量衡”
如果说ChatModel
是AI用来与世界沟通的“嘴巴”,那么EmbeddingModel
就是AI用来理解和度量世界的“尺子”。
// spring-ai-model/src/main/java/org/springframework/ai/model/embedding/EmbeddingModel.java
public interface EmbeddingModel extends Model<EmbeddingRequest, EmbeddingResponse> {
// ... 默认方法
}
它的核心职责,是将人类的语言(文本)“翻译”成机器能够理解和比较的数字“指纹”——也就是高维向量(Vector)。这是实现RAG(检索增强生成)和“以文搜文”等高级功能的关键所在,是构建AI“长期记忆”和“深度理解力”的基石。
请求与响应:沟通的“快递包裹”
光有接口(沟通渠道)还不够,数据交换还需要统一的“信封”和“信纸”。Spring AI通过一系列精心设计的DTO(Data Transfer Object)来规范这场跨物种对话的格式。
请求对象(发出的“快递”)
Prompt
: 这绝不是一个简单的字符串,而是一个结构化的“剧本”。它封装了一个Message
列表,共同构成了一次完整的对话上下文。Message
: 这是一个消息接口,拥有多个实现,让你能够导演一出好戏:SystemMessage
: “系统指令”,用于给AI设定角色和行为准则,比如“你是一个只说骚话、乐于助人的资深程序员”。UserMessage
: 用户的提问,也就是我们凡人输入的指令或问题。AssistantMessage
: AI助手的历史回复,用于构建多轮对话的上下文,让AI知道“前情提要”。
EmbeddingRequest
: 这是发给“翻译官”(EmbeddingModel
)的请求,里面包含了一批需要被转换成向量的文本。
响应对象(收到的“快递”)
ChatResponse
:ChatModel
的回复包裹,里面装着一个Generation
列表(因为一次提问,模型可能会生成多个候选答案)。Generation
: 代表AI的一次“创作成果”。除了最核心的内容(content
),它还附带了一堆非常有用的“元数据”(metadata
),比如模型为什么停止回答(finishReason
)、这次调用消耗了多少Token(usage
)等。这些信息对于成本控制和问题调试至关重要。EmbeddingResponse
: “翻译官”的工作成果,包含了一批新鲜出炉的、可用于计算相似度的向量。
ChatClient
: 开发者的“神兵利器”
如果说ChatModel
等底层接口是需要开发者自行组装的“引擎零件”,那么ChatClient
就是Spring AI官方为你精心打造好的一台“超级跑车”,它提供了极其便利的流式API,让你用最少的代码完成最常见的任务。
// spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/ChatClient.java
public interface ChatClient {
static ChatClient.Builder builder(ChatModel chatModel) {
return new DefaultChatClient.Builder(chatModel);
}
// ... call() 和 stream() 方法
}
还记得上一章我们提到的建造者模式吗?在这里它被发挥得淋漓尽致,创造出了丝滑的编码体验:
String response = ChatClient.create(chatModel).prompt()
.user("给我讲个关于Java的冷笑话")
.call()
.content();
这种优雅的API设计,让开发者可以完全专注于业务逻辑的实现,而无需与底层的API细节进行“肉搏”,极大地提升了开发效率和代码可读性。
小结
通过本章的探索,我们发现Spring AI通过一套设计精妙的Model
接口、Prompt
/`