Skip to content

综合实战:用 EPubLoader 加载 .epub 电子书(按章节拆分),RecursiveCharacterTextSplitter 再把每章分成 500 字符的 chunk,嵌入模型向量化后存入 Milvus。查询时 query 向量化做余弦相似度匹配,拿到相关片段当上下文,调用大模型生成回答。关键词搜索做不到的事,语义检索可以。这就是 RAG 的完整流程:Loader → Splitter → Embedding → Milvus → 检索 → LLM 生成。后面还可以和 MySQL 联动,通过 book_id 关联查出更多元数据。

Milvus + RAG 实战:电子书语义检索助手

把 Loader、Splitter、Milvus、RAG 串起来做一个完整项目:电子书语义检索助手。

场景

《天龙八部》很厚,想查"段誉会什么武功"——关键词搜索做不到,因为不知道该搜什么关键词。只能用向量语义检索 + 大模型生成回答

整体流程

text
Loader(EPubLoader)
  → 按章节加载 EPUB 文件
    → Splitter(RecursiveCharacterTextSplitter)
      → 每章再分成 500 字符的 chunk
        → 嵌入模型向量化
          → 存入 Milvus

查询:query 向量化 → Milvus 语义检索 → 相关片段当上下文 → 大模型生成回答

第一步:加载电子书 + 分块 + 存入 Milvus

bash
pnpm install @langchain/community epub2 html-to-text @langchain/textsplitters
js
import { EPubLoader } from '@langchain/community/document_loaders/fs/epub';
import { RecursiveCharacterTextSplitter } from '@langchain/textsplitters';

// 1. 按章节加载 EPUB
const loader = new EPubLoader('./天龙八部.epub', { splitChapters: true });
const documents = await loader.load();

// 2. 每章再分块
const textSplitter = new RecursiveCharacterTextSplitter({
  chunkSize: 500,
  chunkOverlap: 50,
});

// 3. 流式处理:遍历每章,分块后立即插入 Milvus
for (let chapterIndex = 0; chapterIndex < documents.length; chapterIndex++) {
  const chunks = await textSplitter.splitText(documents[chapterIndex].pageContent);

  // 为每个 chunk 生成向量并插入
  const insertData = await Promise.all(
    chunks.map(async (chunk, chunkIndex) => ({
      id: `${bookId}_${chapterIndex + 1}_${chunkIndex}`,
      book_id: bookId,
      book_name: BOOK_NAME,
      chapter_num: chapterIndex + 1,
      index: chunkIndex,
      content: chunk,
      vector: await getEmbedding(chunk),
    }))
  );

  await client.insert({ collection_name: COLLECTION_NAME, data: insertData });
}

第二步:语义检索

js
const query = '段誉会什么武功?';
const queryVector = await getEmbedding(query);

const searchResult = await client.search({
  collection_name: COLLECTION_NAME,
  vector: queryVector,
  limit: 3,
  metric_type: MetricType.COSINE,
  output_fields: ['id', 'book_id', 'chapter_num', 'index', 'content'],
});

第三步:RAG 生成回答

js
// 构建上下文
const context = retrievedContent
  .map((item, i) => `[片段 ${i + 1}]\n章节: 第 ${item.chapter_num} 章\n内容: ${item.content}`)
  .join('\n\n━━━━━\n\n');

// 构建 prompt
const prompt = `你是一个专业的《天龙八部》小说助手。基于小说内容回答问题:

${context}

问题: ${question}`;

const response = await model.invoke(prompt);

Milvus Schema 设计

js
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: 1024 },
]
  • book_id 可以对应 MySQL 的 book 表 id,方便后续Milvus + MySQL 联动
  • chapter_numindex 记录片段位置,方便追溯

要点

  • Loader + Splitter + Milvus + RAG 完整串联 — 这就是实际 AI Agent 项目的 RAG 流程
  • 流式处理 — 遍历章节,分块后立即向量化插入,不用等全部处理完
  • EPubLoader 按章节加载splitChapters: true 自动拆分章节
  • 语义检索是关键词搜索做不到的 — "段誉会什么武功"无法用关键词匹配
  • Milvus + MySQL 联动 — Milvus 的 book_id 对应 MySQL 的 book 表,可以关联查询更多元数据