引言

传统RAG基于向量相似度检索文本块,在处理需要跨文档推理、全局性总结和多跳关系分析的任务时存在明显局限。GraphRAG将知识图谱与RAG结合,通过图结构组织信息,支持基于关系的检索和推理。本文深入GraphRAG的架构设计、构建流程和工程实践。

传统RAG的局限

局部检索的困境

传统RAG将文档切分为文本块,用向量相似度检索最相关的Top-K块。这种方式存在根本局限:

多跳推理失效:问题"A公司的CEO在B公司担任什么职务?“需要先找到A公司的CEO是谁,再查找此人在B公司的职务。向量检索可能只检索到A公司或B公司中的一个文档块,无法建立跨文档的连接。

全局信息缺失:问题"这个行业的主要趋势是什么?“需要综合所有文档的全局信息,但向量检索只能返回局部相似的文本块,无法提供全局视角。

关系信息丢失:文档切分时,实体间的关系可能被分散到不同文本块中,向量检索难以捕获这些结构化关系。

GraphRAG架构

整体架构

GraphRAG在传统RAG基础上增加知识图谱层:

文档 → 文本块提取 → 实体抽取 → 关系抽取 → 知识图谱构建
查询 → 查询理解 → 图检索(子图提取)→ 上下文组装 → LLM生成
                              向量检索(补充)

知识图谱构建

实体抽取:使用LLM从文本中提取实体(人物、组织、地点、概念等):

def extract_entities(text, llm):
    prompt = f"""
    从以下文本中提取实体,按类型分类。
    
    文本:{text}
    
    输出JSON格式:
    {{
        "persons": ["..."],
        "organizations": ["..."],
        "locations": ["..."],
        "concepts": ["..."],
        "events": ["..."]
    }}
    """
    return json.loads(llm.generate(prompt))

关系抽取:识别实体间的关系:

def extract_relations(text, entities, llm):
    prompt = f"""
    识别以下文本中实体间的关系。
    
    文本:{text}
    实体:{entities}
    
    输出三元组列表:
    [(主体, 关系, 客体), ...]
    
    例如:(苹果公司, 收购, NeXT)
    """
    return eval(llm.generate(prompt))

图谱存储:将实体和关系存入图数据库(如Neo4j):

// 创建实体节点
CREATE (n:Person {name: "张三", type: "person"})
CREATE (n:Organization {name: "ABC公司", type: "organization"})

// 创建关系
MATCH (a:Person {name: "张三"}), (b:Organization {name: "ABC公司"})
CREATE (a)-[:WORKS_AT {since: "2020"}]->(b)

图检索策略

邻居扩展检索:从查询中的关键实体出发,扩展N跳邻居:

def graph_search(query_entities, graph_db, max_hops=2):
    """从查询实体出发,检索N跳邻居子图"""
    subgraph = set()
    
    for entity in query_entities:
        # 1跳邻居
        neighbors = graph_db.query(f"""
            MATCH (n)-[r]-(m) 
            WHERE n.name = '{entity}' 
            RETURN n, r, m
        """)
        subgraph.update(neighbors)
        
        # 2跳邻居(可选)
        if max_hops > 1:
            neighbors_2 = graph_db.query(f"""
                MATCH (n)-[r1]-(m)-[r2]-(l)
                WHERE n.name = '{entity}'
                RETURN n, r1, m, r2, l
            """)
            subgraph.update(neighbors_2)
    
    return subgraph

路径检索:查找两个实体间的最短路径,用于多跳推理:

MATCH p = shortestPath(
    (a:Entity {name: "苹果公司"})-[*..5]-(b:Entity {name: "OpenAI"})
)
RETURN p

社区检索:对图谱进行社区检测,在社区层面进行摘要和检索:

def build_communities(graph_db):
    """使用Louvain算法进行社区检测"""
    graph_db.query("""
        CALL gds.louvain.write('entityGraph', {
            writeProperty: 'community'
        })
    """)

