在 Java 生态中,Spring AI 已经成为开发者拥抱大模型时代的首选利器。很多人用它写出了酷炫的对话机器人、智能客服,甚至成功接入了私有知识库。
然而,“知其然更要知其所以然”。当我们熟练地敲下 ChatClient.builder().build() 时,底层到底发生了什么?
- 对话记忆:
ChatClient是怎么跨越无状态的 HTTP,记住历史对话的? - 工具调用:AI 是怎么学会调用你写的本地 Java 方法的?
- 检索增强:RAG 所谓的“开卷考试”,在源码里究竟被拆分成了哪几个关键步骤?
今天,我们就基于最新的 Spring AI 1.1.4 版本,拨开代码的迷雾,进行一次深度的底层原理解析!
💡 前情回顾 如果你想先了解框架的落地应用,欢迎阅读我昨天的文章: 🔗 Spring AI 实战:基于钉钉的智能 Agent 架构设计与实现
一、 模块架构概览
1. 核心模块职责
spring-ai-model - 核心抽象接口:ChatModel, EmbeddingModel, VectorStore
spring-ai-client-chat - ChatClient fluent API,Advisor SPI
spring-ai-vector-store - VectorStore 接口,ANTLR4 Filter 表达式解析器
spring-ai-rag - RAG 实现:QueryTransformer, DocumentRetriever, DocumentJoiner
spring-ai-commons - 通用工具类
spring-ai-retry - 重试机制
2. 各模块依赖关系
┌─────────────────────────────────────────────────────────────┐
│ Application │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ spring-ai-client-chat │
│ ChatClient │ Advisor SPI │ MessageChatMemoryAdvisor │
└─────────────────────────────────────────────────────────────┘
│
┌─────────────────┼─────────────────┐
▼ ▼ ▼
┌───────────────────┐ ┌──────────────┐ ┌────────────────────┐
│ spring-ai-rag │ │spring-ai-model│ │spring-ai-vector-store│
│ RetrievalAugment- │ │ ChatModel │ │ VectorStore │
│ ationAdvisor │ │ EmbeddingModel│ │ FilterExpression │
└───────────────────┘ └──────────────┘ └────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ models/{provider} (具体实现) │
│ OpenAiChatModel │ OpenAiEmbeddingModel │ ... │
└─────────────────────────────────────────────────────────────┘
💡 架构精髓:你的业务代码永远只依赖
spring-ai-model和spring-ai-client-chat,这就意味着,如果明天你想把底层模型从 OpenAI 换成 智谱 AI 或者 阿里通义千问,业务代码一行都不用改!
二、 核心驱动引擎:ChatModel 与 EmbeddingModel
1. ChatModel (聊天模型):多继承与适配器模式的妙用
ChatModel 是所有对话模型的鼻祖(万物起源)。它的核心职责极其纯粹:接收提示词(Prompt),返回响应(ChatResponse) 。
public interface ChatModel extends Model<Prompt, ChatResponse>, StreamingChatModel {
// 核心同步方法
ChatResponse call(Prompt prompt);
// 便捷方法:字符串直接转 Prompt,这就是适配器模式的体现
default String call(String message) {
return this.call(new Prompt(new UserMessage(message)));
}
}
亮点解析:在类定义中,它巧妙地使用了多继承,同时继承了 Model 泛型接口和 StreamingChatModel 流式接口,在高度抽象的同时保证了能力的完整性。
2. StreamingChatModel (流式接口):拥抱响应式编程
为了应对大模型打字机效果的流式输出,Spring AI 专门抽离了流式接口:
@FunctionalInterface
public interface StreamingChatModel extends StreamingModel<Prompt, ChatResponse> {
// 便捷方法:自动包装 Prompt,并直接提取流式文本
default Flux<String> stream(String message) {
return stream(new Prompt(new UserMessage(message)))
.map(chatResponse -> chatResponse.getResult().getOutput().getText());
}
// 核心流式方法
Flux<ChatResponse> stream(Prompt prompt);
}
关键特性:
- 使用
@FunctionalInterface标记,完美支持 Java Lambda 表达式。 - 返回值采用 Project Reactor 的
Flux<ChatResponse>,全面拥抱响应式流(Reactive Streams)规范。
3. EmbeddingModel (嵌入模型):维度的动态探测
大模型本质上无法直接理解文字,它们只认识“向量”(多维数组)。EmbeddingModel 的职责就是将文本转化为 float[]。在 AbstractEmbeddingModel 基类中,隐藏着一个极其巧妙的细节:
// 缓存维度数,避免每次请求都调用 API 去计算
protected final AtomicInteger embeddingDimensions = new AtomicInteger(-1);
public int dimensions() {
if (this.embeddingDimensions.get() < 0) {
// 如果不知道维度,先拿 "Test" 跑一次 API 探探路,然后缓存起来
this.embeddingDimensions.set(dimensions(this, "Test"));
}
return this.embeddingDimensions.get();
}
通过这种“探路+缓存”机制,框架在处理复杂的向量库操作时,避免了频繁的元数据查询,极大地节约了 Token 和网络请求时间。
4. OpenAiChatModel:真实的干活逻辑
看完了接口,我们来看看真正干活的实现类(比如 OpenAI 的客户端)。这里隐藏着重试机制与工具调用的核心奥秘:
Java
public class OpenAiChatModel implements ChatModel {
@Override
public ChatResponse call(Prompt prompt) {
Prompt requestPrompt = buildRequestPrompt(prompt);
return this.internalCall(requestPrompt, null);
}
// 内部递归调用,处理工具执行
private ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse) {
// 1. 构建底层 API 请求
ChatCompletionRequest request = createRequest(prompt, false);
// 2. 观察上下文(Micrometer 链路追踪指标)
ChatModelObservationContext observationContext = ChatModelObservationContext.builder()...;
// 3. 执行重试和观察
return ChatModelObservationDocumentation.CHAT_MODEL_OPERATION
.observation(...).observe(() -> {
// 使用 Spring RetryTemplate 包裹网络请求,提升容错率
ResponseEntity<ChatCompletion> completionEntity =
this.retryTemplate.execute(ctx ->
this.openAiApi.chatCompletionEntity(request, headers));
// 4. 转换为 Spring AI 标准的 Generation 格式
List<Generation> generations = choices.stream()
.map(choice -> buildGeneration(choice, metadata, request))
.toList();
// 5. 【核心】处理工具执行 (Function Calling)
if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(...)) {
// 内部执行本地 Java 方法
ToolExecutionResult toolExecutionResult =
this.toolCallingManager.executeToolCalls(prompt, response);
// ⚠️ 隐式递归调用:将工具执行结果重新塞回 Prompt,再次请求大模型
return this.internalCall(new Prompt(...), response);
}
return response;
});
}
}
底层基建亮点总结:
| 特性 | 底层实现方式 | 业务价值 |
|---|---|---|
| 重试机制 | 使用 RetryTemplate 包装 API 调用 | 极大提升网络抖动和限流时的容错率 |
| 监控打点 | 接入 Micrometer ObservationRegistry | 配合可观测性组件,轻松实现调用链路追踪 |
| 工具调用 | internalCall() 内部隐式递归 | 实现多次 Tool Call 直到任务完成,对业务透明 |
| 流式处理 | Flux.deferContextual() + MessageAggregator | 优雅合并零散的 Chunk 数据 |
三、 ChatClient 与 Advisor 拦截器:魔法发生的地方
日常开发中,我们赋予大模型“记忆”的代码非常简单:
ChatClient.prompt("我刚才问了啥?")
.advisors(new MessageChatMemoryAdvisor(chatMemory))
.call()
.content();
为什么加了一个 Advisor,无状态的 AI 就有了记忆?因为 Spring AI 设计了一套基于 Deque(双端队列)的弹出式拦截器链 (DefaultAroundAdvisorChain) 。
核心设计:基于 Deque 的弹出式链遍历
public ChatClientResponse nextCall(ChatClientRequest chatClientRequest) {
if (this.callAdvisors.isEmpty()) {
throw new IllegalStateException("No CallAdvisors available");
}
// 弹出下一个 advisor 执行
var advisor = this.callAdvisors.pop();
return AdvisorObservationDocumentation.AI_ADVISOR
.observation(this.observationConvention, ...)
.observe(() -> {
// 当前 advisor 调用链(链包含剩余的 advisors)
var response = advisor.adviseCall(chatClientRequest, this);
return response;
});
}
当请求发出时,犹如过五关斩六将:
before()阶段:MessageChatMemoryAdvisor发挥作用。它会根据ConversationId从ChatMemory(支持InMemory或者Jdbc落库,比如 PostgreSQL、MySQL)里捞出之前的几十条对话,悄悄塞进本次的 Prompt 前面。- 终端调用:走完所有拦截器,最后由
ChatModelCallAdvisor触发真实的大模型 API 请求。 after()阶段:拿到回复后,拦截器栈回溯,MessageChatMemoryAdvisor再次拦截,把 AI 的最新回答存入数据库,完成闭环。
执行顺序:
[Request Path - before() methods]
1. toolCallAdvisor.before() --> 初始化工具调用循环
2. ragAdvisor.before() --> 检索文档,增强查询
3. memoryAdvisor.before() --> 添加对话历史
[Terminal - 实际 AI 调用]
4. ChatModelCallAdvisor.adviseCall() --> 调用 chatModel.call()
[Response Path - after() methods]
5. memoryAdvisor.after() --> 存储助手回复到记忆
6. ragAdvisor.after() --> 添加文档到响应元数据
7. toolCallAdvisor.after() --> 检测并执行工具调用,循环直到完成
四、 Function Calling (工具调用):AI 怎么学会执行 Java 代码?
1. Function Calling (工具调用)
大模型本身不能查天气、不能查数据库,必须依赖本地代码。Spring AI 的
ToolCallAdvisor 实现了一个极其精妙的递归执行循环。
非流式执行流程:
public ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) {
// 1. 禁用模型内部的工具执行,由 Advisor 处理
var optionsCopy = (ToolCallingChatOptions) request.prompt().getOptions().copy();
optionsCopy.setInternalToolExecutionEnabled(false);
boolean isToolCall;
do {
// 2. 调用链中的下一个 advisor(最终到达 ChatModel)
ChatClientResponse response = chain.nextCall(processedRequest);
// 3. 检查响应中是否包含工具调用
isToolCall = response.chatResponse().hasToolCalls();
if (isToolCall) {
// 4. 执行工具调用
ToolExecutionResult toolResult = this.toolCallingManager
.executeToolCalls(processedRequest.prompt(), response.chatResponse());
if (toolResult.returnDirect()) {
break; // 直接返回工具结果,跳过 LLM
}
// 5. 获取下一轮指令,继续循环
instructions = doGetNextInstructionsForToolCall(...);
}
} while (isToolCall);
return doFinalizeLoop(response, chain);
}
假设你注册了一个 @Bean 或者 java.util.function.Function 作为工具:
ToolCallAdvisor拦截请求,修改配置,告诉底层的OpenAiChatModel:“遇到工具调用别自己瞎处理,抛出来给我”。- 请求到达 OpenAI,OpenAI 返回一个特殊的标识,附带一个 JSON 结构:
{"name": "getWeather", "args": {"city": "北京"}}。 ToolCallAdvisor捕获到这个特殊的响应(response.chatResponse().hasToolCalls() == true),启动一个do-while循环。- 内部执行:通过
ToolCallingManager找到你写的getWeather方法,通过反射传入“北京”,拿到结果“晴天,25度”。 - 隐式递归:拦截器自动把“晴天,25度”拼接成
ToolResponseMessage,再次向 OpenAI 发起请求。 - OpenAI 结合天气信息,生成最终的自然语言回复。循环打破(
isToolCall == false),结果返回给用户。
对于流式(Stream)响应,情况更复杂,因为工具调用的 JSON 是一段段返回的。Spring AI 在底层的 OpenAiStreamFunctionCallingHelper 中实现了极其复杂的 merge(合并)逻辑,把散落的 chunks 拼成完整的工具调用参数。
2. ToolContext 上下文传递
public final class ToolContext {
public static final String TOOL_CALL_HISTORY = "TOOL_CALL_HISTORY";
private final Map<String, Object> context;
// 工具可访问的内容
public Map<String, Object> getContext() { return this.context; }
// 获取工具执行前的对话历史
public List<Message> getToolCallHistory() { ... }
}
上下文构建:
private static ToolContext buildToolContext(Prompt prompt, AssistantMessage assistantMessage) {
Map<String, Object> toolContextMap = new HashMap<>();
// 用户提供的上下文
if (prompt.getOptions() instanceof ToolCallingChatOptions options
&& !CollectionUtils.isEmpty(options.getToolContext())) {
toolContextMap.putAll(options.getToolContext());
}
// 添加对话历史
toolContextMap.put(ToolContext.TOOL_CALL_HISTORY,
buildConversationHistoryBeforeToolExecution(prompt, assistantMessage));
return new ToolContext(Collections.unmodifiableMap(toolContextMap));
}
大模型在调用本地工具(比如查订单)时,往往不能只传一个“订单号”,工具方法可能还需要知道“当前登录的用户是谁?”、“用户的鉴权 Token 是什么?”。这就需要一套机制来跨越层层拦截器,把业务上下文透传给底层工具。
核心容器:ToolContext 类
Map<String, Object> context:这是一个极其灵活的 Map 容器,相当于档案袋的内胆。你可以往里面塞任何业务所需的对象(比如 HTTP Request、User Session 等),工具在执行时可以直接取用。TOOL_CALL_HISTORY常量:这是框架预留的一个专属 VIP 席位。它专门用来存放工具执行前的完整对话历史。这样一来,工具在处理逻辑时,不仅知道当前的参数,还能“回头看”用户之前到底聊了什么。
五、 VectorStore 与 AST 解析:灵活的元数据过滤
我们在检索文档时,不仅要看向量相似度,还要做属性过滤,比如:只搜索“状态为活跃”且“年份大于 2020”的文档。 Spring AI 提供了类似 MyBatis-Plus 的 DSL 语法:
var b = new FilterExpressionBuilder();
var exp = b.and(b.eq("country", "UK"), b.gte("year", 2020));
内置了一个 ANTLR4 语法分析器!你可以直接传字符串:"country == 'UK' && year >= 2020"。 底层通过 FilterExpressionTextParser 解析生成一棵抽象语法树(AST)。然后,框架使用不同的 Converter,把这棵树翻译成各种向量数据库认识的方言(比如转换成 Pinecone 专属的 JSON 结构,或者通过 SpEL 进行内存过滤)。
用户输入: "country == 'UK' && year >= 2020"
│
▼
FilterExpressionTextParser.parse(text)
├── ANTLR4 Lexer/Parser
└── FilterExpressionVisitor.visit()
│
▼
Filter.Expression AST
│
┌───────┴───────┐
▼ ▼
SimpleVectorStore Provider Converter
直接评估 (Pinecone/SpEL/...)
│ │
▼ ▼
Boolean 结果 提供商原生查询
六、 ChatMemory 会话记忆:大模型的“海马体”与“滑动窗口”
前面我们在拦截器中看到了 MessageChatMemoryAdvisor,那这些历史对话具体是存在哪里、又是如何管理的呢?这就不得不提 Spring AI 中极其优雅的记忆抽象设计。
在架构上,Spring AI 采用了经典的门面接口与底层仓储分离的设计:
1. 记忆的“门面”与“仓库”
门面接口:ChatMemory 这是面向业务开发者的顶层接口,相当于大模型的“海马体”(短期记忆区)。它只关心最核心的四个动作:增加、批量增加、查询、清空。
public interface ChatMemory {
String DEFAULT_CONVERSATION_ID = "default";
void add(String conversationId, Message message);
void add(String conversationId, List<Message> messages);
List<Message> get(String conversationId);
void clear(String conversationId);
}
底层实现:ChatMemoryRepository 光有接口不行,记忆最终得落地。ChatMemoryRepository 负责实际的存储逻辑。框架内置了基于内存的 InMemoryChatMemory,同时也支持通过扩展接入 Redis、PostgreSQL 等持久化数据库,确保服务重启后 AI 不会“失忆”。
public interface ChatMemoryRepository {
List<String> findConversationIds();
List<Message> findByConversationId(String conversationId);
void saveAll(String conversationId, List<Message> messages);
void deleteByConversationId(String conversationId);
}
2. 窗口策略:为什么需要 MessageWindowChatMemory?
痛点:大模型的上下文窗口(Token)是极其宝贵且有上限的。如果不加节制地把几百轮历史对话全塞进 Prompt 里,不仅每次调用的 API 费用会原地起飞,还极易触发“Token 爆栈”(Context Window Exceeded)错误。
为此,Spring AI 提供了 MessageWindowChatMemory(滑动窗口记忆)来精准控场。它的底层驱逐算法(Eviction Algorithm)遵循着极其严密的逻辑:
- 系统人设(SystemMessage)的“绝对特权” : 框架规定,不管窗口怎么滑动,代表 AI 人设的
SystemMessage永远不会被丢弃。如果动态添加了新的SystemMessage,框架会自动覆盖旧的,确保 AI 始终明确“自己是谁”。 - 历史消息的“喜新厌旧” : 默认的最大消息保留量为 20 条(
DEFAULT_MAX_MESSAGES)。当对话超出这个限制时,框架会无情地从最旧的消息开始执行remove剔除操作。
我们来看一眼这段冷酷但高效的剔除算法源码:
// 计算需要剔除多少条旧消息
int messagesToRemove = processedMessages.size() - this.maxMessages;
int removed = 0;
for (Message message : processedMessages) {
// 核心逻辑:如果是 SystemMessage,或者剔除名额已经用完,则保留该消息
if (message instanceof SystemMessage || removed >= messagesToRemove) {
trimmedMessages.add(message);
} else {
// 否则,无情剔除最旧的常规对话
removed++;
}
}
⚠️ 总结:记忆的“滑动窗口”策略 在
MessageWindowChatMemory的管控下,你的对话历史就像是在一个固定长度的传送带上运行。最新的对话不断加入,最旧的对话被悄无声息地推下深渊(剔除)。但唯独SystemMessage被死死地钉在传送带的最前端,确保大模型无论聊得多嗨,都永远不会“忘本”。
七、 RAG 检索增强全链路原理解析
public ChatClientRequest before(ChatClientRequest request, AdvisorChain chain) {
// Step 0: 从用户消息和历史创建 Query
Query originalQuery = Query.builder()
.text(request.prompt().getUserMessage().getText())
.history(request.prompt().getInstructions())
.context(context)
.build();
// Step 1: 通过 QueryTransformer 链变换查询
Query transformedQuery = originalQuery;
for (var queryTransformer : this.queryTransformers) {
transformedQuery = queryTransformer.apply(transformedQuery);
}
// Step 2: 可选的查询扩展
List<Query> expandedQueries = this.queryExpander != null
? this.queryExpander.expand(transformedQuery)
: List.of(transformedQuery);
// Step 3: 并行检索文档
Map<Query, List<List<Document>>> documentsForQuery = expandedQueries.stream()
.map(query -> CompletableFuture.supplyAsync(() -> getDocumentsForQuery(query)))
.toList().stream().map(CompletableFuture::join)
.collect(Collectors.toMap(Map.Entry::getKey, entry -> List.of(entry.getValue())));
// Step 4: 连接多个查询的文档
List<Document> documents = this.documentJoiner.join(documentsForQuery);
// Step 5: 后处理文档
for (var documentPostProcessor : this.documentPostProcessors) {
documents = documentPostProcessor.process(originalQuery, documents);
}
// Step 6: 用文档上下文增强查询
Query augmentedQuery = this.queryAugmenter.augment(originalQuery, documents);
// Step 7: 更新请求
return request.mutate()
.prompt(request.prompt().augmentUserMessage(augmentedQuery.text()))
.context(context).build();
}
很多文章把 RAG 讲得很玄乎,实际上在 RetrievalAugmentationAdvisor 这个拦截器的源码中,把 RAG 清晰地定义为了 7 个 Pipeline(流水线)步骤:
-
Step 0 - 初始化 Query:把你输入的问题(文本)和前面的聊天历史封装成一个
Query对象。 -
Step 1 - Transformer (查询变换) :用户的原始问题往往指代不明(比如“那它有什么缺点?”)。
RewriteQueryTransformer会利用大模型,结合上下文,把问题重写为适合去向量库搜索的独立句子(如“Spring AI 的缺点是什么?”)。 -
Step 2 - Expander (查询扩展) :[可选] 把一个复杂问题拆分成多个子问题,提高检索命中率。
-
Step 3 - Retriever (并发检索) :拿着变换后的查询,通过
VectorStoreDocumentRetriever去向量数据库发起相似度搜索。如果前面裂变了多个问题,这里会使用CompletableFuture进行异步并发检索。 -
Step 4 - Joiner (文档聚合) :把搜回来的所有片段(Documents)扔进
DocumentJoiner,默认通过 ID 去重,并按照相似度得分(Score)倒序排列。 -
Step 5 & 6 - Augmenter (提示词增强) :这是最关键的一步。把刚才高分命中、排好序的文档内容,塞进一个系统预设的 Prompt 模板中:
Context information is below. --------------------- {此处填入检索到的文档片段} --------------------- Given the context information and no prior knowledge, answer the query: {用户的原始问题} -
Step 7 - 执行:带着被厚厚参考资料包裹的终极 Prompt,正式向大模型发起调用。
至此,大模型就能看着你私有库里的资料,一本正经地回答问题了!
RAG 完整流程图
用户消息
│
▼
[QueryTransformer 链]
│
▼
[QueryExpander] (可选)
│
▼
[DocumentRetriever] --> VectorStoreDocumentRetriever --> VectorStore.similaritySearch()
│
▼
[DocumentJoiner] --> 去重 + 按分数排序
│
▼
[DocumentPostProcessor] (可选)
│
▼
[QueryAugmenter] --> 用文档上下文增强用户提示
│
▼
LLM (携带增强提示的调用)
总结
- 高度抽象 (
spring-ai-model) :抹平了 OpenAI、Ollama、千问等各种模型提供商的接口差异。 - 拦截器哲学 (
Advisor SPI) :把记忆(Memory)、外挂工具(Tools)、检索增强(RAG)全部做成了非侵入式的切面,像搭积木一样随插随拔。 - 组件化流水线 (
spring-ai-rag) :将看似黑盒的 RAG 拆解为 Transformer、Retriever、Augmenter 等标准组件,支持极细粒度的自定义。
附录:关键文件索引
| 组件 | 文件路径 |
|---|---|
| ChatModel | spring-ai-model/src/main/java/org/springframework/ai/chat/model/ChatModel.java |
| StreamingChatModel | spring-ai-model/src/main/java/org/springframework/ai/chat/model/StreamingChatModel.java |
| EmbeddingModel | spring-ai-model/src/main/java/org/springframework/ai/embedding/EmbeddingModel.java |
| OpenAiChatModel | models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java |
| ChatClient | spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/ChatClient.java |
| DefaultChatClient | spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java |
| Advisor | spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/api/Advisor.java |
| DefaultAroundAdvisorChain | spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/DefaultAroundAdvisorChain.java |
| ToolCallAdvisor | spring-ai-client-chat/src/main/java/org/springframework/ai/chat/client/advisor/ToolCallAdvisor.java |
| ToolCallback | spring-ai-model/src/main/java/org/springframework/ai/tool/ToolCallback.java |
| VectorStore | spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/VectorStore.java |
| SearchRequest | spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/SearchRequest.java |
| FilterExpressionBuilder | spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/filter/FilterExpressionBuilder.java |
| Filters.g4 | spring-ai-vector-store/src/main/antlr4/org/springframework/ai/vectorstore/filter/antlr4/Filters.g4 |
| ChatMemory | spring-ai-model/src/main/java/org/springframework/ai/chat/memory/ChatMemory.java |
| MessageWindowChatMemory | spring-ai-model/src/main/java/org/springframework/ai/chat/memory/MessageWindowChatMemory.java |
| RetrievalAugmentationAdvisor | spring-ai-rag/src/main/java/org/springframework/ai/rag/advisor/RetrievalAugmentationAdvisor.java |
| VectorStoreDocumentRetriever | spring-ai-rag/src/main/java/org/springframework/ai/rag/retrieval/search/VectorStoreDocumentRetriever.java |
| ContextualQueryAugmenter | spring-ai-rag/src/main/java/org/springframework/ai/rag/generation/augmentation/ContextualQueryAugmenter.java |