graphify 数据增补实战:用 `add` 抓取任意 URL、用 `--watch` 监视文件夹并自动更新知识图谱
graphify 的核心默认流程是扫描本地 ./raw 语料并构建可查询的知识图谱(graph.json 与 GRAPH_REPORT.md)。但真实项目中,语料往往来自外部:一篇文章、一篇论文、一条推文、一段视频或一个网页。本文围绕 skill 参考文档 add-watch(其在 Droid 等各平台 skill 中的同名副本见 tools/skillgen/expected/graphify__skills__droid__references__add-watch.md),讲解两套非默认的"数据增补"能力:/graphify add <url> 负责把远程 URL 抓取进语料库,--watch 负责让文件夹变更自动触发增量重建。读完你会掌握这两种能力的触发条件、完整命令、URL 自动识别规则、代码级行为差异,以及在多 Agent 工作流中的正确用法。
两种能力概览与触发时机
这两个功能不属于默认构建流程,只有在用户显式触发时才需要加载对应技能逻辑:
/graphify add <url>:抓取一个远程 URL,将内容保存进语料库(默认写入./raw),再触发后续更新管线把新文件合并进已有图谱。--watch:以后台进程监视一个文件夹,文件变化时自动更新图谱。
从源码结构看,二者分别对应 graphify/ingest.py(URL 抓取与落盘)和 graphify/watch.py(目录监视与增量重建)。CLI 入口均在 graphify/cli.py(add 子命令见 cli.py 的 add 分支,watch 子命令见 cli.py 的 watch 分支)。
一、/graphify add:把 URL 变成可建图的语料
1. 完整调用命令
skill 中通过 graphify-out/.graphify_python 文件记录"真正安装了 graphify 的解释器路径",因此所有 Python 调用都经 $(cat graphify-out/.graphify_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 |
用户名 | 用户提供了才填写,会写入文件 frontmatter |
CONTRIBUTOR |
贡献者名 | 同理;团队图谱场景用于溯源 |
错误处理策略是硬性要求:ingest 抛出 ValueError(典型如 URL 校验不通过)或 RuntimeError(典型如网络拉取失败)时,命令以非零码退出并向用户说明失败原因,不得静默继续。
2. 命令行等价写法
同样的能力在 cli.py 的 add 分支 有原生 CLI 入口,参数解析逻辑(--author/--contributor/--dir)与 Python API 一一对应:
graphify add <url> [--author Name] [--contributor Name] [--dir ./raw]
其中默认 --dir 即 ./raw;成功后终端会提示 "Saved to …" 并提醒 "Run /graphify --update in your AI assistant to update the graph."——这与参考文档中"保存成功后自动对 ./raw 执行 --update 管线,把新文件合并进既有图谱"的要求一致。
3. 底层实现:ingest() 的五步流程
核心函数为 ingest(),其流程可拆为:
- URL 类型自动检测:调用 _detect_url_type() 按域名与路径后缀分类。
- 安全校验:调用 graphify/security.py 暴露的
validate_url(校验失败抛ValueError),实际网络请求通过safe_fetch/safe_fetch_text完成。 - 按类型抓取:不同类型走不同抓取函数(见下表),网络/IO 异常统一包装为
RuntimeError抛出。 - 安全命名落盘:_safe_filename() 把 URL 转成磁盘安全文件名(截断至 80 字符);若文件已存在则自动追加
_1、_2计数器,不覆盖已有文件。 - 返回保存路径:调用方拿到
Path后打印结果,再进入--update管线。
4. 支持的 URL 类型(自动识别)
参考文档列出 6 类来源,其底层判定逻辑与落盘格式如下:
| URL 类型 | 判定依据 | 抓取方式与产物 | 关键实现 |
|---|---|---|---|
| YouTube / 任意视频 | 域名含 youtube.com/youtu.be |
用 yt-dlp 下载音频,下次运行时转写成 .txt;需先 pip install 'graphifyy[video]' |
download_audio() |
| Twitter / X | 域名含 twitter.com/x.com |
经 oEmbed 拉取,存为 .md(含推文正文与作者) |
_fetch_tweet() |
| arXiv | 域名含 arxiv.org |
抓取摘要+元数据,存为 .md |
_fetch_arxiv() |
路径以 .pdf 结尾 |
直接二进制下载为 .pdf |
_download_binary() | |
| 图片 | .png/.jpg/.jpeg/.webp/.gif |
直接下载原图,下次运行由 Claude vision 提取语义 | 同上 |
| 任意网页 | 兜底 | HTML 转 Markdown 后存为 .md |
_fetch_webpage() |
几点值得展开的细节:
- 文本类来源统一产出带 YAML frontmatter 的
.md。例如 tweet 会写入source_url、type: tweet、author、captured_at、contributor元数据;arXiv 额外带arxiv_id、type: paper、paper_authors。这些 frontmatter 会被后续语义提取读作节点元数据。 - YouTube 视频是"分步"而非"一步到位":
ingest只负责下载音频到语料目录,真正的语音转写发生在下一次运行(转成.txt后再进图谱)。对应的可选依赖声明在 pyproject.toml 的videoextra(faster-whisper +yt-dlp>=2026.6.9),因此文档明确要求先安装graphifyy[video](注意包名是双 y 的graphifyy,见 pyproject.toml)。 - 网页转 Markdown 有容错:参考文档提到 html2text,而实际源码 _html_to_markdown() 优先使用
markdownify(属于pdfextra 依赖),并总是先剥除<script>/<style>内容防止脚本文本泄漏;若两者都不可用则退化为基础标签剥离并截断到 8000 字符。 - frontmatter 做了 YAML 注入防护:_yaml_str() 会转义换行、制表符、
\0、U+2028/U+2029 等所有 YAML 视作换行的字符,防止恶意网页标题等外部内容逃逸出双引号标量、注入兄弟 YAML 键。 - GitHub 链接也在识别范围内:_detect_url_type() 对
github.com有独立分类,但因不属于上述专门分支,最终落入网页抓取逻辑(保存为.md)。
二、--watch:让图谱跟上文件夹的变化
1. 完整命令与防抖参数
$(cat graphify-out/.graphify_python) -m graphify.watch INPUT_PATH --debounce 3
INPUT_PATH 是要监视的文件夹;--debounce 默认 3 秒,表示文件活动停止后的等待窗口——目的是让"一波并行的 Agent 写入"只触发一次重建,而不是每个文件各自触发一次。等价 CLI 写法为 graphify watch [path](默认监视当前目录,见 cli.py 的 watch 分支)。
启动后会输出监视路径与防抖值,按 Ctrl+C 停止。监视的扩展名集合 _WATCHED_EXTENSIONS 由 CODE_EXTENSIONS ∪ DOC_EXTENSIONS ∪ PAPER_EXTENSIONS ∪ IMAGE_EXTENSIONS 合并而来(见 graphify/watch.py 与 graphify/detect.py 中定义,代码/文档/论文/图片四大类的来源)。
2. 变更分流:两种截然不同的响应
监视器发现变化后按"变了什么"决定动作,这是 --watch 最核心的行为契约:
- 纯代码文件变化(
.py、.ts、.go等):立即重跑 AST 提取 + 重建 + 聚类,全程不需要 LLM。graph.json与GRAPH_REPORT.md自动更新。 - 文档、论文或图片变化:写入
graphify-out/needs_update标记文件并打印通知,提示运行/graphify --update(这类文件需要 LLM 语义重提取,AST 快速通道覆盖不了)。
对应实现位于 watch() 主循环,用两个谓词做分流:
- _batch_triggers_rebuild():批次含代码扩展名或含删除事件即为
True。代码重建无需 LLM;任何类型文件的删除也会触发重建,因为语料对账(reconcile)需要把磁盘上已消失的源节点逐出图谱,无需 LLM 也能做。 - _batch_needs_llm_flag():批次里存在仍存活的非代码文件才写
needs_update标记——纯删除批次不该留下一个过期的标记。
写标记与提示的落点在 _notify_only():在 watch_path/graphify-out/needs_update 写入内容并打印"Run /graphify --update in Claude Code to update the graph"。配套的 _check_update() 则用于 cron 等场景安全地探测该标记是否存在(始终返回 True,不会让 cron 误报警)。
3. 增量重建的底层保障
--watch 的"快"来自多层工程设计(源码均在 graphify/watch.py):
- 防抖循环 + watchdog:主循环每 0.5 秒轮询一次
pending状态,距离最后事件超过debounce才触发。macOS 上改用轮询式观察器(PollingObserver),规避 FSEvents 在某些编辑器快速保存时的漏事件问题。 - 只读事件过滤:Linux 的 inotify 会对每次文件 open/close 上报
opened/closed_no_write事件,若不过滤,监视器会把自己重建时的读取行为当成变更,陷入自我触发死循环。_is_read_only_event() 专门排除这类只读事件。 - 忽略规则一次加载:启动时一次性解析
.graphifyignore(并尊重持久化的 gitignore 设置),避免每个文件系统事件都重新解析规则文件;dot 目录与graphify-out/自身的写入也会被过滤,防止监视器重建产物再次触发自身。 - 锁与防塌缩:重建通过 _rebuild_lock() 的
flock互斥;对既有图的合并走 _reconcile_existing_graph(),采用"fail-closed"策略——文件仍存在但离开扫描语料 ≠ 删除,只有活的忽略规则命中或磁盘上确实消失才做逐出,避免过滤规则误伤导致节点被静默批量清空。 - 变化集不丢失:无法取得重建锁的 post-commit 钩子进程会把变更路径追加写入
.pending_changes,持锁进程重建前后各排空一次并合并(见 _queue_pending())。
相关行为有测试覆盖,例如 tests/test_watch.py 与 tests/test_watch_manifest_location.py。
4. Agent 工作流中的正确姿势
参考文档给出的最佳实践是:
把
--watch跑在一个后台终端里。Agent 各轮(waves)产生的代码变更会在波次之间被自动拾取;但如果 Agent 同时也在写文档或笔记,你需要在这些波次之后手动执行一次/graphify --update。
原因正在于第二节的分流契约:代码走免 LLM 的即时重建,文档/论文/图片必须经 LLM 语义重提取。因此理想节奏是——Agent 写代码时让 --watch 兜底增量更新;当一波文档类产出结束后,人(或编排层)补一次 --update,让 needs_update 标记清空、语料语义节点完整合并。
三、依赖、适用范围与限制
--watch依赖第三方库watchdog(未安装时会明确抛出ImportError: pip install watchdog),对应 pyproject.toml 的watchextra。- 视频类 URL 需额外安装
graphifyy[video](yt-dlp + faster-whisper);网页转 Markdown 的推荐路径依赖markdownify。 add写入的新文件默认落在./raw,但实际目标目录可用--dir调整;保存成功后仍需显式(或由 skill 自动)触发--update才能真正进入图谱——add只负责"入料",不负责"建图"。- 以上能力面向 Claude Code、Cursor、Codex、Gemini CLI、Droid 等各平台 skill,完整装配流程见对应平台主 skill 文档,例如 graphify/skill-droid.md 与 graphify/skill.md。
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