Milvus加RAG实战电子书语义检索
综合实战:用 EPubLoader 加载 .epub 电子书(按章节拆分),RecursiveCharacterTextSplitter 再把每章分成 500 字符的 chunk,嵌入模型向量化后存入 Milvus。查询时 query 向量化做余弦相似度匹配,拿到相关片段当上下文,调用大模型生成回答。关键词搜索做不到的事,语义检索可以。这就是 RAG 的完整流程:Loader → Splitter → Embedding → Milvus → 检索 → LLM 生成。后面还可以和 MySQL 联动,通过 book_id 关联查出更多元数据。
# Milvus + RAG 实战:电子书语义检索助手
把 Loader、Splitter、Milvus、RAG 串起来做一个完整项目:电子书语义检索助手。
# 场景
《天龙八部》很厚,想查"段誉会什么武功"——关键词搜索做不到,因为不知道该搜什么关键词。只能用向量语义检索 + 大模型生成回答。
# 整体流程
Loader(EPubLoader)
→ 按章节加载 EPUB 文件
→ Splitter(RecursiveCharacterTextSplitter)
→ 每章再分成 500 字符的 chunk
→ 嵌入模型向量化
→ 存入 Milvus
查询:query 向量化 → Milvus 语义检索 → 相关片段当上下文 → 大模型生成回答
# 第一步:加载电子书 + 分块 + 存入 Milvus
pnpm install @langchain/community epub2 html-to-text @langchain/textsplitters
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 });
}
# 第二步:语义检索
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 生成回答
// 构建上下文
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 设计
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_num和index记录片段位置,方便追溯
# 要点
- Loader + Splitter + Milvus + RAG 完整串联 — 这就是实际 AI Agent 项目的 RAG 流程
- 流式处理 — 遍历章节,分块后立即向量化插入,不用等全部处理完
- EPubLoader 按章节加载 —
splitChapters: true自动拆分章节 - 语义检索是关键词搜索做不到的 — "段誉会什么武功"无法用关键词匹配
- Milvus + MySQL 联动 — Milvus 的 book_id 对应 MySQL 的 book 表,可以关联查询更多元数据
编辑 (opens new window)
上次更新: 2026/06/17, 14:57:34