一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

从零构建电子书RAG问答系统:Milvus + LangChain实践指南

时间:2026-07-29 12:35:56 编辑:袖梨 来源:一聚教程网

引言

AI幻觉问题随着大语言模型的普及而更需要有效方案,RAG(Retrieval-Augmented Generation,检索增强生成)技术由此成为关键选择。围绕一个完整的电子书问答系统,本文依次带你掌握文档向量化、语义检索、智能问答这套RAG核心实现流程。

从零搭建电子书RAG问答系统:Milvus + LangChain实战指南

一、RAG是什么?

RAG的基本思想

传统LLM主要存在两大问题:

问题1: 知识截止日期后的信息无法获取问题2: 私有领域知识(如企业文档、专业书籍)无法回答

针对这些问题,RAG采用检索+生成的方式加以解决:

用户提问 ↓将问题转为向量(Embedding) ↓在向量数据库中检索相似内容 ↓将检索结果+问题一起发给LLM ↓LLM基于检索内容生成答案

本文的案例场景

用户提出"段誉会什么武功?"时,准确答案将由系统检索小说原文中的相关章节后给出;我们要构建的正是这样一个《天龙八部》小说问答系统。

二、选择技术栈

关键组件

向量数据库@zilliz/milvus2-sdk-node// Milvus官方Node.js SDKLangChain生态@langchain/openai// OpenAI集成(Embedding + LLM)@langchain/community // 文档加载器(EPUB)@langchain/textsplitters // 文本分割工具

为什么选择Milvus?

优势1: 专为向量检索优化,支持十亿级规模优势2: 支持COSINE/L2等多种相似度算法优势3: 提供丰富的索引类型(IVF_FLAT、HNSW等)优势4: 云原生架构,易于扩展

三、系统架构设计

整体流程

┌─────────────────┐│电子书文件││(EPUB格式) │└────────┬────────┘ │ ▼┌─────────────────┐│文档加载││按章节拆分│└────────┬────────┘ │ ▼┌─────────────────┐│文本分块││(500字/块) │└────────┬────────┘ │ ▼┌─────────────────┐│向量化││(Embedding)│└────────┬────────┘ │ ▼┌─────────────────┐│存入Milvus││建立索引│└─────────────────┘查询流程:问题 → 向量化 → 语义检索 → LLM生成答案

四、核心代码实现

步骤1:创建向量集合

javascript

