Graph RAG:当你的知识有了'关系网'

周三下午的AI问答演示,老李信心满满地站在投屏前。

“这是我们新上线的知识库问答系统,各位随便问。”

技术总监老赵第一个举手:“公司有哪些产品?”

系统秒回,列出五款产品,附带简介。老李嘴角上扬。

老赵又问:“这些产品里,哪些是由同一个团队开发的?”

系统沉默了几秒,然后开始胡编:“产品A和产品D可能属于同一产品线,建议咨询相关团队。”

会议室里响起窃窃私语。老李的保温杯尴尬地悬在半空。

演示结束,小王被老李拽进了茶水间。

“怎么回事?产品信息我们明明都有!每份产品文档里都写了开发团队,为什么AI就是回答不了‘哪些产品由同一个团队开发’?”

小王接了一杯咖啡,不慌不忙:“老李你想啊,咱们的RAG系统是怎么存这些产品文档的?”

“就……一份一份存进去,检索的时候找最相似的。”

“问题就在这儿。每份文档在系统里是一个孤岛。产品A的文档里写着‘开发团队:基础架构部’,产品B的文档里也写着‘开发团队:基础架构部’。但系统不知道这两个‘基础架构部’是同一个东西。它看到的是两串长得像的文本,不是一个有明确标识的实体。”

小王喝了一口咖啡:“它知道老张是CTO,也知道老李是技术总监,但它不知道老李向老张汇报。因为‘汇报关系’不在任何一篇文档里,而在文档与文档之间。普通的RAG,只看到点,看不到线。”

老李沉默了几秒:“所以我们缺的不是知识本身,是知识之间的关系?”

“精准。”


知识也有社交网络

小王把老李拉到工位前,打开一个画图工具。

“咱们公司的人,你认识多少个?”

老李想了想:“直接认识的,大概五六十个。”

“那你知道采购部的小周,跟前端组刚入职的小刘,是什么关系吗?”

老李愣了:“这我怎么可能知道,我跟他们都不熟。”

“但你只需要问三个人——你问采购部的老吴,老吴认识小周;老吴跟HR的小陈熟,小陈负责入职培训认识小刘。三步之内,你就能知道小周和小刘的关系。”

小王在屏幕上画了几个点和连线:“这就是六度分隔理论。任何两个陌生人之间,最多隔着六层关系。知识也一样——表面上独立的文档,通过共同的实体连接,形成一张巨大的关系网。产品A和产品D,通过‘基础架构部’这个实体,被连接在一起。”

图:产品A和产品D通过“基础架构部”相连,产品B通过“数据工程部”连到CTO

“你问‘哪些产品由同一个团队开发’,本质上是问‘哪些产品共享同一个开发团队实体’。这个问题,靠语义相似度回答不了,但靠关系图——一秒出结果。”


知识图谱三要素:节点、边、属性

“这不就是知识图谱吗?”老李盯着屏幕上的图,“我听说过,但我们以前搞数据库的,这叫ER图。”

“本质上是同一个东西,但知识图谱更灵活。”小王在图上标注起来,“三个基本要素——节点是实体,比如人、产品、部门、文档。边是关系,比如‘开发了’、‘属于’、‘汇报给’。属性是实体的特征,比如‘老李,工号E0042,入职2015年’。”

“那Graph RAG跟普通RAG,到底差在哪?”

“普通RAG检索方式是‘找跟问题语义最相似的文档块’,Graph RAG多了一条路径——先找到问题里的实体,沿着图的边去遍历,把关联实体和它们的文档拉回来。”

小王打开终端,敲了一段示意代码:

python
# 图检索示意:沿着实体关系找相关文档
def graph_retrieval(question, kg, vector_db):
    # 1. 从问题中抽取实体
    entities = extract_entities(question)  
    # 问题:"哪些产品由基础架构部开发?"
    # 实体:["基础架构部"]
    
    # 2. 在图谱中遍历关联
    related = []
    for entity in entities:
        node = kg.find(entity)
        # 沿着"开发了"这条边,找到所有关联产品
        products = kg.traverse(node, relation="开发了")
        related.extend(products)
    
    # 3. 用关联实体作为检索条件
    docs = []
    for product in related:
        docs.extend(vector_db.search(product.name))
    
    return docs  # 返回的是精确关联的文档,而非仅语义相似的文档

