大模型幻觉无解?7步搭建Milvus+LangChain电子书RAG,私有小说精准问答零编造

前言

直接用大模型问《天龙八部》武功、人物,经常乱编剧情、混淆人物,这是通用LLM的天生短板------训练数据有截止时间,没有私有文本记忆。

今天用Node.js完整落地一套端到端RAG知识库,把EPUB武侠小说存入Milvus向量库,实现基于原著片段的精准问答,彻底解决AI幻觉。

读完你能收获:

  1. 完整RAG四大核心模块实操:文档加载、文本分块、Embedding向量化、Milvus向量检索
  2. 可直接复制运行的4套工程代码:入库、单独检索、完整问答、基础配置
  3. Milvus建表、IVF_FLAT索引、余弦相似度调优实战经验
  4. RAG工程高频踩坑点与解决方案
  5. 可迁移到企业文档、知识库、产品手册的通用流程

一、整体技术栈与RAG流程拆解

1.1 用到的核心依赖

  • LangChain :统一封装文档加载、文本分割、Embedding、LLM调用
    • EPubLoader:解析epub电子书,按章节拆分文档
    • RecursiveCharacterTextSplitter:文本智能分块
    • OpenAIEmbeddings:文本转1024维向量
    • ChatOpenAI:对话大模型生成答案
  • @zilliz/milvus2-sdk-node:Milvus向量数据库Node客户端,存储&语义检索
  • dotenv:统一管理API、Milvus地址密钥

1.2 RAG完整流水线(四大环节)

scss 复制代码
EPUB电子书 → Loader加载分章节 → Splitter切片(chunk) → Embedding生成向量 → Milvus入库存储
用户提问 → 问题向量化 → Milvus余弦相似度TopK检索 → 拼接原文片段上下文 → Prompt注入LLM → 输出原著可信答案

四大核心模块详解:

  1. Loader文档加载器:支持epub/csv/txt/pdf等多格式,本次用EPubLoader自动拆分小说章节,保留章节元数据,方便溯源
  2. Splitter文本分割器:设置chunk_size、chunk_overlap,避免上下文断裂,是检索准确率关键
  3. Embedding向量模型:输出1024维稠密向量,把文字语义转为数字,用于相似度匹配
  4. Milvus向量数据库:存储文本+元数据+向量,提供高速Cosine余弦检索,支持百万级文本检索

二、环境准备与配置文件

2.1 安装依赖

bash 复制代码
npm install dotenv @zilliz/milvus2-sdk-node @langchain/openai @langchain/community @langchain/textsplitters

2.2 .env环境配置

env 复制代码
# Milvus向量库配置
MILVUS_ADDRESS=你的Milvus服务地址
MILVUS_TOKEN=你的Milvus密钥

# 大模型&向量嵌入配置
OPENAI_API_KEY=你的APIkey
OPENAI_BASE_URL=代理地址
EMBEDDINGS_MODEL_NAME=text-embedding-xxx
MODEL_NAME=gpt-3.5-turbo

三、第一步:EPUB电子书入库Milvus(main.js完整代码)

该脚本实现:连接Milvus → 自动创建集合&索引 → 加载天龙八部epub → 按章节切割文本 → 批量生成向量入库。

javascript 复制代码
import "dotenv/config";
import { parse } from 'path';
import {
  MilvusClient,
  DataType,
  MetricType,
  IndexType
} from '@zilliz/milvus2-sdk-node'
import { OpenAIEmbeddings } from '@langchain/openai'
import { EPubLoader } from '@langchain/community/document_loaders/fs/epub';
import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters"

// 全局常量配置
const COLLECTION_NAME = 'ebook2';
const VECTOR_DIM = 1024;
const CHUNK_SIZE = 500;
const CHUNK_OVERLAP = 50;
const EPUB_FILE = './天龙八部.epub'
const ADDRESS = process.env.MILVUS_ADDRESS;
const TOKEN = process.env.MILVUS_TOKEN;
const { name: BOOK_NAME } = parse(EPUB_FILE);

// 初始化Embedding
const embeddings = new OpenAIEmbeddings({
  apiKey: process.env.OPENAI_API_KEY,
  model: process.env.EMBEDDINGS_MODEL_NAME,
  configuration: { baseURL: process.env.OPENAI_BASE_URL },
  dimensions: VECTOR_DIM
});

// Milvus客户端
const client = new MilvusClient({ address: ADDRESS, token: TOKEN });

// 生成向量工具函数
async function getEmbedding(text) {
  return await embeddings.embedQuery(text);
}

/**
 * 校验/创建Milvus集合,定义表结构+索引
 */
