☰
基于LangChain的RAG知识库问答智能体落地实践
2026/10/26 12:13:19 网站建设 项目流程

做知识库问答这类项目,最容易被低估的不是模型,而是“检索”和“切块”这两件看起来最土的活。我前后折腾过几套基于 LangChain 的自有知识库 GPT 智能体,从最开始把一堆 PDF 直接丢进去问出满屏胡话,到后来能把答案连出处一起甩出来,中间踩的坑基本都集中在文档处理链路上。这篇就把整套东西拆开讲:它本质上是一个“检索增强生成”系统,用 LangChain 把文档加载、切分、向量化、检索、提示词拼装、工具调用、会话记忆这几段串成一条流水线,让大模型在回答时只看你给它的资料,而不是靠训练时的记忆瞎编。适合两类人看:一类是手上有内部文档、产品手册、历史工单,想让它们“能对话”的工程同学;另一类是想搞清楚 RAG 到底怎么落地、不想只停留在 demo 层面的技术负责人。下面的参数、代码、排查表都是我实际跑过的配置,可以直接抄,也可以按你的数据规模改。

1. 整体方案设计与技术选型思路

1.1 为什么先做检索增强,而不是一上来就微调

很多人第一反应是“我把文档喂给模型训练一下不就行了”。这个思路在小数据量、格式统一、问题类型固定的场景下确实可行,但放到自有知识库上,问题会立刻暴露:文档每周都在更新,微调一次的成本和周期根本跟不上;模型被“灌”进去的知识没法溯源,答错了你连是哪份文档害的都查不到;更要命的是,微调容易让模型对原本擅长的通用能力产生退化,出现“答什么都像在念手册”的情况。

检索增强生成(RAG)解决的是另一类问题:把“记住知识”和“组织语言”这两件事拆开。知识放在外部向量库里,随时可增删改;模型每次只负责读你递过去的那几段材料,然后组织成人话。这么做的好处是知识更新只涉及重新嵌入,不用碰模型权重;每段回答都能挂上来源文档,用户点开就能核对;出问题时排查路径非常清晰——要么是没检索到,要么是检索到了但提示词没用好,两者互不干扰。

我一般的判断标准是这样:知识更新频率高于每月一次、需要给出处、文档总量在几百到几十万段之间,优先 RAG;如果是固定领域的语言风格迁移(比如让模型学会某种话术),那才考虑微调。两者也不冲突,实际项目里常见的是 RAG 打底、微调调风格。

1.2 系统分层与每层的职责边界

整套东西我习惯拆成四层,每层出问题时的表现完全不同,拆清楚了好定位。

层级核心职责典型组件出问题时的表现
数据层文档解析、清洗、切块、元数据标注加载器、文本分割器答案残缺、跨段断裂、表格错乱
索引层向量化、存储、相似度检索嵌入模型、向量库、检索器答非所问、召回为空、重复内容
编排层提示词拼装、工具调用、多轮记忆链、智能体执行器、记忆组件上下文超限、忘记前文、乱调工具
应用层接口、鉴权、日志、评测服务框架、监控延迟高、成本失控、无法复盘

分层最大的价值在于“可替换”。嵌入模型换了、向量库换了,只要接口契约不变,上层代码一行不用动。LangChain 在这件事上的作用其实就是提供了一套统一的抽象:不管底层是哪种向量库、哪个模型供应商,上层都是同一套调用方式。这一点在早期选型阶段特别省事,你可以先用本地轻量方案跑通链路,再换成生产级组件。

1.3 组件选型的几个关键取舍

选型这块我踩过最贵的坑是“一步到位”。第一个项目直接上了分布式向量库加集群部署,结果数据量只有两万段,运维成本比开发成本还高。后来我的原则变成:按数据量分档,先跑通再扩容。

  • 一万段以内,本地文件型向量库完全够用,持久化到磁盘,服务重启直接加载,零运维。
  • 十万到百万段,考虑单机部署的向量数据库,支持增量写入和元数据过滤。
  • 百万段以上再考虑分布式方案,这时候才需要关心分片和副本。

