graphify 语料扩展与增量重建实战:`/graphify add` 拉取外部资料与 `--watch` 目录监控自动刷新知识图谱
本指南面向使用 graphify 将代码库、文档与外部资料沉淀为可查询知识图谱的开发者,围绕 Codex 平台的 graphify skill 参考文档 add-watch.md 展开。你将掌握两条把“新内容”接入既有图谱的官方路径:用 /graphify add <url> 抓取并落盘一篇外部网页/推文/论文/PDF/视频,再触发语义重建;或用 --watch 让目录监控在代码变更时无需 LLM 即可自动重建图谱。读完即可在 Claude Code、Codex 等 agent 工作流中组合使用两者,实现“边写代码、边抓资料、图谱始终新鲜”。
说明:文中讨论的两条能力都不是默认构建的一部分,需要显式触发。本文同时给出 graphify/skill-codex.md 中的调用约定(见其
For /graphify add and --watch小节)与底层 Python 实现 graphify/ingest.py、graphify/watch.py,方便按需查证。
一、能力定位:为什么 add 与 watch 不属于默认构建
graphify 的主流程是“把目录变成可查询知识图谱”的一次性(或增量)构建:detect 扫描语料 → extract 解析 → build/cluster 建图 → 产出 graphify-out/graph.json、GRAPH_REPORT.md 与可选的 graph.html。而本文要讲的两条路径是它的“内容入口”和“时效保障”:
/graphify add <url>:把语料库之外的 URL 抓取成本地文件并纳入./raw,随后执行更新管线把它合并进既有图谱;--watch:守护某个目录,当其中的代码文件变化时立即自动重建(无需 LLM),当文档/论文/图片变化时提示需要一次 LLM 语义更新。
两者都“不是默认构建的一部分”,因此文档要求 agent 只有在用户显式运行了 /graphify add 或传入了 --watch 时才加载对应的参考。Codex skill 的总览把它们的调用方式列为:
/graphify <path> --watch—— 监听目录,代码变更自动重建(不需要 LLM);/graphify add <url>—— 抓取 URL、存入./raw并更新图谱;/graphify add <url> --author "Name"/--contributor "Name"—— 记录写入者与语料贡献者。
当用户真正执行这些子命令时,agent 应查阅 add-watch.md,也就是本文展开的核心文档(同名参考在 agents、claude 等多个平台目录下各有一份,内容一致,平台包体积靠 tools/skillgen 生成)。
二、/graphify add <url>:把 URL 变成图谱里的一个文件
2.1 执行模型与参考命令
文档给出的“抓取 URL → 加入语料 → 更新图谱”动作,本质上是一次对 Python 函数 graphify.ingest.ingest 的调用。参考命令如下(原样继承自文档):
$(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 |
待抓取链接 | 替换为真实 URL |
AUTHOR |
写入者 | 用户提供了姓名才填,否则按源码回退逻辑处理(见下) |
CONTRIBUTOR |
语料贡献者 | 同上 |
2.2 $(cat graphify-out/.graphify_python) 是什么
命令中没有硬编码 python3,而是读取 graphify-out/.graphify_python 文件得到“正确解释器”路径。该文件的写入发生在 skill 初始化时(参考 hooks.py 的探测顺序:先看固定环境、再看 .graphify_python 文件、再按绑定目录推导、最后回退到 python3),其作用是让同一段 shell 代码在 venv、uv、系统 Python 等不同环境下都能命中“装着 graphify 的那个解释器”。因此后续所有 bash 块都应以 $(cat graphify-out/.graphify_python) 取代裸的 python3。
2.3 错误处理契约
文档对失败路径有严格要求:
ingest抛出ValueError(典型的如 URL 未通过校验)或RuntimeError(网络抓取失败等),一律打印error: <原因>到stderr并以退出码1结束;- 不得静默跳过——命令出错必须如实告知用户,避免 agent “假装成功”导致图谱缺失该内容;
- 成功落盘(输出
Saved to <路径>)之后,自动对./raw跑一遍--update管线,把新文件合并进既有图谱。
2.4 URL 类型自动识别:六类来源
文档列出了 ingest 自动识别的 URL 类型,均可由 ingest.py 中的 _detect_url_type(L64-L81)印证:
| 类型 | 判定依据(源码中的匹配逻辑) | 落地行为 | 后续处理 |
|---|---|---|---|
| Twitter / X | URL 含 twitter.com 或 x.com |
经 publish.twitter.com/oembed 拉取,存为带推文正文与作者的 .md |
下一次 update 由语义层抽取 |
| arXiv | URL 含 arxiv.org 且能提取 \d{4}\.\d{4,5} 编号 |
抓摘要页,把标题/作者/摘要连同元数据存为 .md |
同上 |
路径以 .pdf 结尾 |
直接以二进制形式下载到 ./raw |
PDF 切片/语义抽取 | |
| 图片 | .png/.jpg/.jpeg/.webp/.gif |
二进制下载 | 依赖视觉模型在下一轮抽取内容 |
| YouTube / 视频 | URL 含 youtube.com 或 youtu.be |
通过 yt-dlp 下载音频 | 下一轮转写为 .txt,需要 video 可选依赖 |
| 普通网页 | 以上都不命中 | 拉 HTML,抽取 <title>,转成 Markdown 存盘 |
作为文档抽取 |
几个值得注意的实现细节(均可在源码中核对):
- 安全前置校验:
ingest在分支处理前先调用validate_url(url)(来自 graphify/security.py),校验失败抛ValueError,对应上面try/except的第一个分支; - 网页正文清洗:先正则剔除
<script>/<style>再转换,优先用markdownify(ATX 标题、-列表、丢弃<img>),没有该库时才退回朴素的标签剥离(并截断到 8000 字符),详见_html_to_markdown(L88-L100); - YAML 前端元数据:每个
.md都会带上source_url、type、captured_at(UTC ISO 时间)、contributor等字段,作者/论文作者分别存入author/paper_authors。所有可被远程页面污染的值(如标题)都经_yaml_str转义,防止注入出兄弟 YAML 键(L13-L52); - 防覆盖命名:文件名由 URL 主机的 netloc+path 清洗而成,最长 80 字符;若同名文件已存在,则自动追加
_1、_2…… 计数器(上限 1000),避免抓取重复推文/文章时互相覆盖(L259-L268)。
2.5 命令行直用
除 skill 的 python -c 包装外,ingest.py 自带 __main__ 入口(L344-L353),可直接运行以便人工调试:
$(cat graphify-out/.graphify_python) graphify/ingest.py <url> ./raw --author "Name" --contributor "Team"
其参数与 skill 路径一致:位置参数 url、可选目标目录(默认 ./raw)、--author 与 --contributor。
三、--watch:目录守护与免 LLM 自动重建
3.1 启动方式
$(cat graphify-out/.graphify_python) -m graphify.watch INPUT_PATH --debounce 3
把 INPUT_PATH 替换为要监听的目录;--debounce 3 表示“停止收到文件事件 3 秒后才触发一次重建”。模块入口等价实现见 watch.py 末尾的 argparse,默认监听 .、默认 --debounce 3.0。
3.2 触发后的行为分支
文档把变更分成两类,watch.py 中对应的判定逻辑分别位于 _batch_triggers_rebuild(L2107-L2118)与 _notify_only(L2092-L2100):
| 变更内容 | 系统行为 | 是否需 LLM |
|---|---|---|
仅代码文件(.py/.ts/.go 等所有 AST 可解析扩展名,甚至 .md/.mdx 这类有抽取器的文档) |
立即重跑“AST 抽取 + rebuild +(必要时)cluster”,自动更新 graph.json 与 GRAPH_REPORT.md(拓扑无变化时保留原产物并顺带自愈缺失的 graph.html) |
否 |
| 文档、论文、图片等非代码文件 | 在 graphify-out/needs_update 写入标志并打印通知,提示运行 /graphify --update 做 LLM 语义重抽取 |
是 |
| 纯删除(无论何类文件) | 视同需要 rebuild——删除的节点逐出无需 LLM,由全量语料 reconcile 完成 | 否 |
3.3 debounce 的意义
默认 3 秒的防抖窗口,是为了让“一波并行写入”合并成一次重建,而不是每个文件各触发一次。这在 agent 场景尤其重要:一批 agent 并发落盘几十个文件时,若没有防抖,守护进程会反复重建、浪费 CPU 并不断重写产物。实现上,观察线程每 0.5 秒巡检一次,只有“距最近一次事件已超过 debounce”才取出批处理(见 watch() 主循环)。
3.4 停止与后台运行
前台按 Ctrl+C 即可停止,观察器会优雅退出(observer.stop()/observer.join())。文档明确建议的 agent 工作流是:
在后台终端里跑
--watch:agent 波浪式写入的代码改动会在各波之间被自动拾取并重建;若 agent 同时也在写文档或笔记,则需要在这些波之后手动执行一次/graphify --update。
也即“代码交给守护进程、语义留给手动更新”的分工,与 3.2 的分支完全对应。
3.5 源码级加固细节(供排查时参考)
- 忽略规则:启动时一次性加载
.graphifyignore(可选叠加持久化的gitignore开关),事件处理器先在扩展名/隐藏目录过滤之前短路掉被忽略路径;graphify-out自身的写入、以.开头的路径段都不会触发自身循环重建(L2171-L2208)。 - 只读事件过滤:Linux inotify 下的
opened/closed_no_write只算“被读”不算“被改”,避免守护进程自己读文件引发无限重触发(_READ_ONLY_EVENT_TYPES,L2121-L2136)。 - 重建互斥:rebuild 全程持有基于
fcntl.flock的.rebuild.lock,拿不到锁的增量请求会先把变更集写入graphify-out/.pending_changes,等持锁进程重建后回吸合并——保证并发的 post-commit hook 与 watch 触发不丢变更(L161-L218)。 - macOS 用轮询观察器:
PollingObserver用于规避 FSEvents 对某些编辑器快速保存的漏报(L2212)。 - 持久化排除项:初次
extract的--exclude/gitignore决策被写入.graphify_build.json,watch/update 的 rebuild 会重新套用,避免已排除路径被悄悄召回(L80-L137)。 - 外部队列可读的进程 ID:持锁期间
.rebuild.lock内容为持有者 PID,发布脚本等外部轮询者可以据此判断重建是否结束(L182-L218)。
四、两套机制串联成的“自更新语料管线”
将以上能力组合,可得到一条不打断开发节奏的知识图谱保鲜链路:
- agent 执行
/graphify add <url>→ingest把六类外部资料归一化落盘到./raw; - 成功后自动对
./raw跑--update,把新文件合并进既有图谱,产出刷新graph.json/GRAPH_REPORT.md; - 后台常驻
graphify.watch <项目目录> --debounce 3; - 代码变更波(
.py/.ts/.go/可解析的 Markdown 等)被守护进程捕获,防抖后走 AST-only 重建:detect → extract(仅变更文件,配合从旧图挑选的 resolution context)→ build → cluster → report,全程不调用 LLM,因此可以放心高频率运行; - 新下载的推文/论文转写稿、新增的 PDF/图片/长文档触发
needs_update标志,agent 在合适的波次后执行一次/graphify --update完成语义重抽取。
值得注意的边界条件(源码注释可印证):已携带语义(LLM)层节点的文档不会被 AST 快扫重复建节点(避免同一文件双重表示造成约 4 倍膨胀,见 watch.py L1420-L1482 附近 semantic_doc_files 的判定);而“节点数骤减但文件仍存在”会被 shrink guard 拦截,只有 --force 或显式删除证据才能放行较小写入(_check_shrink,L1080-L1150)。这些设计让 add + watch 的高频重建不会意外摧毁既有图谱。
五、验证与测试入口
仓库对这两条路径均有专门测试,可作为行为规范的“可执行文档”:
- ingest 侧:tests/test_ingest.py、tests/test_mcp_ingest.py;
- watch 侧:tests/test_watch.py、tests/test_watch_manifest_location.py,以及覆盖增量合并边界行为的 tests/test_incremental.py、tests/test_incremental_mtime_collision.py;
- 与
/graphify add密切相关的视频转写走 graphify/transcribe.py,对应 tests/test_transcribe.py。
平台分发侧的权威参考位于各 skill 目录的 references/add-watch.md(如 codex 版),codex skill 正文 skill-codex.md 中“For /graphify add and --watch”一节会指引 agent 加载它——本文即为该参考的完整展开。
六、快速结论
- 想让“外部链接”进入图谱,用
/graphify add:六类 URL 自动识别、安全落盘、成功后自动--update,失败必须显式报错; - 想让“代码变化”即时反映到图谱,用
--watch(后台终端 + 默认 3 秒 debounce):代码变更免 LLM 自动重建graph.json/GRAPH_REPORT.md;文档/图片变更只写needs_update标志,等你手动跑一次/graphify --update; - 两者叠加即形成“agent 时代”的语料保鲜闭环:抓取与代码演进交给自动化,语义理解保留给 LLM 驱动的 update,全程不打断开发节奏。
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 StartedRust0626
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