async function ensureCollection(bookId) {
  const hasCollection = await client.hasCollection({ collection_name: COLLECTION_NAME });
  if(!hasCollection.value) {
    console.log('创建ebook2集合....');
    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 }
      ]
    });
    console.log('集合创建成功,开始构建IVF_FLAT索引');
    // IVF_FLAT索引,余弦相似度,nlist=1024适配百万级文本
    await client.createIndex({
      collection_name: COLLECTION_NAME,
      field_name: 'vector',
      index_type: IndexType.IVF_FLAT,
      metric_type: MetricType.COSINE,
      params: {nlist: 1024}
    })
    console.log('索引创建完成');
  }
  // 加载集合至内存
  try {
    await client.loadCollection({ collection_name: COLLECTION_NAME });
    console.log('集合加载成功');
  } catch(err) {
    console.log('集合已处于加载状态,无需重复加载');
  }
}

/**
 * 批量切片向量化,插入Milvus
 */
async function insertChunksBatch(chunks, bookId, chapterNum) {
  const EMBED_BATCH_SIZE = 10; // 接口单次批量上限
  const vectors = [];
  for (let i = 0; i < chunks.length; i += EMBED_BATCH_SIZE) {
    const batch = chunks.slice(i, i + EMBED_BATCH_SIZE);
    const batchVectors = await embeddings.embedDocuments(batch);
    vectors.push(...batchVectors);
  }
  // 组装入库数据,携带书籍、章节、切片索引元数据
  const data = chunks.map((content, index) => ({
    id: `${bookId}_${chapterNum}_${index}`,
    book_id: String(bookId),
    book_name: BOOK_NAME,
    chapter_num: chapterNum,
    index,
    content,
    vector: vectors[index]
  }));
  await client.insert({ collection_name: COLLECTION_NAME, data });
  return data.length;
}

/**
 * 核心:加载EPUB、分章节、文本切片、批量入库
 */
async function loadAndProcessEPubStreaming(bookId) {
  console.log(`开始加载EPUB文件: ${EPUB_FILE}`);
  // splitChapters=true 自动按章节拆分文档,保留章节结构
  const loader = new EPubLoader(EPUB_FILE, { splitChapters: true });
  const documents = await loader.load();
  console.log(`加载完成,共${documents.length}个章节`);

  // 文本分割器配置
  const textSplitter = new RecursiveCharacterTextSplitter({
    chunkSize: CHUNK_SIZE,
    chunkOverlap: CHUNK_OVERLAP
  });

  let totalInserted = 0;
  for (let chapterIndex = 0; chapterIndex < documents.length; chapterIndex++) {
    const document = documents[chapterIndex];
    const chapterContent = document.pageContent;
    console.log(`正在处理第${chapterIndex+1}/${documents.length}章`);
    const chunks = await textSplitter.splitText(chapterContent);
    console.log(`本章切分${chunks.length}个文本切片`);
    if(chunks.length === 0) continue;
    const insertedCount = await insertChunksBatch(chunks, bookId, chapterIndex+1);
    totalInserted += insertedCount;
    console.log(`累计入库切片:${totalInserted}`);
  }
  console.log(`全部处理完成,总入库切片${totalInserted}条`);
  return totalInserted;
}

// 程序入口
const main = async () => {
  console.log('==========电子书向量入库程序启动==========');
  await client.connectPromise;
  console.log('Milvus连接成功');
  const bookId = 1;
  await ensureCollection(bookId);
  await loadAndProcessEPubStreaming(bookId);
}
main().catch(err => console.error('入库程序异常:',err))

入库关键参数说明

  1. chunkSize:500:单块文本500字符,平衡检索精度与向量接口消耗
  2. chunkOverlap:50:切片重叠50字符,避免章节交界处上下文丢失
  3. MetricType.COSINE:余弦相似度,文本语义匹配最优算法
  4. IVF_FLAT:轻量索引,百万级文本检索速度快,无需高性能硬件

四、第二步:单独向量检索测试(query.js)

入库完成后,可单独运行检索脚本,验证向量匹配是否正常,用于调试相似度得分。

javascript 复制代码
import 'dotenv/config'
import { MilvusClient, MetricType } from '@zilliz/milvus2-sdk-node'
import { OpenAIEmbeddings } from '@langchain/openai'

const ADDRESS =process.env.MILVUS_ADDRESS
const TOKEN=process.env.MILVUS_TOKEN
const COLLECTION_NAME = 'ebook2';
const VECTOR_DIM=1024;

