写在前面:今天这节课太有趣了!之前我们做的 RAG 都是小打小闹——手动写几篇"光光和东东"的故事。今天老师直接把《天龙八部》的 EPUB 电子书扔进了 Milvus 向量数据库!Loader 加载 → Splitter 分块 → Embedding 向量化 → Milvus 存储 → 语义检索 → LLM 生成回答——一整条 RAG 流水线完整跑通了。然后我问:"段誉会什么武功?" AI 答上来了。我再问:"鸠摩智会什么武功?" AI 又答上来了。那一刻我感觉自己拥有了一个"AI 版的金庸百科"。
一、RAG 完整链路:从文件到回答
1.1 天龙八部 RAG 的"五步法"
老师说:
"我们学习了 Loader、Splitter、Milvus、RAG 流程完整跑通了。"
天龙八部.epub(电子书文件)
↓ ① Loader(EPubLoader)
Document 对象数组(按章节分)
↓ ② Splitter(RecursiveCharacterTextSplitter)
小的 Document 切片(chunkSize=500)
↓ ③ Embedding(text-embedding-v3)
1024 维向量
↓ ④ Milvus 向量数据库(存储 + 索引)
持久化存储,IVF_FLAT 索引加速
↓ ⑤ RAG 检索 + 生成
问题 → 向量化 → 搜索 → 增强 Prompt → LLM 回答
这五步串起来,就是一个完整的生产级 RAG 流程。
二、第一步:EPubLoader 加载电子书
2.1 EPUB 是什么?
EPUB 是电子书的标准格式,金庸全集、网络小说大多是这个格式。
LangChain 提供了 EPubLoader:
import { EPubLoader } from '@langchain/community/document_loaders/fs/epub';
const loader = new EPubLoader('./天龙八部.epub', {
splitChapters: true, // 按章节拆分
});
const documents = await loader.load();
console.log(`加载完成,共${documents.length}个章节`);
splitChapters: true 表示按照电子书的章节自动拆分成多个 Document。 每章一个 Document。
老师说:
"Loader 从各种来源加载文档。
EPUB、CSV……,相应的 Loader 加载器?LangChain 有 180 多种。"
三、第二步:文本切分
3.1 RecursiveCharacterTextSplitter
import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";
const textSplitter = new RecursiveCharacterTextSplitter({
chunkSize: 500, // 每块约 500 个字符
chunkOverlap: 50, // 重叠 50 个字符,保持语义连贯
});
每章可能有几千字,不能整章向量化——太大了检索不精准。需要切成语义完整的块。
老师说:
"切割符号——
。!?,chunk_size大小,overlap重叠。"
四、第三步:流式处理,一边切一边向量化
4.1 按章节分批处理
for (let chapterIndex = 0; chapterIndex < documentLen; chapterIndex++) {
const chapter = documents[chapterIndex];
const chunks = await textSplitter.splitText(chapter.pageContent);
console.log(`拆分为${chunks.length}个数据切片`);
const insertedCount = await insertChunksBatch(chunks, bookId, chapterIndex + 1);
totalInserted += insertedCount;
}
老师设计了一个"流式处理"方案——不一次把所有内容加载到内存,而是一章一章处理。 每章切完立即向量化、插入数据库。
4.2 向量化并插入
async function insertChunksBatch(chunks, bookId, chapterNum) {
const insertData = await Promise.all(
chunks.map(async (chunk, chunkIndex) => {
const vector = await getEmbedding(chunk);
return {
id: `${bookId}_${chapterNum}_${chunkIndex}`,
book_id: bookId,
book_name: '天龙八部',
chapter_num: chapterNum,
index: chunkIndex,
content: chunk,
vector: vector,
};
})
);
const insertResult = await client.insert({
collection_name: COLLECTION_NAME,
data: insertData,
});
return Number(insertResult.insert_cnt) || 0;
}
每条切片在插入前都生成 1024 维向量,和切片内容一起存入 Milvus。
五、Milvus 集合设计
5.1 创建集合
await client.createCollection({
collection_name: COLLECTION_NAME,
fields: [
{ name: 'id', data_type: DataType.VarChar, max_length: 100, is_primary_key: true },
{ name: 'book_id', data_type: DataType.VarChar, max_length: 100 },
{ name: 'book_name', data_type: DataType.VarChar, max_length: 200 },
{ name: 'chapter_num', data_type: DataType.Int32 },
{ name: 'index', data_type: DataType.Int32 },
{ name: 'content', data_type: DataType.VarChar, max_length: 10000 },
{ name: 'vector', data_type: DataType.FloatVector, dim: VECTOR_DIM },
]
});
这个集合设计包含了所有需要的信息:
| 字段 | 用途 | 举例 |
|---|---|---|
id | 唯一标识 | 1_1_0(第 1 章第 0 块) |
book_id | 书的编号 | 支持多本书 |
book_name | 书名 | 天龙八部 |
chapter_num | 章节号 | 第 3 章 |
index | 切片序号 | 第 5 块 |
content | 原文内容 | "段誉跟随钟灵……" |
vector | 1024 维语义向量 | 用于相似度搜索 |
5.2 创建索引
await client.createIndex({
collection_name: COLLECTION_NAME,
field_name: 'vector',
index_type: IndexType.IVF_FLAT,
metric_type: MetricType.COSINE,
params: { nlist: 1024 }
});
老师说:
"
IVF_FLAT——聚簇索引,毫秒级。COSINE——高维相似度,数据量大了也不慢。"
六、RAG 检索与生成
6.1 搜索相关片段
async function retrieveRelevantContent(question, k = 3) {
const queryVector = await getEmbeddings(question);
const searchResult = await client.search({
collection_name: COLLECTION_NAME,
vector: queryVector,
limit: k,
metric_type: MetricType.COSINE,
output_fields: ['id', 'book_id', 'chapter_num', 'index', 'content'],
});
return searchResult.results;
}
6.2 增强 Prompt + 生成
const prompt = `你是一个专业的《天龙八部》小说助手。
基于小说回答问题,用准确、详细的语言。
请根据以下小说片段内容回答问题:
${context}
用户问题:${question}
回答要求:
1. 如片段中有相关信息,请结合小说内容给出详细准确的回答。
2. 可以综合多个片段的内容,提供完整的答案。
3. 如果片段中没有相关信息,请如实告诉用户。
4. 回答要准确,符合小说的情节和人物设定。
5. 可以引用原文内容来支持你的回答。
AI 助手的回答:`;
6.3 实际问答效果
用户问:"段誉会什么武功?"
搜索到的相关片段(余弦相似度排序):
1. [Score: 0.8921] 章节:第3章
段誉按照图中所示,开始修炼"北冥神功"……
2. [Score: 0.8745] 章节:第5章
段誉使出"凌波微步",脚步飘忽不定……
3. [Score: 0.8312] 章节:第7章
段誉心中焦急,六脉神剑便不由自主地……
AI 的回答:
"段誉身怀多项绝世武功。他从无量山玉璧中习得了北冥神功,能够吸收他人内力为己用;后来又在天龙寺学得了六脉神剑,以指代剑,威力无穷;此外他还精通凌波微步,这是一门极其精妙的轻功身法,脚步飘忽、变化莫测……"
这就是 RAG 的威力——AI 不是凭空"背"知识,而是从向量库里检索到相关内容后,基于原文生成回答。
七、老师的"天龙八部 RAG"总结
老师说:
"Loader 从各种来源加载文档——EPUB、CSV……相应的 Loader 加载器。Splitter 分块——Separator 切割符号
。!?,chunk_size,overlap。Embedding——1024 维→百万字。Milvus 数据库。RAG——Cosine,top_k。"
这五个模块串起来,就是一个完整的、可扩展的 RAG 知识库系统。 换一本书?把文件名改了就行。换一个知识库类别?换 Loader 就行。
八、总结:RAG 完整链路一览
| 步骤 | 技术 | 核心参数 | 做了什么 |
|---|---|---|---|
| ① 加载 | EPubLoader | splitChapters: true | 读取电子书,按章拆分 |
| ② 切分 | RecursiveCharacterTextSplitter | chunkSize: 500, overlap: 50 | 切成语义完整的块 |
| ③ 向量化 | OpenAIEmbeddings | dimensions: 1024 | 文本 → 1024 维向量 |
| ④ 存储 | MilvusClient.insert | IVF_FLAT 索引, COSINE 相似度 | 存入向量数据库 |
| ⑤ 检索 | client.search | limit: 3, COSINE | 语义搜索最相关片段 |
| ⑥ 生成 | ChatOpenAI.invoke | temperature: 0.1 | 基于原文生成回答 |
从文件到回答,六步走完。这就是现代 AI 应用的知识库技术栈。
写在最后
今天这节课真的太有趣了!亲手把《天龙八部》喂给 AI,然后问它武功、人物、情节,它都能答上来。更让我兴奋的是,这一整套流程——Loader → Splitter → Embedding → Milvus → RAG——是通用的。
换个 Loader 就能处理 PDF,换个文件就能处理金庸全集。这套"知识库流水线",可以应用到任何需要"AI + 私有知识"的场景。
下次面试官问你:"生产环境的 RAG 完整流程是什么?"
你可以淡定地说:
"生产环境 RAG 的完整流程是六步。① Loader——用 EPubLoader、PDFLoader 等从文件加载内容,转为标准 Document;② Splitter——用 RecursiveCharacterTextSplitter 按句号等分隔符切分成语义完整的块,设置 chunkSize 和 chunkOverlap;③ Embedding——用 Embedding 模型将每个块转成高维向量(如 1024 维);④ 存储——将向量和原文存入 Milvus 等向量数据库,建 IVF_FLAT 索引和 COSINE 相似度度量;⑤ 检索——用户提问时,将问题向量化,通过 client.search 在 Milvus 中搜索最相关的 K 个片段;⑥ 生成——将检索到的片段组装成增强 Prompt,交给 LLM 生成最终回答。六步串起来就是一个完整的 RAG 知识库系统。"
然后看着面试官满意的表情,心里默念:这波,又稳了。
本文所有代码示例均来自课堂学习资料,真实可运行。