基于 Milvus 构建小说知识库 RAG,实现图书智能问答(天龙八部实战)

在 AI 时代,大模型(LLM)虽然聪明,但往往缺乏特定领域的私有知识,容易产生"幻觉"。如何让它"引经据典"?答案就是 RAG(检索增强生成)

今天,我们将以大家耳熟能详的金庸名著《天龙八部》为例,使用 Node.js + LangChain + Milvus,手搓一个专属的 AI 小说助手。本文不堆砌冗长的代码,而是带你从底层逻辑出发,深度剖析 RAG 的核心链路,让新手也能轻松掌握企业级 AI 应用开发的精髓。


一、 核心心法:RAG 的修炼路径

在写代码之前,我们必须先懂原理。RAG 的核心流程其实就像把大象装进冰箱,分为四个关键步骤:

  1. 资源获取 (Loader) :把 PDF、EPUB 等格式的数据搬进内存。
  2. 文本切分 (Splitter) :大模型的上下文窗口有限,我们需要把长篇小说切成一个个语义完整的小片段(Chunk)。
  3. 向量化 (Embedding) :计算机看不懂中文,必须把文字片段转换成高维数字(向量),存入向量数据库(Milvus)。
  4. 检索与生成 (RAG) :当用户提问时,将问题转为向量去数据库寻找最相似的片段(Top-K),最后拼接上下文交由大模型生成答案。

二、 铸剑:环境配置与组件初始化

在实战中,我们需要用到 LangChain(大模型开发的瑞士军刀)和 Milvus(高性能向量数据库)。以下是基础的组件初始化代码:

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

// 1. 初始化 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: 1024 // ️ 核心细节:向量维度必须与模型匹配,否则无法入库
});

// 2. 初始化大语言模型
const model = new ChatOpenAI({
  temperature: 0.1, // 降低随机性,让小说问答更严谨、准确
  model: process.env.MODEL_NAME,
  apiKey: process.env.OPENAI_API_KEY,
  configuration: { baseURL: process.env.OPENAI_BASE_URL }
});

// 3. 连接 Milvus 向量数据库
const client = new MilvusClient({
  address: process.env.MILVUS_ADDRESS,
  token: process.env.MILVUS_TOKEN
});

代码深度解析:

  • dotenv/config :这行代码的作用是"读取密码本"。在开发中,API Key 这种机密信息绝对不能直接写在代码里,而是放在 .env 文件中。这行代码会自动把 .env 里的变量加载到 process.env 中,保护你的资产安全。
  • dimensions: 1024 :这是极其关键的配置!Embedding 模型的作用是把文字变成一串数字(向量)。1024 代表这串数字有 1024 个维度。如果这个数值和你使用的模型不匹配,Milvus 数据库会直接报错拒绝存储。
  • temperature: 0.1:大模型有个参数叫"温度(temperature)",控制它回答的"发散程度"。默认通常是 0.7,但在做小说问答这种需要"引经据典"的场景时,我们需要它严谨,所以把温度调到 0.1,让它尽量克制发挥,不瞎编乱造。

三、 藏经阁检索:语义搜索的底层逻辑

当用户提问"鸠摩智会什么武功?"时,我们不能用传统的关键词匹配,而要用向量相似度

javascript 复制代码
async function retrieveRelevantContent(question, k = 3) {
  try {
    // 1. 将用户问题转化为向量
    const queryVector = await embeddings.embedQuery(question);
    
    // 2. 在 Milvus 中进行向量检索
    const searchResult = await client.search({
      collection_name: 'ebook2',
      vector: queryVector,
      limit: k, // 取相似度最高的 Top-K 个片段
      metric_type: MetricType.COSINE, // 使用余弦相似度计算距离
      output_fields: ["id", "book_id", "chapter_num", "index", "content"]
    });
    return searchResult.results;
  } catch (err) {
    console.error('检索内容时出错:', err);
    return [];
  }
}

代码深度解析(小白必看):

  • embeddings.embedQuery(question) :这一步是"翻译官"。把用户的中文问题,翻译成计算机能懂的 1024 维数字向量。
  • metric_type: MetricType.COSINE:为什么用余弦相似度?在海量文本中,COSINE 衡量的是两个向量在"方向"上的相似程度,而不是绝对距离。这意味着即便两段文本长度不同,只要它们表达的语义方向一致,就能被精准召回。
  • output_fields:这相当于告诉数据库:"除了给我最相关的原文(content),顺便把它的章节号(chapter_num)也带回来"。这为后续我们告诉用户"答案出自第几章"做好了准备。