嵌入模型的选择更影响最终效果。我的经验是:中文场景优先选在中文语料上训练过的嵌入模型,别迷信参数量。有些模型维度很高但中文语义对齐一般,检索出来的东西看着相似其实不相关。维度也不是越高越好,768 维和 1536 维在多数业务场景下差距远小于切块策略带来的差距。前面那句话很重要,值得单独说一遍:切块策略对召回质量的影响,通常大于换一个更强的嵌入模型。

2. 知识库构建:从原始文档到可检索向量

2.1 文档清洗:脏数据是万恶之源

我见过太多项目在这一步偷懒,直接把 PDF 丢给加载器,然后抱怨模型胡说。PDF 解析出来经常带着页眉页脚、页码、水印、断行、表格错位,这些噪声会被原样切进块里,既占 token 又干扰语义。一份 30 页的说明书,光页眉页脚就能贡献上千个无用字符。

实操上我会做这几件事:解析后先做一遍正则清洗,去掉重复出现的页码模式、公司落款、页眉;把连续换行压成单换行,把被 PDF 硬断开的句子接回去(判断依据是行尾没有标点且下一行首字母非大写或非数字编号);表格单独抽出来转成 Markdown 表格或“字段:值”的键值对文本,不要让它以空格对齐的方式混在正文里。

注意:清洗规则一定要写成可配置的、按文档类型分组的规则集,不要写成一把梭的全局正则。产品手册和会议纪要的噪声模式完全不同,混在一起改一处崩一片。

清洗完还要做一轮“有效性过滤”:纯目录页、纯版权页、字符数低于阈值的块直接丢弃。这一步能砍掉 5% 到 15% 的无效内容,对召回准确率的提升比调参明显。

2.2 切块策略与参数计算过程

切块是整套系统里最需要手工调的地方。切太大,一个块里混了三四个主题,检索命中后模型抓不住重点;切太小,一句话被腰斩,语义不完整,检索出来也答不上来。

我的默认起点是:块大小 500 个中文字符,重叠 50 个字符。为什么是这两个数,算一下就很清楚。假设一份文档正文 10 万字,按步长 450(500 减 50)滑动,块数约为 (100000 - 500) / 450 + 1 ≈ 222 块。检索时取 top 4,注入上下文约 2000 字,折算成 token 大概 1400 到 1600(中文按 1 字 ≈ 0.7 到 0.8 token 估),加上系统提示和用户问题,整轮请求控制在 2500 token 以内,对绝大多数模型都很宽裕。

重叠 50 的意义在于防止关键句正好被切在边界上。我做过对比:重叠 0 时,涉及“条件 + 结论”跨句表述的问题召回失败率明显偏高;重叠调到 80 到 100 后继续下降,但块数增加带来的存储和检索开销也开始显现。50 是个性价比不错的平衡点。

不同文档类型要区别对待:

  • 制度、条款类文档按标题层级切,先按“章—条”分段,再在段内按长度兜底切分,保证每条完整。
  • FAQ、工单类一条一问一答,天然一块,千万别再切。
  • 长篇小说式的叙述文档用固定长度滑动切,但要在块头补一句所属章节标题作为上下文。

LangChain 里的递归字符分割器支持按分隔符优先级递归下切,默认顺序是段落、换行、句号、逗号。中文场景我会把分隔符列表改成["\n\n", "\n", "。", "!", "?", ";", ",", ""],让它在句号处优先断开,避免把一句话从中间劈开。

2.3 元数据设计:被严重低估的一环

绝大多数教程只讲怎么切块,不讲元数据。但在真实项目里,元数据决定了你能不能做过滤、能不能给出处、能不能做权限隔离。

我至少会给每个块打这几类标签:来源文件名、章节路径、块序号、更新时间、密级或可见范围、文档类型。前四个用于溯源和展示,第五个用于权限过滤,第六个用于检索时按类型加权(比如问操作步骤时优先召回手册类而非会议纪要)。

