设计:增量更新与实体去重¶
日期: 2026-05-04 Issue: #698(增量更新)、实体去重(无 issue,主动行为) 分支: v7
问题¶
graphify extract每次运行都会从头重建完整图——无论什么文件变化,都会将所有文件重新发送给 LLM。对于每天更新的 1000 个 Markdown 文件语料库来说,这代价高昂。- LLM 提取是逐 chunk 进行的——同一个真实世界概念在不同 chunk 中可能得到不同标签(
AuthManager、AuthenticationManager、auth_mgr)。除精确字符串归一化外,没有语义去重。
处理流程(每次 graphify extract 运行){#pipeline-every-graphify-extract-run}¶
detect (full or incremental, auto-detected)
↓
AST extract (code files, AST cache-aware)
↓
Semantic LLM extract (doc/paper/image files, semantic cache-aware)
↓
build_merge (merge into existing graph, prune deleted nodes)
↓
deduplicate_entities (normalize → entropy gate → MinHash/LSH → Jaro-Winkler → community boost → optional LLM)
↓
cluster (full graph, always re-run)
↓
score_all + god_nodes + surprising_connections
↓
write graph.json + .graphify_analysis.json + manifest.json
功能 1:增量更新¶
自动检测¶
如果 graphify-out/manifest.json 和 graphify-out/graph.json 都存在 → 进入增量模式。无需指定 flag。首次运行始终为完整模式。
增量模式变更¶
- 使用
detect_incremental(target)替代detect(target)——返回new_files、unchanged_files、deleted_files - 只有
new_files会经过 AST + LLM 提取 - 使用
build_merge(new_chunks, prune_sources=deleted_files)替代build_from_json——合并到现有图,并剪除已删除文件对应的节点 - 只有在成功完成后才写入
manifest.json(运行中途崩溃不会破坏下一次运行的 diff)
语义缓存(完整与增量模式){#semantic-cache-both-full-and-incremental-mode}¶
- 调用 LLM 前:
check_semantic_cache(files)将文件分为(cached_results, uncached_files) - 只有
uncached_files被发送给extract_corpus_parallel - LLM 调用后:
save_semantic_cache(fresh_results)——以内容哈希作为键 - 文件重命名时:缓存结果中的
source_file会更新为新路径(与现有 AST 缓存相同模式)
输出摘要¶
[graphify extract] incremental: 20 changed, 980 cached, 2 deleted
[graphify extract] graph: 4,821 nodes, 12,304 edges, 43 communities
[graphify extract] tokens: 18,432 in / 6,201 out, est. cost: $0.08
变更文件¶
graphify/__main__.py——仅elif cmd == "extract":代码块(约 5 处定向修改)
功能 2:实体去重¶
新模块:graphify/dedup.py¶
单一职责。在图构建完成后由 build.py 调用。返回去重后的 (nodes, edges)。
处理流程¶
步骤 1 — 精确归一化
启用 build.py 中休眠的 deduplicate_by_label。可捕获跨文件的大小写/标点变体。这是零成本的现成收益。
步骤 2 — 熵门控
将模糊匹配限制在熵 ≥ 2.5 bits/char 的标签。短而有歧义的名称("AI"、"DB"、"x")自动合并风险过高。只有高熵标签进入步骤 3-4。
步骤 3 — MinHash + LSH 分块(datasketch)
3-gram shingles,128 个 permutation,阈值 0.7。以 O(n) 而非 O(n²) 生成候选对。在 10k 节点上亚秒级完成。
步骤 4 — Jaro-Winkler 验证(rapidfuzz)
每个候选对需要 ≥ 0.92 才能通过。可捕获拼写错误、复数形式、空格变体。低于阈值的候选对被丢弃。
步骤 5 — 同社区加权 两个节点共享同一 Leiden 社区 ID 时,得分 +0.05。这是 Graphify 特有的优势——社区结构是 GraphRAG/LightRAG 未利用的强信号。
步骤 6 — 并查集合并 确认后的候选对送入并查集 → 连通分量 → 每个分量合并为一个节点。边重新连接到保留节点。丢弃自环。优先选择不带 chunk 后缀的较短 ID 作为保留节点。
步骤 7 — 可选 LLM 决胜(--dedup-llm flag)
模糊对(得分 0.75–0.85)按每批 30 个分组,每批一次 LLM 调用。10k 节点总计约 $0.01。默认关闭。
集成点¶
去重在 build_merge / build_from_json 之后、cluster 之前运行。顺序很重要:更干净的图 → 更好的社区检测。
# in build.py
G = build_merge(...) # or build_from_json
G = deduplicate_entities(G) # new step
communities = cluster(G) # unchanged
新依赖¶
datasketch——始终必需(加入[project.dependencies])rapidfuzz——始终必需(加入[project.dependencies])- 不引入
sentence-transformers/ PyTorch 依赖
变更文件¶
graphify/dedup.py——新模块,完整处理流程graphify/build.py——在图构建后调用deduplicate_entities;启用休眠的deduplicate_by_labelgraphify/__main__.py——在 extract 块中增加--dedup-llmflag 解析pyproject.toml——将datasketch、rapidfuzz加入基础依赖
测试¶
- 针对每个去重步骤的独立单元测试(
tests/test_dedup.py) - 集成测试:两个 chunk 含重叠实体标签 → 输出图中合并为单一节点
- 增量测试:运行两次 extract,断言第二次对未变更文件零 LLM 调用
- 重命名测试:重命名文件,断言缓存命中且
source_file正确更新 - 删除测试:删除文件,断言其节点从图中剪除
非目标¶
--dedup embed(MiniLM 余弦相似度)——明确排除,不引入 PyTorch 依赖- 对
graphify update(仅 AST)的增量支持——已有现有 AST 缓存处理 - 跨多个 graph.json 文件的去重(合并两个图)——独立功能
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 graph/graphify