graphify 的 add 与 --watch 深度解析:URL 内容摄入知识图谱与文件夹监听自动更新
2026/9/7 6:18:33 网站建设 项目流程

graphify 的 add 与 --watch 深度解析:URL 内容摄入知识图谱与文件夹监听自动更新

【免费下载链接】graphifyTurn any codebase, with its docs, SQL schemas, configs, and PDFs, into a queryable knowledge graph. A /graphify skill for Claude Code, Cursor, Codex, and Gemini CLI: local deterministic AST parsing, every edge explained, no vector store.项目地址: https://gitcode.com/GitHub_Trending/graph/graphify

graphify 除了对本地代码、文档、SQL、配置做确定性 AST 解析外,还提供两条把"外部世界"纳入知识图谱的通道:/graphify add <url>将任意 URL(视频、推文、arXiv 论文、PDF、图片、网页)抓取落盘到./raw语料库,以及--watch文件夹监听器在文件变动时自动重建图谱。本文以 graphify 的 skill 参考文档(add-watch.md)为骨架,结合 graphify/ingest.py 与 graphify/watch.py 的源码实现,完整讲解这两条通道的命令用法、URL 类型自动分派机制、防抖与双路更新策略,以及needs_update标志如何与/graphify --update形成闭环。

这两个入口在 skill 体系中的定位

参考文档开头就明确了加载条件:只有当用户执行了/graphify add <url>或传入了--watch时,Agent 才需要加载这份参考文档,二者都不属于默认构建流程(原文:"Neither is part of the default build")。也就是说,一次普通的 graphify 构建只扫描本地目录;add--watch是两个可选项,分别解决"语料从哪来"与"图谱如何保鲜"的问题。

两个命令都通过同一行模板调用:

$(cat graphify-out/.graphify_python) -c "..."

其中graphify-out/.graphify_python是 graphify 构建时写入的解释器标记文件,保证 Agent 使用的是安装 graphify 的那个 Python 环境(graphify/hooks.py 中的 hook 逻辑也读取同一标记来定位解释器)。这一点在下面的两段命令中同样适用。

/graphify add:把 URL 摄入语料库

命令模板与替换规则

文档给出的完整命令如下,需要替换三个占位符:

