graphify 的视频转写机制:Whisper 集成、God-Node 领域提示与 Step 2.5 流水线
本文以 Claude 技能参考文档 transcribe.md 为核心,完整拆解 graphify 把视频/音频语料纳入知识图谱的 Step 2.5 环节:从 detect 检测到 video 文件,到由 god node 标签生成 Whisper 领域提示(domain hint),再到执行本地 faster-whisper 转写、产出 .txt 文稿并回流到文档抽取流程的全过程。读完你可以掌握两个环境变量的正确用法(export 而非赋值)、避免 JSON 输出被 stdout 进度污染的经典陷阱(#1392),并能对照 graphify/transcribe.py 源码理解缓存、URL 下载与容错机制的实现细节。
Step 2.5 在流水线中的定位:仅在检测到视频时触发
graphify 的构建流水线中,转写是一个"按需加载"的分支步骤。技能主文档 graphify/skill.md 明确规定:
Skip this step entirely if
detectreturned zerovideofiles. When the corpus has video or audio, seereferences/transcribe.mdto transcribe them to text first, then treat the transcripts as doc files in Step 3.
也就是说,语料库里没有任何视频文件时,Agent 根本不会读取 transcribe 参考文档——这是原文档开宗明义的第一句约束。它的触发链是:
- Step 2 的
detect按扩展名把语料分类,视频/音频文件归入video桶(判定表见 graphify/detect.py 中的VIDEO_EXTENSIONS:.mp4 .mov .webm .mkv .avi .m4v .mp3 .wav .m4a .ogg,与 graphify/transcribe.py#L11 中的集合保持一致),结果写入graphify-out/.graphify_detect.json; - 若
video列表非空,进入 Step 2.5:先把视频转成文本,再在 Step 3 中把文稿当作普通文档参与语义抽取; - 同一份参考文档也被复制到其他平台技能目录下(如 graphify/skills/codex/references/transcribe.md),各平台技能的行为是等价的。
领域提示(domain hint):让 god nodes 免费指导 Whisper 的 initial_prompt
参考文档给出的核心策略是:不额外调用任何 API。你自己(编码 Agent)本来就是一个语言模型,直接读取 god node 标签,亲手写一句领域提示,再把它作为 Whisper 的 initial_prompt 传入即可。
数据来源。 god node 标签来自 graphify-out/.graphify_detect.json(首次运行)或上一次运行留下的分析文件。god nodes 本身由 graphify/analyze.py#L109-L130 的 god_nodes(G, top_n=10) 计算:按度(degree)降序取前 10 个真实实体,并排除文件级 hub 节点、概念节点与 JSON 键噪声节点——这些是语料库中"连接最多的核心抽象",天然适合作为话题锚点。
提示的写法。 原文档给出两个示范:
- 标签为
transformer, attention, encoder, decoder→"Machine learning research on transformer architectures and attention mechanisms. Use proper punctuation and paragraph breaks." - 标签为
kubernetes, deployment, pod, helm→"DevOps discussion about Kubernetes deployments and Helm charts. Use proper punctuation and paragraph breaks."
兜底规则。 如果语料库只有视频文件、没有任何其他文档或代码可供推断领域,直接使用通用兜底提示:
"Use proper punctuation and paragraph breaks."
这个字符串在源码中就是 _FALLBACK_PROMPT 常量(graphify/transcribe.py#L16),保证文档与实现一致。
环境变量传递与源码印证。 提示必须命名为 GRAPHIFY_WHISPER_PROMPT 并且必须 export(不是 shell 里的普通变量赋值),因为转写是在一个子 Python 进程中运行的,只有导出的环境变量才对子进程可见。这一点在源码里有双重印证:
- graphify/transcribe.py#L95-L115 的
build_whisper_prompt()会优先检查os.environ.get("GRAPHIFY_WHISPER_PROMPT"),一旦存在就短路掉基于 god node 的自动拼装;测试用例test_build_whisper_prompt_env_override(见 tests/test_transcribe.py#L40-L44)专门验证了这个短路行为; - 未设置时,
build_whisper_prompt()会取前 10 个 god node 标签中的前 5 个拼成"Technical discussion about {topics}. Use proper punctuation and paragraph breaks."作为次级默认。
执行转写:参考文档给出的完整命令
原文档 Step 2 的完整可复制命令如下,建议原样保留使用:
export GRAPHIFY_WHISPER_MODEL=base # or whatever --whisper-model the user passed (must be exported)
export GRAPHIFY_WHISPER_PROMPT="<the one-sentence domain hint you composed in Step 1>"
$(cat graphify-out/.graphify_python) -c "
import json, os, sys
from pathlib import Path
from graphify.transcribe import transcribe_all
detect = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\"utf-8\"))
video_files = detect.get('files', {}).get('video', [])
prompt = os.environ.get('GRAPHIFY_WHISPER_PROMPT', 'Use proper punctuation and paragraph breaks.')
transcript_paths = transcribe_all(video_files, initial_prompt=prompt)
# Write the JSON from Python (NOT a shell '>' redirect): transcribe_all/Whisper
# print progress to stdout, which would otherwise corrupt the JSON file (#1392).
Path('graphify-out/.graphify_transcripts.json').write_text(json.dumps(transcript_paths, ensure_ascii=False), encoding=\"utf-8\")
print(f'Transcribed {len(transcript_paths)} file(s)', file=sys.stderr)
"
命令中每个细节都有出处,逐条说明:
| 要素 | 作用 | 源码/文档依据 |
|---|---|---|
$(cat graphify-out/.graphify_python) |
使用 Step 1 解析出的、真正装有 graphify 依赖的解释器路径,而不是假设的 python3 |
graphify/skill-agents.md#L98 写入该文件 |
detect.get('files', {}).get('video', []) |
从 detect 输出中取出视频文件列表 | graphify-out/.graphify_detect.json 结构 |
GRAPHIFY_WHISPER_PROMPT |
子进程读取的领域提示,缺省时回退到兜底提示 | graphify/transcribe.py#L106 |
GRAPHIFY_WHISPER_MODEL |
选择 Whisper 模型,默认 base |
graphify/transcribe.py#L14-L20:_model_name() 读环境变量,缺省 _DEFAULT_MODEL = "base" |
用 Python 写 JSON 而非 shell > 重定向 |
transcribe_all/Whisper 会向 stdout 打印进度,重定向会把进度混进 JSON 文件导致其损坏 |
文档中标注的 #1392 回归 |
print(..., file=sys.stderr) |
计数信息走 stderr,同样保护 JSON 通道 | 同上 |
模型选择。 默认 base;如果用户在命令中传了 --whisper-model <name>(例如 graphify/skill.md#L21 示范的 /graphify <path> --whisper-model medium),则必须 export GRAPHIFY_WHISPER_MODEL=<name> 后再执行上面的命令——再次强调是 export 而非赋值。
转写完成后:文稿如何回流到 Step 3
参考文档对"转写之后"的动作有明确约定:
- 从
graphify-out/.graphify_transcripts.json读回所有文稿路径; - 在派发 Step 3B 的语义子 Agent 之前,把这些路径并入文档(docs)清单——从此它们与
.md文档享受同等待遇; - 打印
Transcribed N video file(s) -> treating as docs告知用户数量; - 若某个文件转写失败,打印警告并继续处理其余文件,不让单点失败中断整条流水线。
最后一条容错语义由 graphify/transcribe.py#L166-L186 的 transcribe_all() 实现:它逐个调用 transcribe(),except Exception 时打印 warning: could not transcribe ... 并跳过,最终只返回成功项的路径列表;空输入直接返回 []。对应的测试 test_transcribe_all_skips_failed(tests/test_transcribe.py#L136-L147)验证了"失败即跳过、不抛异常"的行为。
深入 transcribe.py:缓存、URL 下载与本地推理参数
参考文档只描述了调用面,而 graphify/transcribe.py 的实现面还有几个值得了解的机制:
输出与缓存。 文稿默认写入 graphify-out/transcripts/ 目录(_TRANSCRIPTS_DIR 经由 graphify/paths.py#L295-L301 的 out_path("transcripts") 解析,尊重 GRAPHIFY_OUT 覆盖)。文件名取音频文件的 stem 加 .txt;若该文件已存在且未传 force=True,transcribe() 直接返回缓存路径,完全不加载 Whisper 模型。测试 test_transcribe_uses_cache 与 test_transcribe_force_reruns 分别覆盖了"命中缓存"与 force 强制重转两条路径。
本地确定性推理。 转写使用 faster-whisper,固定参数 WhisperModel(model_name, device="cpu", compute_type="int8"),转写时 beam_size=5 并传入 initial_prompt(graphify/transcribe.py#L150-L155)。从源码结构看,这是一个纯 CPU + int8 量化配置——与 graphify "本地、无向量库、确定性"的整体立场一致,不需要 GPU 或云端 ASR 服务。
URL 支持。 若输入不是文件路径而是 http://、https://、www. 开头的 URL,transcribe() 会先经 download_audio() 用 yt-dlp 只拉音频流:以 URL 的 SHA-1 前 12 位生成稳定文件名 yt_<hash>.<ext>,命中已有缓存则直接复用;下载前还会调用 graphify.security 的 validate_url() 拦截私有 IP 与非法 scheme(graphify/transcribe.py#L50-L92)。
依赖安装。 视频能力是可选依赖,在 pyproject.toml 中声明为 video extra:faster-whisper(要求 Python ≥ 3.11)与 yt-dlp>=2026.6.9。若未安装,_get_whisper() / _get_yt_dlp() 会抛出带安装提示的 ImportError:pip install 'graphifyy[video]';test_transcribe_missing_faster_whisper 验证了该异常会向上传播而不是静默吞掉。
小结:这条参考文档的工程价值
transcribe.md 篇幅不长,但每一行都是踩过坑的约定:
- 条件加载——没有视频的语料永远不读它,避免无关上下文稀释 Agent 注意力;
- 零额外 API 的领域提示——god node 标签 → 一句话 domain hint → Whisper
initial_prompt,测试test_build_whisper_prompt_returns_topic_string确认了"不发 LLM 请求"的拼装逻辑; - 两个必须
export的环境变量——GRAPHIFY_WHISPER_PROMPT(可选,缺省回退兜底提示)与GRAPHIFY_WHISPER_MODEL(默认base); - JSON 输出走 Python、进度走 stderr——#1392 的教训被固化进了命令注释;
- 失败不阻塞——
transcribe_all的逐文件容错保证单个坏文件不拖垮整个图构建。
想继续深入,可以从 tests/test_transcribe.py 的全部用例出发逐条对照源码,或查看 graphify/skill.md 中 Step 2 → Step 2.5 → Step 3 的完整调度关系。
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 StartedRust0624
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