在大语言模型(LLM)的落地实践中,检索增强生成(Retrieval-Augmented Generation, RAG)已成为提升模型回答准确性、时效性和可控性的关键技术。

在这里插入图片描述

本文将基于 LangChain.js(截至 2025 年 12 月的最新稳定版本)的最新标准,带你通过代码实战,逐步了解 RAG 的底层机制与工程实现。


什么是 RAG?

在这里插入图片描述

RAG 是一种结合信息检索(Retrieval)与文本生成(Generation)的技术范式。其核心思想是:

  • 在用户提问时,先从外部知识库中检索与问题最相关的文档片段;
  • 将这些片段作为上下文,连同原始问题一起输入给大语言模型;
  • 模型基于增强后的上下文生成更准确、有依据的回答。

相比纯参数化的大模型,RAG 具备以下优势:

  • 动态更新知识:无需重新训练模型即可引入新数据;
  • 减少幻觉(Hallucination):回答有据可依;
  • 领域定制性强:可针对特定业务场景构建私有知识库。

为什么需要 RAG?

在这里插入图片描述

尽管当前主流 LLM(如 GPT-4、Claude、Qwen 等)具备强大的通用能力,但仍存在明显局限:

问题 RAG 的解决方案
知识截止于训练数据 实时接入最新文档、数据库或网页
对私有/内部数据无感知 构建专属向量知识库
回答缺乏引用来源 可追溯答案出处
容易“一本正经地胡说八道” 基于真实文档生成,降低幻觉

因此,RAG 成为企业级 AI 应用(如智能客服、知识助手、法律咨询等)的首选架构。


如何导入不同格式的数据源?

在这里插入图片描述

LangChain.js 提供了丰富的 Document Loaders,支持从多种格式加载原始文本。以下是常见数据源的加载方式(v0.3+ 语法):

1. 加载本地文本文件(.txt)

import { TextLoader } from "@langchain/classic/document_loaders/fs/text";

const loader = new TextLoader("./data/faq.txt");
const docs = await loader.load();

2. 加载 PDF 文件

import { PDFLoader } from "@langchain/community/document_loaders/fs/pdf";

const loader = new PDFLoader("./data/manual.pdf", {
  splitPages: true, // 按页分割
});
const docs = await loader.load();

💡 需安装 pdf-parsenpm install pdf-parse

3. 加载网页内容

import { BrowserbaseLoader } from "@langchain/community/document_loaders/web/browserbase";

const loader = new BrowserbaseLoader(["https://example.com"], {
  textContent: true,
});
const docs = await loader.load();

4. 加载 CSV 或 JSON

import { CSVLoader } from "@langchain/community/document_loaders/fs/csv";
// 或
import { JSONLoader } from "@langchain/community/document_loaders/fs/json";

const csvLoader = new CSVLoader("./data/products.csv", "description");
const jsonLoader = new JSONLoader("./data/faq.json", ".[].answer");

const csvDocs = await csvLoader.load();
const jsonDocs = await jsonLoader.load();

如何存储和索引数据?

RAG 的核心在于将文本转化为向量表示,以便进行语义相似度检索。LangChain.js 通过 Embeddings + VectorStore 实现这一过程。

在这里插入图片描述

步骤 1:切分文档(Chunking)

使用 RecursiveCharacterTextSplitter 是最常用的策略,它会优先按段落、句子分隔符切分,保持语义完整性。

import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";

const splitter = new RecursiveCharacterTextSplitter({
  chunkSize: 1000,    // 每个块的大小(字符数)
  chunkOverlap: 200,  // 重叠部分,防止上下文在切分处丢失
});

const splitDocs = await splitter.splitDocuments(docs);
console.log(`分割后文档数量: ${splitDocs.length}`);

步骤 2:Embedding 与 向量存储 (VectorStore)

我们需要一个 Embedding 模型将文本转为向量,以及一个 VectorStore 来存储这些向量。

这里以 OpenAI 和 MemoryVectorStore (内存存储,适合测试) 为例。生产环境推荐使用 Chroma, Pinecone 或 Supabase。

import { OpenAIEmbeddings } from "@langchain/openai";
import { MemoryVectorStore } from "langchain/vectorstores/memory";
// 如果使用 Chroma: import { Chroma } from "@langchain/community/vectorstores/chroma";

// 初始化 Embedding 模型
const embeddings = new OpenAIEmbeddings({
  model: "text-embedding-3-small", // 性价比极高的模型
  apiKey: process.env.OPENAI_API_KEY,
});

// 创建向量库并存入文档
const vectorStore = new MemoryVectorStore(embeddings);
await vectorStore.addDocuments(splitDocs);
console.log("向量库构建完成!");