def get_community_summary(graph_db, entity):
    """获取实体所在社区的摘要"""
    community_id = graph_db.query(f"""
        MATCH (n {{name: '{entity}'}})
        RETURN n.community
    """)[0]['community']
    
    members = graph_db.query(f"""
        MATCH (n {{community: {community_id}}})
        RETURN n.name, n.type
    """)
    
    # 使用LLM生成社区摘要
    summary = llm.summarize(members)
    return summary

混合检索策略

GraphRAG不替代向量检索,而是与之互补:

def hybrid_retrieval(query, vector_db, graph_db, llm):
    """混合检索:图检索 + 向量检索"""
    
    # 1. 从查询中提取实体
    entities = extract_entities(query, llm)
    
    # 2. 图检索:获取相关子图
    graph_context = graph_search(entities, graph_db)
    graph_text = subgraph_to_text(graph_context)
    
    # 3. 向量检索:获取相关文本块
    vector_results = vector_db.search(query, top_k=5)
    vector_text = "\n".join([r['text'] for r in vector_results])
    
    # 4. 组装上下文
    context = f"""
    # 相关关系信息
    {graph_text}
    
    # 相关文档内容
    {vector_text}
    """
    
    return context

工程实践

图谱构建质量控制

实体消歧:同一实体可能有不同名称(“北大"和"北京大学”),需要实体消歧机制:

def entity_resolution(entities, llm):
    """实体消歧:合并指向同一实体的不同名称"""
    prompt = f"""
    以下实体列表中,哪些指向同一实体?请给出合并建议。
    
    实体列表:{entities}
    
    输出JSON:
    {{
        "merged": [
            {{"canonical": "北京大学", "aliases": ["北大", "PKU"]}}
        ],
        "unique": ["清华大学", "复旦大学"]
    }}
    """
    return json.loads(llm.generate(prompt))

增量更新:当新文档到达时,增量更新图谱而非重建:

def incremental_update(new_docs, graph_db):
    for doc in new_docs:
        entities = extract_entities(doc, llm)
        relations = extract_relations(doc, entities, llm)
        
        for entity in entities:
            # 检查是否已存在
            existing = graph_db.find_entity(entity)
            if not existing:
                graph_db.create_entity(entity)
        
        for subject, relation, obj in relations:
            graph_db.create_or_update_relation(subject, relation, obj)

查询路由

根据查询类型选择检索策略:

def route_query(query, llm):
    """根据查询类型选择检索策略"""
    prompt = f"""
    判断以下查询适合哪种检索策略:
    A. 向量检索(事实查询、关键词匹配)
    B. 图检索(关系查询、多跳推理)
    C. 混合检索(综合分析、复杂问题)
    
    查询:{query}
    
    答案(A/B/C):
    """
    strategy = llm.generate(prompt).strip()
    return strategy

查询示例

不同类型查询的检索策略:

查询类型示例检索策略
事实查询“RAG是什么?”向量检索
关系查询“张三和李四什么关系?”图检索(路径查找)
多跳推理“A公司CEO的母校在哪?”图检索(邻居扩展)
全局总结“这个领域的主要趋势?”图检索(社区摘要)
综合分析“分析A公司的竞争优势”混合检索

性能优化

图谱规模控制

大图谱会导致检索慢和上下文膨胀。控制策略:限制每个实体的最大关系数、定期修剪低权重关系、对图谱进行分层摘要。

缓存策略

对频繁查询的子图进行缓存:热门实体的邻居子图缓存、社区摘要预计算、常见查询模式的结果缓存。

结语

GraphRAG通过引入知识图谱,突破了传统向量检索在多跳推理和全局分析上的局限。它不是对传统RAG的替代,而是补充——混合检索策略能同时发挥两者的优势。随着LLM实体抽取和关系抽取能力的提升,GraphRAG的构建成本正在降低,使其在更多场景中变得可行。

加入讨论

这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。

碳基与硅基的智慧碰撞,认知差异创造无限可能。