“你看到没有?普通RAG的输入只有‘问题’,Graph RAG的输入是‘问题+实体关系’。多出来的这部分,就是图给它的超能力。”

老李盯着代码看了好一会儿,然后问了一个致命问题:“这知识图谱谁来建?总不能让我手工画吧?”


实体关系抽取:让AI自己来建图

“当然不能手工画。”小王调出一段代码,“现在有现成的信息抽取模型,能从非结构化文本里自动提取实体和关系。你把产品文档丢进去,它吐出三元组——‘产品A,开发团队,基础架构部’。”

python
# 用大模型从文档中抽取实体关系三元组
def extract_triplets(document_text):
    prompt = f"""
    请从以下文本中提取所有的实体关系三元组。
    三元组格式:(实体1, 关系, 实体2)
    
    文本:{document_text}
    
    例如:
    "API网关由基础架构部开发,负责人是老李。"
    输出:
    (API网关, 开发团队, 基础架构部)
    (基础架构部, 负责人, 老李)
    """
    response = llm.generate(prompt)
    return parse_triplets(response)

# 然后把这些三元组存入图数据库
# neo4j_query = "MERGE (a:Product {name: 'API网关'}) " \
#               "MERGE (b:Department {name: '基础架构部'}) " \
#               "MERGE (a)-[:开发团队]->(b)"

“其实可以分两步走,”小王补充道,“先用已有的结构化数据建图的骨架——你那个员工信息表、产品目录、组织架构,直接就能转成节点和边。再用非结构化的文档补肉的细节——从会议纪要、技术文档里抽关系,补充到图上。”

老李若有所思:“那这种Graph RAG,是不是特别适合那种‘关系型问题’?”

“对。谁向谁汇报、哪个项目依赖哪个组件、这份合同跟哪个供应商有关——这些问题的答案不在某一篇文档里,而在多篇文档的交叉点上。普通RAG像查字典,Graph RAG像查族谱。工具不同,适合的问题不同。”

图:两种RAG的分工——事实查询走语义,关系查询走图


Graph RAG的代价

“听起来很美好,”老李拧开保温杯,“代价是什么?”

“三个代价。”小王伸出手指。

“第一,构建成本。你需要信息抽取管线把非结构化文本转成三元组,还需要图数据库来存。Neo4j或者更轻量的NebulaGraph,都是新的基础设施。”

“第二,抽取质量。AI抽取实体关系,不可能100%准确。‘苹果’有时候是水果,有时候是手机,有时候是公司。实体消歧是知识图谱领域的老大难问题。刚开始建图,你会遇到各种张冠李戴的乌龙。”

“第三,查询延迟。Graph RAG一次查询要做两件事——图遍历找关联实体,向量检索找文档。两个操作的延迟是叠加的。如果图很密,遍历跳数多,延迟会明显增加。”

老李叹了口气:“又是一笔成本账。”

“但值不值,看场景。如果你的用户主要问‘是什么’、‘怎么做’,普通RAG就够了。如果你的用户经常问‘跟谁有关’、‘属于哪个’、‘谁负责’——这种关系型问题占到了查询量的两成以上,Graph RAG就值得投资。”

小王补充道:“而且微软刚开源了GraphRAG,把知识图谱的构建和检索打包成了一套方案。你不需要从零开始搭,可以直接用它的pipeline跑一遍你的文档,自动生成实体和社区结构。”

老李掏出小本本,在上面写了几个关键词:节点、边、抽取、图遍历。

他合上本子,站起来,保温杯在手里转了半圈:“先拿组织架构和产品目录建一个小图试试。就咱们团队内部的,看看效果。”

走到门口,他又转身:“那什么,图数据库你熟悉哪个?Neo4j那个Cypher语法,我看过一次,跟SQL完全两回事。你再给我讲讲。”

小王笑了:“老李,SQL出身的人学Cypher,其实就是把JOIN变成箭头。你坐,我给你写几个例子——”

午后的阳光透过百叶窗打在显示器上,屏幕上那几个节点和连线,慢慢从抽象的概念变成了老李眼里可以触摸的结构。那些曾经孤立的知识,终于在这张网上找到了彼此。

实体抽取:教 AI 像人类一样"读懂"文本
结构化 vs 非结构化知识:两个世界的碰撞与融合