权限过滤这件事必须提前设计。等到上线后才发现某份文档不该被某个角色看到,再回头改架构就难受了。做法很简单:检索时把用户可见范围作为过滤条件传给向量库,只在该子集内做相似度计算,而不是先检索再过滤——后者会让 top_k 变小、召回率骤降。

2.4 增量更新与重复内容治理

知识库不是一次性工程,文档会增删改。我的做法是给每份文档算一个内容指纹(对清洗后的正文做哈希),存进一张映射表。更新流程是:新文档进来先算指纹,指纹没变就跳过;变了就按来源文件名删掉旧的所有块,再插入新块。

提示:删除一定要按“来源 + 版本”精确删,不要按内容相似度删。按相似度删会误伤那些表述接近但实际不同的条款。

重复内容也是常见问题。同一份制度在三个目录下各放一份,检索时 top 4 里三块内容一样,白白浪费上下文预算。我在入库阶段做了近似去重:对块向量做两两相似度比较,超过阈值 0.97 的只保留一份,并在元数据里记下多个来源路径。这样既省空间,展示时还能告诉用户“该内容同时出现在 A、B、C 三处”。

3. 智能体核心链路实现

3.1 检索器配置:别只用最朴素的相似度

最朴素的相似度检索有个毛病:它倾向于召回“和问题长得像”的段落,而不是“能回答问题”的段落。问题里出现“怎么申请”,召回的全是含“申请”二字但讲的是别的流程的段落。

我一般叠加两个手段。第一个是MMR(最大边际相关性),它在相关性和多样性之间做权衡,能把内容重复的块压下去,让 top 4 覆盖更多角度。参数上把多样性系数设在 0.5 左右,太高会牺牲相关性,太低等于没开。

第二个是多查询改写。让模型先把用户问题改写成 2 到 3 个不同表述的检索式(比如把口语化问题改写成书面术语、补充同义词),分别检索后取并集再去重。实测在术语和口语差异大的场景下,召回率提升很明显,代价是多一两次模型调用和少量延迟。

还有一个容易被忽略的点:混合检索。纯向量检索对精确匹配不敏感,用户问某个编号、某个型号,向量检索可能召回一堆语义相近但编号不同的段落。加上关键词检索(BM25 之类)做融合,用倒数排名融合的方式合并两路结果,对包含专有名词、编号、代码的查询提升很大。

3.2 提示词模板:把“不知道”这个出口留出来

提示词这块,我见过最常见的错误是把模板写得太软:“请根据以下资料回答问题”。模型看到这句,资料里没有答案时它就会开始自己编。

我的模板结构是这样的:

from langchain_core.prompts import ChatPromptTemplate SYSTEM_TEMPLATE = """你是一个严谨的知识库助手。请严格依据下面提供的资料回答问题。 规则: 1. 只使用资料中的信息作答,不得引入资料之外的知识。 2. 如果资料不足以回答,直接回复"根据现有资料无法确定",并说明缺少什么信息。 3. 回答末尾用 [来源: 文件名#块序号] 标注依据。 4. 涉及步骤时按顺序列出,涉及数字时原样引用,不要换算。 资料: {context} """ prompt = ChatPromptTemplate.from_messages([ ("system", SYSTEM_TEMPLATE), ("placeholder", "{chat_history}"), ("human", "{input}"), ])

几个细节值得说。规则 2 是关键,它给了模型一个体面的退路,不给退路它就一定编。规则 3 让溯源变成回答的一部分,用户信任度完全不一样。规则 4 防止模型“贴心地”把“三个工作日”改成“三天”,这种改写在对内问答里会引发真实争议。

资料拼接时我还会做两件事:给每块加上序号和来源标题,方便模型引用;按相关性从高到低排列,因为部分模型对上下文中间位置的信息利用率偏低,把最相关的放最前面更保险。

3.3 链式编排与工具调用

