graphify 语料持续进化指南:用 `/graphify add` 摄取外部 URL、用 `--watch` 监控文件夹自动重建知识图谱
本指南以 graphify 的 add-watch 技能参考文档为核心,围绕两个「默认构建之外」的进阶能力展开:/graphify add <url> 把网页、论文、PDF、图片、推文甚至视频一键摄入语料并并入已有图谱,以及 --watch 让 graphify 在后台实时监听文件夹、代码一有改动就自动做无 LLM 的 AST 重建。读完本文,你将掌握这两条能力的确切命令、支持的全部 URL 类型与各自产出格式、watch 的触发策略与防抖逻辑,并沿源码链路理解它们在 graphify/ingest.py 与 graphify/watch.py 中的真实实现。
一、先看懂执行前提:这两条命令在哪个环节工作
/graphify add 与 --watch 都不是默认构建(default build)的一部分。在 graphify/skill.md 的主流程说明里,它们与 --update、--cluster-only 一样属于「按需加载」的子命令:默认的 /graphify 是一次性全量构建并出图,而 add 与 watch 服务于已有语料和已有图谱的持续增量演进。
运行任何子命令前,skill 要求先确认 graphify-out/.graphify_python 存在——这个文件记录了真正安装了 graphify 的那个 Python 解释器路径,后续所有 bash 命令都通过 $(cat graphify-out/.graphify_python) 调用它,避免误用 python3 而走到未安装 graphify 的虚拟环境(详见 graphify/skill.md 的解释器守卫逻辑)。输出目录默认是 graphify-out,且可用环境变量 GRAPHIFY_OUT 覆盖以支持 worktree 或多输出场景(见 graphify/paths.py 的常量定义)。
二、/graphify add <url>:把外部资料变成图谱节点
/graphify add 的职责是:抓取一个 URL,把它作为「graphify-ready」文件保存进语料目录,随后立即跑 --update 增量管线,把新文件并入已存在的图谱。参考文档给出的核心 Python 调用如下:
$(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 同理。要点有两处:
- 错误不能静默吞掉:
ValueError(通常是 URL 校验失败)与RuntimeError(通常是抓取失败)都要把原因打到 stderr 并向用户说明,不能假装成功。 - 成功后必须续接增量管线:保存文件只是第一步,还应自动在
./raw上运行--update,让新文件被重新抽取、合并进既有图结构。这条「抓取 → 落盘 → 增量合并」的链条,与参考文档对--update的描述(graphify/skill.md:「re-extract only new/changed files」)完全对应。
2.1 支持什么 URL,源码里如何自动识别
「哪些 URL 该走哪条抓取路径」完全由 graphify/ingest.py 中的 _detect_url_type() 判定,规则按优先级命中即返回:
| 触发特征 | 判定类型 | 说明 |
|---|---|---|
twitter.com / x.com |
tweet |
经 oEmbed 抓取推文 |
arxiv.org |
arxiv |
抓摘要页并存为论文元数据 |
github.com |
github |
归类预留 |
youtube.com / youtu.be |
youtube |
yt-dlp 下载音频 |
路径以 .pdf 结尾 |
pdf |
二进制下载 |
路径以 .png/.jpg/.jpeg/.webp/.gif 结尾 |
image |
二进制下载 |
| 其余全部 | webpage |
转 Markdown |
在 graphify/ingest.py 的 ingest() 入口,先做目录创建与 validate_url() 安全校验(拦截私网 IP、非法 scheme,见 graphify/security.py),再按类型分派。
2.2 各类 URL 的产出与落盘格式
YouTube / 任意视频 URL:走 graphify/transcribe.py 的 download_audio(),用 yt-dlp 下载纯音频流。依赖不在默认安装里,需额外安装:pyproject.toml 中 video extra 声明为 faster-whisper + yt-dlp(见 pyproject.toml),即参考文档所指 pip install 'graphify[video]' 的能力。下载得到的是音频文件本身;把它转写成 .txt 发生在下一次重建/语义抽取流程(transcribe 能力在 graphify/transcribe.py 等段落体现:若输入是 URL,则先经 yt-dlp 下载音频再处理)。
Twitter/X:源码 graphify/ingest.py _fetch_tweet() 把 x.com 归一化为 twitter.com,请求 publish.twitter.com/oembed,将推文 HTML 剥离成纯文本,连同作者一起保存为 .md;抓取失败时仍保存一个「无法抓取」的占位文件而非静默放弃。文件带 YAML frontmatter,例如:
---
source_url: "https://x.com/..."
type: tweet
author: "..."
captured_at: 2026-...T...Z
contributor: "..."
---
arXiv:源码 graphify/ingest.py _fetch_arxiv() 先用正则 \d{4}\.\d{4,5} 从 URL 提取论文编号,再请求 export.arxiv.org/abs/<id> 解析标题、作者、摘要,保存为 arxiv_<id下划线版>.md,frontmatter 携带 arxiv_id、paper_authors、type: paper 等元数据,正文含 Authors 与 Abstract 小节,供后续抽取直接作为论文语义节点。若 URL 中解析不出 arXiv 编号,则回退为普通网页处理。
PDF:由 _download_binary() 直接写字节流存为 .pdf(graphify/ingest.py),后续构建对 PDF 走 pypdf 抽取(pdf extra 同时带来 markdownify,见 pyproject.toml)。
图片(.png/.jpg/.webp 等):同样二进制下载到 ./raw。图片内容本身 graphify 不自己读——下一次运行时由带视觉能力的模型(Claude vision)做语义抽取,这也是为什么图片改动在 watch 场景里会被归入「需要 LLM 重抽取」的一类(后文详述)。
任意普通网页:抓取 HTML,先预剥离 <script>/<style>,再转成 Markdown 正文。参考文档将其表述为「转换为 markdown」(html2text);从源码 graphify/ingest.py 的 _html_to_markdown() 看,实际优先使用 markdownify(pdf extra 附带),未安装时降级为标签剥离并截断到 8000 字符;网页正文最终写入上限 12000 字符(graphify/ingest.py)。落盘格式同样是「YAML frontmatter(source_url、type: webpage、title、captured_at、contributor)+ 正文 + 原文链接」。
2.3 两个不易察觉的安全与命名细节
- YAML 注入防护:抓取到的页面标题、推文作者都来自不可信外部内容,若直接拼进 YAML 可能用换行/制表符/Unicode 行分隔符逃逸出双引号标量、注入兄弟键(对应问题编号 F-009/F-019)。源码专门实现了
_yaml_str()(graphify/ingest.py),手工对\n、\r、\t、\0、U+2028、U+2029 及全部控制字符做转义,而不依赖 PyYAML。 - 文件名校验 + 防覆盖:URL 经
_safe_filename()清洗成最长 80 字符的合法文件名;同名文件存在时用_1.md、_2.md递增后缀,绝不覆盖旧语料(graphify/ingest.py)。
三、--watch:后台监听文件夹并自动重建图谱
--watch 解决的是「语料总在变、图谱不能总是手动重建」的问题。参考文档给出的启动命令:
$(cat graphify-out/.graphify_python) -m graphify.watch INPUT_PATH --debounce 3
把 INPUT_PATH 换成要监听的目录即可。从源码看,graphify/watch.py 的入口还允许省略路径(默认 .),--debounce 是浮点秒数、默认 3.0。watch 依赖第三方库 watchdog(对应 pyproject.toml 的 watch extra,见 pyproject.toml),未安装时入口会明确抛出 ImportError 提示 pip install watchdog。
3.1 触发后做什么,取决于「什么变了」
watch() 主循环(graphify/watch.py)把变化分成两类处理,这是整个 watch 设计的分水岭:
- 只有代码文件变了(.py、.ts、.go 以及 graphify/detect.py 中
CODE_EXTENSIONS列出的全部扩展名):立即重跑 AST 抽取 + 重建 + 聚类,全程不需要 LLM。产物graph.json与GRAPH_REPORT.md自动更新——源码中重建成功后还会顺带按需刷新 HTML 可视化与用户先前生成过的*-callflow.html(graphify/watch.py)。 - 文档、论文或图片变了:watch 不自行做语义抽取,因为这类内容需要 LLM 理解。它只在
graphify-out/needs_update写一个标志位,并打印提示,要求运行/graphify --update做 LLM 语义重抽取(见 graphify/watch.py 的_notify_only()与 graphify/watch.py 的check_update(),后者对 cron 场景安全返回 True)。扩展名集合在 graphify/detect.py:DOC_EXTENSIONS(.md/.txt/.rst/.html/.yaml 等)、PAPER_EXTENSIONS(.pdf)、IMAGE_EXTENSIONS(png/jpg/gif/webp/svg)。
值得强调的是,被监听范围是三者的并集 _WATCHED_EXTENSIONS(graphify/watch.py);但一个批次里只要包含现存非代码文件,就仍需写 needs_update 标志;而只要包含代码变更或任何删除事件就立即重建(_batch_triggers_rebuild(),graphify/watch.py:删除任何受监听文件都触发重建,因为「剔除失效节点」同样不需要 LLM,纯删除批次不该一直卡在标志位后面)。
3.2 防抖(debounce)为什么默认 3 秒
多个 agent 并行写文件时,文件系统事件会像波浪一样涌来。若每个事件都触发一次重建,既浪费又可能在文件尚未写完时抢跑。因此 watch 的 Handler(graphify/watch.py)只负责把变更路径累积进集合并刷新 last_trigger 时间戳;主循环每 0.5 秒检查一次,直到最后一次事件过去满 debounce 秒才真正开工(graphify/watch.py)。这样一“波”并行写入被聚合成一次重建,默认 3 秒通常足以让一批 agent 提交落定。
3.3 事件层面的防抖与防自噬细节
从实现还能看到几个容易被忽略的健壮性设计(均集中在 graphify/watch.py):
- 过滤只读事件:Linux 的 inotify(watchdog ≥ 2.3)会对每次文件 open/close 上报
opened、closed_no_write——包括 watcher 自己重建时读文件触发的那些。若不过滤,watcher 会陷入「发现变化 → 重建 → 重建读文件又触发变化」的无限循环。_is_read_only_event()只把创建、修改、移动、删除与 write-after-close 视为真变化。 - 一次加载 ignore 规则:
.graphifyignore与持久化的--exclude模式在启动时读入一次(graphify/watch.py),事件回调里不重复解析文件,避免高负载卷上烧满 CPU。 - 排除自身输出:
graphify-out目录自身、隐藏文件(dotfile)都不参与触发。
3.4 重建背后的「增量合并」保障
代码变更触发的 _rebuild_code 不是无脑全量重建。它会与既有 graph.json 做 reconcile(_reconcile_existing_graph(),graphify/watch.py):只对变更/删除的源文件做新抽取,保留未变文件的既有节点、边与超边;删除判定采用「fail-closed」策略——文件仍存在但因过滤条件离开语料的,默认保留并给出提示,只有磁盘确实缺失或命中实时 ignore 规则才剔除(graphify/watch.py)。写入侧则是先落临时文件、做 shrink 防护检查、再原子替换,并在写 graph.json 前置过期标记,保证中断后可重试(graphify/watch.py)。
若最终图与报告与上次完全一致,重建会打印 No code-graph changes detected 并保持产物不动(graphify/watch.py)——这正是增量语义下「改了没影响图」时的省钱行为。
3.5 停止与 agentic 工作流建议
按 Ctrl+C 即可停掉监听(源码在 KeyboardInterrupt 分支打印 Stopped. 并优雅关闭 observer,graphify/watch.py)。
参考文档对 agentic 工作流给出明确建议,这也是 watch 最有价值的用法:
- 在后台终端跑
--watch; - agent 一波波提交代码改动,wave 与 wave 之间由 watcher 自动吸收重建,图谱始终新鲜、且不消耗 LLM;
- 如果 agent 同时还在写文档或笔记,由于它们需要 LLM 语义重抽取,wave 结束后需手动执行一次
/graphify --update收尾。
四、运行前提与依赖速查
把全文涉及的环境约束汇总如下(均以当前仓库为准):
| 能力 | 前置条件 | 依据 |
|---|---|---|
| 任意子命令 | graphify 已安装,graphify-out/.graphify_python 已生成 |
graphify/skill.md |
| URL 视频下载 | pip install 'graphify[video]'(yt-dlp、faster-whisper) |
pyproject.toml |
| PDF/网页转 Markdown | pypdf、markdownify(随 pdf extra) |
pyproject.toml |
--watch |
watchdog(随 watch extra) |
pyproject.toml |
| 图片/文档/论文语义层 | LLM 能力,经 /graphify --update 触发 |
graphify/watch.py |
| 自定义输出目录 | 设置 GRAPHIFY_OUT 环境变量 |
graphify/paths.py |
仓库测试目录中另有 tests/test_watch.py、tests/test_incremental.py 等针对 watcher 行为与增量重建机制的用例,可作为深入理解这两条能力行为边界的补充阅读。
五、结语:两条命令拼出「图谱随代码呼吸」的闭环
把 add 与 watch 放在一起看,它们共同完成了语料生命周期的两翼:/graphify add 向外生长——网页、论文、推文、PDF、图片、视频都可以变成语料里带结构化 frontmatter 的文件,随后并入图谱;--watch 向内保鲜——代码改动被无 LLM 地即时吸收,文档类改动则通过 needs_update 标志精准提示何时该花一次 LLM 预算做语义重抽取。理解 graphify/ingest.py 的类型分派与安全落盘、graphify/watch.py 的防抖与增量 reconcile,你就能在 agent 化开发中把「喂语料」和「保图谱」都变成后台自动完成的工程能力。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00