让AI不再"一本正经胡说八道":Spring AI RAG与VectorStore源码全解
你的私有文档、技术手册、产品知识库——如何变成AI的"外挂大脑"?一文拆解从PDF到向量到检索增强生成的完整链路
你有没有遇到过这些情况?
❶ 让AI帮你查公司的内部API文档,它回答得头头是道——但全是编的?
❷ 想搭一个基于自己知识库的智能客服,但不知道怎么把文档喂给AI?
❸ 听说RAG(检索增强生成)能解决AI幻觉问题,但看了半天概念还是不知道代码怎么写?
如果你对其中任何一个点了头——这篇就是为你写的。
RAG(Retrieval-Augmented Generation)是目前解决AI"幻觉"问题的最有效方案,也是Spring AI里数据流最复杂、组件最多的一块。今天我们从源码层面把这条管线从头到尾拆干净。
一张图看懂RAG在干什么
┌──────────────────────────────────────────────────────────────────────┐
│ Spring AI RAG 全流程架构 │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌─────────────┐ ┌──────────────┐ ┌───────────┐ │
│ │ 原始文档 │ → │ 文档读取 │ → │ 文本分割 │ → │ 向量化 │ │
│ │ PDF/MD │ │DocumentReader│ │TextSplitter │ │EmbedModel │ │
│ └──────────┘ └─────────────┘ └──────────────┘ └─────┬─────┘ │
│ │ │
│ float[]向量 │
│ ↓ │
│ ┌─────────────┐ │
│ │ VectorStore │ │
│ │ Redis/Pg/Milvus│ │
│ └──────┬──────┘ │
│ │ │
│ ═════════════════════ 用户提问时 ════════════════════════ │
│ ↓ │
│ ┌──────────┐ ┌──────────────────┐ ┌──────────────┐ │
│ │ 用户提问 │ → │ 向量化+相似度搜索 │ → │ Top-K相关文档 │ │
│ └──────────┘ │ similaritySearch │ └──────┬───────┘ │
│ └──────────────────┘ │ │
│ ↓ │
│ ┌──────────┐ │
│ │ 注入提示词 │ │
│ │ SystemPrompt│ │
│ └─────┬─────┘ │
│ ↓ │
│ ┌──────────┐ │
│ │ LLM生成 │ ← 有据可依!
│ │ 最终回答 │ │
│ └──────────┘ │
│ │
│ 核心公式:用户问题 → 检索相关知识 → 注入上下文 → AI有依据地回答 │
└──────────────────────────────────────────────────────────────────────┘
一句话总结:RAG = 给AI开卷考试,允许它翻书再答题。
一、VectorStore:AI的"图书馆"
1.1 核心接口设计
VectorStore是整个RAG系统的基石——它负责存储文档向量并提供相似度搜索能力:
// org.springframework.ai.vectorstore.VectorStore
public interface VectorStore {
// 📥 入库:将文档列表写入向量数据库
void add(List<Document> documents);
// 🗑️ 出库:按ID批量删除文档
void delete(List<String> documentIds);
// 🔍 简单搜索:返回最相关的文档
List<Document> similaritySearch(String query);
// 🔍 高级搜索:支持TopK、阈值过滤、元数据过滤
List<Document> similaritySearch(SearchRequest request);
}
1.2 SearchRequest:搜索的瑞士军刀
public class SearchRequest {
private String query; // 查询文本
private int topK = 4; // 返回前K个结果
private double similarityThreshold = 0.0; // 相似度阈值(0~1)
private Map<String, Object> filterExpression; // 元数据过滤条件
}
这四个参数组合起来可以覆盖绝大多数检索场景:
topK=3+threshold=0.7→ 只要最相关的3条高质量结果topK=10+threshold=0.0+ 过滤条件 → 按分类精确筛选topK=1+threshold=0.9→ 只要高度匹配的唯一答案
1.3 Document:文档的数据模型
// org.springframework.ai.document.Document
public class Document {
private final String id; // 唯一标识(UUID)
private final String content; // 文档正文内容
private final Map<String, Object> metadata; // 元数据(来源、分类等)
private final float[] embedding; // 向量表示(1536维等)
// 快捷构造器
public Document(String content) {
this(content, Map.of()); // 自动生成ID和空元数据
}
}
metadata是隐藏的神器——你可以存文件名、章节号、作者、创建日期等任意信息,后面做过滤搜索全靠它。
1.4 五种VectorStore实现怎么选?
这是实际项目中最常遇到的问题——Spring AI内置支持多种向量数据库:
| 维度 | Redis | PgVector | Milvus | Qdrant | Chroma |
|---|---|---|---|---|---|
| 部署难度 | ⭐ 最简单 | ⭐⭐ 中等 | ⭐⭐⭐ 较复杂 | ⭐⭐⭐ 较复杂 | ⭐⭐ 简单 |
| 性能(百万级) | ⭐⭐⭐ 够用 | ⭐⭐⭐⭐ 强 | ⭐⭐⭐⭐⭐ 最强 | ⭐⭐⭐⭐ 很强 | ⭐⭐ 一般 |
| 运维成本 | 低(你可能本来就在用) | 低(PostgreSQL扩展) | 中(需独立部署) | 中(需独立部署) | 低(嵌入式) |
| 适合场景 | 已有Redis的项目 | 已有PG的项目 | 大规模生产环境 | 需要高性能过滤 | 开发测试/小规模 |
| 额外依赖 | redis-stack(带向量模块) | pgvector扩展 | Milvus服务端 | Qdrant服务端 | 无(内存模式) |
💡 选型建议:
- 如果项目已经用了Redis或PostgreSQL→ 优先选对应的实现,零额外基础设施成本
- 如果是从零开始做生产级RAG → Milvus 或 PgVector
- 本地开发验证 → Chroma(纯内存,pip install即用)
二、向量化:把文字变成数字
2.1 EmbeddingModel接口
public interface EmbeddingModel {
// 对单个文本向量化
EmbeddingResponse embed(EmbeddingRequest request);
// 批量向量化(默认实现逐个调用)
default List<float[]> embed(List<String> texts) {
return texts.stream()
.map(text -> embed(new EmbeddingRequest(text)))
.map(response -> response.getResult().getOutput())
.toList();
}
}
2.2 一个文本的向量化之旅
原始文本: "Spring AI 是一个 AI 应用开发框架"
↓
EmbeddingModel.embed() 被调用
↓
HTTP POST 到 Ollama / OpenAI / 智谱AI 等
↓
模型内部处理(分词 → 编码 → 映射到高维空间)
↓
返回: float[] embedding
= [0.1234, -0.5678, 0.9012, ..., 0.3456]
↑ ↑
共 768/1536/4096 个浮点数(取决于模型)
关键认知:这个向量不是随便生成的数字——语义相近的文本在向量空间中距离更近。这就是相似度搜索的理论基础。
2.3 从文档到入库的完整代码
// 准备文档
List<Document> documents = List.of(
new Document("Spring AI 是一个 AI 框架",
Map.of("source", "docs", "category", "framework")),
new Document("Spring Boot 是一个 Web 框架",
Map.of("source", "docs", "category", "web"))
);
// Step 1: 提取所有文本内容
List<String> texts = documents.stream()
.map(Document::getContent)
.toList();
// Step 2: 批量向量化
List<float[]> embeddings = embeddingModel.embed(texts);
// Step 3: 将向量写回每个Document对象
for (int i = 0; i < documents.size(); i++) {
documents.get(i).setEmbedding(embeddings.get(i));
}
// Step 4: 存入VectorStore
vectorStore.add(documents); // 完成!
三、文本分割:大文档怎么切成小块?
3.1 为什么需要分割?
LLM有上下文长度限制(比如4K/8K/128K tokens)。一份100页的PDF不可能一次性塞进去。解决方案:先切小块,每块单独向量化存储,查询时只检索最相关的几块。
3.2 TokenTextSplitter:按Token粒度切分
// org.springframework.ai.document.TokenTextSplitter
public class TokenTextSplitter implements TextSplitter {
private final int chunkSize; // 每块最大Token数(如256/512/1024)
private final int chunkOverlap; // 块间重叠Token数(如20~50)
private final Tokenizer tokenizer;// Token计数器
public List<String> split(String text) {
List<String> chunks = new ArrayList<>();
// 第一层:按段落分割(保留语义完整性)
String[] paragraphs = text.split("\n\n");
for (String paragraph : paragraphs) {
int tokens = tokenizer.countTokens(paragraph);
if (tokens <= this.chunkSize) {
// 小段落直接作为一个chunk
chunks.add(paragraph);
} else {
// 大段落继续细分
chunks.addAll(splitLargeChunk(paragraph));
}
}
return chunks;
}
// 第二层:按句子粒度细切
private List<String> splitLargeChunk(String text) {
List<String> chunks = new ArrayList<>();
String[] sentences = text.split("。|!|?");
StringBuilder currentChunk = new StringBuilder();
for (String sentence : sentences) {
int currentTokens = tokenizer.countTokens(currentChunk.toString());
int sentenceTokens = tokenizer.countTokens(sentence);
if (currentTokens + sentenceTokens <= this.chunkSize) {
// 还装得下 → 追加
currentChunk.append(sentence).append("。");
} else {
// 装不下了 → 当前块完成,开启新块
if (currentChunk.length() > 0) {
chunks.add(currentChunk.toString());
}
currentChunk = new StringBuilder(sentence).append("。");
}
}
if (currentChunk.length() > 0) {
chunks.add(currentChunk.toString());
}
return chunks;
}
}
3.3 切割效果示意
原文档(约1000 tokens):
"Spring AI 是一个 AI 应用开发框架...[中间500 tokens]...
它提供了 ChatModel、VectorStore...[结尾部分]..."
↓ TokenTextSplitter(chunkSize=256, chunkOverlap=20)
┌─────────────────────────────────────────────────┐
│ Chunk 1 [tokens 0 ~ 256] │
│ "Spring AI 是一个 AI 应用开发框架..." │
├──────────────────┬──────────────────────────────┤
│ Chunk 2 [236~492]│ ← 与Chunk1重叠20 tokens │
│ "...框架的核心特性包括 ChatModel..." │
├──────────────────┬──────────────────────────────┤
│ Chunk 3 [472~728]│ ← 与Chunk2重叠20 tokens │
│ "...ChatModel 支持 GPT 和 Ollama..." │
├──────────────────┬──────────────────────────────┤
│ Chunk 4 [708~1000]│ ← 与Chunk3重叠20 tokens │
│ "...Ollama 还支持本地部署..." │
└─────────────────────────────────────────────────┘
💡 为什么要有overlap?
→ 保证被切断的句子/概念不会丢失上下文信息
3.4 chunkSize怎么选?
| 场景 | 推荐chunkSize | 原因 |
|---|---|---|
| FAQ类短问答 | 128~256 | 问题本身很短,不需要大块 |
| 技术文档 | 512~1024 | 需要完整的代码示例/说明 |
| 法律合同 | 256~512 | 条款之间相对独立 |
| 小说/长文 | 1024~2048 | 需要叙事连贯性 |
四、相似度搜索:如何在向量空间中找到"最相近"的内容?
4.1 余弦相似度:衡量两段文字有多"像"
/**
* 余弦相似度计算 — RAG检索的核心算法
* 返回值范围:-1.0(完全相反)~ 1.0(完全相同),通常关注 0~1 区间
*/
public float cosineSimilarity(float[] a, float[] b) {
float dotProduct = 0.0f; // 点积
float normA = 0.0f; // A向量的模
float normB = 0.0f; // B向量的模
for (int i = 0; i < a.length; i++) {
dotProduct += a[i] * b[i];
normA += a[i] * a[i];
normB += b[i] * b[i];
}
return dotProduct / (float)(Math.sqrt(normA) * Math.sqrt(normB));
}
直观理解:
0.95→ 两段文字语义几乎相同0.75→ 相关但不完全一样0.50→ 有点关系但不太确定0.20→ 基本无关
4.2 以Redis为例看完整搜索过程
// RedisVectorStore 的 similaritySearch 实现
public class RedisVectorStore implements VectorStore {
@Override
public List<Document> similaritySearch(SearchRequest request) {
// Step 1: 把用户的查询文本也变成向量
float[] queryEmbedding = embeddingModel.embed(request.getQuery());
// Step 2: 从Redis取出所有已存储的文档向量
List<Document> allDocuments = getAllDocuments();
// Step 3: 逐一计算余弦相似度
List<DocumentWithScore> results = allDocuments.stream()
.map(doc -> new DocumentWithScore(
doc,
cosineSimilarity(queryEmbedding, doc.getEmbedding()) // 核心计算
))
// Step 4: 过滤掉相似度不够的结果
.filter(dws -> dws.score >= request.getSimilarityThreshold())
// Step 5: 按相似度从高到低排序
.sorted((a, b) -> Float.compare(b.score, a.score))
// Step 6: 只取前K个
.limit(request.getTopK())
.map(dws -> dws.document)
.toList();
return results;
}
}
4.3 一次真实搜索的全过程
用户提问: "Spring 框架有什么特点?"
↓
Step 1: 向量化问题
"Spring 框架有什么特点?" → [0.234, 0.567, 0.891, ...]
Step 2: 计算与所有文档的相似度
├─ "Spring AI 是一个 AI 框架" → 相似度 0.85 ✅ 高度匹配
├─ "Spring Boot 是一个 Web 框架" → 相似度 0.78 ✅ 匹配
├─ "Java 是一门编程语言" → 相似度 0.32 ❌ 太低
└─ "Python 的Django框架很流行" → 相似度 0.18 ❌ 不相关
Step 3: 设 threshold=0.5 → 过滤后剩2条
Step 4: 取 topK=2 → 返回这两条给AI作为参考素材
五、RAG Pipeline:让AI"开卷答题"
前面的内容都是准备工作。真正的魔法发生在这里——QuestionAnswerAdvisor 把检索到的文档注入到AI的提示词中。
5.1 QuestionAnswerAdvisor源码解析
/**
* QuestionAnswerAdvisor — RAG的核心拦截器
*
* 工作原理:
* 在请求发送给LLM之前,自动执行以下步骤:
* 1. 从用户问题提取查询词
* 2. 在VectorStore中做相似度搜索
* 3. 将搜索结果拼接到系统提示词
* 4. LLM基于"参考资料"生成回答
*/
public class QuestionAnswerAdvisor implements CallAroundAdvisor {
private final VectorStore vectorStore;
private final EmbeddingModel embeddingModel;
private final int topK; // 默认检索前4条
@Override
public String getName() {
return "QuestionAnswerAdvisor";
}
@Override
public int getOrder() {
return 2; // 执行顺序:MemoryAdvisor之后,其他Advisor之前
}
/**
* ★★★ 核心方法:在请求发出前注入检索结果 ★★★
*/
@Override
public AdvisedRequest before(AdvisedRequest request) {
// Step 1: 提取用户的问题文本
String userQuestion = extractUserQuestion(request);
// Step 2: 🔑 在VectorStore中搜索相关文档
List<Document> relevantDocs = vectorStore.similaritySearch(
SearchRequest.builder()
.query(userQuestion)
.topK(this.topK)
.similarityThreshold(0.5)
.build()
);
// Step 3: 将文档列表格式化为可读文本
String ragContext = buildRagContext(relevantDocs);
// Step 4: 🔑 将检索结果追加到系统提示词
String enhancedSystemPrompt = request.getSystemText() + "\n\n" +
"【参考文档】\n" + ragContext + "\n" +
"请基于以上参考文档回答用户问题。如果文档中没有相关信息,请如实告知。";
// Step 5: 返回增强后的请求(对上层透明)
return AdvisedRequest.from(request)
.systemText(enhancedSystemPrompt)
.build();
}
// 格式化检索结果
private String buildRagContext(List<Document> documents) {
return documents.stream()
.map(doc -> "- " + doc.getContent())
.collect(Collectors.joining("\n"));
}
@Override
public ChatResponse after(AdvisedRequest request,
AdvisedResponse<ChatResponse> response) {
return response.getChatResponse(); // 后置处理无特殊逻辑
}
}
5.2 RAG完整数据流
用户提问: "Spring AI 有哪些核心模块?"
│
▼
╔══════════════════════════════════════════════════╗
║ QuestionAnswerAdvisor.before() 开始工作 ║
╠══════════════════════════════════════════════════╣
║ ║
║ ① 向量化问题 ║
║ "Spring AI有哪些核心模块?" → [0.12, 0.78...] ║
║ ║
║ ② VectorStore.similaritySearch(topK=3) ║
║ 检索结果: ║
║ • "Spring AI包含ChatModel、EmbeddingModel..." ║
║ • "Spring AI支持OpenAI、Ollama等多种模型..." ║
║ • "VectorStore提供统一的向量存储抽象..." ║
║ ║
║ ③ 构建增强的系统提示词 ║
║ 原始系统提示词: ║
║ "你是一个AI助手" ║
║ + ║
║ 【参考文档】 ║
║ • Spring AI包含ChatModel、EmbeddingModel... ║
║ • Spring AI支持OpenAI、Ollama... ║
║ • VectorStore提供统一向量存储抽象... ║
║ + ║
║ 请基于以上参考文档回答用户问题。 ║
║ ║
╚══════════════════════════════════════════════════╝
│
▼
发送给 LLM(此时LLM已经拿到了"参考资料")
│
▼
LLM 生成回答:
"根据参考文档,Spring AI 的核心模块包括:
├── ChatModel:对话模型抽象层
├── EmbeddingModel:文本向量化
├── VectorStore:统一向量存储接口
├── AdvisorChain:请求处理管道
└── Tool Calling:工具调用机制"
↑ 这次的回答有根有据,不再是瞎编的了!
六、ETL管道:从PDF到VectorStore的自动化流水线
6.1 DocumentReader:什么格式的文档都能读
// org.springframework.ai.document.DocumentReader
public interface DocumentReader {
List<Document> read(Resource resource); // 统一入口
}
// Spring AI 内置了多种实现:
| Reader | 支持格式 | 典型场景 |
|---|---|---|
PdfDocumentReader | 技术手册、论文、合同 | |
MarkdownDocumentReader | Markdown (.md) | 技术博客、README、Wiki |
WebDocumentReader | HTML网页 | 在线文档、新闻文章 |
JsonDocumentReader | JSON | 结构化数据、API响应 |
6.2 DocumentTransformer:文档加工流水线
// org.springframework.ai.document.DocumentTransformer
public interface DocumentTransformer {
List<Document> transform(List<Document> documents);
}
// 最常用的转换器:文本分割
public class TextSplitterDocumentTransformer implements DocumentTransformer {
private final TextSplitter textSplitter;
@Override
public List<Document> transform(List<Document> documents) {
return documents.stream()
.flatMap(doc -> {
// 1. 分割文档内容为多个chunk
List<String> chunks = textSplitter.split(doc.getContent());
// 2. 为每个chunk创建新的Document(继承原元数据)
return chunks.stream()
.map(chunk -> new Document(
chunk,
doc.getMetadata() // 元数据透传!
));
})
.toList();
}
}
6.3 完整的ETL Pipeline代码
从一份PDF到可检索的向量存储,全部代码如下:
// ========== ETL Pipeline 完整流程 ==========
// Step 1: 读取原始文档
DocumentReader reader = new PdfDocumentReader();
List<Document> documents = reader.read(
new FileSystemResource("company-docs/product-manual.pdf")
);
// Step 2: 文本分割(大文档 → 小chunks)
DocumentTransformer splitter = new TextSplitterDocumentTransformer(
new TokenTextSplitter(
512, // 每个chunk最大512 tokens
50 // chunk之间重叠50 tokens
)
);
List<Document> chunks = splitter.transform(documents);
// 假设原来2个文档 → 切割后变成了15个chunk
// Step 3: 向量化
List<float[]> embeddings = embeddingModel.embed(
chunks.stream().map(Document::getContent).toList()
);
for (int i = 0; i < chunks.size(); i++) {
chunks.get(i).setEmbedding(embeddings.get(i));
}
// Step 4: 写入VectorStore
vectorStore.add(chunks); // ✅ 入库完成!
// ===== 之后就可以检索了 =====
List<Document> results = vectorStore.similaritySearch(
SearchRequest.builder()
.query("产品的定价策略是什么?")
.topK(3)
.similarityThreshold(0.7)
.build()
);
七、在ChatClient中使用RAG:一行注册搞定
说了这么多底层原理,实际用起来有多简单?
// 构建带有RAG能力的ChatClient
ChatClient chatClient = ChatClient.builder(chatModel)
// 注册RAG Advisor —— 一行代码让AI具备知识检索能力
.defaultAdvisors(new QuestionAnswerAdvisor(vectorStore, embeddingModel, 3))
// 还可以叠加记忆能力
.defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory))
.build();
// 使用 —— 和普通对话完全一样的API
String answer = chatClient.prompt()
.user("Spring AI 有哪些核心模块?") // 用户不需要知道RAG的存在
.call()
.content();
/*
* 幕后自动完成的操作:
*
* ① QuestionAnswerAdvisor 拦截请求
* ② 向量化用户问题
* ③ 在VectorStore中搜索最相关的3篇文档
* ④ 将文档内容注入系统提示词
* ⑤ 发送给LLM(此时LLM看到了参考资料)
* ⑥ LLM基于文档生成有据可依的回答
* ⑦ 返回给用户 —— 全程透明!
*/
八、实战踩坑指南
8.1 chunkSize调不好会怎样?
| 问题 | 现象 | 解决方法 |
|---|---|---|
| 太大(如2048) | 检索到的内容太泛泛,噪声多,回答质量下降 | 缩小到256~512 |
| 太小(如64) | 丢失上下文,AI看到的信息支离破碎 | 放大到256以上 |
| 没有overlap | 关键信息恰好在切割边界处丢失 | 设置20~50的重叠 |
8.2 相似度阈值设多少合适?
推荐阈值范围(基于实践经验):
0.7 ~ 0.9 → 严格要求匹配(适合FAQ、事实查询)
0.5 ~ 0.7 → 适中(适合大多数知识库场景)✅ 推荐
0.3 ~ 0.5 → 宽松(适合探索性搜索、创意类场景)
🚨 注意:首次上线建议先不加阈值(设为0),
观察实际的相似度分布后再调整。
8.3 检索不到结果怎么办?
常见排查顺序:
- 确认文档是否成功入库 →
vectorStore.similaritySearch("")看看有没有数据 - 检查chunkSize是否合理 → 可能文档被切得太碎了
- 检查EmbeddingModel是否一致 → 入库和查询必须用同一个模型!
- 降低阈值试试 → 可能是阈值设太高了
本篇小结
| 主题 | 一句话总结 | 重要度 |
|---|---|---|
| RAG核心思想 | 给AI开卷考试——检索相关知识后再回答 | ⭐⭐⭐⭐⭐ |
| VectorStore接口 | 统一的向量存储抽象,屏蔽底层差异 | ⭐⭐⭐⭐⭐ |
| 五种实现选型 | Redis/PgVector/Milvus/Qdrant/Chroma各有适用场景 | ⭐⭐⭐⭐ |
| Document数据模型 | 内容+元数据+向量三合一 | ⭐⭐⭐⭐ |
| EmbeddingModel | 文本→向量的转换器,RAG的基础设施 | ⭐⭐⭐⭐⭐ |
| TokenTextSplitter | 按Token粒度切分大文档,chunkSize+overlap是关键参数 | ⭐⭐⭐⭐ |
| 余弦相似度 | 向量空间中的"距离度量",决定检索相关性 | ⭐⭐⭐⭐ |
| QuestionAnswerAdvisor | RAG的核心——自动检索+注入提示词 | ⭐⭐⭐⭐⭐ |
| ETL Pipeline | Read→Split→Embed→Store 四步标准流程 | ⭐⭐⭐⭐ |
关键类速查
| 类 / 接口 | 所在包 | 职责 |
|---|---|---|
VectorStore | spring-ai-vectorstore | 向量存储核心接口 |
Document | spring-ai-common | 文档数据模型 |
EmbeddingModel | spring-ai-model | 向量化模型接口 |
TextSplitter | spring-ai-document | 文本分割策略接口 |
TokenTextSplitter | spring-ai-document | 按Token切分的具体实现 |
SearchRequest | spring-ai-vectorstore | 搜索请求封装 |
QuestionAnswerAdvisor | spring-ai-client | ★ RAG核心拦截器 |
DocumentReader | spring-ai-document | 多格式文档读取 |
DocumentTransformer | spring-ai-document | 文档转换处理链 |
📚 系列导航
本篇文章属于「Spring AI 系列」源码篇,建议按顺序阅读:
| 篇 | 主题 | 关键字 |
|---|---|---|
| 入门:环境搭建与第一个 AI 对话 | Quick Start | |
| 一次对话的完整生命线 | prompt→call→response | |
| Message 体系:User/System/Assistant | 消息格式 | |
| RAG 基础:检索增强生成入门 | VectorStore | |
| Advisor 拦截器链 | 责任链 / 自定义扩展 | |
| Tool Calling 工具调用 | Function Callback | |
| ★ 7 | VectorStore与RAG Pipeline | 检索增强生成 |
👋 我是亦暖筑序,专注 Java 后端开发者的 AI 应用落地指南。
如果这篇文章对你有帮助,欢迎 点赞 + 收藏, 我们下期见!🚀