基础问答用链就够了,需要“先查再算”“先查再查”的场景才上智能体。区分标准很朴素:如果一个问题的答案需要多次检索或调用外部接口才能凑齐,就用智能体;否则用固定链,稳定性和延迟都好得多。

固定链的写法现在推荐用表达式语言,把检索、拼装、生成串起来:

from langchain_core.runnables import RunnablePassthrough from langchain_core.output_parsers import StrOutputParser def format_docs(docs): return "\n\n".join( f"[{i+1}] 来源: {d.metadata.get('source')}#{d.metadata.get('chunk_id')}\n{d.page_content}" for i, d in enumerate(docs) ) rag_chain = ( {"context": retriever | format_docs, "input": RunnablePassthrough()} | prompt | llm | StrOutputParser() )

需要工具的场合,比如“查一下这个型号的参数,再和上一代做个对比”,我会给智能体挂两个工具:知识库检索工具和计算工具。检索工具的描述一定要写清楚“什么时候该用”,模型是靠描述来决定调不调的。描述里写“查询产品参数、政策条款、操作流程时使用”,比写“搜索知识库”有效得多。

注意:智能体的工具数量不要贪多,超过 5 个之后选择准确率会明显下降。能合并的工具就合并,能固化成链的就别做成工具。

3.4 会话记忆:窗口不是越大越好

多轮对话必须带记忆,但记忆不是把历史全塞进去。我的配置是滑动窗口保留最近 6 轮,同时对更早的历史做摘要压缩。6 轮这个数不是拍的,是算出来的:一轮问答平均 150 字,6 轮约 1800 字,加上检索上下文 2000 字和提示词,总 token 控制在 4000 出头,延迟和成本都可控。

还有一个细节:用户追问“那第二种情况呢”这种指代性问题,直接拿原句去检索是检不到的。做法是在检索前先用模型把问题改写成独立完整的句子(这叫问题压缩或指代消解),再拿改写后的句子去检索。这一步不加,多轮体验会断崖式下跌。

4. 效果调优与常见问题排查

4.1 答非所问的四类根因与定位方法

线上收到的“答得不对”,九成能归到四类,定位方法也不一样。

第一类是根本没召回。表现是回答里出现“根据现有资料无法确定”,但你明知道库里有。排查顺序:先看检索日志里的相似度分数,如果 top 1 分数低于 0.5(不同模型尺度不同,看相对值),基本就是召回失败;再检查是不是切块把关键段切碎了;最后看是不是权限过滤把文档挡掉了。

第二类是召回了但排得太后。top 10 里有正确段落,但 top 4 里没有。这时候调 top_k 治标,加混合检索或加多查询改写治本。

第三类是召回了但提示词没用上。现象是资料里明明写着一,模型答了二。多半是上下文太长、关键信息被淹没,或者模板里没强调“只用资料”。

第四类是资料本身有矛盾。库里同时存在新旧两版制度,检索各召回一半,模型只好和稀泥。这种只能靠版本管理和元数据里的生效时间来解决,别指望调提示词。

4.2 幻觉抑制的三个实操手段

幻觉不是模型坏,是它被放在了必须回答的位置上。除了前面说的“留退路”,我还会做两件事。

一是答案后校验。拿到回答后,再用一次轻量调用判断“这段话是否被给定资料支持”,不支持就打回或降级成“未找到确切依据”。这一步会增加成本,所以我只对高风险场景(对外客服、合规问答)开启,内部查询场景用抽样校验就够了。

二是相似度阈值兜底。检索结果最高分低于阈值时,直接不进模型,返回“未检索到相关内容,建议换个问法或联系文档负责人”。这比让模型硬答体面得多,也省了一次调用。

4.3 性能与成本的实际账单

延迟主要由三块构成:检索、模型生成、网络。检索在十万级向量库上通常是几十毫秒量级,占比很小;大头是模型生成,尤其是输出长回答时。我的优化顺序是:先做流式输出改善体感,再做检索结果缓存(相同问题命中缓存直接返回),最后才考虑换更小的模型。

