标签: community-report

  • GraphRAG 中 Community Report 的”幽灵分身”:同一份报告为何被创建了两次

    现象

    在 FalkorDB 中查询 HAS_REPORT 边的 Top 10 节点时,发现有 4 个 community_report 节点各有 4 条 HAS_REPORT 边指向它们。按照设计,每个 community 应该唯一对应一个 report,为什么会出现一对多?

    Edge type: HAS_REPORT
    Rank  Title                                                          Count
    1     技术部核心团队:后端架构与系统设计                                    4
    2     产品部:用户增长与商业化策略                                         4
    3     运维部:服务稳定性与监控体系                                         4
    4     测试部:质量保障与自动化测试                                         4
    

    理论上每个 community 只有一个 report,每个 report 只属于一个 community,HAS_REPORT 应该是 1:1 的关系。

    用一个通俗的例子来理解

    想象你在管理一个公司的组织架构

    假设你的公司有这样的部门结构:

    技术部 (278人)
      └── 后端组 (253人)
    

    “后端组”是”技术部”的子部门。现在 HR 要给每个部门写一份部门简介

    HR 发现”后端组”的核心成员和”技术部”高度重叠(后端组的人就是技术部的主力),于是 AI 给两个部门生成了几乎一模一样的简介

    部门 简介标题 部门人数
    技术部 (community 1491) “核心技术团队:后端架构与系统设计” 278人
    后端组 (community 2790) “核心技术团队:后端架构与系统设计” 253人

    两份简介的标题和内容完全相同(因为描述的本质上是同一群人),只有”部门人数”(size)不同。

    由于内容相同,系统给它们算出了相同的 ID(基于内容的 hash)。

    对应到我们实际发现的 4 组问题数据:

    部门类比 实际 community 简介标题 人数(size)
    技术部 community 1491 “技术部核心团队:后端架构与系统设计” 278
    └── 后端组 community 2790 “技术部核心团队:后端架构与系统设计” 253
    产品部 community 200 “产品部:用户增长与商业化策略” 796
    └── 产品一组 community 1100 “产品部:用户增长与商业化策略” 631
    运维部 community 1909 “运维部:服务稳定性与监控体系” 180
    └── 运维一组 community 3073 “运维部:服务稳定性与监控体系” 178
    测试部 community 953 “测试部:质量保障与自动化测试” 21
    └── 测试一组 community 2343 “测试部:质量保障与自动化测试” 19

    问题出在哪里?

    当把这些数据导入图数据库时:

    第一步:创建 report 节点

    以”技术部”和”后端组”为例。系统看到 parquet 里有两行数据(同一个 ID,不同的 community),就无脑创建了两个节点

    Report 节点 A: {id: "abc123", community: 1491, size: 278}  -- 技术部的简介
    Report 节点 B: {id: "abc123", community: 2790, size: 253}  -- 后端组的简介
    

    第二步:创建 HAS_REPORT 边

    系统遍历每个 report 记录,用 id 去匹配 report 节点:

    -- 处理技术部 (community 1491)
    MATCH (c:communities {community: 1491})
    MATCH (r:community_reports {id: "abc123"})  -- 匹配到 2 个节点(A 和 B)!
    CREATE (c)-[:HAS_REPORT]->(r)
    -- 结果:技术部 → 节点A, 技术部 → 节点B(2 条边)
    
    -- 处理后端组 (community 2790)
    MATCH (c:communities {community: 2790})
    MATCH (r:community_reports {id: "abc123"})  -- 同样匹配到 2 个节点!
    CREATE (c)-[:HAS_REPORT]->(r)
    -- 结果:后端组 → 节点A, 后端组 → 节点B(2 条边)
    

    最终结果:这个 report 标题下有 4 条 HAS_REPORT 边(2 个部门 × 2 个同 ID 节点 = 4)。

    而正确的结果应该是:技术部 → 技术部的简介(1 条),后端组 → 后端组的简介(1 条),共 2 条。

    根因分析

    问题由两个因素叠加导致:

    1. Leiden 层级聚类产生了内容相同的 Report

    GraphRAG 使用 Leiden 算法做层级社区发现。当子社区的成员与父社区高度重叠时,LLM 为它们生成了内容几乎相同的 report。由于 report ID 是基于内容的 hash,内容相同 → ID 相同。

    实际数据验证:

    report id communities sizes 层级关系
    6516e2f4… 2790, 1491 253, 278 2790 是 1491 的子社区
    feda9fa0… 1100, 200 631, 796 1100 是 200 的子社区
    d8f25d09… 2343, 953 19, 21 2343 是 953 的子社区
    223c76c6… 3073, 1909 178, 180 3073 是 1909 的子社区

    2. 导入逻辑缺乏去重和精确匹配

    导入代码中:

    # 创建节点:无条件 CREATE,不去重
    "UNWIND $batch AS p CREATE (n:community_reports) SET n = p"
    
    # 创建边:只按 id 匹配,没有加 community 条件
    "MATCH (r:community_reports {id: p.rid})"  # 匹配到多个同 ID 节点 → 笛卡尔积
    

    解决方案

    HAS_REPORT 创建时精确匹配

    在创建 HAS_REPORT 边时,同时匹配 idcommunity,避免笛卡尔积:

    # Before (有 bug)
    "MATCH (r:community_reports {id: p.rid}) "
    
    # After (修复)
    "MATCH (r:community_reports {id: p.rid, community: p.cnum}) "
    

    这样每个 community 只会匹配到属于自己的那个 report 节点,创建 1 条边。

    教训:在图数据库中用 MATCH + CREATE 模式创建关系时,如果匹配条件不够精确(目标节点有重复),就会产生意料之外的笛卡尔积。始终确保 MATCH 条件能唯一定位到目标节点。