async function ensureBookCollection(bookId) {try {const hasCollection = await client.hasCollection({collection_name: COLLECTION_NAME,});if (!hasCollection.value) {console.log(`${COLLECTION_NAME} 集合不存在,创建集合`);// 定义数据Schemaawait 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: 100 },{ 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: VECTION_DIM },]});// 创建向量索引await client.createIndex({collection_name: COLLECTION_NAME,field_name: 'vector',index_type: IndexType.IVF_FLAT,metric_type: MetricType.COSINE,// 余弦相似度params: {nlist: VECTION_DIM,}});}// 加载集合到内存await client.loadCollection({collection_name: COLLECTION_NAME,});} catch (err) {console.error('集合创建失败', err.message);throw err;}}

核心要点:

  • FloatVector字段用于存储1024维向量
  • COSINE该度量适用于文本语义相似度计算
  • IVF_FLAT此索引兼顾了精度与速度

步骤2:处理并加载EPUB文件

javascript

async function loadAndProcessEPubStreaming(bookId) {try {// 加载EPUB文件const loader = new EPubLoader(EPUB_FILE,{ splitChapters: true }// 按章节拆分);const documents = await loader.load();// 文本分割器配置const textSplitter = new RecursiveCharacterTextSplitter({chunkSize: 500,// 每块500字chunkOverlap: 50,// 块间重叠50字,保持上下文连贯});let totalInserted = 0;for (let chapterIndex = 0; chapterIndex < documents.length; chapterIndex++) {const chapter = documents[chapterIndex];console.log(`处理第 ${chapterIndex + 1}/${documents.length} 章`);// 章节内容进一步切块const chunks = await textSplitter.splitText(chapter.pageContent);console.log(`拆分为 ${chunks.length} 个片段`);if (chunks.length === 0) continue;const insertedCount = await insertChunksBatch(chunks, bookId, chapterIndex + 1);totalInserted += insertedCount;}console.log(`累计插入 ${totalInserted} 个片段`);return totalInserted;} catch (err) {console.error('加载EPUB文件失败', err.message);throw err;}}

为什么要分块?

原因1: Embedding模型有长度限制(通常8192 tokens)原因2: 小块检索精度更高,减少噪音原因3: 重叠设计避免关键信息被切断

步骤3:执行批量向量化与插入

javascript

async function insertChunksBatch(chunks, bookId, chapterIndex) {try {if (chunks.length === 0) return 0;// 并发生成Embedding(性能优化关键)const insertData = await Promise.all(chunks.map(async (chunk, chunkIndex) => {const vector = await getEmbedding(chunk);return {id: `${bookId}_${chapterIndex}_${chunkIndex}`,book_id: bookId,book_name: BOOK_NAME,chapter_num: chapterIndex,index: chunkIndex,content: chunk,vector};}));const insertResult = await client.insert({collection_name: COLLECTION_NAME,data: insertData,});return Number(insertResult.insert_cnt) || 0;} catch (err) {console.error('插入数据失败', err.message);throw err;}}

性能优化亮点:

  • 采用Promise.all处理速度得到大幅提升,源于Embedding API的并发调用
  • 通过批量插入降低网络往返次数

步骤4:实现语义检索

javascript

async function retrieveRelevantContent(question, k=3) {try {// 将问题转为向量const queryVector = await getEmbedding(question);// 向量相似度检索const searchResult = await client.search({collection_name: COLLECTION_NAME,vector: queryVector,limit: k,// 返回Top K个最相似结果metric_type: MetricType.COSINE,output_fields: ['id', 'content', 'book_id', 'chapter_num', 'index', 'book_name'],});return searchResult.results;} catch (err) {console.log('检索相关内容失败', err.message);return [];}}

检索原理:

问题: "段誉会什么武功?"Embedding: [0.23, -0.45, 0.67, ...](1024维向量) ↓与数据库中所有片段向量计算余弦相似度 ↓返回相似度最高的3个片段

步骤5:RAG问答的核心逻辑

javascript

async function answerEbookQuestion(question, k=3) {try {console.log('开始回答问题: ', question);// 1. 检索相关内容const retrievedContent = await retrieveRelevantContent(question, k);if (retrievedContent.length === 0) {return '抱歉,没有找到相关内容';}// 2. 构建上下文const context = retrievedContent.map((item, i) => `[片段 ${i+1}]章节:第${item.chapter_num}章内容:${item.content}`).join('nn---nn');// 3. 构建Promptconst prompt = `你是一个专业的《天龙八部》小说助手,基于小说内容回答问题。请根据以下小说片段内容回答问题:${context}用户问题:${question}回答要求:1. 如果片段中有相关信息,请结合小说内容给出详情2. 可以综合多个片段的内容,提供完整的答案3. 如果片段中没有相关信息,请如实告知4. 回答要准确,符合小说的情节和人物设定5. 可以引用原文内容来支持你的回答AI助手的回答:`;// 4. 调用LLM生成答案const response = await model.invoke(prompt);console.log(response.content);return response.content;} catch (err) {return '抱歉,处理您的问题时出现了错误';}}

Prompt工程要点:

  • 明确角色定位("小说助手")
  • 提供结构化上下文(内容+章节信息)
  • 制定回答规范(引用原文、准确性)
  • 应对边界情况(没有相关信息时的处理)

五、性能方面的优化建议

1. 控制Embedding并发

javascript

// 避免API限流const BATCH_SIZE = 10;for (let i = 0; i < chunks.length; i += BATCH_SIZE) {const batch = chunks.slice(i, i + BATCH_SIZE);await Promise.all(batch.map(chunk => getEmbedding(chunk)));}

2. 索引选择策略

javascript

// 小规模数据(<100万): IVF_FLATindex_type: IndexType.IVF_FLAT// 大规模数据(>100万): HNSWindex_type: IndexType.HNSWparams: { M: 16, efConstruction: 200 }

3. 分块参数调优

javascript

// 技术文档: 较大块保持完整性chunkSize: 1000, chunkOverlap: 100// 问答场景: 较小块提升精度chunkSize: 500, chunkOverlap: 50

内容总结

本文完整搭建了一个RAG电子书问答系统,下面回顾其中的核心要点:

  1. 文档处理: 文本分块前先拆分章节,起点为EPUB加载
  2. 向量化: Milvus批量插入的数据来自Embedding API并发调用
  3. 语义检索: Top K检索之前先完成问题向量化和COSINE相似度计算
  4. 智能问答: 检索上下文 + Prompt工程 → LLM生成答案

该架构能够方便地扩展到:

  • 企业知识库智能问答
  • 法律文档内容检索
  • 技术文档智能助手
  • 客服场景智能问答

让AI应用完成从"聊天玩具"到"生产力工具"的进化,关键在于掌握RAG技术。

热门栏目