NestJS与LangChainJS构建企业级RAG知识库实战指南

RAGNestJSLangChainJS
于 2026-08-30 04:29:09 修改
·本内容遵循CC 4.0 BY-SA版权协议

各位做后端或者全栈开发的朋友,应该多少遇到过类似的情况:项目里的技术文档、需求文档、会议记录越来越多,真到写方案或者回答业务问题的时候,反而不知道该去哪份资料里找依据。直接让大模型生成答案,体验虽然流畅,但模型没有看过企业内部资料,容易一本正经地“编造”。在企业真实业务里,这种幻觉是很致命的。

RAG 是目前比较成熟的解决方案。它先把文档切块并向量化,存到向量数据库里;收到用户问题时,先从库里检索出最相关的片段,再把片段拼进 Prompt 交给大模型生成回答。整个链路可以理解为“先搜索,后回答”。答案有出处、可调试、好更新,非常适合企业内部知识库、客服问答、研发助手等场景。

本文围绕 NestJS + LangChainJS 搭建一套完整的 RAG 系统:从项目初始化、文档加载与切块、向量存储、检索与生成,到 API 流式输出,最后给出基于宝塔面板和 Docker 的部署思路。文章中的代码会尽量完整可运行,同时也会解释清楚每一步的“为什么”,方便你照着落地,而不是只复制片段。

1. RAG 系统架构与核心原理

1.1 RAG 解决的是什么问题

传统大模型问答有一个很强的限制:模型学到的知识停留在训练数据截止时间,无法自动覆盖企业私有资料,也无法保证回答内容的真实性。直接问模型“我们系统的登录超时时间是多少”,模型只能根据猜测回答,因为训练数据里根本没有这份内部配置文档。

传统的关键词搜索也有局限。输入“登录超时设置”可能搜出很多包含关键词的文档,但用户需要的是精确答案,而不是一屏搜索结果。更重要的是,不同文档对同一问题可能有不同描述,搜索系统很难自动聚合。

RAG 的思路恰好处于两者之间。它用向量检索的能力代替关键词匹配,把用户问题转换为向量,在向量空间中寻找语义最接近的文本片段。找到候选片段后,再让大模型基于这些片段生成最终回答。这样做有三个好处:

  • 答案有依据:模型被约束在知识库片段内回答,减少编造概率。
  • 知识更新方便:替换或新增文档即可,不需要重新训练模型。
  • 过程可追溯:每个回答都可以回溯到命中了哪些文档片段。

从工程角度看,RAG 比微调模型更容易落地。微调需要准备高质量训练集,成本高、周期长,而 RAG 只需要解决数据导入、向量检索和 Prompt 拼接这些问题。

1.2 为什么选择 NestJS + LangChainJS

在 Node.js/TypeScript 生态中,NestJS 是目前企业级后端项目的首选之一。它借鉴了 Angular 的模块化思想,提供依赖注入、装饰器、拦截器、管道、守卫等成熟的开发模式。对于一个 RAG 服务来说,外部依赖很多:大模型 API、向量数据库、文档解析、任务队列、鉴权系统。如果不用依赖注入和模块化拆分,代码很快会变成一坨难以维护的“胶水代码”。

LangChainJS 是 LangChain 的 TypeScript 版本,提供了一整套构建大模型应用的工具抽象。你可以用它加载文档、切分文本、生成向量、访问向量数据库、调用大模型,甚至编排复杂的 Agent 流程。相比自己从零封装 OpenAI SDK,LangChainJS 最大的价值是“抽象统一”:今天用 MemoryVectorStore,明天想切换到 pgvector 或 Milvus,业务代码改动量很小。

另外,NestJS 和 LangChainJS 都是 TypeScript 技术栈,类型定义完善,前端开发转过来也没有太大门槛。如果你所在团队的前端使用 React/Vue,后端使用 Node,那么整套 AI 全栈项目都可以在一种语言里完成,不用再单独维护一套 Python 服务。

