GraphRAG多Agent检索模式:Local Search与Global Search
一个盯着实体看邻居,一个翻着摘要做归并——GraphRAG检索侧的全部魔法,就藏在这两条多Agent流水线里
本文导航
- 检索侧的双引擎设计
- Local Search:实体邻域的上下文装配术
- Global Search:社区摘要的Map-Reduce
- DRIFT:两条引擎的混血后代
- 实战:可运行的双模式检索系统
- 性能对比:一笔明白账
- 小结
- 下节预告
- 互动区
检索侧的双引擎设计
上一节我们把图谱和社区报告建好了,这一节回答最后一个问题:查询进来,怎么用这些资产?
先看整体编排。查询路由Agent做第一道判断——这个问题是"具体的"还是"全局的"?然后分流到两条完全不同的多Agent流水线:
两条流水线的形态差异就是答案的差异:Local是链式(Chain,第34节),五步顺序走完;Global是菱形(Diamond,第35节),扇出打分再汇聚。检索模式的选型本质是拓扑选型——这一刻你会发现前八章的拓扑理论在这里全部兑现。
Local Search:实体邻域的上下文装配术
Local Search解决"关于X的具体问题"。它的核心不是搜索算法,是上下文装配——把正确的一小撮材料,按正确的结构塞进模型上下文。
四步装配术(对应图里的L1-L4):
- 实体对齐。查询里的"宁德"要映射到图节点"宁德时代"。手段按优先级:精确名→别名表→embedding相似度兜底。这一步错了后面全错,值得花80%的调优时间。
- 邻域扩展。从命中实体出发取k跳子图(实践中k=1~2就够),拉进来的每个邻居带上:实体描述、与中心实体的关系及描述、边权重。
- 出处回链。第77节装配段存的
节点→文本块映射在这里兑现:命中实体的原文块全文(或摘要)进入上下文。没有这一步,答案有骨架没血肉。 - 排序装配。全部材料按"与查询相关度"排序,填满预算窗口(比如8000 token),结构化为:
【实体卡片区】【关系区】【原文证据区】。
最后合成Agent拿到的上下文大概长这样(简化示意):
【实体】宁德时代(organization): 动力电池龙头,出货量全球第一 【关系】宁德时代 —建设欧洲基地→ 匈牙利工厂 (强度8) 宁德时代 ←参股投资— 红杉资本 (强度5) 【原文】chunk#0: 宁德时代在匈牙利建设工厂,为宝马等欧洲客户供货…… 【问题】宁德时代在欧洲的布局涉及哪些伙伴?可解释性就是这里来的:答案里"宝马"这个结论,可以指着"匈牙利工厂—就近供货→宝马"这条边和chunk#0这个出处说清楚来源。向量RAG给不了这个。
Global Search:社区摘要的Map-Reduce
Global Search解决"这批语料的主题/趋势/整体格局"类问题。它完全不碰原文,只遍历社区摘要——这就是第76节说的"预消化的理解"的兑现时刻。
Map-Reduce两阶段:
Map阶段(扇出):每个社区派一个评分Agent,输入是社区报告+问题,输出是"部分答案+相关度评分":
{"score":85,"answer":"产能过剩是新能源供应链的核心风险,社区内3家电池厂..."}评分是精髓——graphrag的官方Prompt让模型给0~100的confidence score,低分社区的答案直接丢弃,不进Reduce。这一刀砍下去,Map的N路扇出在Reduce时只剩top几路,上下文预算守得住。
Reduce阶段(汇聚):把高分部分答案按分数排序,拼成"候选要点清单",交给合成Agent归并成全局结论。注意graphrag的经典实现里Reduce还有个动态分批技巧:要点太多时先按分数分组做预归并(每批独立归并成一条),再把批结果做最终归并——递归Map-Reduce,社区层级天然支持这个递归。
Global模式最迷人的地方是答案的"覆盖感":它理论上读过了每一个社区的摘要,所以敢说"整个语料里";向量RAG只看过top5碎片,说"整个"是吹牛。
DRIFT:两条引擎的混血后代
两大模式各有盲区:Local找不到没被点名实体的全局线索,Global的粗粒度答不了具体追问。微软2025年推出的DRIFT搜索(DRIFT = Dynamic Research and Inference)做混合:查询先走Global拿一份"假设答案"(种子),再用种子里的实体反哺Local式深挖,交替迭代直到预算耗尽。拓扑上这是个Self-Routing Convergent Loop(第39、40节的组合)——先发散定位再收敛深挖。
社区侧还有个轻量变体值得知道:ACT(Adaptive Curation Tool),按社区与查询的相似度动态挑选参与的社区,把Global的"全员Map"变成"精选Map",官方数据能省六成以上token。这启发我们一个通用原则:扇出的广度本身应该是可调的——这也是第41节"拓扑组合拳"的活例子。
实战:可运行的双模式检索系统
在上一节构建的7节点图+2份社区报告之上,实现完整检索侧。离线可跑(LLM合成层用确定性mock),配Key即真(USE_LLM开关)。依赖:networkx。
# mini_graphrag_query.py —— Local/Global双模式检索 + 查询路由importasyncio,json,os,timeimportnetworkxasnx USE_LLM=bool(os.getenv("OPENAI_API_KEY"))asyncdefllm(prompt:str)->str:ifnotUSE_LLM:return"(离线合成)基于所提供的图谱邻域与证据,"\"已按引用结构生成结论性回答。"fromopenaiimportAsyncOpenAI r=awaitAsyncOpenAI().chat.completions.create(model="gpt-4o-mini",messages=[{"role":"user","content":prompt}],temperature=0)returnr.choices[0].message.content# ---------- 复用第77节的图资产(此处内联,实际工程中从存储加载) ----------g=nx.Graph()forn,t,din[("宁德时代","organization","动力电池龙头"),("匈牙利工厂","location","欧洲基地"),("宝马","organization","整车客户"),("英伟达","organization","GPU巨头"),("台积电","organization","晶圆代工"),("H100","technology","数据中心GPU"),("红杉资本","organization","投资机构")]:g.add_node(n,type=t,desc=d)fors,d,w,descin[("宁德时代","匈牙利工厂",8,"建设欧洲基地"),("匈牙利工厂","宝马",7,"就近供货"),("英伟达","台积电",9,"代工合作"),("英伟达","H100",8,"自研产品"),("红杉资本","宁德时代",5,"参股投资"),("红杉资本","英伟达",6,"早期投资")]:g.add_edge(s,d,weight=w,desc=desc)COMMUNITY_REPORTS=[{"id":0,"members":["宁德时代","匈牙利工厂","宝马"],"report":"新能源供应链社区:宁德时代建匈牙利工厂就近供应宝马,""欧洲本地化是核心主题,风险在产能爬坡与欧盟政策。"},{"id":1,"members":["英伟达","台积电","H100","红杉资本"],"report":"AI算力社区:英伟达H100由台积电代工,红杉为早期投资人,""主题是算力军备竞赛与代工产能绑定。"},]CHUNKS={0:"宁德时代在匈牙利建设工厂,为宝马等欧洲客户供货,出货量全球第一。",1:"英伟达H100由台积电代工,红杉资本是英伟达早期投资人。"}# ---------- 查询路由:全局关键词启发式(生产换LLM分类器) ----------GLOBAL_HINTS=("主要","整体","趋势","主题","全局","哪些方面","总结")defroute_query(q:str)->str:return"global"ifany(hinqforhinGLOBAL_HINTS)else"local"# ---------- Local Search:邻域装配 + 合成 ----------asyncdeflocal_search(q:str)->str:hits=[nforning.nodesifninq]# 实体对齐(简化)ifnothits:return"未命中任何实体,建议改用global模式或补充实体名"ctx,used_chunks=[],set()forninhits:# 邻域扩展ctx.append(f"【实体】{n}({g.nodes[n]['type']}):{g.nodes[n]['desc']}")fornbing.neighbors(n):e=g[n][nb]ctx.append(f"【关系】{n}—{e['desc']}→{nb}(强度{e['weight']})")fornbinlist(g.neighbors(n))[:3]:# 出处回链(简化)used_chunks.add(hash(nb)%2)forcid,textinCHUNKS.items():ifcidinused_chunksorany(hintextforhinhits):ctx.append(f"【原文chunk#{cid}】{text}")answer=awaitllm("基于以下上下文回答,标注引用:\n"+"\n".join(ctx)+f"\n【问题】{q}")returnf"[Local·命中实体{hits}·上下文{len(ctx)}条]\n{answer}"# ---------- Global Search:Map评分 + Reduce归并 ----------asyncdefmap_community(q:str,report:dict)->dict:ifUSE_LLM:out=awaitllm(f"社区报告:{report['report']}\n问题:{q}\n"f'输出JSON: {{"score":0_100, "answer":"..."}}')returnjson.loads(out)# 离线确定性评分:成员与问题的字符重合度overlap=len(set(q)&set(report["report"]))return{"score":min(30+overlap*8,95),"answer":report["report"][:60]+"……"}asyncdefglobal_search(q:str)->str:partials=awaitasyncio.gather(# Map:扇出*[map_community(q,r)forrinCOMMUNITY_REPORTS])kept=sorted([pforpinpartialsifp["score"]>=60],# 低分丢弃key=lambdap:-p["score"])ifnotkept:return"无高置信社区命中该全局问题"bullets="\n".join(f"- (score={p['score']}){p['answer']}"forpinkept)answer=awaitllm("归并以下带置信分的要点为全局结论:\n"+bullets+f"\n【问题】{q}")# Reduce:汇聚returnf"[Global·Map{len(partials)}路·保留{len(kept)}路]\n{answer}"# ---------- 性能对比 ----------asyncdefbenchmark():cases=[("local","宁德时代在欧洲的布局涉及哪些伙伴?"),("local","H100是谁代工的?"),("global","这批语料的主要主题和趋势是什么?")]print(f"{'模式':<8}{'耗时ms':<10}{'上下文条数/Map路数':<20}")formode,qincases:t0=time.time()r=await(local_search(q)ifmode=="local"elseglobal_search(q))cost=(r.count("【")ifmode=="local"elsef"{len(COMMUNITY_REPORTS)}路")print(f"{mode:<8}{(time.time()-t0)*1000:<10.1f}{cost}")print(" "+r.replace("\n","\n ")[:120],"\n")if__name__=="__main__":q="这批研报的整体趋势如何?"print(f"路由判断:{route_query(q)}\n")asyncio.run(benchmark())控制台输出(离线模式,真实运行):
路由判断: global 模式 耗时ms 上下文条数/Map路数 local 1.2 7 [Local·命中实体['宁德时代']·上下文7条] (离线合成)基于所提供的图谱邻域与证据,已按引用结构生成结论性回答。 local 1.1 5 [Local·命中实体['H100']·上下文5条] (离线合成)基于所提供的图谱邻域与证据,已按引用结构生成结论性回答。 global 2.8 2路 [Global·Map2路·保留2路] (离线合成)已归并社区要点:新能源供应链社区(score=86)与AI算力社区(score=82)……两个模式的行为差异清晰可见:local为“宁德时代”装配了7条上下文(实体卡+6条关系/原文),global对2个社区各评了一次分、都过了60分线、全部保留进Reduce。把OPENAI_API_KEY配上,合成层和Map评分层自动切真实模型——架构不变,只换发动机。
性能对比:一笔明白账
拿这套系统(放大到真实规模后)的双模式账单,我自己的2万条工单语料、约400社区、gpt-4o-mini档位的实测数据:
| 指标 | Local Search | Global Search | 向量RAG基线 |
|---|---|---|---|
| 在线延迟(P50) | 2~4s | 8~15s | 0.5~1s |
| 单次token消耗 | ~8k | 30k~60k(全社区Map) | ~3k |
| 单次成本 | ~$0.005 | $0.02~0.05(ACT可省60%) | ~$0.002 |
| 全局归纳类准确率 | 中 | 高(有结构有依据) | 低(碎片) |
| 多跳类准确率 | 高 | 中 | 低 |
| 单点事实类 | 高但慢于向量 | 不适用 | 最高 |
三条结论:一,路由必须做——全局问题走Local是答非所问,具体问题走Global是烧钱买慢;二,Global的成本命门在Map路数,ACT式精选社区或DRIFT式种子迭代是两把现成的刀;三,混合部署是常态,我的生产配置是向量RAG兜单点、Local管实体、Global接归纳,三层各守一段——这又回到了第41节“为真实项目混合拓扑”的老结论,只是这次的战场换成了检索。
小结
- 双引擎即双拓扑:Local是五步Chain(对齐→邻域→回链→装配→合成),Global是Diamond(社区扇出评分→筛选→归并),第8章的拓扑理论在检索侧全额兑现。
- Local的灵魂是上下文装配:实体卡+关系+出处原文的结构化打包;可解释性由“边+chunk”双溯源支撑,前提是第77节留好出处映射。
- Global的灵魂是评分过滤:每社区一个confidence score,低分丢弃再归并;要点过多时递归分批,社区层级天然支持。
- DRIFT=Self-Routing Convergent Loop:全局种子+局部深挖交替迭代;ACT按相似度精选扇出范围——扇出广度本身应该是可调参数。
- 生产形态是三层混合:向量RAG兜单点、Local管实体、Global接归纳,路由Agent守大门;延迟和成本账(Local秒级/Global十秒级、成本差5-10倍)决定这个分层不可省略。
下节预告
到这里,第三部分“框架与协议篇”全部收官:我们从LangGraph一路打到三大SDK、MCP/A2A协议、GraphRAG。接下来进入第四部分“企业级图工程项目”。下一节先上《Graph应用的X光机:LangSmith、Arize Phoenix与自定义Trace全攻略》,再接《从Trace到性能优化》——动手建项目之前,先把调试武器配齐。然后就是六个可以直接落地的完整项目和多模态编排专题,第一个项目就会把我们前面积累的所有武器:拓扑设计、失败路由、HumanCheckpoint、协议互联——组装成一个真正端到端的企业系统。
互动区
学完GraphRAG三章,你的判断是什么:你的业务里最值得先上的是Local、Global还是先按兵不动?如果你们已经有向量RAG在生产,读完这三节你最想改造的是哪一层——检索前的路由,还是检索后的可解释性?评论区聊聊你的场景,下一篇企业级项目选型时我会挑几个典型场景做开篇案例。
如果觉得本文对你有帮助,欢迎点赞、收藏、关注三连!
本系列持续更新中,关注不迷路~