资讯动态

RAG实战指南:用LangChain构建可靠的知识库问答系统

发布时间:2026/10/22 5:07:09 来源:尧图企业网站定制
调通大模型的API之后我遇到的第一个问题是它回答问题时根本分不清自己知道的事和自己编的事。你问它公开的常识它能讲得头头是道你让它回答某个内部项目的细节它会把完全不相干的东西说得像真的一样。这种一本正经地胡编就是RAG要解决的核心问题。RAG架构的出现把知识从模型的参数里挪了出来放到一个可以随时查证的数据库里让模型从凭记忆答题变成先翻书、再答题。这篇文章聊的是基于LangChain做RAG开发这件事。我会把RAG拆成文档处理、向量化、检索、生成这几段结合自己踩过的坑讲清楚每一段里最关键的选择是什么、为什么这么选、实际操作中应该注意什么。适合正在做知识库问答、私有文档问答、客服辅助这些场景的朋友参考也适合刚接触LangChain、想搞清楚RAG完整链路的开发者。1. RAG为什么成了落地标配从幻觉问题聊起1.1 大模型的知识不是你想的那样先说一个经常被人忽略的事实大模型内部其实没有一张数据库表。它的参数里存的是词与词之间的概率关系它输出答案的过程本质上是沿着这个概率场一步步生成文字。所以它知道的东西是训练语料里的高频规律不是像人查字典那样精确存储的事实。这个特性带来三个明显的局限。第一训练语料有截止时间。模型不知道训练之后发生的新事件、新政策、新版本功能你问它三个月前刚发布的某个参数配置它只能给你一个看起来合理的猜测。第二长尾知识覆盖不足。公开语料里被反复讨论的问题它答得很好一旦变成某个行业内部的术语体系、某家机构特有的制度流程它的回答就开始含糊不清了。第三也是RAG最核心的痛点——模型在不确定的时候不会坦率地说我不知道它会用最流畅的语言编一个最自洽的答案。这就是幻觉。我习惯用一个类比来解释这件事大模型像一个记忆力模糊的通才老员工你问它行业常识它讲得头头是道但你问它公司某份合同里的付款条款它会把你上一个项目的付款条款张冠李戴过来。它不是想骗你而是它的记忆方式就是这样——模糊、关联、概括而不是精确、逐字、可检索。在LangChain出现之前这种问题只能靠提示词去警告模型不要瞎编。但提示词只是语气上的约束模型该编还是编。RAG的思路完全不一样它不试图让模型记得更准而是直接让每次回答都能先查资料、再作答把回答建立在可验证的证据之上。1.2 为什么偏偏是RAG而不是微调或卷长上下文很多人第一次想到的解决方案不是RAG而是微调。这个选择本身没有错但要看你到底要解决什么问题。微调擅长的是改变模型的行为模式和表达风格让模型更像某个角色、更贴合某种文风。但如果你想用微调来塞进新知识那你就得准备大量的高质量问答对训练完了还得担心模型过拟合和遗忘旧知识。更要命的是知识是动态的——今天微调进去的文档下个月更新了一版你又得重新训练。这在知识库场景里成本高得离谱。长上下文是另一个思路既然模型窗口越来越大那把整份文档直接塞进prompt里不就行了这个思路在小文档、单次问答的场景里确实有效但一旦文档总量达到几十份甚至几百份token成本会迅速失控而且模型对过长上下文的注意力会被稀释真正关键的那一段反而容易被淹没。窗口再大天花板也是有限的。RAG的优势恰恰在这两个方向之间找到了平衡。它的本质是把知识这个变量从模型内部挪到模型外部——用一个外挂的向量数据库保存文档片段每次问答先检索相关内容再把检索结果拼进提示词交给模型作答。更新知识就是更新数据库里的文档不需要碰模型本身控制幻觉就是让模型的回答必须基于当前检索到的证据而不是基于它自己的模糊记忆。下面这张表是我在做技术选型时给自己看的对比应该能帮你更直观地理解这三条路的分工。方案知识更新成本幻觉控制力度实现复杂度适用场景RAG低换文档即可强有证据可追溯中链路较多私有知识问答、动态文档库、垂直领域助手微调高每次都要重训弱模型仍可能泛化高需要数据工程风格迁移、角色人格、固定输出格式长上下文中但受token限制中依赖模型注意力低直接塞上下文单文档深度分析、小体量资料问答从我实际做过的一些项目来看垂直领域的知识问答多半都适合RAG。法律条文问答、产品手册客服、内部制度查询、学术论文综述这些场景的共同点是文档是确定的、权威的问题答案都在文档里用户要求回答有出处。RAG几乎是为此而生的架构。1.3 LangChain在RAG里扮演什么角色聊到这里你已经知道RAG大致是哪几个环节了加载文档、切分、向量化、存储、检索、生成。但知道环节和能跑起来之间还差一层编排的功夫。LangChain在这里扮演的就是编排者的角色。它不是一个模型也不是一个向量数据库而是把一堆组件按顺序组装成一条流水线。文档加载有专门的Loader切分有专门的TextSplitter向量化有Embeddings的抽象接口存储有VectorStore的统一封装检索有Retriever生成又回到LLM调用。每一段都可以替换每一段都有社区维护好的实现。我刚开始用LangChain时对这种组件化设计没有直观感受只觉得代码啰嗦。后来我接手了一个项目文档从PDF换成了网页抓取向量库从本地文件换成了生产环境的分布式服务检索方式从单纯向量召回换成了向量加全文检索的混合召回——每换一次我都只需要改对应位置的组件整条链路的代码骨架几乎没有变。这个时候我才明白RAG不是一个固定算法而是一个变化的流程流程里的每个环节都会随着场景调整。LangChain的价值就是让这种调整变成换零件而不是重造一辆车。2. 地基没打好后面全白干文档清洗与切分策略2.1 文档加载远比你想象的复杂很多教程会把文档加载一笔带过好像PDF读进来就是文本。这个认知到了真实项目里会害死你。先说PDF。PDF是一个为打印设计的格式它内部存的是哪段文字放在哪个坐标而不是先读这段再读那段的语义流。所以天真地解析一份多栏排版、带页眉页脚的PDF你得到的结果往往是错乱的第一栏没读完就跳去了页眉标题被漏掉表格里的数字挤成了一坨。扫描件更麻烦本质上是一张图片必须先过OCR而OCR又会有识别错误。我的经验是如果源文档能导出成Markdown或Word优先用结构化文本格式。如果只能拿PDF先看清楚是文本型还是扫描型再决定用解析库还是OCR服务。HTML页面则要先把导航、广告、版权声明这些页面的衣服脱掉只保留正文内容。另一个容易被忽视的问题是文档分类意识。你的知识库可能同时包含操作手册、制度文件、FAQ、合同、会议纪要它们的结构完全不同。操作手册适合按章节切分FAQ本身就是一个问答对合同要保留条款编号。一股脑全用同一个解析流程处理结果就是检索时语义混乱。我建议在加载阶段就把文档按类型分好类不同类型走不同的预处理模板。2.2 切分粒度chunk_size和overlap怎么配加载进来之后下一步是把长文档切成小块。为什么要切因为embedding模型有输入长度上限也因为向量检索需要一个语义单元——你拿一小段文本去和用户的问题算相似度粒度太粗整篇文章的向量会稀释掉关键句子的信号。LangChain里最常用的切分器是RecursiveCharacterTextSplitter。它的逻辑是递归地按分隔符优先级去切先尝试按双换行切切出来还是太长就降级按单换行切再不行按句号切最后按字符数硬切。这个设计的用意是尽可能保住语义边界不在一句话的中间粗暴截断。参数上chunk_size和chunk_overlap是最重要的两个。chunk_size是每个片段的字符数上限chunk_overlap是相邻片段之间保留的重叠字符。为什么要重叠因为一个话题可能正好跨在两个片段的边界上如果完全不相交检索的时候就会只命中其中一半另一半的关键信息丢了。我的实践建议是中文场景下chunk_size从300到500字起步chunk_overlap取10%到20%。英文场景可以适当放宽到500到800个token。这个区间不是凭空来的——切太碎每个片段的信息密度不够模型给出的答案缺乏上下文切太大向量包含太多无关信息相似度计算被稀释。你可以在自己的文档上跑几组对照看检索命中率再微调。还有一个更稳妥的切分思路如果你能拿到文档的标题层级优先按标题结构切。每个二级标题下的内容作为一个大块块内再按段落切成小段这样切出来的每个片段都有明确的主题归属语义完整性远超纯字符切分。类似地很多项目会在切分时把章节标题写入metadata让检索结果天然带出处。实操中我踩过最典型的坑是合同类文档的交叉条款问题。合同里经常会出现违约责任详见第九条这种表述单看一个片段根本不知道它在说什么。overlap设太小前后两个片段各缺一半设太大边界反而模糊。后来我改成按条款子条款的结构切分并在每个片段里保留父条款的编号和标题这个问题才算解决了。2.3 metadata被忽视的索引卡片切分之后每个片段就变成了一个独立的检索单元。如果只有文本内容那就像仓库里的货物没有标签——你能找到它但不知道它是什么、从哪里来、保质期到什么时候。这就是metadata存在的意义。metadata是附着在片段上的键值对常见的有来源文件名、页码、章节标题、文档类型、更新时间、作者等信息。它的第一个作用是溯源。RAG系统给用户的答案如果带引用那么引用内容就是从这些metadata里读出来的。没有metadata你连这段内容来自哪份文件都说不清产品层面根本过不了关。第二个作用是过滤。实际应用里知识库往往不是一堆平铺的文档而是有时间、有部门、有类型的。用户在检索时可能会限定只看2024年之后的制度文件或者只看这个产品线的FAQ。如果你在写入向量库时把时间、类型、产品线都写进了metadata检索时就能通过filter参数做结构化筛选。这比单纯靠语义排序靠谱得多因为它是一个精确条件不是概率匹配。我见过一个项目开发阶段没写metadatademo演示效果很好一上线用户问这句话出自哪份文件就答不上来整个项目的可信度直接崩了。后来补metadata还要把原本已经向量化的数据全部重灌一遍工作量翻倍。所以我现在的习惯是从第一步就把来源、页码、标题这些写全哪怕当下用不到也别等要用的时候再补。3. 向量化与存储选型Embedding模型和向量库怎么挑3.1 Embedding模型选型决定你检索效果的上限Embedding模型把一段文本映射成一个高维向量让语义相近的文本在向量空间里距离更近。检索质量的上限就是由这个映射的质量决定的。如果你的embedding模型不理解你的领域术语那后面做再多的检索优化都是在坏数据上做雕花。选型时我先看三件事。一是语言支持。中文场景和英文场景的embedding空间差异很大直接用面向英文优化的模型处理中文效果通常不如专门训练的中文模型。开源的中文向量模型我试过好些BGE这类的国产开源模型在中文语义理解上的表现就很好M3E这类也常见于社区项目。英文场景选择面更广各主流模型开箱即用。二是最大输入长度。embedding模型的输入长度有上限通常几百到几千token不等。如果你的chunk_size超过了模型上限要么截断要么分段再聚合这会影响语义完整性。所以chunk_size的设计和embedding模型的输入长度是联动的不是拍脑袋定的。三是领域适配。通用模型在通用文本上表现均衡但到了法律、医疗、金融这些术语密集的领域通用模型的区分度会明显下降。预算允许的话可以在自己的文档集上做一个小批量对比测试用几十条真实问题分别检索看哪个模型的命中率更稳定。不用跑复杂的指标肉眼看top5的结果就能看出差距。一个容易被低估的经验是换一个更好的embedding模型往往比调半天提示词收益更明显。我在一个中文技术文档库项目里把embedding从通用模型换成领域微调过的开源模型同一套检索逻辑召回命中率提升了将近两成。所以如果项目检索效果不理想第一步不是优化流程而是先怀疑embedding模型。3.2 向量库选型从Demo到生产的路径向量数据库负责存储向量并提供相似度检索。选型这件事最大的误区是一上来就想选最强大的方案结果开发成本全耗在了运维上。原型阶段我最常用的是Chroma和FAISS这类轻量方案。它们可以直接在本地跑数据存在本地文件里零运维成本非常适合验证RAG流程和调试检索逻辑。Chroma还能直接在返回结果里带上metadata做原型验证很顺手。一旦进入生产阶段就要考虑并发、数据规模、权限过滤这些因素了。这时候再看Milvus、Qdrant、pgvector这些方案。它们各有所长pgvector适合你的系统已经用了PostgreSQL想顺便把向量也放进去Qdrant在过滤能力和部署友好度上做得不错Milvus更适合大规模集群场景。还有一个常见选项是直接用Elasticsearch的向量检索能力如果团队本来就有ES可以少维护一套系统。在LangChain里VectorStore的接口是统一的。你换了向量库主体代码基本不用动只需要改实例化的那一行。这也是我强调先跑通原型再迁移的原因——RAG的流程框架和底层存储是解耦的没必要一开始就被存储方案的运维复杂度拖住。向量库部署复杂度并发扩展性过滤能力适合阶段Chroma低本地文件弱一般原型验证FAISS低本地文件弱弱实验探索pgvector中依赖现有库中强中小规模生产Qdrant中强强生产环境Milvus高集群部署很强强大规模生产索引类型上HNSW是目前最常见的向量索引适合大多数场景。距离度量一般选余弦相似度它对向量的模长不敏感更符合语义比较直觉。这些细节现在不用深究但生产环境里它们会直接影响检索性能和精度值得在迁移时仔细看一遍文档。3.3 向量检索效果不好怎么排查很多人遇到检索结果不对第一反应是换向量库或者换embedding其实大部分问题出在链路的前半段。我先说最常见的三类症状和它们的排查方向。第一类召回结果语义相近但答非所问。比如用户问如何开通权限检索回来的片段都在讲权限管理但就是没有开通步骤。这种多半是chunk粒度问题——步骤分散在长段落里向量被压缩了。你需要把切分调细或者按操作类文档的结构重新切分。第二类精确ID、型号、订单号查不到。向量检索擅长语义匹配不擅长精确匹配。型号ABC-123这个字符串如果你的文档里写的是ABC123或者ABC 123向量距离就远了。这不是bug是向量检索的天性。解决办法是引入全文检索或关键词检索做混合召回后面专门讲。第三类冷门专有名词召回为空。如果embedding模型在训练时很少见过这个术语它映射出来的向量质量就很差。排查方法是先单独拿这个术语去向量库搜一下如果结果为空可以考虑加同义词改写或者用HyDE之类的思路换一种表达方式去检索。我的排查顺序是固定的先看召回top5结果确认问题出在没召回还是排序不对再判断是chunk切分的问题还是embedding的问题最后再看query的表达和文档用词是否一致。每一步都有对应的验证手段不要直接凭感觉改参数。改完参数之后通过离线评测去验证而不是人工试几个例子就下结论。4. 检索链路才是重头戏召回、重排、查询改写一个都不能少4.1 纯向量检索的盲区混合检索为什么是主流纯向量检索看起来很美好语义相近就能命中。但真实场景里用户不总是用语义提问——他们会问你订单号会问你和文档里字面不同但意思相同的问题甚至会把问题写得口语化到连自己都认不出。那时候你就会发现纯向量检索有两个绕不开的盲区。第一个盲区是精确匹配。向量检索是模糊的它在高维空间里找最近邻而不是在字符串里找完全相等。订单号、型号、合同编号这类文本容不得一点模糊向量距离一偏就召回错了。第二个盲区是专有名词的同义改写。文档里写的可能是售后服务工单用户问的是报修申请语义相近但向量模型对这两个词的关系理解不够好时召回质量就会掉。解决办法是混合检索向量召回加全文检索。向量负责懂语义全文检索负责抠字眼。LangChain里可以用EnsembleRetriever把两者合并向量检索器负责语义召回BM25检索器负责词面匹配最后按加权分合并排序。weights参数是两路召回的权重系数。通常从0.5对0.5起步然后根据自己的测试集去调。如果一个领域里用户提问普遍很口语化、偏离书面用词就把向量权重调高如果文档里充满了精确编号和固定名称就把BM25权重调高。这不是玄学是靠离线评测数据说话的。4.2 查询改写让用户的人话变成检索语言检索的效果不仅取决于检索器还取决于你拿什么去检索。用户真实会问的问题和文档里的表述方式经常差得很远。比如用户问上次那个会议纪要有结论吗而文档里写的是会议决定将于下季度启动项目X。这两个表达在向量空间里不一定能完美对齐。你直接把原始问题拿去检索可能什么都召不回。解决方案是在检索之前加一步查询改写让LLM先把用户的自然提问转换成更适合检索的表达。这里有两种做法可以参考。第一种是多query生成。让LLM基于原始问题生成多个不同角度、不同表述的检索query分别去检索再把结果合并去重。比如上次那个会议纪要有结论吗可以改写为会议决定的结论会议纪要中的决定事项下季度启动项目X的决策依据。多个query从不同方向逼近命中率会显著提升。第二种是HyDE假设性文档嵌入。思路是让LLM先根据用户问题假设性地写一段可能正确的答案然后用这段假设答案去做向量检索。为什么有效因为文档和假设答案都是书面化的完整陈述语言风格更接近向量空间的距离更近检索结果往往更准。多轮对话场景里还要加一步历史对话压缩。用户问那它的截止日期是什么时候这个它指的是上一轮提到的某个项目。你需要把对话历史喂给LLM让它把指代词还原成完整表述比如项目X的交付截止日期是什么时候再拿这个完整的query去检索。否则永远检索不对。在LangChain里查询改写通常放在检索器之前用RunnableLambda或者链式调用包一层逻辑不复杂但对效果的影响非常直接。4.3 Rerank最后一公里的排序优化召回阶段模型返回50到100条候选文档但最终能拼进prompt的通常只有5到8条。怎么从这100条里选出最相关的就是重排要解决的问题。初召回阶段用的是双编码器架构query和文档分别编码成向量算余弦相似度。它快但精度有限因为query和文档是独立编码的细粒度的交互信息丢了。重排阶段用的是交叉编码器把query和每一条候选文档拼在一起作为整体输入模型输出一个相关度分数。模型能看到query中的每个词与文档中每个词的交互判断更精细但速度慢得多。所以标准的RAG流程是两段式先用快速的双编码器召回百余条再用慢速的交叉编码器对这批候选精排只取前几条。LangChain里对应的是ContextualCompressionRetriever加CrossEncoderReranker的组合。重排的成本不低每多一条候选就多一次模型推理。工程上我一般控制在召回50条以内重排后取top5到top8再拼进prompt。这个数量和token成本、回答质量都能达到平衡。加了重排之后最直观的变化是top结果的准确率显著提升。有一次我在一个FAQ项目里做前后对比同样20条测试问题加了重排之后答案完全命中的比例从六成提高到了八成多。代价就是每次检索多几十毫秒的耗时在知识库问答场景里完全值得。4.4 检索质量可量化别让效果停留在感觉RAG项目改起来容易让人上头今天调一下chunk_size明天换个embedding后天加个重排。改完了你觉得好像效果好一点了但如果你说不出好在哪这个调优就是不可持续的。我的做法是给项目搭一个离线评测闭环。核心就是准备一组带标准答案的评测问题比如从真实用户日志里抽20到50条问题标注出正确的知识来源文档或片段。每一轮改动之后批量跑一遍检索用Recall5、MRR这些指标衡量效果。Recall5的意思是正确结果出现在前5条里的比例。MRR平均倒数排名则更严格它看的是正确结果排在第几名。这些指标不需要多复杂能告诉你这轮改动是变好了还是变坏了就够了。评测集一定要从真实用户问题里收集不要自己凭空编。自己编的问题往往和文档里的文字高度契合测不出真实水平。从日志里抽出来的问题才是用户真实会问的话它们往往口语化、不标准、带着上下文依赖测出来才有参考价值。迭代流程固定成三步改参数、跑评测、看bad case。看bad case尤其重要它告诉你到底哪一步出了问题——是没召回、召回错排、还是生成了但没引用对。RAG的优化不能靠感觉要靠这个闭环把每次改动变成可验证的增量。5. 生成层的最后一道防线提示词与答案质量把控5.1 提示词模板好模板让模型用证据说话检索做得再好最后一步还是要模型用自然语言组织答案。这一步的质量很大程度取决于你喂给模型的提示词。RAG的提示词模板核心不是教模型回答问题而是约束模型只能基于给定的上下文回答。一个实用的模板通常包含四部分角色的定义、检索到的上下文、用户的原始问题、以及严格的使用约束。常见的约束表达是严格按照给定的上下文作答如果上下文中没有相关信息请直接回答你不知道不要编造。这句话看起来简单但在控制幻觉上非常有效——它给了模型一个合法放弃作答的出口。没有这个出口模型宁可编一个看起来合理的答案也不愿承认自己不知道。引用格式也要在模板里约定好。比如要求模型在答案末尾标注来源的文档编号和页码形如来源产品手册第3章第12页。这个信息可以直接从片段的metadata里取放进上下文的时候顺便带上。这里给出一个我常用的LangChain模板结构你可以直接套用。from langchain.prompts import PromptTemplate prompt_template PromptTemplate( input_variables[context, question], template你是企业内部知识库助手。请仅根据下面的上下文回答问题。 如果上下文中没有相关信息请明确回答根据现有资料无法确认。 上下文 {context} 问题{question} 请给出简洁准确的回答并在结尾标注来源片段编号。 )实际使用的过程中不同任务的约束强度不一样。面向客户的问答系统我会严格约束模型只能引用上下文面向内部辅助的工具可以适当允许模型结合自身知识做一些分析但核心事实仍然以检索内容为准。不管哪种模板的边界一定要清晰给模型的自由度要显式控制。5.2 答案与上下文的一致性校验即使模板约束到位模型偶尔还是会忍不住想自由发挥。所以在正式交付的项目里我建议在生成答案之后再加一道校验而不是完全信任生成结果。校验的思路很朴素把答案和检索到的上下文一起交给模型问它这个答案的内容是否都能从上下文中找到依据哪句话是上下文里没有的如果有模型自己编的部分就截断或者重新生成。另一个更轻量的校验手段是引用有效性检测。模型在答案里标注了引用片段编号你可以在代码里检查这个编号是否真的存在于本次检索返回的候选集合里。如果模型引用了一个不存在的片段那基本可以断定它在编造来源。还有一类工程上常用的兜底策略是关键词覆盖度。抽取答案里的关键实体和上下文做比对计算覆盖比例。如果答案中大段的实体在上下文中完全找不到这通常意味着答案脱离了给定的资料。这个方法粗糙但胜在便宜适合做线上日志层面的异常检测。这套校验不一定每个项目都要做但to B交付、医疗法律等严肃场景强烈建议加上。用户选择RAG而不是纯大模型核心诉求就是可信。你要在生成这一层多花一点功夫把可信这件事做实。5.3 多轮对话与流式输出的产品化细节RAG一旦进入真实产品就躲不开多轮对话。而多轮对话给RAG带来的坑比单轮问答多得多。最大的坑是上下文指代。用户在第一轮问项目X的验收标准是什么第二轮问那它的截止日期呢如果你只拿那它的截止日期呢去检索向量库里什么都找不到。所以多轮RAG系统一定要做对话历史压缩在每一轮检索之前把历史会话和当前问题一起交给LLM生成一个含指代还原的完全问题再拿这个问题去检索。流式输出也是一个经常被提起的需求。LangChain里实现流式输出不难用链的stream方法就可以。但产品层面有一个细节值得注意引用信息怎么展示。比较好的做法是正文按流式逐字显示引用的文档标题列表等正文生成完再统一展示避免流式过程中引用信息闪烁跳动影响阅读体验。拿我参与过的某客服问答系统来说上线初期用户反馈最集中的并不是答案错而是我不信任这个答案。后来我们在答案下方把引用的文档片段完整展示出来——这段内容是从哪份文档的哪一节抓出来的用户可以点开核对。上线两周后用户对答复的接受率明显上了一个台阶。这说明RAG产品到了真实使用阶段可验证本身是有产品价值的不仅是一个技术功能。再提一个教训别把RAG做成搜索后把结果贴给模型这么简单。一个完整的RAG产品检索质量、引用规范、多轮管理、流式体验每一环都直接影响用户对系统的信任度。技术链路跑通只是第一步产品层面把这些细节补齐才是真正能用的系统。我在实际项目中体会最深的一件事是RAG项目的天花板不在模型而在数据质量和检索链路。文档切分不讲究后面所有环节都在凑合没有评测闭环所有的调优都是在原地打转。如果你准备做一个RAG项目我建议先把数据清洗和切分策略想清楚再搭一个哪怕只包含20条问题的评测集然后才开始调检索和生成。这顺序看着不起眼但它决定了你的项目是在稳步往上走还是在反复回到原点。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