🍉 我把《天龙八部》喂给了 AI,问它"段誉会什么武功?"答案让我惊呆了

1 阅读5分钟

写在前面:今天这节课太有趣了!之前我们做的 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原文内容"段誉跟随钟灵……"
vector1024 维语义向量用于相似度搜索

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 完整链路一览

步骤技术核心参数做了什么
① 加载EPubLoadersplitChapters: true读取电子书,按章拆分
② 切分RecursiveCharacterTextSplitterchunkSize: 500, overlap: 50切成语义完整的块
③ 向量化OpenAIEmbeddingsdimensions: 1024文本 → 1024 维向量
④ 存储MilvusClient.insertIVF_FLAT 索引, COSINE 相似度存入向量数据库
⑤ 检索client.searchlimit: 3, COSINE语义搜索最相关片段
⑥ 生成ChatOpenAI.invoketemperature: 0.1基于原文生成回答

从文件到回答,六步走完。这就是现代 AI 应用的知识库技术栈。


写在最后

今天这节课真的太有趣了!亲手把《天龙八部》喂给 AI,然后问它武功、人物、情节,它都能答上来。更让我兴奋的是,这一整套流程——Loader → Splitter → Embedding → Milvus → RAG——是通用的。

换个 Loader 就能处理 PDF,换个文件就能处理金庸全集。这套"知识库流水线",可以应用到任何需要"AI + 私有知识"的场景。

下次面试官问你:"生产环境的 RAG 完整流程是什么?"

你可以淡定地说:

"生产环境 RAG 的完整流程是六步。① Loader——用 EPubLoaderPDFLoader 等从文件加载内容,转为标准 Document;② Splitter——用 RecursiveCharacterTextSplitter 按句号等分隔符切分成语义完整的块,设置 chunkSizechunkOverlap;③ Embedding——用 Embedding 模型将每个块转成高维向量(如 1024 维);④ 存储——将向量和原文存入 Milvus 等向量数据库,建 IVF_FLAT 索引和 COSINE 相似度度量;⑤ 检索——用户提问时,将问题向量化,通过 client.search 在 Milvus 中搜索最相关的 K 个片段;⑥ 生成——将检索到的片段组装成增强 Prompt,交给 LLM 生成最终回答。六步串起来就是一个完整的 RAG 知识库系统。"

然后看着面试官满意的表情,心里默念:这波,又稳了。


本文所有代码示例均来自课堂学习资料,真实可运行。