成本上算一笔账:假设日均 2000 次问答,每次输入 2500 token、输出 400 token,模型单价按每百万输入 token 若干元计,一天的成本是可以精确算出来的。真正容易失控的是两处:一是智能体陷入工具调用循环,二是上下文没做截断,某次检索返回了超大块直接把输入撑到几万 token。前者要设最大迭代次数,后者要在拼装上下文时做硬截断。

4.4 常见问题速查表

现象最可能的原因优先动作
回答里出现资料中没有的内容提示词没约束 / 没留退路加“无法确定”规则,加相似度阈值
明明有答案却说不知道切块过碎 / 召回未命中检查 top 分数,调大块或加重叠
回答只覆盖问题一半top_k 太小 / 上下文被截断提高 top_k,检查截断逻辑
多轮追问答非所问未做问题改写加指代消解后再检索
相同问题两次答案不同温度参数过高问答场景把温度降到 0 到 0.2
表格类问题答错表格解析失败表格单独转结构化文本入库
延迟突然变高检索返回超大块 / 工具循环限制单块长度,设最大迭代次数
新旧制度混答未做版本隔离元数据加生效时间,检索时过滤

4.5 评测:没有评测集的调优都是玄学

调参最怕“感觉好像好了一点”。我每个项目都会在开工第二周就搭一个最小评测集:从真实提问里抽 50 到 100 条,人工标注每条的标准答案出处(哪份文档哪一段)。跑评测时只看两个指标——召回命中率(正确段落是否出现在 top_k 里)和答案正确率(回答是否与标准答案一致)。

这两个指标要分开看。召回命中率低,说明问题在检索层,调切块和检索策略;召回命中率高但答案正确率低,说明问题在提示词或模型,跟检索没关系。分开看能省掉大量无效调参。

评测集要随业务演进持续补充。每次线上出现答错的 case,就把那条加进去,跑一遍确认修复且没有引入回归。这套机制建立起来之后,迭代速度会快一个量级。

5. 上线前的工程化收尾

5.1 服务化与可观测性

跑通 demo 和上线之间隔着一段距离,这段距离主要花在三件事上:接口、日志、限流。

接口层把链包成一个服务端点,入参是问题和会话标识,出参是答案加引用列表。这里要注意超时设置,生成环节经常是长尾的,把超时设成 60 秒、配合流式返回,比设成 10 秒然后大量失败体验好得多。

日志我至少记这几项:原始问题、改写后的问题、召回的块 id 和分数、最终提示词的 token 数、模型输出、总耗时、用户反馈。有了这些,线上任何一个“答得不对”的反馈,都能在五分钟内定位到是哪一层出的问题。没有这些日志,排查全靠猜。

限流和配额放在网关层,按用户维度做。知识库问答的成本曲线很陡,一个用户写个脚本批量刷,一天就能把月度预算烧掉。

5.2 我在实际项目里的几点体会

第一个体会是,先做窄再做宽。第一个版本不要覆盖全公司所有文档,挑一个知识密度高、问题相对集中的领域(比如某一类产品的售后问答)跑通全链路,把评测集建起来,再往外扩。上来就全量入库,问题会多到你不知道从哪下手。

第二个体会是,用户的提问方式永远超出你的想象。测试阶段我们预设的都是完整句式,上线后大量是“那个退货的”“上次说的那个流程”这种碎片表达。所以问题改写那一步不是锦上添花,是必需品。

第三个体会是,把“不知道”当成一个正常功能来设计。早期我总想让系统尽可能多回答,结果错答率居高不下,用户反而不信任。后来把阈值调紧,允许它说“查不到”,同时给一个“反馈这个问题”的入口,用户满意度反而上升了——因为错的少了,而且反馈的问题第二天就进了知识库。

最后分享一个排查小技巧:当你怀疑是切块问题时,把那个块的原文和检索分数打印出来,和用户问题并排放一起看。十次里有八次,你一眼就能看出这块为什么不该被召回,或者为什么该被召回却没召回。这比看任何指标都快。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询