三国杀武将觉醒马超怎么样 三国杀武将觉醒手游安卓手机版马超角色详解
2026-07-29 3433719
2026-07-29 0
AI幻觉问题有了一项关键解决方案:RAG(Retrieval-Augmented Generation,检索增强生成)技术,其兴起源于大语言模型的普及。借助一个完整的电子书问答系统,本文将带你依次掌握语义检索、文档向量化、智能问答这套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 // 文本分割工具
优势1: 专为向量检索优化,支持十亿级规模优势2: 支持COSINE/L2等多种相似度算法优势3: 提供丰富的索引类型(IVF_FLAT、HNSW等)优势4: 云原生架构,易于扩展
┌─────────────────┐│电子书文件││(EPUB格式) │└────────┬────────┘ │ ▼┌─────────────────┐│文档加载││按章节拆分│└────────┬────────┘ │ ▼┌─────────────────┐│文本分块││(500字/块) │└────────┬────────┘ │ ▼┌─────────────────┐│向量化││(Embedding)│└────────┬────────┘ │ ▼┌─────────────────┐│存入Milvus││建立索引│└─────────────────┘查询流程:问题 → 向量化 → 语义检索 → LLM生成答案
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;}}
核心要点:
FloatVector1024维向量存入该字段COSINE文本语义相似度计算适合采用此度量IVF_FLAT索引兼顾了速度与精度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: 重叠设计避免关键信息被切断
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并发调用大幅提升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个片段
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工程要点:
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)));}
javascript
// 小规模数据(<100万): IVF_FLATindex_type: IndexType.IVF_FLAT// 大规模数据(>100万): HNSWindex_type: IndexType.HNSWparams: { M: 16, efConstruction: 200 }
javascript
// 技术文档: 较大块保持完整性chunkSize: 1000, chunkOverlap: 100// 问答场景: 较小块提升精度chunkSize: 500, chunkOverlap: 50
本文完整搭建了一个RAG电子书问答系统,下面回顾核心要点:
这套架构能够轻松扩展至:
让你的AI应用告别"聊天玩具",进化为"生产力工具",关键在于掌握RAG技术。