1.3 系统整体流程

本文要搭建的系统包含以下步骤:

  1. 用户上传文档(txt、md 等)。
  2. 文档服务把文件解析为纯文本。
  3. 文本切分器将长文档拆成固定大小的 chunk。
  4. Embedding 模型把每个 chunk 转换为向量。
  5. 向量库存储向量和原始文本。
  6. 用户通过 API 提问,系统把问题也转换为向量。
  7. 向量库执行相似度检索,返回最相关的 k 个 chunk。
  8. 系统把 chunk 拼进 Prompt,调用大模型生成回答。
  9. 响应以 JSON 或 SSE 流式方式返回给前端。

在这个流程里,业务代码层面我们要拆成配置模块、向量存储模块、文档处理模块和 RAG 问答模块。前两步是数据准备,中间两步是数据索引,最后几步是问答链路。

2. 环境准备与项目初始化

2.1 版本与依赖说明

不同技术版本的兼容性问题在 AI 项目里尤其明显。LangChainJS 迭代速度比较快,早些时候所有能力都集中在 langchain 包里,现在拆分出了 @langchain/core@langchain/openai@langchain/community 等子包。

本文示例以 Node.js 20 为例,建议使用 18 或 20 的 LTS 版本。Node 版本过低可能导致部分依赖的 ESM 语法或原生模块无法使用。

需要准备的环境如下:

依赖项 说明
Node.js 18+ 或 20 LTS
pnpm / npm 包管理器,任选其一
NestJS CLI 用于创建项目,pnpm add -g @nestjs/cli
大模型 API Key OpenAI 或兼容 OpenAI 协议的模型服务
向量数据库 演示环境用内存版,生产建议 pgvector / Chroma / Milvus

如果你使用的是通义、智谱、DeepSeek 等兼容 OpenAI 协议的模型服务,只需要修改 baseURL 和 Key,不需要更换代码主体。

2.2 初始化 NestJS 项目

执行下面的命令创建项目:

BASH
nest new rag-knowledge-base
cd rag-knowledge-base
pnpm install

安装后续会用到的依赖:

BASH
pnpm add @nestjs/config
pnpm add langchain @langchain/core @langchain/openai @langchain/community
pnpm add -D @types/multer

@nestjs/config 用于统一读取环境变量;langchain 聚合包提供文档加载和切分能力;@langchain/openai 提供对话模型和 Embedding 模型;@langchain/community 里有很多向量数据库和文档加载器的社区实现。@types/multer 用于文件上传的类型提示。

2.3 项目目录结构

为了让代码职责清晰,我建议按模块目录组织:

TEXT
src/
main.ts
app.module.ts
config/
configuration.ts
modules/
document/
document.module.ts
document.service.ts
document.controller.ts
vector-store/
vector-store.module.ts
vector-store.service.ts
rag/
rag.module.ts
rag.service.ts
rag.controller.ts
data/
sample.txt

config 目录存放环境配置,document 模块负责文件解析和切块,vector-store 模块负责向量的写入与检索,rag 模块负责问答链路的组装。

3. 核心概念拆解:从文档到向量再到答案

3.1 文档加载

RAG 系统第一步是让程序能读取文档。LangChainJS 提供了很多 Loader,常见的包括:

  • TextLoader:读取 txt / md 等纯文本文件。
  • PDFLoader:解析 PDF,需要额外安装 pdf-parse 等依赖。
  • DirectoryLoader:批量读取目录下的多种文件。
  • CSVLoaderDocxLoader 等。

在企业项目里,建议先以 txt / md 为主,后续再逐步支持 PDF、Word。PDF 解析很容易出现表格错位、分栏混乱的问题,直接会影响后期检索效果。

下面是最基本的文本加载写法:

TS
import { TextLoader } from "langchain/document_loaders/fs/text";
 
const loader = new TextLoader("src/data/sample.txt");
const docs = await loader.load();
 
