首页
/ graphify 实战:`/graphify add` 的 URL 摄取与 `--watch` 的文件夹自动重建

graphify 实战:`/graphify add` 的 URL 摄取与 `--watch` 的文件夹自动重建

2026-09-06 18:11:26作者:尤峻淳Whitney

导读

Graphify 把"把外部内容加进知识图谱"和"让图谱跟随代码仓库演进"做成了两条自动化通道:/graphify add <url> 负责抓取网页、推文、论文、PDF、图片甚至视频并把它们落盘到语料目录,而 --watch 则在后台上监控一个文件夹,在代码变更时无需 LLM 即可自动重建 graph.jsonGRAPH_REPORT.md,遇到文档/论文/图片变更时再提示你执行一次 /graphify --update 做语义级重提取。本文基于项目内 VSCode 等各平台 Agent 共用的技能参考文档 graphify/skills/vscode/references/add-watch.md 展开,并结合 graphify/ingest.pygraphify/watch.py 等源码说明其底层实现。读完你将掌握:两种能力的触发场景与确切命令、6 类 URL 的自动识别与落盘规则、错误处理与"摄取后自动更新"的完整闭环,以及如何把 --watch 接入多 Agent 波次式开发工作流。

该参考文档的加载条件写得很明确:仅当用户执行了 /graphify add <url> 或显式传入 --watch 时才加载它——这两个能力都不属于默认构建流程,是图构建之外的增量扩展通道。它在 agentsampclaudecodexkiroopencodevscodewindows 等各平台技能目录下都有同名副本,本文以 vscode 目录为例讲解,其他平台完全一致。


一、/graphify add:把一个 URL 变成语料并立即入图

/graphify add 的目标是:抓取一个 URL → 保存为 graphify 可消费的文件 → 随后把新文件合并进既有图谱。整个动作由两段组成——先调用 ingest() 完成抓取与落盘,成功后自动对 ./raw 跑一遍 --update 流水线。

1.1 摄取调用的标准写法

参考文档给出的调用脚本如下(其中的 $(cat graphify-out/.graphify_python) 用于读取构建时记录在 graphify-out/.graphify_python 中的 Python 解释器路径,确保与当初构建图谱的解释器一致,避免 PATH 漂移):

$(cat graphify-out/.graphify_python) -c "
import sys
from graphify.ingest import ingest
from pathlib import Path

try:
    out = ingest('URL', Path('./raw'), author='AUTHOR', contributor='CONTRIBUTOR')
    print(f'Saved to {out}')
except ValueError as e:
    print(f'error: {e}', file=sys.stderr)
    sys.exit(1)
except RuntimeError as e:
    print(f'error: {e}', file=sys.stderr)
    sys.exit(1)
"

需要替换的三个占位符:

占位符 替换内容
URL 用户给出的真实链接
AUTHOR 用户提供姓名时填写,否则保持占位(语义上为空)
CONTRIBUTOR 团队图谱场景下的贡献者名,规则同 AUTHOR

这两个元数据会被写进落盘文件头部的 YAML frontmatter,其中 contributor 字段的取值逻辑在 graphify/ingest.py 的各抓取函数中统一为 contributor or author or 'unknown'——也就是说即使调用方只传了 author,它也会被继承到 contributor,保证图谱节点永远带有可溯源的人名信息。

1.2 错误处理契约:绝不静默继续

脚本对 ingest() 抛出的异常做了分类处理,这一点值得强调,它是 skill 的硬性行为契约

  • ValueError:URL 校验失败。在 graphify/ingest.py 中,ingest() 会先调用 graphify.securityvalidate_url()(配合 safe_fetch 系列函数做安全抓取),校验不通过即以 ValueError 抛出——这可以拦截私有 IP、非法 scheme 等不安全目标。
  • RuntimeError:抓取阶段失败。ingest() 会把 urllib.error.HTTPError / URLError / OSError 统一包装成 RuntimeError("ingest: failed to fetch ...") 再抛出。

无论哪种失败,参考文档都要求:把错误原样转告用户,解释清楚哪里出了问题,然后用 sys.exit(1) 退出,而不是假装成功继续往下走。只有 print(f'Saved to {out}') 正常输出、保存成功之后,才允许自动触发 --update 流水线把新文件 merge 进既有图谱。

1.3 六类 URL 的自动识别与落盘规则

ingest() 会根据 URL 形态自动分流,不要求用户声明类型。类型判定集中在 _detect_url_type()(见 graphify/ingest.py),规则与最终处理如下:

URL 类型 判定依据 抓取与落盘方式 后续管线
YouTube / 任意视频 域名含 youtube.com/youtu.be 通过 yt-dlp 下载纯音频流(默认存为 yt_<url_hash>.<ext>.m4a/.opus 等),见 download_audio()graphify/transcribe.py 下次运行时由 faster-whisper 转写成 .txt 文本
Twitter / X 域名含 twitter.com/x.com 走 oEmbed API(publish.twitter.com/oembed),剥离 HTML 后连同作者名存为 .md,见 _fetch_tweet() 直接可被 Markdown 提取
arXiv 域名含 arxiv.org 从摘要页 API 提取标题、作者列表与 Abstract,存为带 frontmatter 的 .md,见 _fetch_arxiv() 直接可被 Markdown 提取
PDF 路径以 .pdf 结尾 直接以二进制方式下载为 .pdf 下一次 /graphify --update 时经语义管线切片提取
图片 路径以 .png/.jpg/.jpeg/.webp/.gif 结尾 直接下载原图 下一次运行时由 Claude vision 做视觉提取
任意网页 以上皆非 将 HTML 清洗为 Markdown 后存为 .md,见 _fetch_webpage() 直接可被 Markdown 提取

几点值得注意的源码细节:

  • 网页转 Markdown 的实现graphify/ingest.py 中的 _html_to_markdown() 会先用正则剥掉所有 <script>/<style> 内容(防止页面脚本文本泄漏进正文),然后优先使用 markdownify 转换,缺少该依赖时退化为"去标签 + 压缩空白"的基础清洗,正文上限 8000 字符。
  • 视频下载需要可选的 video extra:依赖声明在 pyproject.toml[project.optional-dependencies] 中,即 video = ["faster-whisper; python_version >= '3.11'", "yt-dlp>=2026.6.9"]。参考文档明确指出需要 pip install 'graphifyy[video]'(注意包名是双 y 的 graphifyy),transcribe.py 在缺依赖时抛出的 ImportError 提示信息也与之一致。
  • 音频文件名稳定可缓存download_audio() 用 URL 的 SHA-1 前 12 位命名(yt_<hash>.<ext>),二次抓取同一视频时直接命中缓存返回,不重复下载。
  • 落盘防覆盖:Markdown 类抓取产物若与既有文件重名,ingest() 会用 _1_2…… 递增后缀(上限 999)自动改名,绝不静默覆盖已有语料。
  • 文件名安全化_safe_filename() 会把 URL 的 netloc+path 中非 [\w\-] 的字符替换为下划线并截断到 80 字符,避免 URL 中的特殊字符污染文件系统路径。

1.4 命令行等价入口

ingest.py 自带 __main__,因此在终端里也能以同样的方式单发抓取:

python graphify/ingest.py <URL> [target_dir] [--author NAME] [--contributor NAME]

target_dir 缺省为 ./raw,与 /graphify add 的落盘位置保持一致。--contributor 用于团队图谱场景;输出会以 Ready for graphify: <path> 提示文件已经可以进入提取管线。


二、摄取成功之后:把新文件合并进既有图谱

/graphify add 的成功路径并不会停留在"文件已保存"。参考文档要求:保存成功后,自动对 ./raw 运行一次 --update 流水线,把新落盘的文件增量合并进现有 graph.json,而不是等到用户下次手动触发。

这条流水线的完整分步脚本记录在同目录的技能参考 graphify/skills/vscode/references/update.md 中,核心机制是"增量重提取":

  1. detect_incremental() 对比上次 manifest,找出 new/changed/deleted 文件,并把结果写入 graphify-out/.graphify_incremental.json
  2. 将增量结果改写成 .graphify_detect.json,供后续 AST 提取与语义提取步骤读取;
  3. 分流判断:若变更全部是代码文件(.py/.ts/.js/.go/.rs/.java/.cpp/.c/...),则只跑 AST 提取(无需 LLM、零 token 消耗);一旦混入 doc/paper/image/video,才进入完整语义子代理管线;
  4. build_merge() 把新提取结果与 graph.json 合并——它直接读图而不走 NetworkX 往返,从而保留 calls/implements/imports 等有向边的方向语义(directed 参数需与初始构建一致);
  5. graph_diff() 展示新旧图差异摘要,并把本次状态写回 manifest,让下一次 --update 从今天的状态开始 diff。

简言之:代码变更走零成本 AST 通道,非代码变更走 LLM 语义通道,二者共用同一个 merge 骨架。这个分层思想在下一节 --watch 中会再次出现。


三、--watch:后台监听文件夹,让图谱自动跟随演进

--watch 是"实时增量"的另一面:在后台启动一个文件夹监听器,当文件发生变化时自动更新图谱。它与 /graphify add 互为补充——前者管"外部新内容进图",后者管"仓库内部日常演进"。

