从零到一:用 Node.js + LangChain + Milvus 打造《天龙八部》专属 RAG 问答机器人

0 阅读3分钟

引言

在人工智能浪潮席卷的今天,大语言模型(LLM)的能力令人瞩目。然而,模型本身的知识是静态的,截止于其训练数据。如何让模型能够访问和理解我们私有的、最新的知识库,成为了构建智能应用的核心课题。这,就是 RAG(Retrieval-Augmented Generation,检索增强生成) 的用武之地。

本文将带您一步步构建一个完整的 RAG 应用——一个能够阅读并回答关于《天龙八部》这部经典武侠小说的机器人。我们将使用 Node.js 作为开发语言,LangChain 作为 AI 应用框架,以及 Milvus 作为高效的向量数据库。通过这个实战项目,您将深入理解 RAG 的工作原理,并掌握其核心组件的集成方法。

一、准备工作:构建数据基石

RAG 的第一步是将非结构化的文本数据转化为结构化的向量知识库。我们从加载电子书开始。

1. 加载与分割

我们首先加载《天龙八部.epub》文件,并将其分割成大小合适的文本块(chunks)。这一步至关重要,因为它决定了后续检索的粒度。

// ebook-load.mjs
import { EPubLoader } from '@langchain/community/document_loaders/fs/epub';
import { RecursiveCharacterTextSplitter } from '@langchain/textsplitters';

// 加载 EPUB 文件
const loader = new EPubLoader('./天龙八部.epub', { splitChapters: true });
const documents = await loader.load();

// 按章节分割文本,设置块大小和重叠部分
const textSplitter = new RecursiveCharacterTextSplitter({
    chunkSize: 500,      // 每个块的最大字符数
    chunkOverlap: 50,    // 相邻块间的重叠字符数,保持上下文连贯
});

const chunks = await textSplitter.splitText(documents[0].pageContent);
// chunks 现在是一个个小段落组成的数组

2. 生成向量嵌入

接下来,我们将这些文本块送入一个嵌入模型(Embedding Model),例如 OpenAI 的 text-embedding-ada-002。嵌入模型会将每段文本转换为一个高维的数值向量(例如 1024 维)。这个向量在数学空间中捕捉了文本的语义信息。

// 定义嵌入模型
const embeddings = new OpenAIEmbeddings({
    apiKey: process.env.OPENAI_API_KEY,
    model: "text-embedding-ada-002",
});

// 为单个文本块生成向量
const vector = await embeddings.embedQuery("北冥神功");
// vector 是一个包含 1024 个数字的数组

3. 存储到向量数据库

最后,我们需要一个能高效存储和检索向量的数据库。这就是 Milvus 的主场。我们将每个文本块及其对应的向量、元数据(如章节、ID)一同存入 Milvus 集合中。

// 连接到 Milvus
const client = new MilvusClient({ address: process.env.MILVUS_ADDRESS });

// 创建一个集合(类似数据库中的表)
await client.createCollection({
    collection_name: 'ebook',
    fields: [
        { name: 'id', data_type: DataType.VarChar, max_length: 100, is_primary_key: true },
        { name: 'content', data_type: DataType.VarChar, max_length: 10000 },
        { name: 'vector', data_type: DataType.FloatVector, dim: 1024 },
    ],
});

// 批量插入数据
const insertData = chunks.map((chunk, index) => ({
    id: `doc_${index}`,
    content: chunk,
    vector: await embeddings.embedQuery(chunk), // 同步或异步生成向量
}));

await client.insert({
    collection_name: 'ebook',
    data: insertData,
});

至此,我们完成了知识库的构建,为 RAG 系统打下了坚实的数据基础。

二、核心机制:实现向量检索

当用户提出问题时,RAG 系统的第一步是找到与问题最相关的知识片段。

1. 查询向量化

用户的自然语言问题,例如“段誉会什么武功?”,同样需要通过嵌入模型转换成一个向量。

const question = "段誉会什么武功?";
const queryVector = await embeddings.embedQuery(question);

2. 相似度搜索

我们将这个查询向量与 Milvus 中存储的所有文本向量进行比较,找出最相似的几个。Milvus 内置了高效的相似度算法(如余弦相似度),能在海量数据中毫秒级完成这项任务。

// ebook-query.mjs
const searchResult = await client.search({
    collection_name: 'ebook',
    data: [queryVector], // 注意:传入的是一个数组
    limit: 3, // 返回最相似的 3 个结果
    metric_type: MetricType.COSINE,
    output_fields: ['id', 'content'], // 指定返回哪些字段
});

// searchResult.results 包含了最相关的 3 个文本块

三、智能整合:调用大模型生成答案

检索到的相关内容(Context)和原始问题一起,被构造成一个精心设计的 Prompt,发送给大语言模型(如 GPT-4)。模型基于这些信息,生成一个准确、连贯且自然的回答。

// ebook-rag.mjs
async function answerEbookQuestion(question) {
    // 1. 执行向量检索
    const relevantChunks = await retrieveRelevantContent(question);

    // 2. 构造 Prompt
    const context = relevantChunks.map(item => item.content).join('\n');
    const prompt = `
    你是一个专业的《天龙八部》电子书阅读器,请根据以下上下文回答用户的问题。
    上下文:${context}
    用户问题:${question}
    回答要求:仅基于上下文回答,不要编造。
    `;

    // 3. 调用大模型
    const model = new ChatOpenAI({ /* ... */ });
    const response = await model.invoke(prompt);

    return response.content;
}

总结

通过以上三个步骤——数据加载与向量化存储向量检索大模型生成——我们成功构建了一个完整的 RAG 问答系统。这个系统结合了向量数据库的高效检索能力和大模型的强大生成能力,实现了对私有知识库的智能化问答。

从一个简单的 Node.js 脚本开始,到一个能读懂《天龙八部》的 AI 助手,这不仅是一次技术的探索,更是对 AI 如何赋能知识管理的生动实践。您可以将这套流程应用到任何领域,构建属于自己的智能知识库。