console.log(docs.length);

loader.load() 返回一个 Document[]。每个 Document 包含两个核心字段:

  • pageContent:文档正文。
  • metadata:元数据,比如 source 表示来源文件。

加载完原始文档后,不能直接把整篇文档塞给模型。如果文档很长,一方面可能超过模型上下文长度,另一方面,用户提问时只关心某个细节,整篇文档会引入大量无关信息,降低检索精度。

3.2 文本切块策略

文本切块是 RAG 系统里最容易被低估的环节。切块质量直接决定了检索命中率,而检索命中率又直接决定最终回答质量。

LangChainJS 中最常用的是 RecursiveCharacterTextSplitter。它会按照一组分隔符依次递归切分文本,优先保留语义相对完整的段落。

TS
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";
 
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 500,
chunkOverlap: 80,
separators: ["\n\n", "\n", "。", "!", "?", ";", " ", ""],
});
 
const chunks = await splitter.splitDocuments(docs);
console.log(chunks.length);

这里有两个参数需要重点关注:

  • chunkSize:每个 chunk 的目标字符数。500 是相对稳妥的初始值。太小容易丢失上下文,太大则会使向量包含太多噪声。
  • chunkOverlap:相邻 chunk 之间重叠的字符数。重叠的作用是防止切块时把一句完整语义从中间切断。比如一句话横跨两个 chunk,如果不设置 overlap,后半句在检索时可能因为缺少主语而无法被理解。

中文场景下,默认分隔符对中文的支持并不友好。英文按空格分词效果很好,但中文词与词之间没有空格。所以我建议把分隔符数组改成:

TS
separators: ["\n\n", "\n", "。", "!", "?", ";", " ", ""]

这样切分器会优先按段落、换行、句号、感叹号、问号、分号来切,减少“一句话被腰斩”的情况。

切块大小没有绝对标准。需要根据实际语料去测试。可以先从 300 到 500 开始,再通过后续的问答测试观察命中情况。后续如果引入父子切块、语义切块等进阶方案,效果会更好,但工程复杂度也会上升。

3.3 Embedding 与向量存储

Embedding 的作用是把文本变成一个固定维度的向量数组。语义相近的文本,在向量空间中的距离也更近。LangChainJS 中使用 OpenAIEmbeddings 来调用 OpenAI 的 embedding 模型,也可以替换成其他兼容模型。

TS
import { OpenAIEmbeddings } from "@langchain/openai";
 
const embeddings = new OpenAIEmbeddings({
model: "text-embedding-3-small",
apiKey: process.env.OPENAI_API_KEY,
});

Embedding 生成之后,需要存到向量数据库里。本地演示阶段可以使用 MemoryVectorStore

TS
import { MemoryVectorStore } from "langchain/vectorstores/memory";
 
const store = new MemoryVectorStore(embeddings);
await store.addDocuments(chunks);

MemoryVectorStore 是最简单的实现,所有数据保存在进程内存中,没有网络开销,适合学习和功能验证。但它有三个明显缺点:

  • 服务重启后数据全部丢失。
  • 不能跨实例共享。
  • 数据量大了以后,检索性能会下降。

所以生产环境一定要替换为持久化向量数据库。常见选择有:

  • pgvector:在 PostgreSQL 上加向量字段,适合已有 PostgreSQL 的团队。
  • Chroma:轻量级向量数据库,部署简单。
  • Milvus / Qdrant:更专业的向量数据库,适合海量数据和高并发场景。

替换起来并不困难。LangChainJS 对多种向量库做了统一抽象,核心的 addDocumentssimilaritySearch 方法签名基本一致。这也是引入 LangChainJS 的价值之一。

3.4 检索、增强、生成

检索时,系统把用户问题也转成向量,然后调用向量库的 similaritySearch 找到最接近的 k 个 chunk:

TS
const results = await store.similaritySearch(question, k);
const context = results.map((r) => r.pageContent).join("\n\n");