const embedding = new OpenAIEmbeddings({
 apiKey: process.env.OPENAI_API_KEY,
 model: process.env.EMBEDDINGS_MODEL_NAME,
 configuration: { baseURL: process.env.OPENAI_BASE_URL },
 dimension: VECTOR_DIM,
})
const client =new MilvusClient({ address :ADDRESS , token : TOKEN })

const getEmbedding = async (text) => await embedding.embedQuery(text);

async function main(){
  console.log('正在连接Milvus...')
  await client.connectPromise;
  console.log("连接成功")
  // 测试提问:段誉会什么武功
  const query = "段誉会什么武功"
  const queryVector=await getEmbedding(query);
  // Top3相似度片段召回
  const searchResult= await client.search({
    collection_name: COLLECTION_NAME,
    vectors: [queryVector],
    limit: 3,
    metric_type: MetricType.COSINE,
    output_fields: ['id', 'book_id', 'chapter_num', 'index', 'content'],
  })
  // 打印检索结果、相似度得分、章节号、原文
  searchResult.results.forEach((item,index)=>{
    console.log(`
=====检索片段${index+1}=====
相似度得分:${item.score.toFixed(4)}
章节号:${item.chapter_num}
切片ID:${item.id}
原文片段:${item.content}
    `)
  })
}
main().catch(console.error)

五、第三步:完整RAG问答链路(rag.js)

核心业务代码:检索原文片段 + 构造约束Prompt + 调用LLM生成基于原著的答案,解决AI幻觉。

javascript 复制代码
import 'dotenv/config'
import { MilvusClient, MetricType } from '@zilliz/milvus2-sdk-node'
import { OpenAIEmbeddings, ChatOpenAI } from '@langchain/openai'
import { SystemMessage } from '@langchain/core/messages'

const ADDRESS =process.env.MILVUS_ADDRESS
const TOKEN=process.env.MILVUS_TOKEN
const COLLECTION_NAME = 'ebook2';
const VECTOR_DIM=1024;

// 向量模型初始化
const embedding = new OpenAIEmbeddings({
 apiKey: process.env.OPENAI_API_KEY,
 model: process.env.EMBEDDINGS_MODEL_NAME,
 configuration: { baseURL: process.env.OPENAI_BASE_URL },
 dimension: VECTOR_DIM,
})
// 对话大模型
const model = new ChatOpenAI({
  temperature: 0.7,
  model: process.env.MODEL_NAME,
  apiKey: process.env.OPENAI_API_KEY,
  configuration: { baseURL: process.env.OPENAI_BASE_URL },
})
// Milvus客户端
const client =new MilvusClient({ address :ADDRESS , token : TOKEN })

const getEmbedding = async (text) => await embedding.embedQuery(text);

/**
 * 向量检索:召回TopK相关小说片段
 */
const retrievevRelevantContent = async (question,k=3) => {
  try{
    const queryVector =await getEmbedding(question)
    const srarchResult = await client.search({
      collection_name: COLLECTION_NAME,
      vectors: [queryVector],
      limit: k,
      metric_type: MetricType.COSINE,
      output_fields: ['id','content',"book_id","chapter_num"],
    })
    return srarchResult.results
  }catch(error){
    console.error('检索异常:', error);
  }
}

/**
 * 组装RAG提示词,强约束模型只能基于原文回答
 */
const answerEbookQuestion = async (question,k=3) => {
  try{
    console.log('='.repeat(50))
    console.log(`用户问题:${question}`);
    console.log('='.repeat(50))
    const retrievedDiaries = await retrievevRelevantContent(question,k)
    if(!retrievedDiaries || retrievedDiaries.length===0){
      return "知识库中未找到《天龙八部》相关内容,无法回答";
    }
    // 打印召回的原文片段与相似度
    retrievedDiaries.forEach((diary,index)=>{
      console.log(`检索片段${index+1} | 相似度:${diary.score.toFixed(4)}\n章节${diary.chapter_num}原文:${diary.content}\n`)
    })
    // 拼接上下文,携带章节溯源信息
    const context = retrievedDiaries.map((diary,i)=>`[片段${i+1}] 章节号:${diary.chapter_num},原文内容:${diary.content}`).join('\n\n----\n\n')
    // 强约束Prompt,杜绝编造内容
    const prompt =`你是专业《天龙八部》原著问答助手,所有回答必须严格依据下方提供的小说原文片段。
参考原文片段:
${context}
用户提问:${question}
回答硬性要求:
1. 仅使用片段内存在的信息作答,无相关内容直接回复"书中暂无相关记载";
2. 可整合多段原文综合回答,支持引用原著文字佐证;
3. 严格贴合小说人物、武功、剧情设定,禁止编造不存在的情节;
4. 回答条理清晰,分点说明,标注对应章节来源。
`
    return prompt
  }catch(error){
    console.error('构造prompt异常:', error);
  }
}