$(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:团队图谱场景下的贡献者名,同样可选。

文档同时规定了两条行为约束:

  1. 出错必须显式上报——命令以错误退出时,要告诉用户哪里出了问题,而不是静默继续(对应上面捕获ValueError/RuntimeError并写 stderr、退出码 1 的处理);
  2. 成功后自动续跑--update——文件保存成功后,立即在./raw上运行--update流水线,把新文件合并进已有图谱,而不需要用户再手动触发。

源码印证:ingest() 的分派流程

上述命令最终调用的是 graphify/ingest.py 中的ingest(url, target_dir, author, contributor)。其执行顺序在源码中非常清晰:

  1. 确保目标目录存在(target_dir.mkdir(parents=True, exist_ok=True));
  2. 调用_detect_url_type(url)分类 URL;
  3. 调用validate_url(url)做安全校验(来自 graphify/security.py);
  4. 按类型走不同分支,网络异常统一包装为RuntimeError,URL 非法则抛ValueError——这正与命令模板中捕获的两种异常类型一一对应。

URL 类型自动检测

参考文档列出的类型表("Supported URL types, auto-detected")与源码_detect_url_type()(graphify/ingest.py)完全吻合,检测规则是纯字符串/后缀判断:

URL 类型检测规则落盘形式后续处理
YouTube / 任意视频youtube.comyoutu.be音频文件(.m4a/.opus等)下次构建时由 Whisper 转写为.txt,需要pip install 'graphifyy[video]'
Twitter / Xtwitter.comx.com.md(YAML frontmatter + 推文正文与作者)通过 oEmbed 抓取
arXivarxiv.org.md(摘要 + 元数据)通过 export API 抓取摘要
PDF路径以.pdf结尾.pdf原文件直接下载二进制
图片(.png/.jpg/.webp 等)路径以图片后缀结尾原格式图片文件下次构建时由 Claude vision 抽取
任意网页以上均不命中.md(frontmatter + 正文 markdown)默认兜底分支

几个值得注意的实现细节(均来自 graphify/ingest.py 源码):

  • 视频分支url_type == "youtube"时延迟导入from graphify.transcribe import download_audio(graphify/transcribe.py),用 yt-dlp 下载"最佳音频流",文件名基于 URL 的 SHA-1 前 12 位生成(yt_<hash>.<ext>),已存在同名文件时直接命中缓存、跳过下载。对应的安装依赖即 pyproject.toml 中的可选依赖组:video = ["faster-whisper; python_version >= '3.11'", "yt-dlp>=2026.6.9"]
  • 推文分支:先把x.com归一化为twitter.com,再请求publish.twitter.com/oembed接口取htmlauthor_name;oEmbed 失败时不报错,而是写入一条 "could not fetch content" 的占位说明,保证 URL 至少以存根形式进入语料库(_fetch_tweet,graphify/ingest.py)。
  • arXiv 分支:用正则(\d{4}\.\d{4,5})从 URL 提取论文 ID,改向export.arxiv.org/abs/<id>抓取标题、作者与摘要;提取不到 ID 时降级为普通网页处理。文件名固定为arxiv_<ID>.md(点号换下划线),与 URL 形态无关,便于去重识别(_fetch_arxiv,graphify/ingest.py)。
  • 网页分支:先提取<title>,再做 HTML → Markdown 转换。参考文档写的是"via html2text",而从源码看,_html_to_markdown()实际优先使用 markdownify(若已安装),否则回退到"去标签 + 空白折叠"的兜底实现(截断 8000 字符);正文最终以markdown[:12000]写入文件,控制单个抓取页面的规模上限(graphify/ingest.py)。
  • 所有文本类落盘文件都带 YAML frontmatter,记录source_urltypeauthor/title/arxiv_idcaptured_at(UTC ISO 时间)与contributor——这些字段会在后续提取时成为图谱节点的元数据,--author/--contributor参数就是在这里生效的。

安全与命名细节

ingest()路径上有几道在参考文档中未展开、但值得了解的保护措施:

  • URL 校验:所有抓取前都经过validate_url(),在请求发出前拦截私有 IP 与非法 scheme(SSRF 防护;graphify/transcribe.py 中同样在下载前调用并附有注释说明)。
  • 文件名净化_safe_filename()netloc + path中所有非[\w-]字符替换为_、折叠连续下划线、截断到 80 字符,避免抓取路径写入非法文件名(graphify/ingest.py)。
  • 防覆盖:目标文件已存在时自动追加_1_2… 计数后缀(上限 1000),不会静默覆盖语料库中的旧文件(graphify/ingest.py)。
  • YAML 注入防护:抓取来的标题/作者等外部字符串一律经过_yaml_str()转义后再嵌入 frontmatter,覆盖\n\r\t\0以及 U+2028/U+2029 等所有 YAML 行分隔符,防止恶意页面标题"逃逸"出双引量标注入兄弟键(graphify/ingest.py)。

--watch:文件夹监听与自动更新

命令模板

参考文档给出的第二条命令:

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

INPUT_PATH替换为要监听的文件夹即可。--debounce的单位是秒,默认值 3 秒。

双路更新策略:按"变了什么"决定怎么做

这是 watch 的核心设计,文档将其表述为两种行为,源码在 graphify/watch.py 的watch()函数中有逐行对应:

  • 只有代码文件变更(.py、.ts、.go 等):立即重跑"AST 提取 + 重建 + 聚类",全程不需要 LLM,graph.jsonGRAPH_REPORT.md自动更新。源码中这一步由_rebuild_code(watch_path)执行,内部串联detect → extract → build → cluster → analyze → report → to_json(graphify/watch.py 中的导入清单即为该调用链)。
  • 文档、论文或图片变更:写一个graphify-out/needs_update标志文件并打印提示,告知需要运行/graphify --update才能完成 LLM 语义重抽取。这一步由_notify_only()实现(graphify/watch.py),它只写标志、不尝试重建——因为语义层节点无法用纯 AST 路径再生。

两条路径的判定函数分别值得看一下(graphify/watch.py):

  • _batch_triggers_rebuild(batch):批内任一文件后缀属于代码扩展名,或任一文件已被删除时立即重建。注意"任意文件删除也触发重建"这一细节:删除后的陈旧节点淘汰(eviction)同样不需要 LLM,若只按"是否有代码变更"判断,一个纯文档删除批就会被搁置在needs_update标志后面。
  • _batch_needs_llm_flag(batch):只有仍然存在于磁盘上的非代码文件才需要写needs_update标志;已删除的非代码文件交给重建时的对账清扫处理,纯删除批不会留下陈旧标志。

另外源码还有一层文档未提的兜底:_rebuild_code用 per-repo 的flock咨询锁(graphify-out/.rebuild.lock)加.pending_changes排队文件保护重建过程,避免多进程并发重建互相覆盖(graphify/watch.py)。

防抖(Debounce)机制

参考文档对防抖的解释是:"等到文件活动停歇后才触发,避免并行 Agent 写入的浪潮按文件逐个触发重建"。源码实现(graphify/watch.py)与此描述一一对应:

  • 主循环每 0.5 秒醒一次;
  • 事件到来时只记录last_trigger = time.monotonic()并把路径累加进changed集合,不立即重建
  • 只有当pending为真且距离最后一次触发已不小于debounce秒时,才把整批路径取出、清空集合,然后打印N file(s) changed并执行上述双路判定。

因此--debounce 3的含义是"最后一次文件变动后静默 3 秒",一波连续写入只会产生一次重建。CLI 参数解析见 graphify/watch.py:位置参数path缺省为.--debounce为浮点数、默认 3.0。

事件过滤:watch 到底看什么

watch()基于 watchdog 库,在注册 handler 之前有一组过滤规则(graphify/watch.py):

  • 扩展名白名单_WATCHED_EXTENSIONS是代码、文档、论文、图片四类扩展名的并集(CODE_EXTENSIONS | DOC_EXTENSIONS | PAPER_EXTENSIONS | IMAGE_EXTENSIONS,来自 graphify/detect.py),白名单之外的事件直接丢弃;
  • .graphifyignore优先短路:模式在启动时只解析一次,每个事件先查忽略规则再查扩展名,避免高负载卷上的无效事件耗尽 CPU;
  • 点目录与输出目录:路径中任何一段以.开头、或位于graphify-out/内的变更一律忽略——后者尤其重要,否则重建产物本身又会被当成输入,形成自我触发;
  • 只读事件过滤:Linux 上 inotify(watchdog ≥ 2.3 / ≥ 4)会对每次打开/关闭文件产生openedclosed_no_write事件,包括 watcher 自己读文件、hook stat 源码在内;源码将这两类显式视为"读"而非"变更",只把创建、修改、移动、删除与 close-after-write 当作变更(_is_read_only_event,graphify/watch.py);
  • macOS 特殊处理sys.platform == "darwin"时改用PollingObserver,因为 FSEvents 在某些编辑器下会漏掉快速保存(graphify/watch.py)。

watch 启动时的标准输出也值得留意,它会直接告诉用户两条通道各自的语义:

[graphify watch] Watching <abs-path> - press Ctrl+C to stop [graphify watch] Code changes rebuild graph automatically. Doc/image changes require /graphify --update. [graphify watch] Debounce: 3.0s

按文档说明,Ctrl+C即可停止,handler 捕获KeyboardException后打印Stopped.并停止 observer。

面向 Agent 工作流的用法

参考文档最后一段给出了在 Agent 化流程中的部署建议:把--watch放在后台终端运行。这样 Agent 分"波"(wave)写代码时,波与波之间的代码改动会被监听器自动拾取并重建图谱;但如果同一批 Agent 还写了文档或笔记,那些变更只会留下needs_update标志,需要在相应波结束后手动补一次/graphify --update。这与上一节的双路策略是同一逻辑的使用侧表述。

needs_update 标志的闭环:check-update 与读取提示

needs_update标志不是只写不读的产物,graphify 为它提供了消费端:

  • graphify check-update <path>子命令:检查标志是否存在,存在则打印"有待处理的非代码变更,请运行/graphify --update";它总是返回成功(cron-safe),适合放进定时任务轮询(命令帮助见 graphify/main.py,实现check_update只读标志、不清除——清标志是执行--update的职责)。
  • 读取钩子:Agent 通过 Read 工具读取项目文件时,graphify/cli.py 中的 hook 逻辑会把graphify-out/needs_update的存在视为"图谱已陈旧"的信号,向 Agent 提示图谱落后于磁盘状态,从而引导其在问答前先触发更新。

这两个消费端保证了"文档变更后忘记跑--update"这件事最终会被系统自己发现,而不是静默地用旧图谱回答问题。

测试覆盖

上述行为均有对应测试可供查证:

  • tests/test_ingest.py:URL 分类、抓取落盘与异常路径;
  • tests/test_watch.py:needs_update标志的写入/读取、check_update不删标志、代码/文档混合批次的分发判定,以及用debounce=0.2在真实文件系统事件下驱动watch()的端到端用例;
  • tests/test_transcribe.py:视频音频下载与转写;
  • tests/test_security.py:validate_url等抓取前安全校验。

适用前提与限制

结合 pyproject.toml 与源码,使用本文两条通道时的适用前提如下:

  • add的可选能力依赖 extras:视频转写需要pip install 'graphifyy[video]'(提供 yt-dlp 与 faster-whisper,且 faster-whisper 要求 Python ≥ 3.11);网页→Markdown 的质量取决于是否安装了 markdownify,否则回退到基础去标签实现并截断正文。
  • 抓取目标必须通过安全校验:私有地址、非法 scheme 会在发出请求前被拒绝,add因此无法用于摄入内网资源 URL。
  • --watch依赖 watchdog:缺失时watch()会明确报出安装提示而非静默失败;macOS 上自动切换为轮询观察器,行为与 inotify 略有差异。
  • watch 只负责"代码层保鲜"与"语义层提示":文档/论文/图片的语义节点永远需要/graphify --update的 LLM 流水线补齐,这是设计上的边界,不是缺陷。

综上,graphify 的add--watch一条解决语料入口(任意 URL 类型自动分派、安全抓取、防覆盖落盘),一条解决图谱时效(防抖批处理、代码变更免 LLM 即时重建、非代码变更以标志文件提示语义更新),二者共同让"任何代码库 + 其文档"构成的知识图谱可以随时间与外部信息持续生长。

【免费下载链接】graphifyTurn any codebase, with its docs, SQL schemas, configs, and PDFs, into a queryable knowledge graph. A /graphify skill for Claude Code, Cursor, Codex, and Gemini CLI: local deterministic AST parsing, every edge explained, no vector store.项目地址: https://gitcode.com/GitHub_Trending/graph/graphify

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询