向量检索不够用?GraphRAG才是多跳推理的答案
05篇讲高级RAG架构时GraphRAG只占了一小节——当时受限于篇幅只给了LlamaIndex的基本用法。但GraphRAG是2024-2025年RAG领域最重要的突破之一微软开源的GraphRAG项目已经拿了2万多star值得专门一篇拆透。你可能会问向量检索已经够用了为什么要搞知识图谱我举个真实场景公司有500份文档用户问我们公司所有产品线之间的技术依赖关系是什么。向量检索能做的是找到跟产品线和技术依赖语义相似的文档片段。但这些片段是分散的——产品A用了技术X产品B依赖产品A产品C集成了产品B——这些跨文档的实体关联向量检索根本串不起来。GraphRAG的思路先从文档里抽取实体和关系构建知识图谱然后用图的结构化信息做检索。向量检索找的是像不像GraphRAG找的是有没有关系。这篇把GraphRAG从原理到实现拆透实体抽取→关系构建→社区检测→全局/局部检索→工程落地。GraphRAG vs 向量RAG什么时候该换方案先搞清楚一个问题不是所有场景都需要GraphRAG。维度向量RAGGraphRAG擅长的问题“X是什么”、“怎么做X”“X和Y什么关系”、“整体趋势是什么”检索粒度文本片段chunk实体关系社区摘要全局性问题差只能找局部相似片段强图结构天然支持全局推理多跳推理靠迭代检索容易丢线索图遍历一次到位建索引成本低切分Embedding高LLM抽取实体关系索引耗时500篇文档约5分钟500篇文档约30-60分钟索引成本Embedding API费用LLM调用费用抽取实体很费token查询延迟200-500ms500-2000ms图遍历社区摘要判断标准很简单如果你的用户经常问A和B什么关系、“整体情况怎么样”、X影响了哪些东西这类需要跨文档关联的问题向量RAG搞不定就该上GraphRAG。如果用户问的都是退货流程是什么这种单文档能回答的向量RAG就够了别折腾。GraphRAG的核心流程四步走微软GraphRAG的流程分四个阶段文档 → ① 文本切分 → ② 实体关系抽取 → ③ 社区检测 → ④ 社区摘要 ↓查询 → 局部检索实体关系 或 全局检索社区摘要每个阶段都有坑咱们逐个拆。阶段一文本切分GraphRAG的切分和向量RAG不一样。向量RAG追求chunk大小均匀200-500字符GraphRAG追求实体完整性——一个chunk里应该包含完整的实体描述和关系信息。from langchain_text_splitters import RecursiveCharacterTextSplitterdef graph_rag_chunking(documents, chunk_size1200, chunk_overlap100): GraphRAG专用切分chunk比向量RAG大保证实体完整 微软默认用1200 token约1800字符比向量RAG的500字符大很多。 原因小chunk会把一个实体描述截断抽取出的关系不完整。 splitter RecursiveCharacterTextSplitter( chunk_sizechunk_size, chunk_overlapchunk_overlap, separators[\n\n, \n, 。, , , ], ) chunks splitter.split_documents(documents) print(f切分完成{len(chunks)}个chunk平均长度{sum(len(c.page_content) for c in chunks)//len(chunks)}字符) return chunks坑1chunk太大导致抽取遗漏。我一开始用2400字符的chunk结果LLM抽取实体时只抽了前半部分的关系后半部分的实体被忽略了。原因是LLM的注意力在长文本里会衰减。微软默认1200 token是经验值别随便改大。阶段二实体和关系抽取这是GraphRAG最核心也是成本最高的一步。用LLM从每个chunk里抽取实体人名、公司名、产品名、技术名和关系A是B的CEO、A依赖B、A包含B。from langchain_core.prompts import ChatPromptTemplatefrom langchain_core.output_parsers import PydanticOutputParserfrom pydantic import BaseModel, Fieldfrom typing import Optionalclass Entity(BaseModel): 实体定义 name: str Field(description实体名称) type: str Field(description实体类型PERSON/ORGANIZATION/PRODUCT/TECHNOLOGY/CONCEPT/LOCATION) description: str Field(description实体描述一句话说明这个实体是什么)class Relationship(BaseModel): 关系定义 source: str Field(description源实体名称) target: str Field(description目标实体名称) description: str Field(description关系描述如是CEO、依赖、包含) strength: float Field(description关系强度0-11表示强关系, default0.5)class ExtractionResult(BaseModel): 单个chunk的抽取结果 entities: list[Entity] relationships: list[Relationship]def extract_entities_relations(chunks, llm): 从每个chunk抽取实体和关系 这一步是GraphRAG成本的大头每个chunk都要调用一次LLM。 500个chunk 500次LLM调用GPT-4o约$30-50。 parser PydanticOutputParser(pydantic_objectExtractionResult) extract_prompt ChatPromptTemplate.from_template( 你是一个信息抽取专家。请从以下文本中抽取实体和关系。抽取规则1. 实体类型PERSON人物、ORGANIZATION组织、PRODUCT产品、TECHNOLOGY技术、CONCEPT概念、LOCATION地点2. 关系实体之间的关联如任职于、开发、依赖、收购、合作等3. 每个实体必须有简短描述4. 关系强度明确提到0.9间接推断0.5弱关联0.35. 只抽取文本中明确出现的信息不要编造文本{text}{format_instructions} ) all_entities [] all_relationships [] for i, chunk in enumerate(chunks): chain extract_prompt | llm | parser result chain.invoke({ text: chunk.page_content, format_instructions: parser.get_format_instructions(), }) # 记录每个实体来自哪个chunk方便后续追溯 for entity in result.entities: entity.description f (来源: chunk_{i}) all_entities.append(entity) for rel in result.relationships: all_relationships.append(rel) if (i 1) % 50 0: print(f已处理 {i1}/{len(chunks)} 个chunk累计抽取{len(all_entities)}个实体{len(all_relationships)}条关系) return all_entities, all_relationships坑2实体名称不统一。同一个张三可能被抽成张三、“张三CEO”、Zhang San三种。后面构建图谱时这会被当成三个不同节点。解决加一层实体合并/消歧。def merge_entities(entities): 合并同名实体简单版名称归一化后合并描述 entity_map {} # 归一化名称 → 合并后的实体 for entity in entities: # 归一化去空格、去括号注释、统一大小写 normalized_name entity.name.strip() # 去掉括号里的注释如 张三CEO → 张三 if in normalized_name: normalized_name normalized_name.split()[0] if ( in normalized_name: normalized_name normalized_name.split(()[0] if normalized_name in entity_map: # 已存在合并描述 entity_map[normalized_name].description | entity.description else: entity.name normalized_name entity_map[normalized_name] entity return list(entity_map.values())Java类比实体合并就像你做用户数据清洗——同一个用户在三个系统里注册了三个账号你得按手机号或邮箱归一化成一个用户。GraphRAG的实体合并是同样的逻辑。不合并的话图里全是孤立节点跟没有图一样。阶段三社区检测抽取完实体和关系后你有一个图节点是实体边是关系。但这个图可能很大很乱——500个文档可能抽出上万个实体几万条关系。直接用这个图检索效率很低。社区检测Community Detection把图分成若干社区——每个社区是一组紧密关联的实体。比如产品线A相关的所有人和技术可能构成一个社区产品线B构成另一个社区。GraphRAG用的是Leiden算法比Louvain更精确的社区检测算法import networkx as nxdef build_graph_and_detect_communities(entities, relationships): 构建图 社区检测 # 1. 构建NetworkX图 graph nx.Graph() # 添加节点 for entity in entities: graph.add_node(entity.name, typeentity.type, descriptionentity.description) # 添加边关系强度作为边权重 for rel in relationships: if graph.has_edge(rel.source, rel.target): # 已有边取最大强度 graph[rel.source][rel.target][weight] max( graph[rel.source][rel.target][weight], rel.strength ) graph[rel.source][rel.target][description] | rel.description else: graph.add_edge(rel.source, rel.target, weightrel.strength, descriptionrel.description) print(f图构建完成{graph.number_of_nodes()}个节点{graph.number_of_edges()}条边) # 2. 社区检测使用贪婪模块度算法NetworkX内置效果接近Leiden communities nx.community.greedy_modularity_communities(graph, weightweight) print(f社区检测完成{len(communities)}个社区) for i, community in enumerate(communities): print(f 社区{i}: {len(community)}个实体 - {list(community)[:5]}...) return graph, communities坑3社区粒度不好控制。社区太大一个社区几百个实体摘要会丢失细节社区太小一个社区2-3个实体摘要没什么信息量。微软GraphRAG用分层社区——先检测大社区再在大社区里检测子社区构建多层级结构。查询时可以根据问题粒度选择不同层级的社区。def build_hierarchical_communities(graph, max_levels3): 分层社区检测每层在前一层的社区内继续细分 all_levels [] # 第0层全图社区检测 communities list(nx.community.greedy_modularity_communities(graph, weightweight)) all_levels.append(communities) # 递归在每个社区内继续检测子社区 for level in range(1, max_levels): sub_communities [] for community in all_levels[-1]: if len(community) 5: # 太小的社区不再细分 sub_communities.append(community) continue subgraph graph.subgraph(community) sub_comms list(nx.community.greedy_modularity_communities(subgraph, weightweight)) sub_communities.extend(sub_comms) if len(sub_communities) len(all_levels[-1]): break # 没有进一步细分停止 all_levels.append(sub_communities) return all_levels阶段四社区摘要每个社区生成一段LLM摘要描述这个社区包含哪些实体、它们之间的关系、整体在讲什么。全局检索时就用这些摘要。def generate_community_summaries(communities, graph, llm): 为每个社区生成摘要 summary_prompt ChatPromptTemplate.from_template( 请总结以下实体及其关系形成一段连贯的概述。实体和关系{graph_info}要求1. 提炼核心主题和关键关系2. 不要简单罗列实体要总结出这个群体在做什么3. 200字以内概述 ) summaries [] for i, community in enumerate(communities): # 收集社区内所有实体和关系 graph_info [] for node in community: desc graph.nodes[node].get(description, ) graph_info.append(f[{node}] {desc}) # 收集社区内部的关系不含跨社区关系 for edge in graph.subgraph(community).edges(dataTrue): source, target, data edge graph_info.append(f{source} --{data.get(description, 关联)}-- {target}) # 生成摘要 chain summary_prompt | llm summary chain.invoke({graph_info: \n.join(graph_info)}).content summaries.append(summary) if (i 1) % 10 0: print(f已生成 {i1}/{len(communities)} 个社区摘要) return summaries坑4社区摘要成本高。100个社区 100次LLM调用。如果你有1000个社区光摘要就要$100。解决小社区5个实体可以用规则摘要直接拼接实体描述不用LLM。只有大社区才用LLM生成摘要。查询阶段局部检索 vs 全局检索GraphRAG有两种查询模式对应不同类型的问题。局部检索查实体关系用户问张三跟A公司什么关系——这是实体级问题用局部检索。def local_search(query, graph, entities, vectorstore, llm, top_k5): 局部检索找到query相关的实体遍历其邻居关系 局部检索结合了向量检索找相关实体和图遍历找关系。 # 1. 用向量检索找到query最相关的实体 relevant_entities vectorstore.similarity_search(query, ktop_k) entity_names [doc.metadata[entity_name] for doc in relevant_entities] # 2. 图遍历找到这些实体的邻居1跳关系 context [] for entity_name in entity_names: if entity_name not in graph: continue # 实体本身的描述 node_data graph.nodes[entity_name] context.append(f实体: {entity_name} ({node_data.get(type, )})) context.append(f 描述: {node_data.get(description, )}) # 邻居关系 for neighbor in graph.neighbors(entity_name): edge_data graph[entity_name][neighbor] neighbor_type graph.nodes[neighbor].get(type, ) context.append(f 关系: {entity_name} --{edge_data.get(description, 关联)}-- {neighbor} ({neighbor_type})) # 3. 用LLM基于图信息生成回答 graph_context \n.join(context) answer_prompt ChatPromptTemplate.from_template( 基于以下知识图谱信息回答问题。知识图谱信息{graph_context}问题{question}回答 ) chain answer_prompt | llm return chain.invoke({graph_context: graph_context, question: query}).content全局检索查社区摘要用户问公司所有产品线的整体技术架构是什么——这是全局问题用社区摘要。def global_search(query, community_summaries, llm, max_communities10): 全局检索把所有社区摘要分批喂给LLM做Map-Reduce Map阶段每个社区摘要单独生成一个中间回答 Reduce阶段汇总所有中间答案生成最终回答 # Map阶段每个社区摘要生成中间回答 map_prompt ChatPromptTemplate.from_template( 基于以下社区摘要回答问题。如果该社区的信息与问题无关回复无关。社区摘要{summary}问题{question}中间回答 ) intermediate_answers [] for summary in community_summaries[:max_communities]: chain map_prompt | llm answer chain.invoke({summary: summary, question: query}).content if 无关 not in answer: intermediate_answers.append(answer) # Reduce阶段汇总中间答案 reduce_prompt ChatPromptTemplate.from_template( 以下是多个信息来源对同一问题的回答请综合成一个完整的答案。问题{question}各来源回答{answers}综合答案 ) chain reduce_prompt | llm return chain.invoke({ question: query, answers: \n---\n.join(intermediate_answers), }).content坑5全局检索延迟高。Map阶段要调用10次LLM10个社区摘要Reduce阶段再调1次。总延迟5-10秒。解决可以并行Map阶段用asyncio或减少社区数量只选跟query最相关的社区用向量检索筛选。完整Pipeline从文档到查询class GraphRAGPipeline: GraphRAG完整Pipeline def __init__(self, llm, embedding_model): self.llm llm self.embedding embedding_model self.graph None self.communities None self.community_summaries None self.entity_vectorstore None # 实体描述的向量索引 def index(self, documents): 建索引切分→抽取→构图→社区检测→摘要 # 1. 切分 chunks graph_rag_chunking(documents) # 2. 抽取实体和关系 entities, relationships extract_entities_relations(chunks, self.llm) entities merge_entities(entities) # 合并同名实体 # 3. 构图 社区检测 self.graph, self.communities build_graph_and_detect_communities(entities, relationships) # 4. 社区摘要 self.community_summaries generate_community_summaries(self.communities, self.graph, self.llm) # 5. 实体描述建向量索引用于局部检索 from langchain_community.vectorstores import Chroma entity_docs [] for entity in entities: entity_docs.append(Document( page_contententity.description, metadata{entity_name: entity.name, entity_type: entity.type} )) self.entity_vectorstore Chroma.from_documents(entity_docs, self.embedding) print(fGraphRAG索引完成{len(entities)}实体, {self.graph.number_of_edges()}关系, {len(self.communities)}社区) def query(self, question, modeauto): 查询auto模式自动选择局部或全局检索 if mode local: return local_search(question, self.graph, None, self.entity_vectorstore, self.llm) elif mode global: return global_search(question, self.community_summaries, self.llm) else: # auto用LLM判断问题类型 route_prompt f判断以下问题的类型 - 如果是具体实体关系问题X和Y什么关系、X是谁→ local - 如果是全局分析问题整体情况、所有X的、趋势→ global 问题{question} 类型local/global route self.llm.invoke(route_prompt).content.strip().lower() if global in route: return global_search(question, self.community_summaries, self.llm) else: return local_search(question, self.graph, None, self.entity_vectorstore, self.llm)GraphRAG的成本优化GraphRAG最大的问题是贵。500篇文档建索引GPT-4o要$30-50。这里有几个省钱方案优化手段节省比例代价实体抽取用小模型GPT-4o-mini省70%抽取质量下降约15%小社区用规则摘要不用LLM省30%小社区摘要质量略差增量索引只处理新文档省80%需要维护索引版本实体抽取缓存相同chunk不重复抽取省50%需要hash去重批量API调用OpenAI Batch API省50%延迟增加24小时我的建议开发阶段用GPT-4o-mini做实体抽取效果验证后再用GPT-4o重新建索引。或者用本地模型Qwen-14B做抽取零API成本。什么时候该上GraphRAG你的RAG遇到什么问题│├── 用户问A和B什么关系→ 向量检索找不到跨文档关联│ └── 该上GraphRAG│├── 用户问整体趋势/所有产品的依赖关系│ └── 该上GraphRAG全局检索│├── 用户问X影响了哪些东西│ └── 该上GraphRAG图遍历│├── 用户问退货流程是什么→ 单文档能回答│ └── 不需要GraphRAG向量RAG够了│└── 预算有限文档量100篇 └── 不建议上LLM抽取成本太高本篇要点要点说明GraphRAG核心流程切分→实体关系抽取→社区检测→社区摘要局部检索向量检索找实体 图遍历找关系适合实体级问题全局检索Map-Reduce社区摘要适合全局分析问题社区检测Leiden算法把图分成紧密关联的子图分层社区支持多粒度查询成本优化小模型抽取 规则摘要 增量索引 缓存适用场景实体关系密集 全局性问题 跨文档推理踩坑清单chunk太大抽取遗漏超过1500字符LLM注意力衰减后半部分实体被忽略。默认1200 token别改大。实体名称不统一同一实体被抽成多个名字图里变成多个孤立节点。必须做实体合并/消歧。社区粒度不可控一个社区太大或太小都不好用。用分层社区检测查询时选合适层级。社区摘要成本高100个社区100次LLM调用。小社区用规则摘要大社区才用LLM。全局检索延迟高Map阶段串行调用10次LLM要5-10秒。用asyncio并行化或减少参与Map的社区数。增量更新困难新增文档后不能只更新局部图可能影响社区结构。微软GraphRAG的增量更新还在迭代中目前最稳妥的方案是定期全量重建。图太大查询慢上万节点的图遍历会卡。给图建索引如Neo4j的索引或者用图数据库替代NetworkX。踩坑清单补充坑症状解决chunk太大LLM抽取后半部分实体被忽略默认1200token别改大实体名称不统一图里全是孤立节点加实体合并层社区粒度不好太大丢细节太小没信息量分层社区检测社区摘要成本高100个社区100次LLM调用小社区用规则摘要全局检索慢Map阶段串行10次LLMasyncio并行增量更新难新文档可能影响社区结构定期全量重建学AI大模型的正确顺序千万不要搞错了2026年AI风口已来各行各业的AI渗透肉眼可见超多公司要么转型做AI相关产品要么高薪挖AI技术人才机遇直接摆在眼前有往AI方向发展或者本身有后端编程基础的朋友直接冲AI大模型应用开发转岗超合适就算暂时不打算转岗了解大模型、RAG、Prompt、Agent这些热门概念能上手做简单项目也绝对是求职加分王给大家整理了超全最新的AI大模型应用开发学习清单和资料手把手帮你快速入门学习路线:✅大模型基础认知—大模型核心原理、发展历程、主流模型GPT、文心一言等特点解析✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑✅开发基础能力—Python进阶、API接口调用、大模型开发框架LangChain等实操✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经以上6大模块看似清晰好上手实则每个部分都有扎实的核心内容需要吃透我把大模型的学习全流程已经整理好了抓住AI时代风口轻松解锁职业新可能希望大家都能把握机遇实现薪资/职业跃迁这份完整版的大模型 AI 学习资料已经上传CSDN朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】