ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

RAG实战:从能跑到能用,文档智能问答系统的工程化踩坑记录

RAG实战:从能跑到能用,文档智能问答系统的工程化踩坑记录 “文海问津”这个名字我们当时起得有点文气说白了就是在文本的海洋里找路。这个创新实训项目做到第二期记录阶段目标已经不再是“把模型跑通”这么简单了而是要把一套面向非结构化文档的智能检索与问答链路从实验室Demo打磨成能稳定交付的工程模块。上一篇我重点写了立项时的技术选型对比这篇我打算换个角度专门聊聊从“能跑”到“能用”之间踩过的那些坑——尤其是链路整合、参数调优和问题排查。先简单交代一下项目背景。我们做的是一套文档智能问答系统核心应用场景是把一堆杂乱的PDF、Word、Markdown文档变成可以自然语言交互的知识库。用户输入一个问题系统需要从文档里找到真正相关的片段再用大模型生成一段有依据的回答。这个方向现在有个很流行的叫法RAG检索增强生成。但这个项目的特殊之处在于文档来源极其混杂有扫描版PDF、有三级标题混乱的Word报告、还有排版本随意的手册整个项目最耗时间的其实不是模型本身而是围绕模型做的数据清洗和链路控制。这一篇记录我会按我们实际推进的节奏来写先复盘整体设计与模块拆解再讲核心实现细节然后是评估方法和问题排查。所有内容都是我在这轮实训中亲手做过、反复试过、也吃亏过的经验希望能给做同类项目的同学一点参考。1. 实训走到第二站我终于学会不炫技1.1 从毕业设计式Demo到交付级模块的落差很多实训项目第一轮能跑通靠的是网上现成的框架和教程。向量数据库选个现成的嵌入模型选个开源的大模型API一接demo就出来了。但到了第二轮我们把真实业务文档灌进去之后整个系统立刻原形毕露——回答质量忽好忽坏有时候明明文档里有答案系统就是答不上来甚至张冠李戴。这个落差的核心原因我后来总结下来就一句话Demo验证的是“能不能通”工程交付考验的是“稳不稳得住”。比如第一版的检索流程我们是标准的“向量化-查库-取前K条-喂给大模型”。但这套流程在文档量超过几百份、内容句式高度相似的时候就非常脆弱。很多业务文档里涉及同一个概念的不同维度描述向量相似度会拉回一堆语义相近但并不是直接回答问题的片段检索这一环就先把错误放大了。所以第二期我们做的最重要的一件事不是换更牛的模型而是把整个系统链路拆开逐个环节做“降噪”。这个过程听着不性感但非常关键——稳定才叫工程化。1.2 重新审视“为了学技术而学技术”的立项陷阱实训类项目特别容易陷入一个误区看到一个新技术觉得“很酷”就强行往项目里塞。比如一开始我们尝试过给每个文档片段都做知识图谱抽取想在检索前加一层实体关系过滤结果发现标注成本和计算成本都扛不住对检索结果的提升又没法量化。最后我们把这个模块砍掉了回到最朴素的“先检索、后重排”架构上。这不是说新技术不好而是创新实训的核心目标是培养工程判断力不是技术堆砌。如果你的技术在落地点上说不清楚“它解决了什么问题、代价是什么、收益怎么衡量”那它就只属于作业层面不属于产品层面。我们后来有一条非常朴素的项目原则每一步改动必须能在评估集上看到量化指标变化否则不做。2. 链路拆解与方案选型把“问津”落到具体模块上2.1 核心业务链路与模块划分第二期我们把系统拆成了五个核心模块文档解析层将PDF、Word、Markdown、HTML转为统一格式的纯文本保留基本结构。文本切分层将长文档切成适合检索和生成的片段chunk。向量化与索引层将文本片段转为向量写入向量库并建立索引。检索与融合层接收问题通过混合检索召回候选片段。生成与引用层将候选片段组织成上下文调用大模型生成最终回答并附上引用来源。这个划分看起来平平无奇但实际做下来会发现每一层都有大量“细节决定成败”的时刻。我举个解析层的例子很多PDF的标题看起来是目录里的词但正文里用的是简写、编号或者换了说法如果解析层不保留页眉页脚和层级信息后期切分时极容易把标题和正文之间的语义关系切断。2.2 检索不能只有向量混合检索的取舍第二期开始前我们天真地以为向量检索已经很强了。但第一批真实用户反馈里有一类问题特别集中精确匹配型查询。比如用户问“《数据安全法》第三条的表述是什么”向量检索往往返回一堆语义相近但并非原文的内容因为嵌入模型会“意译”掉精确的数字、法条编号和引号里的原文。我们最后的方案是走混合检索对问题同时执行向量检索和关键词检索基于BM25再用一个轻量级排序模型做融合。这个方案的原理很简单——向量擅长处理“语义相关但表达不同”的情况关键词擅长处理“专有名词和精确表述”的匹配两者互补。调优的关键在于两条通道返回的分数量纲不一样不能直接相加需要做归一化甚至要给不同通道分配权重。我们试过一组组合参数向量权重和关键词权重从1:1调到7:3观察发现权重偏向量时回答更有“理解力”但容易丢专有名词权重偏关键词时精确匹配好了但同义改写的问题答不上来。最终根据我们的业务数据分布把权重定在了6:4左右再配合重排模型效果是最稳的。2.3 重排模块为什么放在最后才做重排Rerank是这轮实训后期我们才真正重视起来的模块。很多RAG教程会告诉你“检索取TopK然后交给大模型”但如果你真实测过尤其是文档量大或者文档之间高度相似时前几步召回的片段里经常会有一半是无用信息。因为向量检索的返回结果本身是按向量距离排的不是按“是否直接回答问题”排的。重排模型做的事情就是把你拿到的TopK候选再打一次分输入通常是“问题候选片段”输出一个相关度分数最后按这个分数重新截取TopN喂给大模型。效果好到什么程度呢我们在自己构造的100条评测问题上做了对比不加重排时大模型回答里出现无关引用的比例大概是23%加重排后这个数字降到了9%左右。但重排也有代价多一次模型推理耗时增加。我们在实测中发现用一个小体量的重排模型单次推理在CPU上大约要120毫秒对整体时延影响可控。所以我的建议是如果预算允许这个模块一定要加且要放在整个系统的最后面做先把前面的检索和数据搞干净再用它去兜底。3. 几个必须写下来的工程细节3.1 文档解析所有坑都会在这个环节汇合很多人做RAG项目时容易把全部注意力放在模型和Prompt上我却想说整个链路里最影响效果的其实是文档解析。因为解析的好坏直接决定你后面拿到的是什么。我们在文档解析时遇到过一个非常经典的问题表格。业务文档里大量表格。表格在向量化之前如果只是按“单元格文本拼接”粗暴提取那么“项目名称”和“负责人”之间的对应关系就丢了。后来我们专门改了处理逻辑遇到表格时先识别表头把每一行数据组装成“列名: 值”的键值对文本再切片。这一步改动让表格类问题的回答准确率从54%提升到78%涨了24个百分点。这说明什么解析层做的不是一个“格式转换”的事而是“信息结构保真”的事。你在解析时丢掉的每一个结构和关系后续任何模型都无法找回来。3.2 切分策略与Embedding参数试了十几种方案文本切分是RAG里最容易被人忽略但影响极大的环节。切太短片段缺乏上下文语义不完整切太长向量化时信息被平均稀释精确性下降。我们试过固定长度切分、按段落切分、按标题结构切分、递归字符切分等十几种方案最后在“按层级标题切分段落边界补偿”这个组合上效果最好。具体做法是先用正则识别文档里的标题层级以二级标题作为片段边界同时把标题文本拼进每个片段的开头。这个做法的好处是每个片段自带“语境”——如果你只切了一段正文模型很难判断这是哪个章节下的内容但拼接了标题后片段就成了一个“有上下文的信息单元”。参数方面因为我们的嵌入模型是1024维、最大输入512 token我们最终把目标chunk大小设置在300-400 token之间重叠控制在30 token左右保证相邻片段之间有信息衔接但又不至于造成大量重复。这个参数不是拍脑袋定的而是用一组标注数据做了对比实验后选出来的。3.3 问答生成里最容易忽视的上下文管理把检索出来的片段直接拼起来扔给大模型是很多初版RAG的做法但这会带来两个问题一是超出大模型的上下文窗口二是片段之间互相干扰。举个例子你检索出了关于“数据安全”的三个片段分别是三个不同章节的直接拼接后大模型可能会把三处的定义混在一起生成答案。我们采取的做法是将每个片段按“引用块”的方式组织并在每个片段前标注来源文档名和章节路径Prompt里明确要求模型必须基于引用块回答如果引用的内容里没有答案必须明确说不知道不得自行编造。这一步带来的改变非常直接——回答的忠实度高了用户能清楚看到答案是来自哪份文档的哪个章节。4. 实测复盘评估方法比评估结果更重要4.1 标注集构建不要只盯着那几道面试题实训项目到了第二期我们开始意识到一个没有评估体系的项目是没法持续优化的。你改了一版切分参数怎么知道是变好了还是变差了不能靠感觉得靠数据。我们花了三天时间从真实的业务文档里整理出100条“问题-答案-引用来源”三元组覆盖了精确问答、归纳总结、条件筛选、跨文档关联等类型。这个标注集构建过程的痛苦程度超出预期因为很多文档里的知识并不是直接以问句答案的形式存在的需要标注人员阅读原文后自己组织答案。但痛苦归痛苦这套标注集后来成了我们所有调优决策的唯一依据。这里我想特别提醒一点不要在训练集上做决策也不要在演示用的问题上做决策。我们有一版检索参数在当时“看起来很好”因为演示问题全答上来了但一换成标注集指标立刻掉了5个百分点。演示问题往往是你改造过程中“记住”的问题会有很强的幸存者偏差。4.2 指标不涨时先怀疑数据而不是模型调参过程中我们遇到过一段非常头疼的时期换了好几个嵌入模型效果提升都不明显。后来我们把问题定位到数据上——文档解析后的文本里有大量不完整的片段句子被截断、换行符混乱、中英文标点混用。这些问题直接污染了向量化的结果。所以当你发现指标涨不上去时先别急着换模型。可以做一次“数据体检”随机抽50个切分后的片段人眼扫一遍看有没有格式异常、语义截断、明显的脏数据。我们当时扫完就发现将近30%的片段都带着无意义的页眉页脚或者不完整的表格文本。清洗这些数据比换任何模型都见效。4.3 线上效果的隐性问题延迟与失败兜底评测集上的准确率不代表一切。第二期测试阶段我们邀请了一些外部用户试用反馈中出现了两类评测集发现不了的问题。一类是时延问题。当候选文档数量很大时混合检索的耗时明显上升从最初的几百毫秒涨到两三秒这种延迟对问答体验来说是致命的。我们把检索过程加了一层“粗筛-精排”先用便宜的向量检索快速拉回100条候选再做一次轻量级过滤到20条最后才交给重排模型精排。三步下来整体时延被压回800毫秒左右。另一类是失败兜底问题。用户经常问一些系统根本检索不到答案的问题比如文档里完全没有提到的事。第一版系统在这种情况下的表现是大模型“强行回答”编造出一个看起来合理但完全无依据的回答。我们把Prompt改成了强约束模式当检索结果分数整体偏低时直接回复“未在现有资料中找到相关答案”并建议用户换一种问法。虽然这样会让“答不出来”的情况变多但这比一本正经地胡说八道要靠谱得多。5. 问题排查与避坑实录5.1 文档解析阶段最隐蔽的编码与结构问题解析阶段踩过的坑值得单独拎出来说。最常见的两类问题第一类是编码问题。有些PDF导出时使用的不是标准Unicode字体提取出的文本会出现乱码或不可见字符。这类问题最隐蔽因为肉眼在浏览器里看PDF觉得好好的但提取后的文本里全是特殊空格和零宽字符直接导致向量化后的语义产生偏移。解决方案是在文本清洗阶段统一做一次Unicode归一化并且过滤掉不可见控制字符。第二类是目录与正文的重复问题。很多PDF里目录页包含了章节标题正文里又会出现一遍同样的标题如果我们不处理切分时就会出现大量高度重复的片段检索时这些重复内容会霸占TopK的名额。我们的处理方式是在解析阶段识别目录页直接剔除目录内容并尽量保留页码信息以便跨文档引用时能定位。5.2 向量检索召回率低的三种排查手段如果你发现系统经常“答非所问”先别怀疑大模型90%的概率是检索没召回正确的内容。我们的排查思路按下面的顺序进行先看候选片段里到底有没有答案。把Top20的召回结果直接打出来人眼判断正确答案是否在里面。如果正确答案压根没进去问题出在“召回层”如果进去了但最终没被选上问题出在“排序层”。检查查询与文档的语义是否对齐。业务场景里用户的问法比较口语化而文档里的表述往往很正式。这时候单纯的向量检索可能效果不好需要靠混合检索里的关键词通道来兜底或者在切分阶段把文档里的同义词、缩写形式补充进去。检查文档预处理的细节。例如嵌入模型对包含大量数字、代码、特殊符号的片段编码效果很差。遇到这类片段可以考虑先做“字段抽取”把数值型信息和文本描述分开存储检索时再合并比对。5.3 大模型回答幻觉之外的硬伤大模型的幻觉问题大家都知道但除了幻觉我们实际还遇到了两类“硬伤”。一类是指令遵循不一致。同一套Prompt模型有时候严格按引用回答有时候又自由发挥。这在模型服务化部署后表现得特别明显。我们最后的方法是降低温度参数从0.7降到0.2并且在系统提示里加上一条“你只能使用上下文提供的信息禁止使用外部知识”效果立竿见影。代价是回答的多样性会变差但作为知识库问答场景这本来就是合理的取舍。另一类是引用切分导致的错位。有时候检索到的片段和答案之间确实相关但片段边界切在了句子中间导致大模型拿到的上下文语义不完整。我们的对策比较土但实用在把片段喂给模型之前先做一轮“边界修正”如果片段最后一个字符不是句号、叹号、问号就向前回溯到最近的完整句子边界处。这个细节对回答完整度的提升非常明显代码量却很少。写在最后的实操心得如果你也在做类似的文档智能问答实训项目我最想强调的三件事全部来自这一轮踩坑的直观感受第一先把数据和链路做扎实再谈调模型。这个项目里我们最成功的优化项分别是重排模块的引入、表格结构化保留、混合检索融合没有一个是靠“换一个大模型”实现的。第二期结束时我的体会非常明确在RAG架构里模型是上限数据链路是地板大多数人差的不是上限而是地板没铺平。第二建立一套自己的评估集哪怕只有几十条问题也够用。没有评估集你做的每一个优化都可能是自我感觉良好有了评估集哪怕它是粗糙的你至少有了一个可以回退的基准。第三每一轮改动只改一个变量。我们吃过一次亏同时换了切分参数和重排阈值效果确实提升了但根本说不清是哪个改动起了作用后续优化直接失去了方向。从那之后所有实验都严格执行“单变量原则”。“文海问津”的下一期我们计划把精力放在多轮对话与追问处理上——毕竟真实用户不会只问一个问题他们会在一次会话里连续追问、补充条件、切换话题。如果你也在做这个方向欢迎一起交流实训这条路摸着石头过河的时候有人搭把手会走得更稳一些。
返回列表