HiRAG:基于层次化知识图谱的智能检索增强生成技术解析
1. 项目概述从“大海捞针”到“按图索骥”的RAG进化如果你最近在折腾大语言模型的应用尤其是想让模型能“记住”并准确回答你私有知识库里的问题那你肯定绕不开RAG检索增强生成这个技术。传统的RAG说白了就是“切块-存向量-搜相似-喂给模型”思路直接但问题也很明显当你的文档稍微复杂一点比如是一份几十页的技术手册或者一个包含多个子主题的长篇报告这种“一刀切”的检索方式就很容易丢三落四要么漏掉关键细节要么抓来一堆不相关的片段导致生成的答案要么片面要么跑偏。我最近在复现和深度使用一个叫HiRAG的开源项目它来自EMNLP 2025的一篇论文。这个项目给我最大的启发是它不再把文档看成是一堆平铺直叙的文本块而是尝试去理解文档内在的层次结构。想象一下你拿到一本教科书你不会直接从头到尾背下来而是先看目录全局结构再找到相关章节局部主题最后精读段落具体细节。HiRAG干的就是类似的事情它通过聚类和构图自动从你的文档中提炼出“目录”、“章节”和“段落”这种多层级的知识表示然后在回答问题时能智能地在不同层级间穿梭检索确保既把握整体脉络又不遗漏关键论据。简单来说HiRAG试图解决的是RAG领域一个核心痛点如何让检索过程更贴合人类理解复杂文档的思维方式。它特别适合处理那些结构性强、主题分散、细节繁多的文档比如法律条文、学术论文、产品说明书或多领域混合的知识库。经过我的实测在多个专业数据集上它的效果相比传统的Naive RAG和一些其他的图增强RAG变体如GraphRAG、LightRAG都有显著提升尤其是在答案的全面性Comprehensiveness和信息赋能程度Empowerment上。接下来我就结合源码和实操带你彻底拆解HiRAG是怎么工作的以及如何把它用在你自己的项目里。2. HiRAG核心设计思路为什么“分层”是关键在深入代码之前我们必须先搞清楚HiRAG设计背后的“为什么”。传统的向量检索本质上是“语义相似度匹配”。你问“如何配置Nginx的负载均衡”系统就去向量库里找和这句话最像的文本片段。这听起来合理但忽略了一个事实文档是有组织的。一个关于“Web服务器”的章节里可能同时讨论了Nginx和Apache而“负载均衡”可能只是Nginx子章节下的一个小点。扁平化的检索很可能把“Apache的模块化架构”这种全局相关的但对你具体问题帮助不大的片段也捞上来反而漏掉了“Nginx的upstream配置语法”这个关键细节。HiRAG的解决方案是引入层次化知识图谱。它的核心流程可以概括为三步分割、聚类、构图与桥接。这个思路并不完全新奇它借鉴了RAPTORRecursive Abstractive Processing for Tree-Organized Retrieval等工作的思想但在具体实现和与图结构的结合上做出了自己的特色。2.1 第一步智能分割与嵌入项目的第一步是对输入的长文档进行分割。这里它没有使用简单的固定长度滑动窗口因为那样会粗暴地切断句子和语义单元。HiRAG默认采用了基于语义的分割器例如通过嵌入模型计算句子间的相似度在语义发生较大转变的地方进行切分。这样得到的文本块chunk更可能是完整的语义单元比如一个段落阐述一个观点为后续的层次化组织打下了良好的基础。每个文本块随后会被一个嵌入模型如text-embedding-3-small转化为向量。这一步是所有向量检索的基础HiRAG在此处并无特殊之处但它为后续的聚类提供了数据基础。2.2 第二步层次化聚类构建知识树这是HiRAG的精华所在。它没有止步于拥有一堆向量而是对这些向量进行层次化聚类。你可以把这个过程想象成绘制一张地图底层叶子节点就是最初的文本块代表最细粒度的知识。中间层通过聚类算法如K-means、层次聚类将相似的文本块聚合在一起形成一个“主题”。这个主题的表示可以是这些块向量的质心也可以用一个小型语言模型将这些块的内容总结成一段摘要。这个摘要就是这个簇的“标题”或“核心思想”。高层根节点继续对中间层的主题簇进行聚类形成更大的主题范畴并再次生成更高层次的摘要。最终你会得到一棵“知识树”。树根是整个文档的全局主题中间枝干是各大章节叶子则是具体的论述和事实。这种结构完美对应了文档的层次化组织。2.3 第三步构图与桥接——让知识流动起来仅有树结构还不够因为知识间的关系不仅仅是上下级的包含关系还有跨章节、跨主题的关联。HiRAG在这里引入了图结构这也是它名字中“Graph”的由来。它会把每个聚类节点包括叶子文本块和中间层的摘要节点都当作图中的一个顶点Vertex。然后它会在图中创建三种类型的边Edge父子边连接不同层级的聚类节点形成树状结构代表“包含”关系。相似边在同一层级内如果两个节点的向量表示非常相似超过某个阈值就在它们之间建立一条边。这代表了“相关主题”或“平行论述”的关系。桥接边这是HiRAG一个巧妙的设计。它会在不同层级的节点之间如果发现语义高度相关也建立连接。比如一个底层的具体技术细节节点可能直接与高层的某个核心概念节点紧密相连。这打破了严格的树形层次允许检索直接跨越层级捕捉那些隐藏在深层细节中的重要概念与高层主题的强关联。构建好这个富含多层次、多类型关系的图之后HiRAG的检索就不再是简单的向量相似度搜索了。当一个问题进来时系统会将问题也嵌入成向量。在图中的多个层级同时发起检索。既在高层主题中寻找相关方向也在底层细节中寻找具体证据。利用图中的边进行扩散。例如找到一个相关的高层主题节点后可以沿着“父子边”找到其下属的所有细节节点或者通过“桥接边”直接跳转到另一个分支的关键细节上。综合从不同层级、不同路径检索到的节点信息形成一个全面且结构化的上下文最后交给大语言模型生成最终答案。这种“分层检索图扩散”的模式使得HiRAG能同时保证答案的广度覆盖多个相关主题和深度深入关键细节这正是它相比传统RAG表现更优的根本原因。3. 环境搭建与快速上手避开初学者的坑理论说得再多不如跑一行代码。HiRAG项目的安装相对 straightforward但有些细节不注意容易卡住。我们一步步来。3.1 系统与Python环境准备项目推荐使用Python 3.9。我个人的经验是在3.10或3.11上兼容性最好。首先把代码拉下来git clone https://github.com/hhy-huang/HiRAG.git cd HiRAG接下来是安装依赖。项目提供了setup.py所以最干净的方式是使用pip install -e .进行可编辑安装。这里有个关键点如果你在国内可能会遇到下载某些包特别是transformers,torch慢或失败的问题。建议先配置好pip镜像源或者对于torch先去 官网 根据你的CUDA版本获取安装命令单独安装然后再安装HiRAG。# 示例先安装PyTorch (请根据你的环境选择合适版本) pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # 然后安装HiRAG pip install -e .如果安装过程中报错缺少某些系统依赖比如graphviz用于绘图请根据错误提示用系统包管理器安装如apt-get install graphviz或brew install graphviz。3.2 核心配置详解config.yaml是你的控制台安装成功后别急着运行示例先花5分钟看一下项目根目录下的config.yaml文件。这个文件是HiRAG的“大脑”所有重要的模型选择、API密钥、参数都在这里配置。embedding: model: text-embedding-3-small # 嵌入模型可选 openai 或 sentence-transformers 的模型 api_base: https://api.openai.com/v1 # 如果使用OpenAI嵌入此处填写API地址 api_key: your_openai_api_key_here # 你的OpenAI API Key llm: model: gpt-4o-mini # 用于生成摘要和最终答案的LLM api_base: https://api.openai.com/v1 api_key: your_openai_api_key_here graph: storage_path: ./storage # 图数据库的存储路径 similarity_threshold: 0.8 # 构建相似边和桥接边的阈值实操心得嵌入模型选择默认的text-embedding-3-small效果和速度平衡得很好。如果你完全离线部署可以换成sentence-transformers模型比如all-MiniLM-L6-v2但需要在代码中确认HiRAG是否支持直接调用SentenceTransformer库。根据我的测试修改embedding.py中的相关代码可以适配。API密钥安全千万不要把带有真实API密钥的config.yaml上传到Git建议将api_key的值设置为环境变量然后在配置文件中通过os.environ.get(OPENAI_API_KEY)读取。图存储路径storage_path定义了图数据库默认使用networkx在内存中构建但持久化信息会存到磁盘和缓存文件的位置。如果处理大量文档请确保该路径有足够磁盘空间。3.3 第一个例子让HiRAG读懂你的文档配置好后我们来跑通第一个例子。项目根目录下的hi_Search_openai.py或deepseek、glm版本是一个完整的脚本。但为了理解我们拆解一个最简化的交互式流程from hirag import HiRAG from hirag.core.query_param import QueryParam # 1. 初始化HiRAG引擎 # 这里参数很重要 # working_dir: 工作目录存放缓存、临时文件等。 # enable_llm_cache: 强烈建议开启对LLM生成的摘要进行缓存能极大节省成本和时间。 # enable_hierachical_mode: 必须为True这是启用分层模式的核心。 # embedding_batch_num: 嵌入处理的批大小根据你的GPU内存或API速率限制调整。 # enable_naive_rag: 如果设为True会同时维护一个扁平化的向量库用于对比实验。 graph_func HiRAG( working_dir./my_hirag_workspace, enable_llm_cacheTrue, enable_hierachical_modeTrue, embedding_batch_num4, embedding_func_max_async2, # 控制并发数避免超过API限制 enable_naive_ragFalse # 初次使用先专注于HiRAG本身 ) # 2. 知识注入喂给它一篇文档 with open(./my_document.txt, r, encodingutf-8) as f: document_text f.read() graph_func.insert(document_text) print(文档索引完成) # 这个过程会触发分割 - 嵌入 - 层次聚类 - 构图。对于一篇几万字的文档可能需要几分钟。 # 3. 进行查询 question 我的文档中提到了哪些机器学习模型它们分别适用于什么场景 # QueryParam的mode参数指定检索模式 # - hi: 完整的层次化检索默认且推荐 # - naive: 传统扁平检索需enable_naive_ragTrue # - hi_nobridge: 分层检索但不使用桥接边 # - hi_local: 仅检索叶子节点细节 # - hi_global: 仅检索高层摘要节点主题 # - hi_bridge: 仅检索通过桥接边关联的节点 answer graph_func.query(question, paramQueryParam(modehi)) print(HiRAG的回答) print(answer)运行这段代码你会看到HiRAG首先会输出它构建图的过程聚类层级、节点数量等然后给出答案。第一次运行因为要调用LLM生成各层摘要可能会比较慢且消耗API Token但有了缓存之后后续查询和插入新文档都会快很多。注意事项insert操作不是幂等的。重复插入同一份文档会导致图中出现大量重复节点。通常一个知识库初始化时一次性插入所有文档后续以增量文档为单位进行插入。如果文档非常长聚类和摘要生成可能耗时很长。可以关注终端日志了解进度。生成的答案质量很大程度上取决于你使用的LLMconfig.yaml中的llm.model。对于复杂推理gpt-4或gpt-4o系列通常比gpt-3.5-turbo产生更结构化、更准确的答案。4. 深入源码拆解HiRAG的三大核心模块要真正掌握HiRAG不能只当调包侠。我们深入其源码主要看hirag/目录理解它如何实现分层、构图和检索。4.1 模块一splitter与embedder——数据的预处理在insert函数中文档首先被送入splitter。HiRAG提供了多种分割器默认的SemanticSplitter会利用嵌入模型计算句子间的余弦相似度在相似度低于阈值的地方切分。这比按字符数切分智能得多能保证块内的语义连贯性。接着embedder负责将每个文本块转化为向量。代码抽象得很好支持OpenAI API和SentenceTransformers本地模型两种方式。如果你追求低延迟和零网络成本集成一个本地嵌入模型是值得的。4.2 模块二cluster与summarizer——层次化知识提炼这是最核心的部分在hierarchical.py中。HierarchicalBuilder类负责协调整个层次化构建流程。底层叶子节点就是初始的文本块及其嵌入。递归聚类算法采用自底向上的方式。它先对叶子节点进行聚类比如使用K-means形成第一层中间节点。每个簇的“表示”由summarizer模块生成。summarizer会调用配置的LLM将簇内所有文本块的内容作为上下文要求其生成一个简洁、准确的摘要。这个摘要文本会作为一个新的节点加入图并链接到其下属的所有文本块节点。迭代向上然后将这一层新生成的摘要节点作为新的输入重复聚类和摘要生成过程直到满足停止条件如簇数量达到最小值或聚类质量低于阈值。这样就形成了一棵树。一个关键技巧聚类数量K值不是固定的。HiRAG采用了一种自适应策略例如根据轮廓系数silhouette score或节点数量动态调整以找到当前层级最合理的主题划分。4.3 模块三graph与retriever——图上的智能检索图结构在graph.py中构建和维护。KnowledgeGraph类管理所有的节点和边。节点包含id、文本内容、嵌入向量、层级、类型是原始块还是摘要等属性。边如前所述有PARENT_CHILD、SEMANTIC_SIMILAR、BRIDGE三种类型。BRIDGE边的创建是另一个亮点系统会计算所有节点对之间的语义相似度如果两个节点来自不同层级且相似度超过一个较高的阈值如config.yaml中的similarity_threshold就在它们之间建立桥接边。这相当于在知识树中增加了“快捷通道”。检索发生在retriever.py。HierarchicalRetriever的retrieve方法是灵魂多向量查询它不仅仅用问题的向量去搜索。有时它还会将问题稍作改写生成多个相关查询的向量以提高召回率。分层搜索它在图的每一层都执行向量相似度搜索找到该层与问题最相关的Top-K个节点。图扩散以上一步搜到的节点为起点在图上游走。游走策略是既会沿着父子边向下探索细节也会沿着相似边和桥接边向周围探索相关概念。这个过程会收集到一系列节点。去重与排序收集到的节点可能来自不同层级且有重复内容。Retriever会根据节点与原始问题的相关性、节点自身的层级高层摘要通常更重要但底层细节是证据以及节点在图中的度中心性连接多的节点可能更关键进行综合打分和排序。上下文组装将排名靠前的节点文本按一定逻辑如按层级从高到低组装成最终的提示词上下文送给LLM生成答案。通过阅读这部分源码你能深刻理解为什么HiRAG的检索结果更全面。它不是一次搜索而是一个在知识网络上进行的、有策略的“探索”过程。5. 实战用HiRAG构建专业领域问答系统现在我们脱离示例实战一个场景构建一个计算机科学论文知识库的问答系统。假设我们有几百篇AI顶会论文的PDF摘要文本。5.1 数据准备与批量导入论文数据通常是PDF格式我们需要先将其转换为文本。可以使用pdfplumber或PyMuPDF库。假设我们已经有了一个包含所有论文摘要的文本文件papers.txt每篇摘要之间用\n---\n分隔。HiRAG的insert方法一次接受一个文档字符串。对于批量导入我们需要稍作处理from hirag import HiRAG import logging logging.basicConfig(levellogging.INFO) # 查看详细日志 graph_func HiRAG( working_dir./cs_paper_kg, enable_llm_cacheTrue, enable_hierachical_modeTrue, embedding_batch_num8 ) with open(papers.txt, r, encodingutf-8) as f: all_content f.read() # 按分隔符分割成单篇论文摘要 paper_abstracts all_content.split(\n---\n) for i, abstract in enumerate(paper_abstracts): if abstract.strip(): # 跳过空文本 print(f正在插入第 {i1} 篇论文...) graph_func.insert(abstract.strip()) print(所有论文知识导入完成)这个过程可能会比较耗时因为每篇摘要都会触发一次层次化构建。对于海量文档论文中提到可以采用“先分块聚类再整体构建”的两阶段法来优化但当前开源版本是逐文档插入的。对于相关性极强的文档集如同一主题的多篇论文也可以考虑将它们合并成一个“大文档”再插入让HiRAG自行发现内部的层次结构。5.2 设计查询策略与Prompt优化默认的查询可能已经不错但针对专业领域我们可以设计更精准的查询模式。例如我们想让系统不仅能回答问题还能推荐相关论文。我们可以继承或修改QueryParam或者更简单地在调用query前后进行处理from hirag.core.query_param import QueryParam def query_with_recommendation(question, top_k_papers3): # 1. 获取标准答案 answer graph_func.query(question, paramQueryParam(modehi)) # 2. 单独获取检索到的最相关的源文本节点通常是叶子节点 # 注意这里需要稍微hack一下因为当前版本未直接暴露检索到的节点列表。 # 一种方法是修改retriever代码使其返回节点信息。 # 另一种更简单但粗糙的方法用“naive”模式快速检索一次获取最相关的原始文本块这些块通常对应论文摘要。 if graph_func.enable_naive_rag: naive_result graph_func.query(question, paramQueryParam(modenaive)) # 解析naive_result或通过其他方式获取源文本这里需要根据实际返回结构调整 # 假设我们能拿到源文本列表 source_chunks # recommended_titles extract_titles_from_chunks(source_chunks[:top_k_papers]) # 需要自己实现提取标题的函数 pass # 3. 将答案和推荐论文组合返回 final_output f{answer}\n\n【相关论文推荐】\n # \n.join(recommended_titles) return final_output # 使用 question 目前解决大语言模型幻觉问题的主流方法有哪些各自的优缺点是什么 response query_with_recommendation(question) print(response)为了获得更好的答案我们还可以通过config.yaml自定义LLM的system prompt引导模型以更学术、更严谨的风格回答并注重引用检索到的信息。5.3 效果评估与迭代构建完系统后我们需要评估其效果。HiRAG项目自带了一个评估框架./eval目录我们可以借鉴其思路。构建测试集准备一组(问题 标准答案)对。问题应覆盖你知识库的核心主题且答案能从文档中推断出。自动评估使用LLM-as-a-Judge的方式。例如将HiRAG生成的答案和标准答案或检索到的上下文一起交给一个更强的LLM如GPT-4让其从“事实一致性”、“答案完整性”、“逻辑性”等维度打分。人工评估随机抽样一批问题人工判断答案质量。这是最可靠的方法。根据评估结果你可能需要调整分割策略如果答案总是遗漏关键信息可能是分割太碎把连贯内容切开了。可以尝试调整SemanticSplitter的相似度阈值。聚类参数在hierarchical.py中可以调整聚类的停止条件、每层期望的簇数量等以改变知识树的“粒度”。检索参数如图扩散的步数、每层检索的节点数Top-K、相似度阈值等这些会影响召回信息的广度和精度。Prompt工程优化最终生成答案时的提示词让LLM更好地利用检索到的层次化上下文。6. 常见问题与排查技巧实录在实际部署和调试HiRAG的过程中我踩过不少坑这里总结一下希望能帮你节省时间。6.1 问题一构建知识图谱速度太慢特别是摘要生成环节现象插入一个稍大的文档如100页PDF程序运行了半小时还没结束卡在“Summarizing cluster...”阶段。原因分析层次化聚类需要为每一层的每一个簇调用LLM生成摘要。如果文档复杂聚类层数多、簇数量多那么API调用次数会呈指数级增长成为主要瓶颈。解决方案启用并确保LLM缓存有效enable_llm_cacheTrue是必须的。检查working_dir下是否生成了缓存文件如sqlite数据库。相同的文本内容不会重复请求LLM。调整聚类粒度修改hierarchical.py中的聚类参数。例如增大每层聚类的最小簇大小min_cluster_size或者设置一个更早的停止条件如最大层数max_levels以减少总的簇数量。使用更便宜的摘要模型在config.yaml中为摘要生成单独指定一个更小、更快的LLM如gpt-3.5-turbo而最终答案生成再用更强的模型。这需要在代码中区分两种LLM调用当前版本可能需稍作修改。离线摘要模型对于极端追求速度的场景可以研究用小型、开源的文本摘要模型如BART、T5本地运行替代API调用。但这需要较强的工程能力来集成。6.2 问题二检索结果似乎不准确答案包含无关信息现象问一个具体问题返回的答案里混入了其他主题的内容。原因分析可能是“桥接边”或“相似边”的阈值设置得太低导致图中节点之间产生了过多的“弱连接”。在检索扩散时很容易从相关节点“游走”到不相关节点。排查与解决检查similarity_threshold这是config.yaml中graph部分的关键参数。默认0.8已经比较严格。如果问题依旧可以尝试提高到0.85或0.9。你可以写个小脚本统计一下图中各类边的数量如果桥接边数量异常多说明阈值可能太低了。审视嵌入模型嵌入模型的质量直接决定向量相似度的可靠性。如果你用的是小模型可能在专业领域语义表示不佳。尝试换用更强大的嵌入模型如text-embedding-3-large或领域微调过的模型。调整检索参数在retriever.py中可以限制图扩散的步数walk_steps或每个节点扩展的邻居数。避免检索过程“跑得太远”。可视化知识图谱HiRAG项目可能没有直接提供可视化工具但你可以利用networkx和matplotlib将构建的图简单画出来。观察一下与你问题相关的节点周围是否连接了大量无关节点。这能提供最直观的线索。6.3 问题三答案缺乏细节过于笼统现象回答总是停留在高层概念无法给出文档中具体的数据、案例或步骤。原因分析检索过程可能过于偏向高层摘要节点。在综合打分排序时高层节点的权重可能过高导致底层细节节点无法进入最终的上下文。解决方案调整检索模式尝试使用QueryParam(modehi_local)强制只检索叶子节点原始文本块。如果这样能得到细节说明问题出在检索策略上。修改Retriever的评分函数这是更根本的解决方式。在retriever.py的_rank_nodes方法中节点最终得分是多种因素相似度、节点度、层级等的加权和。你可以尝试增加底层节点权重或者降低高层摘要节点的权重。例如给叶子节点一个基础加分。检查摘要质量高层摘要如果过于模糊或丢失关键细节也会导致后续检索无力。可以抽样检查一些簇的摘要生成效果考虑优化摘要生成的Prompt要求其“保留关键实体、数据和结论”。6.4 问题四内存占用过高处理大规模文档时崩溃现象当知识库文档总量很大时程序内存使用量激增甚至被系统杀死。原因分析图结构特别是带向量的节点和缓存全部加载在内存中。networkx图对于超大图数十万节点的内存效率并不算最优。解决思路分片索引不要将所有文档塞进一个HiRAG实例。可以按主题、时间等维度建立多个独立的HiRAG索引。查询时先用一个简单的分类器判断问题属于哪个主题再路由到对应的HiRAG实例进行查询。这需要额外的架构设计。使用外部图数据库将KnowledgeGraph的存储后端从内存networkx切换到真正的图数据库如Neo4j或JanusGraph。这需要重写graph.py中的持久化和查询逻辑工程量大但可扩展性最强。优化节点存储对于叶子节点的原始文本可以考虑不常驻内存而是存储在磁盘数据库如SQLite中图中只保留其ID和嵌入向量。需要时再按ID加载文本。6.5 性能与效果权衡速查表问题场景可能原因建议调整方向潜在风险回答笼统缺细节高层节点权重过高摘要丢失细节1. 调低高层节点权重系数2. 优化摘要Prompt强调保留细节3. 尝试mode“hi_local”可能引入无关噪声降低答案连贯性回答包含无关信息图中弱连接过多检索扩散过度1. 提高similarity_threshold2. 减少图扩散步数3. 换用更准的嵌入模型阈值过高可能导致桥接边消失削弱跨层级检索能力构建/查询速度慢LLM摘要调用频繁图结构复杂1. 确保缓存开启2. 增大聚类最小簇大小3. 使用更快的LLM做摘要聚类粒度变粗可能影响层次划分的准确性内存占用大文档量大图节点多1. 分片索引2. 将文本存储外置增加系统架构复杂性最后我的个人体会是HiRAG代表了一种更接近人类认知的RAG设计哲学。它不再把检索视为一个简单的匹配问题而是一个在结构化知识空间中的导航问题。虽然目前的实现还有优化空间如大规模处理效率但其思想非常值得借鉴。在实际应用中你未必需要完全照搬HiRAG但完全可以将其“分层检索”和“图扩散”的核心思路融入到你自己现有的RAG系统中比如在传统的向量检索之后增加一个基于文档章节结构的重排序模块或许就能带来意想不到的效果提升。