尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

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

从零手搓RAG知识库问答!1套可运行JS代码,搞定LangChain检索增强生成
📅 发布时间:2026/7/28 14:11:45

😩做AI知识库你是不是也踩这些坑

想做私有文档问答,直接丢文本给大模型:

  • 模型记不住长文档,上下文超限报错
  • 回答凭空编造,出现大量事实幻觉
  • 关键词检索只能匹配字面,语义相近内容完全搜不到

试了网上零散教程,要么代码残缺跑不通,要么只讲理论没有完整Demo。

今天这篇Node.js LangChain RAG完整实战,一套代码直接复制运行,手把手拆解向量库、文档、检索器、问答全链路,学完你能独立搭建小型私有知识库问答系统。

读完你能掌握:

  1. RAG完整底层原理,看懂向量嵌入+检索增强流程
  2. LangChain核心组件Document/MemoryVectorStore/Retriever实战用法
  3. 相似度打分、元数据过滤、自定义Prompt完整实现
  4. 新手开发RAG必踩5个坑+对应解决方案
  5. 可直接复用的故事知识库问答Demo

📌先搞懂:RAG到底是什么

RAG全称Retrieval Augmented Generation(检索增强生成),解决大模型知识滞后、幻觉两大核心痛点。

完整执行链路:

  1. 知识库预处理:文件/文本拆分成段落文档Document
  2. Embedding向量化:把文本转为高维数字向量,存入向量数据库
  3. 用户提问:问题同步转为向量,在向量库做相似度匹配
  4. 检索召回:取出Top-K最相关文档片段
  5. 增强Prompt:把检索到的上下文塞进提示词一起发给LLM
  6. 模型生成:模型只能基于给定文档回答,大幅减少瞎编

LangChain核心模块分工

  • @langchain/openai:大模型对话、文本嵌入能力
  • @langchain/core/documents:文档最小单元,存储文本+元数据
  • @langchain/classic/vectorstores/memory:内存向量库,本地测试首选
  • Retriever检索器:封装向量搜索,自带去重、过滤、重排能力

🛠️环境准备

1. 安装依赖

npminstalldotenv @langchain/openai @langchain/core @langchain/classic

2. .env配置文件

# 大模型配置 OPENAI_API_KEY=你的API密钥 OPENAI_BASE_URL=兼容OpenAI接口地址 MODEL_NAME=gpt-3.5-turbo # 嵌入模型 EMBEDDINGS_MODEL_NAME=text-embedding-v2

🔥完整可运行实战代码(故事知识库问答)

新建index.js,全部代码复制即可运行:

import'dotenv/config'import{ChatOpenAI,OpenAIEmbeddings}from'@langchain/openai'import{MemoryVectorStore}from'@langchain/classic/vectorstores/memory'import{Document}from'@langchain/core/documents'// 1. 初始化对话大模型 temperature=0关闭随机,保证回答稳定constmodel=newChatOpenAI({temperature:0,model:process.env.MODEL_NAME,apiKey:process.env.OPENAI_API_KEY,configuration:{baseURL:process.env.OPENAI_BASE_URL,}})// 2. 初始化嵌入模型,文本转向量核心constembeddings=newOpenAIEmbeddings({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. 文档批量向量化,存入内存向量库constvectorStore=awaitMemoryVectorStore.fromDocuments(documents,embeddings)// 5. 生成检索器,k=3代表召回相似度最高的3条文档constretriever=vectorStore.asRetriever({k:3})// 测试提问constquestion="东东和光是怎么成为好朋友的?"console.log('='.repeat(80));console.log(`用户问题:${question}`);console.log('='.repeat(80));// 方式1:检索器获取文档(内置去重、rerank重排序)constdocs=awaitretriever.invoke(question);// 方式2:原生向量检索,附带相似度分数(余弦距离,数值越小越相似)constscoreResults=awaitvectorStore.similaritySearchWithScore(question,3);// 打印检索结果+相似度docs.forEach((doc,i)=>{constscoreResult=scoreResults.find(([scoredDoc])=>{scoredDoc.pageContent===doc.pageContent})constscore=scoreResult?scoreResult[1]:null;constsimilarity=score!=null?(1-score).toFixed(4):"N/A"console.log(`\n[文档${i+1}] 相似度:${similarity}| 原始向量分:${score}`);console.log(`片段内容:${doc.pageContent.substring(0,60)}...`);console.log(`元数据:章节${doc.metadata.chapter},类型${doc.metadata.type}`);})// 6. 拼接检索上下文,自定义约束Promptconstcontext=docs.map((doc,i)=>`[片段${i}]\n${doc.pageContent}`).join("\n\n-----\n\n");constprompt=`你是一个讲友情故事的老师。严格基于下方故事片段回答问题,语言温暖生动。 如果片段里没有对应信息,统一回复:"这个故事里还没有提到这个细节",禁止编造内容。 故事片段:${context}问题:${question}老师的回答:`;// 7. 调用大模型生成答案constresponse=awaitmodel.invoke(prompt);console.log("\n=====AI最终回答=====")console.log(response.content);