这里的 k 值需要根据场景调整。k 太小可能漏掉关键信息,k 太大又可能把无关内容塞进 Prompt。一般初始值建议 3 到 5。

得到候选片段后,需要一个 Prompt 模板把它们组织起来:

TEXT
 
基于NestJS与LangchainJS构建企业级RAG知识库实战指南
本文详解如何基于NestJS与LangchainJS搭建可工程化、可维护的企业级RAG知识库系统。重点涵盖RAG核心链路(文档加载、清洗、分块、向量化、检索、上下文组装)、NestJS在权限控制、队列调度、日志审计等工程能力的落地实践,以及LangchainJS对Loader、Splitter、Embeddings、VectorStore等组件的抽象组合机制。强调检索质量决定RAG效果上限,并提供逐层排错方法混合检索、重排序等关键优化策略。
南瑾i
304
Nestjs + Langchainjs 从零构建企业级 RAG 知识库问答系统
本文基于Nestjs后端框架与Langchainjs大模型编排工具,从零实现企业级RAG知识库问答系统。涵盖文档加载解析、智能文本切分、向量化存储(支持Chroma/PgVector)、检索增强生成链路设计,以及权限控制、异步任务队列、可观测性评估体系等工程化实践。重点解决私有知识注入、检索质量优化生产可交付问题。
香香甜甜圈
333
基于Nestjs+Langchainjs构建企业级RAG知识库系统
本文详解如何基于Nestjs与Langchainjs搭建企业级RAG知识库系统,涵盖文档解析、文本切分、向量化存储、混合检索及问答生成五大核心模块。重点阐述Nestjs在AI工程化中的模块化架构优势,Langchainjs对多模型/向量库的抽象封装能力,并对比Python方案。内容包含Chroma集成、异步处理、权限过滤、可观测性等企业级实践要点。
黄泓毅
218
基于NestjsLangchainjs搭建企业级RAG知识库全栈实践
本文详细阐述基于Nestjs后端框架与Langchainjs AI编排库构建企业级RAG知识库的全栈实现路径,涵盖文档加载切块、Embedding向量化、向量库集成(如pgvector)、检索增强生成链路设计、流式问答接口(SSE)、批量任务队列(BullMQ)及生产部署要点。强调工程化能力、模块化架构、可替换组件设计成本/安全边界控制,适用于私有知识问答、技术文档检索等场景。
谈国平
281
NestjsLangchainjs从零搭建企业级知识库RAG系统
本文详细阐述如何使用NestjsLangchainjs从零搭建可落地的企业级知识库RAG系统。重点涵盖工程架构设计(模块化、依赖注入)、文档处理链路(加载、切块、Embedding选型、向量入库)、RAG核心流程(查询向量化、相似度检索、Prompt组装、流式生成)、API封装(权限控制、状态机上传、SSE流式输出)及企业级能力(多租户隔离、增量同步、日志追踪、Docker部署)。强调数据链路稳定性、AI逻辑解耦生产环境适配。
阿丁的猫
299
Agent智能体(NestJS+TS+React+LangChain+LangGraph)- 全栈开发
本文详细记录基于NestJS+React+TypeScript的Monorepo架构下Agent智能体系统搭建过程,涵盖环境准备、pnpm工作区工程化、RBAC权限体系实现,以及LangChain/LangGraph集成、pgvector向量检索、Embedding模型(bge-m3)部署、RAG知识库与智能问数功能开发。技术栈聚焦AI应用层全栈协同,强调类型安全、流式SSE通信及SQL安全校验。
小菜猿_
222
nest+LangGraph学习路线
本博客介绍如何结合NestJS与LangGraph构建智能应用,涵盖TypeScript基础、NestJS后端架构、LangChain原理及LangGraph工作流编排技术,重点讲解节点、状态、工具调用持久化机制,并指导整合REST API、数据库生产部署。
光影少年
791