从零手搓RAG知识库问答!1套可运行JS代码,搞定LangChain检索增强生成

发布时间:2026/7/28 13:58:58
从零手搓RAG知识库问答!1套可运行JS代码,搞定LangChain检索增强生成 做AI知识库你是不是也踩这些坑想做私有文档问答直接丢文本给大模型模型记不住长文档上下文超限报错回答凭空编造出现大量事实幻觉关键词检索只能匹配字面语义相近内容完全搜不到试了网上零散教程要么代码残缺跑不通要么只讲理论没有完整Demo。今天这篇Node.js LangChain RAG完整实战一套代码直接复制运行手把手拆解向量库、文档、检索器、问答全链路学完你能独立搭建小型私有知识库问答系统。读完你能掌握RAG完整底层原理看懂向量嵌入检索增强流程LangChain核心组件Document/MemoryVectorStore/Retriever实战用法相似度打分、元数据过滤、自定义Prompt完整实现新手开发RAG必踩5个坑对应解决方案可直接复用的故事知识库问答Demo先搞懂RAG到底是什么RAG全称Retrieval Augmented Generation检索增强生成解决大模型知识滞后、幻觉两大核心痛点。完整执行链路知识库预处理文件/文本拆分成段落文档DocumentEmbedding向量化把文本转为高维数字向量存入向量数据库用户提问问题同步转为向量在向量库做相似度匹配检索召回取出Top-K最相关文档片段增强Prompt把检索到的上下文塞进提示词一起发给LLM模型生成模型只能基于给定文档回答大幅减少瞎编LangChain核心模块分工langchain/openai大模型对话、文本嵌入能力langchain/core/documents文档最小单元存储文本元数据langchain/classic/vectorstores/memory内存向量库本地测试首选Retriever检索器封装向量搜索自带去重、过滤、重排能力️环境准备1. 安装依赖npminstalldotenv langchain/openai langchain/core langchain/classic2. .env配置文件# 大模型配置 OPENAI_API_KEY你的API密钥 OPENAI_BASE_URL兼容OpenAI接口地址 MODEL_NAMEgpt-3.5-turbo # 嵌入模型 EMBEDDINGS_MODEL_NAMEtext-embedding-v2完整可运行实战代码故事知识库问答新建index.js全部代码复制即可运行importdotenv/configimport{ChatOpenAI,OpenAIEmbeddings}fromlangchain/openaiimport{MemoryVectorStore}fromlangchain/classic/vectorstores/memoryimport{Document}fromlangchain/core/documents// 1. 初始化对话大模型 temperature0关闭随机保证回答稳定constmodelnewChatOpenAI({temperature:0,model:process.env.MODEL_NAME,apiKey:process.env.OPENAI_API_KEY,configuration:{baseURL:process.env.OPENAI_BASE_URL,}})// 2. 初始化嵌入模型文本转向量核心constembeddingsnewOpenAIEmbeddings({apiKey:process.env.OPENAI_API_KEY,model:process.env.EMBEDDINGS_MODEL_NAME,configuration:{baseURL:process.env.OPENAI_BASE_URL,}})// 3. 构建知识库文档集合// Document结构pageContent待向量化文本metadata不参与向量计算的元数据用于过滤溯源constdocuments[newDocument({pageContent:光光是一个活泼开朗的小男孩他有一双明亮的大眼睛总是带着灿烂的笑容。光光最喜欢的事情就是和朋友们一起玩耍他特别擅长踢足球每次在球场上奔跑时就像一道阳光一样充满活力。,metadata:{chapter:1,character:光光,type:角色介绍,mood:活泼},}),newDocument({pageContent:东东是光光最好的朋友他是一个安静而聪明的男孩。东东喜欢读书和画画他的画总是充满了想象力。虽然性格不同但东东和光光从幼儿园就认识了他们一起度过了无数个快乐的时光。,metadata:{chapter:2,character:东东,type:角色介绍,mood:温馨},}),newDocument({pageContent:有一天学校要举办一场足球比赛光光非常兴奋他邀请东东一起参加。但是东东从来没有踢过足球他担心自己会拖累光光。光光看出了东东的担忧他拍着东东的肩膀说没关系我们一起练习我相信你一定能行的,metadata:{chapter:3,character:光光和东东,type:友情情节,mood:鼓励},}),newDocument({pageContent:接下来的日子里光光每天放学后都会教东东踢足球。光光耐心地教东东如何控球、传球和射门而东东虽然一开始总是踢不好但他从不放弃。东东也用自己的方式回报光光他画了一幅画送给光光画上是两个小男孩在球场上一起踢球的场景。,metadata:{chapter:4,character:光光和东东,type:友情情节,mood:互助},}),newDocument({pageContent:比赛那天终于到了光光和东东一起站在球场上。虽然东东的技术还不够熟练但他非常努力而且他用自己的观察力帮助光光找到了对手的弱点。在关键时刻东东传出了一个漂亮的球光光接球后射门得分他们赢得了比赛更重要的是他们的友谊变得更加深厚了。,metadata:{chapter:5,character:光光和东东,type:高潮转折,mood:激动},}),newDocument({pageContent:从那以后光光和东东成为了学校里最要好的朋友。光光教东东运动东东教光光画画他们互相学习共同成长。每当有人问起他们的友谊他们总是笑着说真正的朋友就是互相帮助一起变得更好的人,metadata:{chapter:6,character:光光和东东,type:结局,mood:欢乐},}),newDocument({pageContent:多年后光光成为了一名职业足球运动员而东东成为了一名优秀的插画师。虽然他们走上了不同的道路但他们的友谊从未改变。东东为光光设计了球衣上的图案光光在每场比赛后都会给东东打电话分享喜悦。他们证明了真正的友情可以跨越时间和距离永远闪闪发光。,metadata:{chapter:7,character:光光和东东,type:尾声,mood:温馨},}),];// 4. 文档批量向量化存入内存向量库constvectorStoreawaitMemoryVectorStore.fromDocuments(documents,embeddings)// 5. 生成检索器k3代表召回相似度最高的3条文档constretrievervectorStore.asRetriever({k:3})// 测试提问constquestion东东和光是怎么成为好朋友的console.log(.repeat(80));console.log(用户问题${question});console.log(.repeat(80));// 方式1检索器获取文档内置去重、rerank重排序constdocsawaitretriever.invoke(question);// 方式2原生向量检索附带相似度分数余弦距离数值越小越相似constscoreResultsawaitvectorStore.similaritySearchWithScore(question,3);// 打印检索结果相似度docs.forEach((doc,i){constscoreResultscoreResults.find(([scoredDoc]){scoredDoc.pageContentdoc.pageContent})constscorescoreResult?scoreResult[1]:null;constsimilarityscore!null?(1-score).toFixed(4):N/Aconsole.log(\n[文档${i1}] 相似度:${similarity}| 原始向量分:${score});console.log(片段内容:${doc.pageContent.substring(0,60)}...);console.log(元数据章节${doc.metadata.chapter}类型${doc.metadata.type});})// 6. 拼接检索上下文自定义约束Promptconstcontextdocs.map((doc,i)[片段${i}]\n${doc.pageContent}).join(\n\n-----\n\n);constprompt你是一个讲友情故事的老师。严格基于下方故事片段回答问题语言温暖生动。 如果片段里没有对应信息统一回复这个故事里还没有提到这个细节禁止编造内容。 故事片段:${context}问题${question}老师的回答:;// 7. 调用大模型生成答案constresponseawaitmodel.invoke(prompt);console.log(\nAI最终回答)console.log(response.content);代码核心逻辑拆解1. Document文档对象RAG最小存储单元分为两部分pageContent核心文本唯一会参与Embedding向量化的字段metadata自定义元数据不做向量计算用于过滤、溯源、分类示例区分章节、角色、内容类型后续可实现按章节检索2. MemoryVectorStore内存向量库优点零额外服务、开箱即用、适合本地Demo/小型测试缺点程序重启向量全部丢失生产环境替换PGVector/Milvus/Chroma3. 两种检索方式区别retriever.invoke(question)封装高阶能力向量检索自动去重过滤结果重排rerank业务开发首选vectorStore.similaritySearchWithScore(question, k)底层原生向量查询仅返回文档余弦距离分数用于调试相似度效果相似度计算公式1 - 向量距离数值越接近1文本语义越匹配4. 增强Prompt设计关键点强制约束模型只能基于检索片段回答从根源抑制幻觉无相关内容时固定话术避免模型自由发挥编造信息统一文风约束控制输出格式⚠️新手做LangChain RAG必踩5大坑坑1metadata也参与向量化很多新手把链接、章节、标签写进pageContent导致向量语义混乱。解决方案业务属性全部放进metadata仅正文存入pageContent。坑2temperature设置过高temperature0会让模型自由发挥容易脱离检索文本编造内容。解决方案知识库问答固定temperature:0关闭随机性。坑3混淆retriever和原生similaritySearch直接用similaritySearchWithScore做业务检索缺少去重、重排序召回脏数据。解决方案业务逻辑统一使用retriever.invoke()打分仅用于调试。坑4文档不分块直接全量入库长篇小说、上万字文档直接存入Document向量语义混杂检索精度暴跌。解决方案生产环境搭配文本分割器RecursiveCharacterTextSplitter拆分长文本。坑5忽略LangChain版本迭代框架更新速度极快导入路径、类名频繁变动网上旧教程直接报错。解决方案优先参考官方最新文档固定依赖版本避免兼容问题。RAG适用场景选型建议适合用这套内存RAG本地小型知识库Demo、内部测试工具文档总量百条以内不需要持久化向量快速验证私有文档问答需求不适合建议换持久向量库线上生产服务、十万级以上文档需要程序重启保留向量数据高并发检索、要求毫秒级查询速度✅全文总结RAG核心逻辑文档向量化存储→语义检索→上下文注入Prompt→模型精准回答解决大模型幻觉问题LangChain标准化组件大幅降低AI应用开发成本Document、向量库、检索器可自由替换MemoryVectorStore是入门最佳选择一套JS代码即可跑通完整问答流程开发重点规范Document结构、关闭模型随机、优先使用Retriever高阶检索、严格约束Prompt减少编造