3.1 启动方式

$(cat graphify-out/.graphify_python) -m graphify.watch INPUT_PATH --debounce 3

INPUT_PATH 换成要监听的目录(默认 .)。--debounce 的单位是秒,默认 3。按 Ctrl+C 即可停止。

从 CLI 层面看,graphify watch 子命令(graphify/cli.py)以及 graphify watch.py__main__graphify/watch.py)都支持该调用;--debouncefloat 类型,允许小数。watch 能力依赖可选 extra:watch = ["watchdog"]pyproject.toml),文件系统事件正是由 watchdog 提供的。

3.2 核心行为:按变更类型走双通道

监听器对"发生了什么变化"极其敏感,处理策略由变更类型决定,这同时也是整个 watch.py 设计的灵魂:

  • 只改了代码文件(.py.ts.go 等一切 AST 可解析扩展名):立即重跑 AST 提取 + 重建 + 社区聚类,全程不需要 LLMgraph.jsonGRAPH_REPORT.md 会被自动更新(源码中的重建路径还会顺带 reconcile 缺失或过期的 graph.html 可视化)。
  • 改了文档、论文或图片:此时写入一个 graphify-out/needs_update 标志文件,并打印通知,提示运行 /graphify --update——因为这类文件的语义级重提取需要 LLM。

也就是说,watch 把"零成本可自动"与"需花钱需人工确认"的两类变更物理隔离了。needs_update 标志的实现见 graphify/watch.py_notify_only():它创建标志文件并写入 "1"check_update()(同一文件 L2083-L2090)则检查该标志并打印 Pending non-code changes in ...graphify check-update <path> 子命令暴露了这一检查能力(graphify/cli.py),适合被 CI 或外部脚本轮询。

值得补充的判定细节(来自源码注释,graphify/watch.py):

  • 删除任何被监听文件也触发立即重建:因为"驱逐"(从图中移除已删源文件的节点/边)同样不需要 LLM,全语料 reconcile 会直接依据磁盘存在性把对应记录清掉——否则纯文档删除会被错误地挂到 needs_update 后面,直到下一个代码事件或手动 update 才被处理。
  • "文件被读"不算变更:只有创建、修改、移动、删除和"写入后关闭"才算。这样当 Agent 正在阅读语料文件时,watcher 不会把读操作误判为变更而陷入自我触发的死循环。

3.3 debounce:抵御"一波并行写入"

参考文档对 debounce 的解释是:等待文件活动完全停止后才触发,避免一波并行 Agent 写入让每次写文件都触发一次重建。默认 3 秒,实践中通常无需修改。

源码层面的循环实现印证了这一点(graphify/watch.py):

  • watcher 把一段时间内积累的变更收集进一个 changed 集合,记录最后触发时间 last_trigger
  • 主循环每 0.5s 醒来一次,只有当存在 pending 变更、且距 last_trigger 已超过 debounce 秒时,才把整个批次合并成一次重建(batch = list(changed))。

这保证了一次 git pull、一次多文件格式保存或一波 Agent 并发写操作,最终只产生一次重建而不是 N 次。--debounce 0 可以用于调试时需要"立即响应"的场景。

3.4 增量重建的可靠性设计(源码纵深)

代码通道的自动重建远比"检测到变化就全量重跑"精细。watch.py 内的增量 rebuild(_rebuild_codegraphify/watch.py)具备一套面向并发与失败场景的保护机制,值得了解,便于排查线上问题:

  • 按变更集增量提取:调用方(如 git post-commit hook)提供 changed_paths 时,只对这批文件重跑 AST 提取;未变更文件的 AST 节点与所有 semantic/LLM 层节点从既有 graph.json保留并合并,删除的路径则从保留集合中驱逐。_reconcile_existing_graph() 负责新旧合并与逐源驱逐。
  • 并发安全_rebuild_lock() 基于 fcntl.flock 实现仓库级 advisory lock,进程被杀时内核自动释放锁,无需清理陈旧锁文件;拿不到锁的调用方(例如并发的多仓库 post-commit hook)会先把变更写入 graphify-out/.pending_changes 排队(_queue_pending),持锁进程在重建前、后各 drain 一次,把队列内容并入自己的变更集,避免变更集被静默丢弃。重建时锁文件 .rebuild.lock 内记录持锁进程 PID,供外部发布脚本轮询。
  • 失败防护(fail-closed)_check_shrink() 是防"塌缩"守卫——若新图节点数骤降且丢失无法归因于被重提取/被删除的文件(例如上一轮语义分块文件缺失导致的批量丢失),重建会拒绝覆盖并提示 Pass --force to override,而不是默默写坏一个几千节点的图。
  • 排除规则持久化:初次 extract 时的 --exclude/.gitignore 设置会被写入 graphify-out/.graphify_build.json_write_build_config),后续 watch/hook 重建自动重新应用,避免"当初排除的路径被无声重新纳入"。
  • hook 环境的健壮性:detached git hook 可能继承一个已被删除的临时工作目录,此时相对路径全部失效;通过 GRAPHIFY_REPO_ROOT 环境变量可以指定仓库根以便重建前 chdir 回去(_stabilize_rebuild_cwd)。另有 GRAPHIFY_REBUILD_MEMORY_LIMIT_MB 可对重建进程做 best-effort 的内存上限与 nice(10) 降优先级(_apply_resource_limits)。
  • 目录名可配置:所有产物目录名统一由 graphify/paths.pyGRAPHIFY_OUT 决定(默认 "graphify-out",可用同名环境变量覆盖,支持相对名或绝对路径),watcher 的 needs_update 标志、锁文件等全部随之迁移。

