首页 / 资讯中心 / 文章详情

LinearRAG:轻量化RAG多跳推理的图路径线性化方案

LinearRAG:轻量化RAG多跳推理的图路径线性化方案 ★ FEATURED ARTICLE
这两年做知识类问答系统最绕不开的就是RAG框架。大家最初接触的RAG基本上是一个三段式结构把文档切成块向量化之后存进向量库用户提问时先做相似度检索再把检索结果拼到Prompt里让大模型生成答案。这套流程在小规模知识库上够用但一旦涉及多跳关系、实体交叉和信息聚合单靠向量召回就很容易翻车。GraphRAG是很多人盯上的方向我也跟风在项目里试过效果确实有明显提升可部署和运维成本也很感人。后来我调整了思路在GraphRAG基础上做了一版“线性化”改进就是标题里说的LinearRAG用一套更轻量的方式保留图关系建模能力同时把索引构建和检索开销压了下来。这篇文章就把这套方案的整个设计过程和落地细节分享出来适合正在做RAG改造、又被图框架复杂度困扰的朋友参考。1. 内容整体设计与思路拆解先说这次项目背景。我要搭的是一个企业内部知识库问答系统文档覆盖产品手册、供应商信息、项目排期和变更记录。看起来不复杂但问题一到“关系”层面就卡壳。比如用户问“供应商B的交付节奏变化会不会拖累项目C的里程碑”朴素RAG会把供应商、项目、变更记录分在多个片段里向量召回只能带回一部分模型答出来经常是猜的。我最初选择GraphRAG也是看中它对多跳关系的处理能力。但整套落地下来我发现GraphRAG的上限高下限也很低需要做一次有针对性的取舍。1.1 传统RAG的痛点在哪里先回头看看传统RAG为什么不够用。向量检索的本质是语义相似度匹配它擅长的是“哪段文字最像这个问题”而不是“哪几段文字加在一起能推出答案”。在单点事实问答上比如“产品A的保修期是多久”分块检索直接命中即可但一旦遇到需要跨多个文档片段联合推理的问题问题就暴露了。拿上面的例子说供应商B的交付信息在采购文档里项目C的排期在项目计划里“影响”关系可能只出现在一次评审纪要里三次向量检索的结果各自都合理但模型拿到手之后很难把三块信息拼成一条完整的证据链。这还不是最麻烦的。传统RAG的分块策略本身也会制造断层一句话前半句在上一块后半句在下一块检索时只命中后半句模型看到的就是不完整的论据。有些人靠增大重叠窗口来缓解比如把块重叠从0调到64或128 token确实能把断句问题减轻但对跨文档的关系推理还是无能为力。只要答案需要把多个来源的信息组合起来单纯“找相似片段”的路子就走到头了。1.2 GraphRAG 的优势和复杂度GraphRAG的思路是先把文档中出现的实体和关系抽成知识图谱然后在回答问题时先定位实体再沿着边做遍历把多跳邻居聚合到上下文里。这样能解决跨片段推理的问题因为“影响”这个关系已经被建模成一条边。这也是很多人优先考虑它的原因。但实际落地GraphRAG有几个绕不开的成本。第一索引构建不再是一次向量化加简单存储的流程需要做实体抽取、关系抽取为了后续聚合往往还要做社区检测或者层次聚类每一步都要调用模型推理耗时按小时算。第二检索阶段为了保持召回完整性遍历时往往设置分支和深度一旦实体密集候选路径数量会爆炸延迟和token消耗都很难控制。第三它的效果对图谱质量极其敏感抽取漏掉一条关键关系后面答案就直接偏掉。我实际跑GraphRAG时还遇到一个运维问题图对象常驻内存文档量一上来内存和CPU占用都很高而且社区摘要的生成是全量更新动一次数据就要重跑一批任务。这让我意识到GraphRAG更像重型方案不是所有项目都扛得住。尤其对于中小规模知识库业务查询的关系模式其实非常固定根本不需要把完整图的所有结构都保留下来。1.3 LinearRAG的设计出发点顺着前两个痛点我想找的中间态是保留GraphRAG在多跳关系上的表达能力但不保留完整图结构在查询期的遍历开销。这就有了LinearRAG。设计出发点很简单把图谱中可能被查询命中的路径提前“拉直”成线性文本查询时不再打开图数据库遍历而是对这些路径做文本检索或向量检索。你可以把它理解成GraphRAG给你一张地图导航时实时计算路线LinearRAG把常用路线提前写成路书按需翻出来读。路书没法应付所有临时绕路但对于一个业务范围固定的知识库覆盖大多数查询足够了。这个思路本质上是一种“以离线换取在线”的做法。离线阶段把实体关系路径全部枚举并存储在线阶段只需要做路径召回和重排避免实时图遍历和实时摘要生成。路径被拉直之后还能直接被向量模型编码从而复用了RAG本身成熟的检索体系不需要单独维护图查询引擎。对团队来说这意味着可以少维护一套重型组件技术栈更简单。1.4 关键假设和两个前置条件这里要强调LinearRAG成立的前提我归纳为两个。第一业务查询必须能落到明确的实体上无论产品、供应商还是项目至少有一个起点实体。如果问题本身没有实体更像开放域写作那线性路径的召回优势发挥不出来。第二答案所需的推理链长度一般在2到3跳以内。超过这个范围路径数量会指数增长提前生成所有路径也不现实。所以在项目启动阶段我先抽样分析了100条真实用户问题发现两类查询占82%一类问“X是否影响Y”另一类问“X有哪些关联对象”这正好是线性路径的射程范围。这两个前置条件也直接影响了我后续的索引设计和剪枝策略。因为路径深度被严格限制索引规模才能控制在可接受范围内。如果你的业务问题总是“从A出发经过B、C、D最后到F”甚至还要聚合一堆节点的统计值LinearRAG就不合适了那属于GraphRAG或者预聚合报表的活。2. 索引构建与离线处理实操2.1 数据预处理从清洗到分块线性路径索引的质量一半取决于源头数据。我要构建一个500篇文档的知识库不能直接拿原始文本去抽取。先做清洗去掉重复模板、页眉页脚、表格里拆坏的句子。这个环节很容易被低估实际上很多实体关系断裂都是因为原始文本里混入了“第X页共X页”之类的噪音模型把噪音也当成了实体。清洗之后是分块。分块策略我调了几轮最终用512 token的块大小、64 token重叠。为什么这么设因为实体和关系抽取需要上下文完整如果块太小跨句关系很容易被切断块太大抽取成本上升且一条路径里塞太多不相关内容会干扰后续路径打分。实际操作时重叠部分要小心重复路径后面去重环节会处理。这里建议把清洗、分块的结果单独存一份方便之后调整抽取Schema时不用重新清洗只重跑抽取即可。2.2 实体与关系抽取 Schema这一步是整个LinearRAG质量的地基。我用的抽取方式不是开放式信息抽取而是限定业务Schema。太开放的抽取会导致图里塞满“位于”“包含”这类噪音边路径打分会乱。以我那个知识库为例实体类型固定为产品、组织、人员、事件、文档关系只保留影响、提供、负责、关联、延迟、变更六类。抽取时给模型一个输出约束用JSON schema让模型返回三元组。比如这段示例{ entities: [ {name: 供应商B, type: 组织}, {name: 项目C, type: 项目} ], relations: [ {source: 供应商B, relation: 影响, target: 项目C, confidence: 0.9} ] }拿到三元组后先存到一个临时图结构里我用了 networkx 的 DiGraph主要做去重和后续路径展开不用来在线查询。关系类型固定还有一个好处在查询改写阶段关系词可以更稳定地映射到索引里的关系标签不会因为模型换了个说法就召回不到。2.3 路径生成与线性化格式路径生成是核心。算法流程从每个根实体出发BFS遍历到深度2或3生成所有可达路径每条路径按 [实体] [关系] [实体] [属性] 的形式串联路径之间用固定的SEP分隔符。单个路径内部节点数上限设为4超过的直接截断因为超过4个节点的路径对生成模型的上下文并不友好而且很容易在后续打分时被过滤。这里给一段简化版的核心逻辑def expand_paths(graph, root, max_depth2, max_nodes4): results [] queue [(root, [root])] while queue: node, path queue.pop(0) if len(path) max_nodes: continue for nxt, relation in graph.edges(node, datarelation): new_path path [(relation, nxt)] text path_to_text(new_path) score path_score(new_path) results.append((text, score, new_path)) if len(new_path) max_depth * 2: queue.append((nxt, new_path)) return results生成出来的路径文本长这样PATH 供应商B | 影响 | 项目C | 延迟 2周 SEP 项目C | 属于 | 2024年度项目群 SEP注意SEP这个分隔符要全局唯一最好找文档里几乎不出现的字符组合。换分隔符意味着全量重建索引后面我会再强调。2.4 路径打分与剪枝生成出来的路径会很多。一个实体如果出现在50个文档里邻居可能上百个全量路径容易超过几万条。必须剪枝。我用的策略是三个信号加权。一是路径覆盖度路径涉及的核心实体是否在用户高频查询里出现过二是关系权重业务上影响、变更类比包含、位于类更重要三是路径长度优先保留短路径。综合打分后每个根实体保留top50路径这样索引规模可控召回也不会明显下降。实际打分公式可以写成score 0.4 * 关系权重 0.3 * 覆盖度 0.3 * (1 - 长度/最大长度)。这个权重不是拍脑袋定的我用了一批历史查询做回放测试观察不同权重下目标路径能否回到top-N。剪枝要谨慎太狠会把可能的路径都砍掉。我一般先保留所有路径跑一轮小规模测试观察被模型正确回答的查询涉及的路径是否在top-N内如果不在就说明剪枝信号选得不对要回头调权重。3. 检索与生成管线的实现要点3.1 查询改写与路径激活查询阶段不再用图数据库流程是先做查询改写。用户问题“供应商B交付延迟影响项目C排期吗”我会先用规则加轻量模型拆成查询三元组起点实体“供应商B”关系“影响/延迟”目标实体“项目C”。拆好之后到索引里做两路召回第一路直接用起点实体名对路径文本做倒排匹配得到候选路径第二路把用户问题整体做embedding到向量索引里找相似路径。然后把两路结果合并按路径得分重新排序。这里我不直接用大模型重排因为重排一次要延迟加数百毫秒业务上不划算用一个简单的BM25加向量分数融合就够了。有一个很关键的点查询改写里的关系词不要翻译成固定词表要保留同义扩展比如“导致”“推迟”都要能映射到影响和延迟否则步骤一里的倒排匹配会漏检。我见过不少RAG项目在查询改写上投入不足只做实体识别不做关系映射导致后面召回路径时把最相关的关系给漏掉。3.2 上下文打包与去重检索回来top-k路径后上下文打包是决定生成质量的核心环节。我使用的方法分三步。第一步按得分截断前top12路径。第二步做路径级去重如果两条路径描述的信息有超过80%实体重合只保留得分高的一条。第三步按路径里是否包含目标实体来调整顺序查询中提到的目标实体如果在路径末尾把这类路径排到前面因为它最接近完整证据链。这一步不用模型靠规则就行但效果明显。上下文长度控制上我会限制打包后文本不超过3500 token。有些人担心路径信息不够想多放几条实验下来超过4000之后模型注意力被稀释反而容易漏掉关键证据。你可以把路径文本想象成地图App里的路线卡片卡片太多用户根本看不过来模型也一样。所以宁可在召回阶段更精准也不要在上下文里堆冗余路径。3.3 Prompt设计Prompt设计直接影响模型能不能把路径证据用好。给一个我实际用的Prompt模板你是一个知识库问答助手。下面是用路径格式提供的证据每条路径用 PATH 开始路径中的关系用 | 分隔。 请只依据证据回答问题。如果证据不足以回答请回答“证据不足”。 最后列出用来回答的证据路径编号。 问题{question} 证据 {context}为什么要强制列出证据编号因为这样可以做自动校验拿模型答案中的实体去跟对应路径比能识别出模型有没有在无中生有。模板里不要加过多Chain-of-Thought提示我试过让模型“逐步推理”在路径拼接场景下它反而会产生很多原文没有的过渡性假设增加幻觉风险。路径证据链本身已经把推理过程交代得很清楚模型需要做的是“读数”不是“再推理”。3.4 答案后校验与引用生成生成完答案后我还会加一个轻量校验步骤把答案中的核心实体与上下文路径实体做比对凡是答案中出现但路径里没有的实体标记为“额外信息”如果额外实体数量大于阈值就返回“信息不足”或触发二次检索。这样能在应用层把幻觉压制到一个可接受范围。二次检索逻辑简单对缺失实体再做一轮路径召回如果找到新的路径重新打包生成如果没找到就老老实实告诉用户“当前知识库没有对应信息”。这个校验层不需要大模型用实体字典加字符串匹配就能跑。真正上线时我觉得这个模块的性价比比换更强的大模型要高得多。很多团队在RAG项目里一味追求模型能力提升却忘了加一层规则校验结果就是模型偶尔“自由发挥”的答案直接送到用户面前体验很不好。4. 效果对比、成本评估与应用边界4.1 三套方案的对比实验结果我用的是一个内部测试集来自某知识库的50条典型问答其中约30条涉及多跳关系。为了避免歧义我把每条问题都配了标准答案。对照组分别是不做图的朴素RAG、完整GraphRAG、以及这套LinearRAG。结果是朴素RAG整体准确率约60%多跳准确率不到四成GraphRAG整体准确率约74%多跳准确率能到六成LinearRAG整体准确率约72%多跳准确率接近58%。单看准确率LinearRAG比GraphRAG略低但差距没有想象中大索引耗时和延迟的优势却很突出。方案整体准确率多跳准确率索引耗时单次查询延迟P95日常维护复杂度朴素RAG61%36%20分钟0.8秒低GraphRAG74%62%4小时3.2秒高LinearRAG72%58%1小时1.2秒中需要说明这次测试的样本量不大数字只反映相对趋势不代表绝对能力。同一个框架换一种文档分布结论可能完全不同。但对我的业务场景来说LinearRAG用很小的代价换到了接近GraphRAG的关系推理效果已经很划算。4.2 成本构成与瓶颈索引耗时差异来自哪里GraphRAG那4小时里除了实体抽取和关系抽取还有社区发现和层级摘要生成后者非常烧token。LinearRAG去掉了社区摘要只保留路径文本LLM调用量大致能减少约50%。查询耗时差异则主要来自图遍历GraphRAG在线查询时为了召回多跳邻居往往要发多轮图查询还要对邻居节点做一次LLM摘要LinearRAG最多做两路检索加规则融合避开了在线摘要。实测在16G内存的普通开发机上LinearRAG索引占内存约800MBGraphRAG跑起来经常到2GB以上而且查询阶段要常驻图对象。如果是在容器化部署环境里这个差距会直接影响规格选型也直接影响每一份账单。4.3 使用边界和选择建议任何框架都有边界。我发现LinearRAG最不擅长的是两类问题。一类是开放式的“总结一下某业务线的整体情况”这种需要全局社区摘要LinearRAG路径给不出全局概览另一类是超过4跳的长链推理比如涉及三层供应商、两轮变更传导提前生成的路径根本不包含完整链路。遇到这两类还是要上GraphRAG或者干脆用数据仓库先聚合好答案。我给自己定了一个选型判断标准先抽样用户Query如果80%以上的问题都有明确起点实体且预期答案所需的中间关系不超过2到3个LinearRAG就是比GraphRAG更划算的起点否则老老实实评估重型方案。5. 常见问题与排查技巧实录5.1 问题一召回路径断裂、证据链接不上实战中最常见的问题是路径断裂。用户问“A变更为什么影响C”索引里却只有“A变更导致B延迟”和“B与C同项目”中间缺了“延迟影响排期”这条关系。原因基本是抽取阶段模型漏了关系或者剪枝阶段把它过滤掉了。我的排查思路是先打开日志查看返回路径里的实体序列确认断层发生在哪个环节。如果是抽取漏了就把这块文档重新做一次针对性抽取并把该关系加入Schema如果是剪枝过滤就把对应路径从回收列表里捞出来看看然后调整score阈值。这里有一个容易被忽略的细节抽取时的模型温度要调到0或接近0否则同一个文档每次抽出来的关系可能都不一样路径断裂问题会变得无法复现。5.2 问题二实体别名导致召回为空用户不会总是用文档里的规范名称。比如文档里叫“供应商B”实际项目里大家叫“B工厂”“西南供应商”路径索引里只有“供应商B”时倒排匹配必然失败。我的处理方法分成两层第一层在离线构建时维护一个业务同义词表所有实体名都做归一索引里同时写入标准名称和别名第二层在向量召回时不做精确匹配用embedding召回相似路径这样即使别名没有完全入库也能靠语义相近拉到候选。实测下来加上别名层以后实体类问题的未命中率下降了约1/3。同义词表不用做得很全先从历史查询日志里挖再让业务方补充一批高频叫法基本就能覆盖主要场景。5.3 问题三上下文超长、生成结果漂移路径检索得分高不代表每一条都对答案有用。早期版本我把top20路径全塞进去经常出现上下文超过4000 token模型被无关路径带偏。后来把topN从20降到12又在打包之前做了一次规则去重和排序。这还不够我还加了一条硬规则路径里如果包含查询中出现的所有核心实体必须保证它在最终上下文里其他高分路径如果与这些路径实体重合度太高只留一条。这个规则虽然简单但对跨文档证据链的保护效果很好。遇到上下文超长时不要先想着换更长的上下文窗口模型那是治标不治本应该做的是让路径选择更精准。5.4 问题四评估时需要看哪些指标光看ROUGE和EM不够。RAG场景里答案可能表述不一致但证据对就行也可能答案看着通顺实际引用了不在上下文里的信息。我会额外记录三个指标路径命中率、证据覆盖率和幻觉实体率。路径命中率指正确答案涉及的关键路径是不是出现在最终上下文中证据覆盖率指模型回答中引用到的证据路径数量占正确答案所需路径数量的比例幻觉实体率指回答里出现了但上下文路径里不存在的实体占比。这三个指标能快速定位问题出在索引还是生成。比如路径命中率低问题在召回和剪枝证据覆盖率低但路径命中率高问题在上下文打包和Prompt幻觉实体率高优先加上校验层。最后说点个人体会。我在这个项目里最大的收获不是LinearRAG本身而是意识到RAG框架没有银弹GraphRAG强的边界在于你需要愿意为效果付出部署和算力成本朴素RAG省资源但接不住关系型问题LinearRAG更适合大多数业务知识库这种“关系有限、查询明确”的场景。如果你正好被GraphRAG的复杂度劝退又觉得朴素RAG不够用可以按我上面这套思路先搭一个最小版本几千条路径的规模用脚本就能生成跑上一天就能看到效果。还有一个小提示路径索引的分隔符和实体归一表一定要定下来就写入项目配置路径索引一旦上线改动代价远比你想的大。这套方案后续想扩展也可以往“动态路径更新”和“多级摘要”两个方向走但先把基础线性路径做好比一开始就追求复杂更实际。
阅读完成 · 觉得有帮助?
咨询建站