ARTICLE DETAIL

资讯详情

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

传统RAG扛不住了?用GraphRAG+知识图谱,我把企业知识库的复杂问答准确率从31%拉到68%

传统RAG扛不住了?用GraphRAG+知识图谱,我把企业知识库的复杂问答准确率从31%拉到68% 传统RAG扛不住了用GraphRAG知识图谱我把企业知识库的复杂问答准确率从31%拉到68%前言一个让我崩溃的真实场景上个月帮一个制造业客户做企业知识库——这也是黑箭科技在AI应用落地服务中经常接到的需求类型。需求听着挺简单——把几百份工艺文档、质量报告、供应商合同扔进去工程师能用自然语言提问。一开始用标准的RAG方案文档分块、向量化、Top-K检索、拼给大模型。简单问题没问题比如304不锈钢的屈服强度是多少秒答。但工程师真正会问的是这种问题哪些供应商提供的原材料在过去3个月出现过质量问题并且涉及到的工艺环节目前在哪些产品线上还在使用标准RAG直接懵了。它去找语义相似的文本块结果返回一堆关于供应商管理流程的泛泛内容。答案是散落在不同文档里的需要跨文档关联实体关系才能拼出来。这个问题我后面用GraphRAG解决了。今天这篇文章就把GraphRAG从原理到代码完整讲一遍。一、标准RAG到底卡在哪先说清楚问题。标准RAG的检索逻辑是把query向量化然后在向量库里找最相似的Top-K个chunk。本质上是在做文本相似度匹配。这套逻辑在三种场景下会翻车场景1跨文档多跳推理答案分布在3-4份文档里需要A文档的实体关联到B文档的关系再跳到C文档的属性。向量检索只能找到听起来像的chunk不会沿着实体关系链去追踪。场景2全局性理解问题比如这批文档讨论的核心主题有哪些这几个部门的业务有什么交叉。这类问题需要对整个语料库有宏观理解不是找几个相似chunk能解决的。场景3精确实体关系查询比如张三负责的项目中哪些项目的预算超过了500万且项目成员中包含外部顾问。这需要精确的实体属性过滤和关系遍历语义相似度根本匹配不上。据2026年NICD的一项研究显示在面对复杂多跳问答时标准向量RAG的准确率大约在28.9%而GraphRAG可以达到65.3%。这个差距还是很明显的。二、GraphRAG到底做了什么不一样的事GraphRAG这个词最早是微软研究院在2024年提出的。核心思路一句话概括不把文档切成孤立的chunk而是从文档中提取出实体和关系构建成一张知识图谱然后基于图谱结构来检索和推理。整个流程分两个阶段索引阶段和检索阶段。2.1 索引阶段从文档到知识图谱第一步文本分块这一步和标准RAG类似把文档切成chunk。不过GraphRAG论文里有个实验结论600 token左右的chunk比2400 token的chunk能提取出多一倍的实体。所以chunk不要切太大。第二步实体和关系提取这一步是核心差异。用LLM对每个chunk做信息抽取识别出实体人、组织、概念、地点和实体间的关系。输出的是三元组格式(张三, 负责, 项目A) (项目A, 使用, 304不锈钢) (304不锈钢, 供应商, 某钢铁公司) (某钢铁公司, 质量问题, 2026年3月批次)第三步图谱构建与实体消歧把所有chunk提取出的三元组合并到一张图里。这里有个巧妙的设计即使LLM在不同chunk中对同一实体的表述不一致比如某钢铁公司和某钢铁集团后续的社区检测算法也能把它们归到同一个社区里间接完成实体消歧。第四步社区检测用Leiden算法对图谱做聚类把关系紧密的实体分到同一个社区。这一步的产出是一组层次化的社区结构。第五步社区摘要让LLM对每个社区生成一段自然语言摘要。这些摘要就是后面全局搜索的基础。2.2 检索阶段Local Search和Global SearchGraphRAG提供两种检索模式Local Search局部搜索适合问具体实体的问题。系统先在图谱中找到query涉及的实体然后沿着关系边向外扩展收集相关实体、关系文本块和社区摘要一起喂给LLM。Global Search全局搜索适合问宏观、全局性的问题。系统遍历不同层级的社区摘要每个摘要给出一个部分回答最后汇总成完整答案。这就像你问一个熟悉公司内部情况的老员工问张三负责什么项目——他脑子里有具体的人和事的关系网Local问公司目前最大的风险点在哪——他对各个部门的情况有整体认知Global三、实战用Python搭建一个GraphRAG原型下面用代码跑一遍完整流程。我会用一个简化版本来演示核心逻辑方便你理解原理后自己扩展。3.1 环境准备# 需要安装的依赖 # pip install langchain langchain-openai networkx leidenalg matplotlib import os import json import re from typing import List, Tuple, Dict, Optional import networkx as nx from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import JsonOutputParser说明一下生产环境里你可以用本地模型Ollama Qwen2.5来替代OpenAI逻辑完全一样。之前黑箭科技在做舆情监控系统的跨文档关联分析时也是类似思路——不同舆情事件之间的人物、组织、话题关系用图谱来管理比纯文本检索靠谱多了。3.2 实体与关系提取class EntityExtractor: 从文本中提取实体和关系 def __init__(self, llm: ChatOpenAI): self.llm llm self.extraction_prompt ChatPromptTemplate.from_template( 请从以下文本中提取所有实体和它们之间的关系。 文本 {text} 请以JSON格式输出格式如下 {{ entities: [ {{name: 实体名, type: 类型(PERSON/ORG/PRODUCT/CONCEPT/LOCATION), description: 简短描述}} ], relationships: [ {{source: 源实体名, target: 目标实体名, relation: 关系描述}} ] }} 注意 1. 实体名使用文本中的原始表述 2. 关系要具体不要只写相关 3. 不要遗漏重要的实体和关系 ) def extract(self, text: str) - Dict: chain self.extraction_prompt | self.llm result chain.invoke({text: text}) try: content result.content # 处理可能被markdown包裹的JSON json_match re.search(rjson\n?(.*?)\n?, content, re.DOTALL) if json_match: content json_match.group(1) return json.loads(content) except json.JSONDecodeError: print(f提取结果解析失败跳过: {text[:100]}...) return {entities: [], relationships: []}3.3 知识图谱构建class KnowledgeGraph: 知识图谱的构建和管理 def __init__(self): self.graph nx.Graph() self.chunks {} # chunk_id - text self.entity_chunks {} # entity_name - [chunk_ids] def add_extraction(self, chunk_id: str, text: str, extraction: Dict): 将一次提取结果加入图谱 self.chunks[chunk_id] text for entity in extraction.get(entities, []): name entity[name] if not self.graph.has_node(name): self.graph.add_node(name, **entity) self.entity_chunks[name] [] self.entity_chunks[name].append(chunk_id) # 更新描述取更长的描述信息量更大 existing_desc self.graph.nodes[name].get(description, ) if len(entity.get(description, )) len(existing_desc): self.graph.nodes[name][description] entity[description] for rel in extraction.get(relationships, []): source rel[source] target rel[target] if self.graph.has_node(source) and self.graph.has_node(target): self.graph.add_edge(source, target, relationrel[relation]) def get_subgraph(self, entity_names: List[str], hop: int 2) - nx.Graph: 获取指定实体的hop跳邻居子图 nodes set(entity_names) for entity in entity_names: if entity in self.graph: for _, neighbor in nx.single_source_shortest_path_length( self.graph, entity, cutoffhop ).items(): nodes.add(neighbor) return self.graph.subgraph(nodes) def get_stats(self) - Dict: return { nodes: self.graph.number_of_nodes(), edges: self.graph.number_of_edges(), chunks: len(self.chunks) }3.4 社区检测与摘要import leidenalg as la import igraph as ig class CommunityDetector: 基于Leiden算法的社区检测 def detect(self, graph: nx.Graph) - Dict[int, List[str]]: 检测图中的社区结构 if graph.number_of_nodes() 2: return {0: list(graph.nodes())} # networkx转igraph node_list list(graph.nodes()) ig_graph ig.Graph() ig_graph.add_vertices(len(node_list)) # 添加边 edge_list [] for u, v in graph.edges(): u_idx node_list.index(u) v_idx node_list.index(v) edge_list.append((u_idx, v_idx)) ig_graph.add_edges(edge_list) # Leiden算法 partition la.find_partition(ig_graph, modularity) communities {} for idx, community_id in enumerate(partition.membership): node_name node_list[idx] if community_id not in communities: communities[community_id] [] communities[community_id].append(node_name) return communities def summarize_communities(self, communities: Dict, graph: nx.Graph, llm: ChatOpenAI) - Dict[int, str]: 让LLM对每个社区生成摘要 summaries {} summary_prompt ChatPromptTemplate.from_template( 以下是一组紧密关联的实体及其关系请用2-3句话总结这个社区的核心主题 实体列表{entities} 关系列表{relationships} 请直接输出摘要不要加前缀。 ) for comm_id, members in communities.items(): entities_info [] for node in members: if node in graph: desc graph.nodes[node].get(description, 无描述) entities_info.append(f{node}: {desc}) relationships_info [] subgraph graph.subgraph(members) for u, v, data in subgraph.edges(dataTrue): relationships_info.append(f{u} --[{data.get(relation, 关联)}]-- {v}) chain summary_prompt | llm result chain.invoke({ entities: \n.join(entities_info[:20]), relationships: \n.join(relationships_info[:30]) }) summaries[comm_id] result.content return summaries3.5 检索与问答class GraphRAGRetriever: GraphRAG检索器 def __init__(self, graph: KnowledgeGraph, community_summaries: Dict, llm: ChatOpenAI): self.graph graph self.summaries community_summaries self.llm llm def local_search(self, query: str, hop: int 2) - str: 局部搜索针对具体实体的问题 # 第一步从query中提取关键实体 extract_prompt ChatPromptTemplate.from_template( 从以下问题中提取关键的实体名称人名、组织名、产品名等 以JSON数组格式输出{query} 只输出JSON数组不要其他内容。 ) chain extract_prompt | self.llm result chain.invoke({query: query}) try: entity_names json.loads(result.content) except: entity_names [] # 第二步在图谱中找到这些实体扩展子图 subgraph self.graph.get_subgraph(entity_names, hophop) # 第三步收集上下文 context_parts [] context_parts.append( 相关实体 ) for node in subgraph.nodes(): desc subgraph.nodes[node].get(description, ) context_parts.append(f- {node}: {desc}) context_parts.append(\n 关系链 ) for u, v, data in subgraph.edges(dataTrue): context_parts.append(f- {u} --[{data.get(relation, 关联)}]-- {v}) context_parts.append(\n 原始文本片段 ) related_chunks set() for entity in entity_names: if entity in self.graph.entity_chunks: related_chunks.update(self.graph.entity_chunks[entity][:3]) for chunk_id in list(related_chunks)[:5]: context_parts.append(self.graph.chunks[chunk_id][:500]) context \n.join(context_parts) # 第四步生成答案 answer_prompt ChatPromptTemplate.from_template( 基于以下知识图谱信息回答问题。如果信息不足以回答请说明。 知识图谱上下文 {context} 问题{query} 请用清晰的中文回答并标注信息来源的实体名称。 ) chain answer_prompt | self.llm return chain.invoke({context: context, query: query}).content def global_search(self, query: str) - str: 全局搜索针对宏观主题的问题 summaries_text \n\n.join([ f[社区{cid}]: {summary} for cid, summary in self.summaries.items() ]) answer_prompt ChatPromptTemplate.from_template( 基于以下各社区的摘要信息回答这个全局性问题。 请综合多个社区的信息给出完整回答。 社区摘要 {summaries} 问题{query} 请给出全面的分析回答。 ) chain answer_prompt | self.llm return chain.invoke({ summaries: summaries_text, query: query }).content3.6 完整Pipeline串联def build_graphrag(documents: List[str], llm: ChatOpenAI): 完整的GraphRAG构建流程 # 1. 实体提取 extractor EntityExtractor(llm) kg KnowledgeGraph() print(f开始处理 {len(documents)} 个文档...) for i, doc in enumerate(documents): extraction extractor.extract(doc) kg.add_extraction(fchunk_{i}, doc, extraction) if (i 1) % 10 0: print(f已处理 {i 1}/{len(documents)} 个文档) stats kg.get_stats() print(f图谱构建完成: {stats[nodes]}个实体, {stats[edges]}条关系) # 2. 社区检测 detector CommunityDetector() communities detector.detect(kg.graph) print(f检测到 {len(communities)} 个社区) # 3. 社区摘要 summaries detector.summarize_communities(communities, kg.graph, llm) print(社区摘要生成完成) # 4. 构建检索器 retriever GraphRAGRetriever(kg, summaries, llm) return retriever # 使用示例 if __name__ __main__: llm ChatOpenAI(modelgpt-4o, temperature0) # 假设这些是你的企业文档chunks docs [ 张三是项目A的负责人项目A使用304不锈钢作为主要材料。项目A预算800万元。, 项目A的304不锈钢由某钢铁公司供应2026年3月该批次材料出现过强度不达标的质量问题。, 某钢铁公司同时还是项目B和项目C的材料供应商。项目B目前仍在量产阶段。, 项目A的产品目前应用在新能源汽车产线上。项目B的产品应用在建筑工程领域。, 项目C的负责人是李四预算300万元使用的是铝合金材料。, 新能源汽车产线在2026年Q1的良品率为92.3%主要质量问题集中在焊接环节。, ] retriever build_graphrag(docs, llm) # 局部搜索具体实体关系查询 answer retriever.local_search(张三负责的项目用了哪家供应商的材料) print(f\n局部搜索回答:\n{answer}) # 全局搜索宏观分析 answer retriever.global_search(这些文档涉及的主要业务风险有哪些) print(f\n全局搜索回答:\n{answer})上面是一个精简但完整的GraphRAG原型。实际生产环境你还需要加上向量检索做混合召回、增量更新机制、权限过滤等但核心思路就是这些。四、GraphRAG vs 标准RAG什么场景用什么方案做了几个项目下来我的经验是不是所有场景都需要GraphRAG。维度标准向量RAGGraphRAG索引速度快一次embedding慢需要多次LLM抽取适合问题单文档事实性查询跨文档多跳推理、全局理解复杂问答准确率~29%NICD 2026研究~65%同研究基础设施向量数据库即可向量库 图数据库可追溯性chunk级引用实体/关系/社区级引用更新成本低重新embed新chunk高新文档需要LLM抽取什么时候用标准RAG就够了FAQ类问答答案在单一段落里简单的知识查询不需要跨文档关联文档数量大但查询模式简单什么时候值得上GraphRAG企业知识库文档之间有大量实体关系需要回答谁跟什么有什么关系这类问题需要全局视角的分析性问题对答案的可追溯性有要求金融、法律、医疗最佳实践是混合使用用query分类器判断问题类型简单问题走向量RAG复杂问题走GraphRAG。腾讯优图实验室今年9月刚开源的Youtu-GraphRAG框架就实现了这种四层知识树属性层、关系层、关键词层、社区层的混合检索据公开数据显示token成本降低了90.71%复杂推理准确率提升了16.62%。五、2026年的新动向GraphRAG这条赛道今年发展很快几个值得关注的进展1. Agentic GraphRAG有人做了对比实验据DEV Community上的TigerGraph Hackathon技术分享标准RAG准确率18%GraphRAG 32.5%而Agentic GraphRAG达到40.4%。Agentic版本的核心改进是让LLM在一个循环里自主决策——调用工具、看结果、判断是否足够、不够就换个策略再试。这解决了GraphRAG一次查询定生死的刚性问题。2. 轻量级替代方案LightRAG如果觉得完整GraphRAG太重LightRAG提出了双层次检索低层实体检索 高层关系检索支持增量更新而不需要全量重建图谱工程落地门槛低很多。3. 本地化部署成熟用Ollama Qwen2.5本地跑GraphRAG已经可行。据tech-insider.org实测13步操作、90分钟左右可以完成本地部署。对于数据敏感的企业场景全本地方案是个很重要的能力。六、踩坑提醒最后分享几个我实际项目里踩过的坑坑1实体提取质量是命脉如果LLM提取的实体和关系质量差后面全白搭。建议在extraction prompt里给few-shot examples并且对关键领域做实体类型的约束。坑2chunk太大实体提取效果断崖式下降GraphRAG论文的实验数据是600 token vs 2400 token前者提取效率是后者的两倍。别偷懒用大chunk。坑3社区摘要的token消耗容易被忽视每个社区都要过一次LLM生成摘要社区数量多的时候这个成本不低。建议控制社区摘要长度设个上限。坑4别迷信全Graph有些查询就是简单的关键词匹配硬要走图谱反而慢。混合路由才是正解。总结GraphRAG不是标准RAG的替代品而是互补品。标准RAG擅长找相似GraphRAG擅长找关系。在企业级知识库这种实体密集、关系复杂的场景里GraphRAG的优势非常明显。2026年了如果你的企业知识库还在用纯向量RAG扛复杂问答建议认真评估一下GraphRAG方案。构建成本确实更高但对于合适的问题类型准确率的提升是实打实的。我们在黑箭科技实际项目中验证过GraphRAG在跨文档实体关联类问答上的表现确实比纯向量方案强很多。核心要点回顾标准RAG在跨文档推理和全局理解上有硬伤GraphRAG通过知识图谱 社区检测 分层检索来解决这些问题实际落地建议混合路由不要一刀切实体提取质量是整个系统的生命线你在项目中用过GraphRAG吗或者你的知识库遇到了标准RAG解决不了的复杂问答欢迎在评论区聊聊你的场景和踩过的坑。如果觉得这篇文章有帮助点赞收藏关注三连后续我会继续更新AI应用开发的一线实战经验。
返回列表