HiRAG:层级知识检索增强生成,破解大模型跨文档问答难题

发布时间:2026/10/17 5:38:47
HiRAG:层级知识检索增强生成,破解大模型跨文档问答难题 去年我帮朋友做一个产品资料问答系统需求其实很简单把几十页内部手册喂给大模型让一线同事随便问。我们一开始就用了最常规的 RAG文档切块、向量化、检索、拼 Prompt跑起来确实快结果一遇到跨章节的问题就露馅。“A 模块和 B 模块怎么配合”“这次升级解决了上次遗留的哪个问题”模型要么从某一段里抠出只言片语要么干脆把不相关的内容缝起来生成一个看着像模像样的答案。这个问题让我开始换一个角度琢磨检索的本质到底是在检索什么是检索“一段文字”还是检索“一个知识”后来真正把一套层级检索的思路跑通我发现在多数场景里答案的准确性并不取决于模型有多聪明而取决于你喂给它的资料组织得有多好。这篇文章就从头讲讲我实践的 HiRAG层级知识检索增强生成它和普通 RAG 的关键差异是把文档从“一堆碎片”变成“三层结构”再让模型按结构去找答案。思路不复杂代码也很直白适合刚接触大模型、想快速把问答落地的小白程序员。1. 先理解普通 RAG 为什么“不够用”1.1 大模型的先天短板它是一场闭卷考试大模型训练完成后知识就定格在训练数据那个时间点上了。你问它训练完之后才发布的新产品、新政策、内部文档它大概率是不知道的。更麻烦的是它不会老老实实说“我不知道”而是会顺着你的问题编一个听上去很合理、实际上可能完全错误的内容这就是常说的“一本正经地胡说八道”。你可以把模型理解成一个参加过海量考试、但再也没更新过教材的学霸。你问他教材范围内的题它答得极好你问它教材里没有的内容它就只能根据见过的相似题目“猜一个”。所以业界的共识是要让模型在真实业务里可被信任与其单纯追求“更大更聪明”不如先解决“它到底有没有参考材料”的问题。闭卷考试不行那就给它开卷。1.2 普通 RAG 干的是什么活RAG 的全称是 Retrieval-Augmented Generation检索增强生成。它要做的事用一句话说在模型回答之前先从外部知识库里捞一批相关的文字片段塞进 Prompt 里当参考资料然后再让模型照着资料回答。最简单的实现是这样的# 离线阶段建索引 chunks split_document(doc_text, chunk_size500, overlap50) vectors embed(chunks) vector_db.add(vectors) # 在线阶段查资料 生成 q_vec embed(query) hits vector_db.search(q_vec, top_k10) prompt f请根据以下资料回答问题\n{hits}\n问题{query} answer llm.chat(prompt)这个流程本身没问题很多线上小工具都是这么跑的。问题出在几个细节上切块、召回、拼接这三步每一步都可能丢信息。1.3 传统方案的三个硬伤第一个硬伤是切块切断了语义。文档里本来是一个完整段落讲一个逻辑你按固定字数一刀切切成几块之后前后文关系就断了。第二个硬伤是召回的“温差”向量检索只看字面相似度很多时候把词面像但意思无关的段落也捞回来了真正关键的那段反而没进 top-k。第三个硬伤是跨文档、跨章节的多跳问题。比如“这个版本解决了上一个版本遗留的哪个问题”答案分布在两个章节里一个切块根本装不下普通 RAG 就会漏掉信息。我当时在项目里遇到的就是第三类问题。那段时间我试过加大 top_k、改切块策略、用重排序模型效果有一些但都不稳定。后来发现方向得变别只想怎么“捞得更准”而是先把知识本身组织出结构让检索顺着结构走。2. HiRAG 的层级知识你不是在找碎片是在查地图2.1 三层结构怎么设计HiRAG 的核心是把知识组织成三层而不是一层第一层叫证据层也就是原始文档切出来的一个个块保留完整的细节这是最终要作为参考资料喂给模型的底料。第二层叫知识点层每个知识点是一个 50 到 200 字的语义摘要。它既不完全等于原始块也不是凭空生成而是把原始块里某个独立的知识点提炼出来。比如原始文档里有一大段说 V2.2 版本修复了调度模块的问题你可以提炼出“V2.2 引入动态优先级队列解决超时重试导致的任务堆积”。第三层叫概念关系层它把知识点之间的关系连起来。比如“动态优先级队列”这个改进关联到“V2.0 超时重试问题”“日志接口优化”关联到“告警延迟”。这一层像一张小地图让你能从“问题 A”走到“解决方案 B”。我建议你用图书馆来理解这个结构概念关系层是图书馆里的楼层导览图告诉你“计算机类在三楼东侧”知识点层是图书卡片摘要让你快速判断这本书值不值得看证据层才是书架上的原书是最终的引用来源。检索顺序也如出一辙先看导览图再翻摘要卡最后取原书。2.2 从文档到三层结构数据是怎么流的建库的时候切块只是第一步。你还需要在切块之后做两件额外的事摘要提炼和关系抽取。摘要提炼可以理解为对每一块文档让大模型“读一遍再浓缩”并且强调一句话规则一个知识点只讲一件事。别指望一次提炼就完美宁可一个块拆出三五个知识点也别揉成一个四不像。关系抽取则要从已经生成的知识点之间找到连接比如“A 引入了 X解决了 B 的问题”“C 依赖 D”“E 替代了 F”。这部分如果完全靠人工做规模一大就崩溃。常规做法是让大模型批量抽取你把抽取 Prompt 写得足够明确输出 JSON 格式比如{ knowledge_points: [ {id: K001, text: V2.2 引入动态优先级队列解决超时重试导致任务堆积, source_chunk: chunk_18} ], relations: [ {from: K001, to: K002, type: solves}, {from: K003, to: K001, type: related_to} ] }这一层抽取质量直接决定 HiRAG 的上限。如果模型把关系抽错了后续检索再准也救不回来。我自己做过几次实验结论是不要省这一步但也不要指望一步到位先跑一个小样本人工盯一遍再放量。这个钱和精力不能省。2.3 查询是怎么“一层一层”找到答案的普通 RAG 是一步到位query 向量化直接到向量库里搜 top_k。HiRAG 则是多步走第一步把用户 query 向量化先在概念关系层里找最相关的几个节点。这一步不需要精确只需要定位“这个问题大概在知识地图的哪个区域”。第二步顺着命中的概念节点找到它关联的知识点。因为知识点是语义摘要信息密度比原始块高所以这一步能快速锁到“哪几条知识真正和问题相关”。第三步从命中的知识点下钻到它们的原始证据块。这一层才把完整的细节拿出来拼成给模型的上下文。第四步合并去重、按相关度排序拼 Prompt交给生成模型。这样设计的好处是它把“模糊的大范围匹配”和“精确的证据定位”拆开了。你看普通 RAG 最大的毛病就是一次性在全库里做向量相似度匹配而向量相似度经常被字面相近的内容干扰。HiRAG 先在地图层面上定位目标——就像你想找小区里的某栋楼先在地图上定位到“这个小区”再进楼门而不是站在原地靠感觉朝所有门牌号扫射。3. 手把手跑通一个 HiRAG 最小实现3.1 准备工作一份测试文档和一个模型接口动手之前先准备三件事一份你熟悉的文档、一个可用的模型接口、一个向量数据库。文档我建议用产品手册、项目周报、制度文件这类信息密度高、有明显“知识点”的文本。模型接口不用最贵能用聊天补全和 Embedding 就行。向量数据库用常见的开源方案就可以本地起一个几行代码能连上。先别纠结性能最小实现跑通了再优化。另外我强烈建议你准备一个测试问题集至少十个问题覆盖单点查询、跨章节查询、对比查询这三类。没有评测集你改完代码都不知道自己是改好了还是改坏了。这是我踩过最深的坑没有之一。3.2 建立一个最简公共调用层下面这段代码把 Embedding 和 Chat 调用封装好后面所有流程都复用。用 HTTP 接口演示方便你替换成自己手头的服务import requests import numpy as np from typing import List, Dict EMBED_URL http://your-service/v1/embeddings CHAT_URL http://your-service/v1/chat/completions EMBED_MODEL your-embedding-model CHAT_MODEL your-chat-model def get_embedding(text: str) - List[float]: resp requests.post(EMBED_URL, json{ model: EMBED_MODEL, input: text }, timeout30) resp.raise_for_status() return resp.json()[data][0][embedding] def chat(prompt: str) - str: resp requests.post(CHAT_URL, json{ model: CHAT_MODEL, messages: [{role: user, content: prompt}] }, timeout60) resp.raise_for_status() return resp.json()[choices][0][message][content]这层封装不是重点重点是后面怎么组织数据。3.3 从文档到三层索引的关键代码实际建库时我会分三个步骤跑。第一步文档切块第二步抽取知识点和关系第三步建立三层索引并写入向量库。切块我这里用的是递归字符切法先按段落切段落太长再按句子边界切。块大小我控制在 400 到 800 字重叠 80 到 120 字。块太大会把不同主题混在一起块太小会截断句子语义400 到 800 是个比较稳的经验区间。def split_document(text: str, chunk_size600, overlap120) - List[str]: # 省略细节按段落和标点边界递归切分 # 核心是要保证每个 chunk 语义相对完整 return chunks第二步是让模型抽知识点和关系。这里的关键是 Prompt 要非常具体。我会让模型这样输出 JSONEXTRACT_PROMPT 请阅读下面文档片段提取其中的知识点和知识点之间的关系。 要求 1. 每个知识点只能描述一个独立含义长度 50~200 字 2. 知识点要保留关键实体和具体数据 3. 关系类型仅限solves(解决), relates_to(相关), depends_on(依赖), replaces(替代) 输出 JSON 格式 { knowledge_points: [{id: K001, text: ...}], relations: [{from: K001, to: K002, type: solves}] } 文档片段 {chunk} def extract_knowledge(chunk: str) - Dict: prompt EXTRACT_PROMPT.format(chunkchunk) result chat(prompt) # 解析 JSON做一层异常兜底 return load_json_with_fallback(result)第三步把数据写入索引。这里我建两张表一张是原始 chunk 的向量索引另一张是知识点的向量索引。关系用一张普通的映射表保存就够了class HiRAGIndex: def __init__(self): self.chunk_store {} # chunk_id - text self.kp_store {} # kp_id - {text: str, chunk_id: str} self.kp_vectors [] # 知识点向量列表简化存储 self.relation_map {} # kp_id - [关联的 kp_id] def add_document(self, chunks: List[str]): # 第 1 步存原始 chunk并向量化 chunk_ids [] for chunk in chunks: cid fchunk_{len(self.chunk_store)} self.chunk_store[cid] chunk chunk_ids.append(cid) # 第 2 步每个 chunk 抽知识点 for cid in chunk_ids: parsed extract_knowledge(self.chunk_store[cid]) for kp in parsed[knowledge_points]: kp_id kp[id] self.kp_store[kp_id] { text: kp[text], chunk_id: cid } self.kp_vectors.append(get_embedding(kp[text])) # 第 3 步记录关系 for rel in parsed[relations]: self.relation_map.setdefault(rel[from], []).append(rel[to]) self.relation_map.setdefault(rel[to], []).append(rel[from])上线之前记得把 kp_vectors 换成真正的向量数据库并给每一个向量加上 kp_id 元数据。这个最小实现的重点是让你理解流程而不是造一个高并发系统。3.4 分层检索的主逻辑检索时最核心的是“先概念、再下钻”的顺序def search(self, query: str, top_k5, max_chunks8) - List[str]: q_vec get_embedding(query) # 第一跳在知识点层找最相关的 top_k kp_hits find_top_k(self.kp_vectors, q_vec, top_k) # 第二跳根据知识点关联关系扩展相关知识点 expanded_kp set(kp_hits) for kp_id in kp_hits: for rel in self.relation_map.get(kp_id, []): expanded_kp.add(rel) # 第三跳从知识点下钻到原始 chunk chunk_ids set() for kp_id in expanded_kp: chunk_ids.add(self.kp_store[kp_id][chunk_id]) # 按顺序拼装证据控制上下文长度 ranked_chunks [] for cid in chunk_ids[:max_chunks]: ranked_chunks.append(self.chunk_store[cid]) return ranked_chunks这里值得解释几个细节。find_top_k只是一个向量和知识点的余弦相似度排序。如果后续性能吃紧你可以在这里换任何常用向量检索库逻辑不变。第二跳为什么要做关系扩展因为用户问题里的关键词可能只命中了一个知识点而这个知识点关联的另一个知识点可能是答案的另一半。比如用户问“V2.2 为什么解决了任务堆积”可能命中了知识点“引入动态优先级队列”但“V2.0 存在超时重试机制”这个知识点也通过solves关系挂在一起你不扩展就丢了上下文。第三跳为什么要下钻到原始 chunk而不是直接用知识点文本拼 Prompt因为知识点是摘要缺少实现细节、参数、表格、例子。真正要让模型生成靠谱答案必须把原始原文拿出来。当然这也会带来一点上下文长度压力所以max_chunks要控制住。我一般限制在 4 到 10 个块总长度控制在 3000 字左右效果和成本比较平衡。3.5 生成答案把检索结果交给模型最后一步很直白把上下文和问题拼好交给 chat 模型。但 Prompt 写法有一点讲究def answer(self, query: str) - str: docs self.search(query, top_k5, max_chunks8) context \n\n.join(docs) prompt f 你是知识库问答助手。请只依据以下资料回答问题资料不足时明确说“资料不足”。 资料 {context} 问题 {query} return chat(prompt)注意 Prompt 里那句“资料不足时明确说不懂”这句话非常关键。它把模型的“硬要回答”的冲动往下压了压我实测之后发现回答里凭空编造的比例明显降低。这不是什么高级技巧但很多人忽略它导致 RAG 上线后效果被幻觉拖垮。4. 踩坑清单从“能跑”到“能用”的几个坎4.1 分层抽取出错重复、遗漏、关系乱我最早跑通 HiRAG 后测出来的结果并不理想后来一查问题不在检索在建库阶段就坏了。最常见的坑是知识点重复同一件事在两个不同的 chunk 里各抽了一遍导致检索 return 一堆相似内容浪费上下文窗口。解决办法是加一层合并策略按命名实体和数据做相似度去重或者在建库 Prompt 里加一句“如果本片段和已有知识点表示同一件事就只输出关系不输出新知识点”。设一个阈值相似度高于 0.85 的视为重复。另一个坑是关系抽取太“虚”。我把 Prompt 限制死了关系类型只允许 solves、relates_to、depends_on、replaces 这四种。最开始我允许模型自由发挥结果它抽出来“is_about”“causes”“describes”十几种类型你后面根本没法过滤。限制关系类型会让你损失一些灵活性但换来的是稳定和可维护对小项目尤其是这样。4.2 检索噪音该下钻到哪一层要有判断HiRAG 不是每一层都要从头跑到尾。如果你的问题很简单“耗材的质保期是多久”直接在第一层向量库里检索就行没必要做两级下钻那样反而慢。真正的分层检索更适合“多跳问题”和“概念型问题”。我的经验是可以先在概念层跑一次如果概念层的 Top1 到 Top3 得分特别集中再用扩展下钻如果概念层本身的得分就很散说明 query 太泛这时候不如直接回退到普通 RAG在证据层海选更多块。也就是说HiRAG 最好的用法是作为多个检索策略里的一个分支而不是全部的替代品。4.3 性能慢、索引膨胀三层结构的信息密度高数据量会比普通 RAG 多出不少。几十万字的知识库还好如果是千万字级别离线构建可能要按小时算。性能优化的方向有两个一是异步流水线切块、抽取、向量化用队列并行跑二是只对知识点层使用模型抽取证据层保持原始文本。还有个容易被忽略的点Embedding 调用次数会飙升最好批量调用一次传几十条文本进去接口成本会直线下降。4.4 别忽略评测这个真得单独拎出来说。我自己早期做 RAG 项目的时候总觉得“效果好一点”“看着挺准”就够了结果没有评测集改完代码完全不知道改动是正向还是负向。HiRAG 因为多了一层抽取改动的变量比普通 RAG 多更需要一个高质量评测集。我建议从真实用户问题里攒至少 30 条再准备一个正确的答案摘要。每次改动之后跑一遍评测集记录三个指标命中率参考文档是否被检索到、答案正确率、上下文冗余度。有这三个数字你才知道每一层修改到底把它变好了还是变坏了。5. 效果对比与适用边界5.1 小型对比普通 RAG 和 HiRAG 差在哪我拿手上的产品手册做了个简单对比问题集分成三类事实查询、流程查询、跨章节因果查询。结果不一定代表所有场景但方向很有参考价值问题类型普通 RAG 表现HiRAG 表现单点事实查询参数、日期、数值优秀直接命中良好略慢多步骤流程查询一般容易缺步骤良好能补齐关联知识点跨章节因果查询差经常答错对象好关系层能把因果接起来低资源/冷启动可快速上线需要额外建库成本从表格能看出HiRAG 不是全面碾压而是在“复杂问题”上优势明显在“简单事实查询”上反而有点杀鸡用牛刀。所以选型不是越先进越好而是看你的知识库复杂度。如果你只有几百条 FAQ、用户问题以查阅为主普通 RAG 完全够用如果你有一堆长文档、问题经常跨章节、逻辑链路长上 HiRAG 就非常值。5.2 什么情况不要硬上 HiRAG有几种情况我建议你先别碰 HiRAG。第一种是文档质量太差。如果你手里的资料满是重复、版本混杂、格式混乱做分层抽取等于在垃圾上盖楼出来的层级关系全是错的。先花时间清洗资料比任何花哨的检索方案都重要。第二种是交互延迟要求极高。HiRAG 天然多跳延迟比普通 RAG 高多一跳就是多一次向量查询如果还带着关系扩展耗时会更多。实时对话系统要慎重。第三种是你连基础 RAG 都还没跑通。这个比较现实我看到太多人一上来就追求复杂方案结果连最基础的链路都没调明白。先把普通 RAG 跑起来再一步步加分层你会更清楚每一层带来的收益在哪里。我自己的实践体会是HiRAG 最大的价值不是“准确率提升几个点”而是它逼着你把知识库想清楚。建索引、抽知识点、定关系的过程本质上就是在梳理业务逻辑。这个梳理过程带来的认知提升比你多调几次模型参数有用得多。如果沿着这个思路继续扩展后面还可以把关系层接到图数据库上做更复杂的多跳推理或者把抽取结果加一层人工审核做成有监督的知识沉淀流程。但第一步还是先把你手头那份文档认真分好层。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询