其他支持的 VectorStore:Pinecone、Supabase、Weaviate、FAISS(via @langchain/community


如何检索并生成答案?

这是 RAG 的灵魂所在。我们这里使用 Agent 来调用 Tool 进行内容检索,最后通过大模型润色后返回结果。

import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";
import { TextLoader } from "@langchain/classic/document_loaders/fs/text";
import { OpenAIEmbeddings } from "@langchain/openai";
import { MemoryVectorStore } from "@langchain/classic/vectorstores/memory";
import { ChatOpenAI } from "@langchain/openai";
import { tool } from "@langchain/core/tools";
import { z } from "zod";
import { createAgent } from "langchain";

/**
 * 基于LangChain框架实现的文档检索增强生成(RAG)核心流程
 * 流程:加载文档 → 文档切分 → 生成向量 → 向量存储 → 构建检索工具 → 创建智能体 → 执行问答
 */
async function main() {
  // 1. 加载本地文本文档
  // TextLoader是LangChain提供的文本文件加载器,支持加载txt等纯文本格式文件
  const loader = new TextLoader("./langchain.txt");
  // 执行加载操作,返回Document类型的数组,每个Document包含文本内容和元数据(如文件路径)
  const docs = await loader.load();

  // 2. 文档切分:解决大文本无法一次性嵌入的问题,同时保留上下文关联性
  const splitter = new RecursiveCharacterTextSplitter({
    chunkSize: 400, // 每个文本块的最大字符数,根据模型上下文窗口和嵌入模型限制调整
    chunkOverlap: 50, // 相邻文本块的重叠字符数,防止切分导致上下文断裂(如句子被截断)
  });
  // 执行文档切分,将原始文档拆分为多个小文本块
  const splitDocs = await splitter.splitDocuments(docs);

  // 3. 初始化嵌入模型:将文本转换为向量表示(Embedding)
  const embeddings = new OpenAIEmbeddings({
    model: "", // 指定嵌入模型名称(如text-embedding-3-small),需根据实际使用的模型填写
    apiKey: "", // OpenAI兼容接口的API密钥,需替换为实际密钥
    configuration: { baseURL: "https://ark.cn-beijing.volces.com/api/v3" }, // 火山方舟OpenAI兼容接口地址
  });

  // 4. 初始化向量存储:将切分后的文本块向量存入内存(仅测试用,生产环境建议用Pinecone/Chroma等)
  const vectorStore = new MemoryVectorStore(embeddings);
  // 将切分后的文档转换为向量并添加到向量存储中
  await vectorStore.addDocuments(splitDocs);

  // 5. 定义检索工具的输入参数校验规则
  // 使用zod库定义schema,确保输入的query是字符串类型,避免非法参数
  const retrieveSchema = z.object({ query: z.string() });

  // 6. 创建自定义检索工具:供智能体调用的核心工具
  const retrieve = tool(
    // 工具执行逻辑:根据用户查询检索相似文档
    async ({ query }) => {
      // 向量相似性检索:返回最相似的2个文档(top-k=2,可根据需求调整)
      const retrievedDocs = await vectorStore.similaritySearch(query, 2);
      // 序列化检索结果:格式化文档信息(来源+内容),便于LLM理解
      const serialized = retrievedDocs
        .map(
          (doc) => `Source: ${doc.metadata.source}\nContent: ${doc.pageContent}`
        )
        .join("\n");
      // 返回序列化后的文本(供LLM生成回答)和原始文档(供后续处理)
      return [serialized, retrievedDocs];
    },
    {
      name: "retrieve", // 工具名称,智能体通过该名称调用工具
      description: "检索与查询相关的信息", // 工具描述,LLM根据描述判断是否调用该工具
      schema: retrieveSchema, // 输入参数校验规则
      responseFormat: "content_and_artifact", // 响应格式:内容+原始数据
    }
  );

  // 7. 初始化大语言模型(LLM):用于生成回答的核心模型
  const model = new ChatOpenAI({
    temperature: 0.7, // 生成温度:0-1之间,越高回答越随机,越低越精准
    model: "", // 指定对话模型名称(如gpt-3.5-turbo),需替换为实际模型
    configuration: { baseURL: "https://ark.cn-beijing.volces.com/api/v3" }, // 火山方舟接口地址
    apiKey: "", // 模型API密钥,需替换为实际密钥
  });

  // 8. 创建智能体(Agent):整合LLM和工具,自动决策是否调用检索工具
  const agent = createAgent({ model: model, tools: [retrieve] });

  // 9. 调用智能体执行问答:用户查询“道家七十二福地”
  const response = await agent.invoke({
    messages: [{ role: "user", content: "道家七十二福地" }], // 构造用户消息
  });

  // 输出智能体的回答结果(包含LLM生成的最终回答、工具调用记录等)
  console.log(response);
}

// 执行主函数,捕获并打印异常
main().catch(console.error);


总结

通过 LangChain.js,我们能够以模块化、声明式的方式快速搭建一个生产就绪的 RAG 系统。关键在于:

  • 数据预处理:合理加载、清洗、切分;
  • 向量索引:选择合适的 Embedding 与 VectorStore;
  • 检索生成协同:设计 Prompt 与 Chain 逻辑,确保上下文有效利用。

RAG 不仅是技术方案,更是连接 LLM 与企业知识资产的桥梁。掌握它,你就掌握了构建可信、可控、可解释 AI 应用的核心能力。


📚 参考资源

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