📖代码核心逻辑拆解

1. Document文档对象

RAG最小存储单元,分为两部分:

  • pageContent:核心文本,唯一会参与Embedding向量化的字段
  • metadata:自定义元数据,不做向量计算,用于过滤、溯源、分类
    示例:区分章节、角色、内容类型,后续可实现按章节检索

2. MemoryVectorStore内存向量库

  • 优点:零额外服务、开箱即用、适合本地Demo/小型测试
  • 缺点:程序重启向量全部丢失,生产环境替换PGVector/Milvus/Chroma

3. 两种检索方式区别

  1. retriever.invoke(question)
    封装高阶能力:向量检索+自动去重+过滤+结果重排rerank,业务开发首选
  2. vectorStore.similaritySearchWithScore(question, k)
    底层原生向量查询,仅返回文档+余弦距离分数,用于调试相似度效果

相似度计算公式:1 - 向量距离,数值越接近1,文本语义越匹配

4. 增强Prompt设计关键点

  • 强制约束模型只能基于检索片段回答,从根源抑制幻觉
  • 无相关内容时固定话术,避免模型自由发挥编造信息
  • 统一文风约束,控制输出格式

⚠️新手做LangChain RAG必踩5大坑

坑1:metadata也参与向量化

很多新手把链接、章节、标签写进pageContent,导致向量语义混乱。
解决方案:业务属性全部放进metadata,仅正文存入pageContent。

坑2:temperature设置过高

temperature>0会让模型自由发挥,容易脱离检索文本编造内容。
解决方案:知识库问答固定temperature:0,关闭随机性。

坑3:混淆retriever和原生similaritySearch

直接用similaritySearchWithScore做业务检索,缺少去重、重排序,召回脏数据。
解决方案:业务逻辑统一使用retriever.invoke(),打分仅用于调试。

坑4:文档不分块直接全量入库

长篇小说、上万字文档直接存入Document,向量语义混杂,检索精度暴跌。
解决方案:生产环境搭配文本分割器RecursiveCharacterTextSplitter拆分长文本。

坑5:忽略LangChain版本迭代

框架更新速度极快,导入路径、类名频繁变动,网上旧教程直接报错。
解决方案:优先参考官方最新文档,固定依赖版本避免兼容问题。

📊RAG适用场景&选型建议

适合用这套内存RAG

  • 本地小型知识库Demo、内部测试工具
  • 文档总量百条以内,不需要持久化向量
  • 快速验证私有文档问答需求

不适合,建议换持久向量库

  • 线上生产服务、十万级以上文档
  • 需要程序重启保留向量数据
  • 高并发检索、要求毫秒级查询速度

✅全文总结

  1. RAG核心逻辑:文档向量化存储→语义检索→上下文注入Prompt→模型精准回答,解决大模型幻觉问题
  2. LangChain标准化组件大幅降低AI应用开发成本,Document、向量库、检索器可自由替换
  3. MemoryVectorStore是入门最佳选择,一套JS代码即可跑通完整问答流程
  4. 开发重点:规范Document结构、关闭模型随机、优先使用Retriever高阶检索、严格约束Prompt减少编造

相关新闻

  • TorchScript vs ONNX vs TFLite 对比:三大模型序列化格式的互操作性与部署生态分析
  • bugku——这是一张单纯的图片
  • static_cast、dynamic_cast、const_cast和reinterpret_cast总结

最新新闻

  • 抓包工具Fiddler的使用(二)
  • 【QA那些事儿】视频SDK测试方法-场景自动化
  • AI Agent工作流实战:从概念到工程落地的完整指南
  • 蚌埠宝妈群验证过的4家靠谱儿童摄影门店清单,可放心加入备选 - 商业快讯早知道
  • 项目文档:基于MATLAB时间卷积网络的心电房颤智能分类系统
  • Twitch Drops Miner完整指南:告别手动观看,智能获取游戏奖励

日新闻

  • 力旷智能:伺服驱动系统在制药收瓶设备中的应用解析
  • 2026 网安入门避坑指南,零基础如何避开无效学习直接上手实战
  • 揭秘CFC项目:如何通过手机摄像头实现850kbps无网络文件传输

周新闻

  • 大连理工大学与东京大学联手打造的“主动型AI助手“
  • 170.2026年国家级科研瓶颈:超精密单点金刚石切削(SPDT)光学表面生成
  • SongBloom:革命性歌曲生成框架深度解析——如何通过交织自回归与扩散模型创作完整音乐

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号