这些机制共同保证了:一个跑在后台的 watcher,即使面对多 Agent 并发写、进程被杀、部分提取失败等恶劣情况,也只会产出一致、完整、不塌缩的图谱


四、Agentic 工作流:把 --watch 变成 Agent 的"后台队友"

参考文档专门给出了面向多 Agent 编程工作流的建议,这也是 --watch 最有价值的应用场景:

后台终端里运行 --watch。Agent 波次之间产生的代码变更会被自动拾取并重建图谱;如果 Agent 同时也在写文档或笔记,那么这些波次结束后需要你手动执行一次 /graphify --update

推荐的落地姿势:

  1. 启动前:先做一次完整的首次构建(生成 graph.jsonGRAPH_REPORT.mdgraphify-out/.graphify_python)。
  2. 启动 watcher:开一个独立后台终端,执行上文 python -m graphify.watch INPUT_PATH --debounce 3
  3. 派出 Agent 波次:每个 Agent 波次改完代码后,watcher 会在 debounce 静默期自动完成 AST 重建——你随时拿到的 graph.json / GRAPH_REPORT.md 都是"刚刚的代码"对应的图谱,查询时不需要再担心图过期。
  4. 文档波次后收尾:当某波 Agent 大量写文档/笔记/图片时,终端会打印 needs_update 通知,此时跑一次 /graphify --update,让 LLM 语义提取把这些文档节点并进图谱。

这套组合的逻辑闭环在于:代码进图零成本、可全自动;文档进图成本高、需要人在场确认——watcher 用 needs_update 标志把"高成本动作"推迟到你有空的时候,而不是悄悄烧 token。


五、依赖与产物速查

add / watch 两条通道跑通所需的可选依赖,全部声明在 pyproject.toml[project.optional-dependencies]

能力 对应 extra 关键依赖
文件夹监听 watch watchdog
视频 URL / 转写 video yt-dlpfaster-whisper(Python ≥ 3.11)
PDF / 网页正文清洗 pdf pypdfmarkdownify

产物约定:一切中间态都落在 graphify-out/(可被 GRAPHIFY_OUT 环境变量整体迁移),其中 needs_update 是"待语义更新"标志,.graphify_incremental.json/.graphify_detect.json/.graphify_extract.json--update 流水线各阶段的交接文件,graph.jsonGRAPH_REPORT.md 是最终图谱与人类可读报告。


六、快速检查清单

  • /graphify add 的脚本把 ValueError(URL 校验失败)与 RuntimeError(抓取失败)都转译为带明确消息的非零退出——任何错误都要转告用户,不要静默继续
  • 视频 URL 需要先 pip install 'graphifyy[video]',否则 transcribe.py 会提示缺 faster-whisper / yt-dlp;
  • 代码文件变更走 AST 通道,graph.json / GRAPH_REPORT.md 立即更新,无需 LLM
  • 文档/论文/图片变更只写 needs_update 标志并打印通知,需要你执行 /graphify --update
  • --debounce(默认 3s)把一波并行写入合并为一次重建,避免每写一文件就重建一次;
  • 纯代码场景请始终让 watcher 跑在后台终端;文档密集的 Agent 波次结束后记得手动补一次 /graphify --update

更细的增量 merge 分步命令见同技能下的 update.md,watch 的并发与驱逐逻辑测试覆盖在 tests/test_watch.pytests/test_watch_manifest_location.py,URL 摄取的输入输出样例可参考 tests/test_ingest.py,需要进一步下钻时可结合源码与用例一起阅读。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
594
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
916
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
516
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388