四、 注入灵魂:Prompt 工程与 RAG 生成

检索只是 RAG 的一半,另一半是如何让大模型"消化"这些片段。Prompt 的设计是 RAG 的灵魂:

javascript 复制代码
async function answerEbookQuestion(question, k = 3) {
  try {
    const retrievedContent = await retrieveRelevantContent(question, k);
    
    // 防御性编程:如果没检索到内容,直接返回,避免浪费大模型 Token
    if (retrievedContent.length === 0) {
      return "抱歉,我没有找到相关的《天龙八部》内容。";
    }

    // 将检索到的片段拼装成结构化上下文
    const context = retrievedContent.map((item, i) => `
      [片段${i + 1}]
      章节: 第${item.chapter_num}章
      内容: ${item.content}
    `).join('\n\n----\n\n');

    // 构造系统级 Prompt,约束模型行为
    const prompt = `你是一个专业的《天龙八部》小说助手。
请根据以下小说片段内容回答问题:
${context}

用户问题: ${question}

回答要求:
1. 仅基于提供的片段回答,如果片段中没有相关信息,请如实告知不知道。
2. 可以综合多个片段,提供完整准确的答案,并尽量引用原文支持。
3. 回答必须符合小说的情节和人物设定。

AI 助手的回答:`;

    const response = await model.invoke(prompt);
    return response.content;
  } catch (err) {
    console.error('生成回答时出错:', err);
    return '系统繁忙,请稍后再试。';
  }
}

代码深度解析:

  • if (retrievedContent.length === 0) :这是"防御性编程"。如果数据库里根本没找到相关内容,就直接告诉用户不知道,千万不要把空数据扔给大模型,否则它会开始"一本正经地胡说八道"。
  • retrievedContent.map(...).join(...) :这段代码的作用是"排版"。它把检索到的多个片段,按照 [片段1] 章节: 第X章 的格式整齐地拼接起来。大模型非常喜欢这种结构化的输入,这能极大提高它理解上下文的准确率。
  • Prompt 中的"防幻觉机制" :注意看 Prompt 里的第 1 条要求:"仅基于提供的片段回答,没有相关信息请告知不知道"。这是 RAG 系统最核心的防线,通过明确的指令,强行约束大模型不要动用它的"训练记忆",而是乖乖做个"阅读理解"。

五、 结语:从 Demo 到企业级应用

至此,我们已经跑通了 RAG 的完整闭环。回顾整个链路:

  • Loader 负责从 EPUB/CSV 等来源加载文档;
  • Splitter 按照 chunk_sizeoverlap 将文本切分,保证语义不断层;
  • Embedding 将百万字的小说化作 1024 维的向量矩阵;
  • Milvus 提供毫秒级的语义检索;
  • RAG 将 Top-K 结果与大模型结合,输出精准答案。

掌握了这套 Node.js 架构,你不仅能做《天龙八部》的问答助手,还可以轻松将其替换为企业内部知识库、产品说明书问答、法律合同审查等真实业务场景。

相关推荐
JavaGuide1 小时前
我最推荐的 4 个 AI 编程 Skills:grill-me、research、diagnosing-bugs、code-review
前端·后端·ai编程
Moment1 小时前
2026 了,前端转 AI 全栈我是这么学的 😍😍😍
前端·后端·面试
野生风长1 小时前
c++(日期类的实现)
前端·c++
恋猫de小郭1 小时前
Android R8 为什么可以让 Kotlin 协程提速 2 倍?
android·前端·flutter
han_hanker1 小时前
SQL语法 , BETWEEN ... AND ...,比较运算符
前端·javascript·sql
PedroQue991 小时前
Vite插件v1.1.0发布:自动导入全面重构
前端·vite
FogLetter1 小时前
三栏布局的七十二变:从Flex到Grid,我学会了响应式设计的"降龙十八掌"
前端·面试
答案answer1 小时前
做了几年 Three.js,我为什么决定自己开发一款3D低代码编辑器
前端·three.js
Revolution611 小时前
首屏变慢后,应该先查资源下载、脚本执行还是接口请求
前端·性能优化·前端工程化