LangChain.js:RAG 深度解析与全栈实践

在大语言模型(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)

ts 复制代码
import { TextLoader } from "@langchain/community/document_loaders/fs/text";

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

2. 加载 PDF 文件

ts 复制代码
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. 加载网页内容

ts 复制代码
import { WebBaseLoader } from "@langchain/community/document_loaders/web/base";

const loader = new WebBaseLoader("https://example.com/article");
const docs = await loader.load();

4. 加载 CSV 或 JSON

ts 复制代码
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 是最常用的策略,它会优先按段落、句子分隔符切分,保持语义完整性。

ts 复制代码
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。

ts 复制代码
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,
});

// 创建向量库并存入文档
// 这一步会自动调用 embeddings.embedDocuments()
const vectorStore = await MemoryVectorStore.fromDocuments(
  splitDocs,
  embeddings
);

console.log("向量库构建完成!");

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


如何检索并生成答案?

这是 RAG 的灵魂所在。在 LangChain.js v0.3 中,我们使用 LCEL (LangChain Expression Language) 或高阶封装函数 createRetrievalChain 来实现。

步骤 1:创建检索器 (Retriever)

将向量库转换为检索器接口,并配置检索参数(如 k 表示取前几条相似数据)。

typescript 复制代码
const retriever = vectorStore.asRetriever({
  k: 3, // 检索最相关的 3 个片段
  searchType: "similarity", // 或 "mmr" (最大边际相关性,用于增加结果多样性)
});

步骤 2:构建 RAG 链 (The RAG Chain)

我们需要两个组件:

  1. LLM: 负责推理。
  2. Prompt: 指导模型如何利用上下文。
  3. Combine Chain: 将检索到的文档列表拼接成字符串。
typescript 复制代码
import { ChatOpenAI } from "@langchain/openai";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { createStuffDocumentsChain } from "langchain/chains/combine_documents";
import { createRetrievalChain } from "langchain/chains/retrieval";

// 1. 初始化 LLM
const llm = new ChatOpenAI({
  model: "gpt-4o",
  temperature: 0, // RAG 任务通常设为 0 以保证严谨性
});

// 2. 设计 Prompt
// {context} 会被检索到的文档内容填充
// {input} 是用户的提问
const prompt = ChatPromptTemplate.fromTemplate(`
作为一个专业的文档助手,请基于以下提供的上下文回答用户的问题。
如果上下文中没有答案,请诚实地说不知道,不要编造。

上下文:
{context}

用户问题:
{input}
`);

// 3. 创建 "Stuff" 链(即把所有文档一次性塞入 Prompt)
const combineDocsChain = await createStuffDocumentsChain({
  llm,
  prompt,
});

// 4. 创建最终的检索增强链
// 这个链会自动处理:拿到 input -> 传给 retriever -> 拿到 docs -> 传给 combineDocsChain
const ragChain = await createRetrievalChain({
  retriever,
  combineDocsChain,
});

步骤 3:运行与测试

typescript 复制代码
const response = await ragChain.invoke({
  input: "LangChain.js v0.3 有什么新特性?",
});

// 输出结果
console.log("参考文档来源:", response.context.map(d => d.metadata));
console.log("AI 回答:", response.answer);

总结

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

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

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


📚 参考资源

相关推荐
Code Warrior2 小时前
【C++】智能指针的使用及其原理
开发语言·c++
05大叔2 小时前
多线程的学习
java·开发语言·学习
lly2024062 小时前
C 位域:深度解析其概念、应用与未来趋势
开发语言
刺客xs2 小时前
多路IO复用
开发语言
培培说证3 小时前
2026大专Java开发工程师,考什么证加分?
java·开发语言·python
qq_336313933 小时前
java基础-方法引用
java·开发语言·算法
总是学不会.3 小时前
【JUC编程】一、线程的基础概念
java·开发语言·jvm
我是唐青枫4 小时前
C#.NET struct 全解析:什么时候该用值类型?
开发语言·c#·.net
沉下去,苦磨练!4 小时前
计算一个字符串在另一个字符串中出现次数
java·开发语言