// 程序入口,完整问答链路
async function main(){
  try{
    await client.connect()
    await client.loadCollection({ collection_name: COLLECTION_NAME })
    console.log("向量库集合加载完成,启动RAG问答");
    // 示例提问:鸠摩智会什么武功?
    const prompt=await answerEbookQuestion("鸠摩智会什么武功?",5)
    const response=await model.invoke([new SystemMessage(prompt)])
    console.log('\n=====AI最终回答=====')
    console.log(response.content)
  }catch(error){
    console.error('RAG主程序异常:', error);
  }
}
main().catch(console.error)

Prompt设计核心亮点

通过硬性规则限制大模型输出,从根源解决幻觉:

  1. 无匹配原文直接告知"无相关记载",禁止脑补;
  2. 强制绑定检索到的章节片段,答案可溯源;
  3. 要求引用原文,降低编造概率。

六、实战高频踩坑汇总(避坑手册)

6.1 向量维度不匹配(最高频报错)

  • 问题:Embedding模型输出维度1024,Milvus建表时dim写错,检索直接报错
  • 解决方案:全局统一VECTOR_DIM=1024常量,建表、Embedding初始化全部复用该变量

6.2 文本切片过大/过小

  • chunkSize过大:单段内容杂乱,语义不聚焦,检索匹配准确率暴跌
  • chunkSize过小:丢失完整上下文,人物、剧情断裂
  • 最优配置:500字符chunk + 50字符重叠,小说/文档通用

6.3 Milvus未加载集合,检索空数据

创建集合后必须执行loadCollection,否则向量库无法读取索引,检索返回空结果。

6.4 Embedding批量超限

多数Embedding接口单次批量上限10条,代码中设置EMBED_BATCH_SIZE=10分批处理,避免接口400报错。

6.5 相似度得分过低,召回无关片段

  • 检查是否使用MetricType.COSINE,文本场景禁用L2距离;
  • 增大检索limit值(5-10),过滤低相似度片段;
  • 调整chunkSize,让文本语义更完整。

6.6 LLM依旧编造内容

Prompt缺少强约束规则,必须增加"无原文则如实告知,禁止编造"硬性条款。

七、流程拓展:不止武侠小说,通用企业知识库

这套RAG架构可无缝迁移到各类私有文档场景:

  1. 企业内部规章制度、培训手册(EPUB/PDF/TXT)
  2. 产品说明书、售后知识库
  3. 项目技术文档、接口手册
  4. 历史资料、合同档案

仅需替换两点:

  1. EPubLoader更换为PDFLoader/TextLoader等对应加载器;
  2. 调整Prompt角色,从"小说助手"改为"企业文档顾问"。

八、完整总结

本文完整落地一套Node.js+Milvus+LangChain工业级简易RAG系统,覆盖全链路:

  1. 数据入库链路:EPUB加载 → 章节拆分 → 文本切片 → 批量向量化 → Milvus建表建索引存储;
  2. 问答检索链路:问题向量化 → 余弦相似度TopK召回原文 → 拼接上下文约束Prompt → LLM生成可信答案;
  3. 掌握三大核心知识点:文档分块调优、向量数据库Schema设计、RAG提示词幻觉抑制方案。
相关推荐
是Dream呀1 小时前
客户催着要数据,我用TRAE Work十分钟完成10万元项目
人工智能·架构·trae
IanSkunk1 小时前
眼健康机构系统化运营:流程拆解与协同机制怎么建?
大数据·人工智能
40kuai1 小时前
四款主流AI模型网关对比与选型指南
人工智能
2601_963282771 小时前
极寒区域无线通信系统工程实战:黑龙江冰雪环境对讲机组网、射频损耗与设备耐寒可靠性优化全指南
大数据·数据库·人工智能
海的辽阔1 小时前
GraphRAG 与 LightRAG 全面解析:传统 RAG 为什么开始走向图检索?
人工智能·rag
WA内核拾荒者1 小时前
WhatsApp 多语言 AI 对话的语言模型选择与本地化适配
人工智能·语言模型·php
OpenMiniServer1 小时前
GULP:宇宙的演化---第5章狭义相对论
人工智能
DLYSB_1 小时前
博灵智能语音通知终端落地应用指南
人工智能·语音识别·报警灯
DataX_ruby821 小时前
2026年数据中台技术路线深度对比:治理架构、AI能力与信创适配全景盘点
人工智能·数据治理·数据中台