graphify 视频/音频转写指南:Whisper 提示词策略与 transcribe 管线全解析
在 graphify 中,视频与音频文件无法像代码或文档那样直接参与抽取——AST 解析器读不了二进制音视频。为此,graphify 的构建流程在 detect 之后插入了一个独立的 Step 2.5:先用 faster-whisper 把音视频转写成文本,再把转写稿当作普通文档文件进入后续语义抽取。本文以 transcribe.md 参考文档为主体,完整讲解"何时触发转写、如何自拟 Whisper 领域提示词、如何导出环境变量并执行批量转写"的实战流程,并结合 transcribe.py 源码揭示缓存、模型配置与容错机制等文档背后的一手实现细节。读完本文,你将能在含音视频的语料库上正确配置并运行 graphify 转写管线,并理解每一步设计的原因。
触发时机:只有 detect 报告了 video 文件才读取本参考
参考文档开篇即明确了加载条件:
Load this only when
detectreported one or morevideofiles. A corpus with no video never reads this.
也就是说,Step 2.5 是条件步骤:detect 返回的 video 类别文件数为零时,整个转写步骤被完全跳过。这由 skill-pi.md 主流程中的 Step 2.5 章节显式引用该参考文档来触发(references/transcribe.md 仅在语料含视频/音频时被读取)。
从源码结构看,video 类别的判定依据是 detect.py 中的扩展名白名单:
VIDEO_EXTENSIONS = {'.mp4', '.mov', '.webm', '.mkv', '.avi', '.m4v', '.mp3', '.wav', '.m4a', '.ogg'}
_classify 逻辑(detect.py)将命中该集合的文件归类为 FileType.VIDEO,随后写入 graphify-out/.graphify_detect.json 的 files.video 键——这正是转写步骤读取的输入来源。值得注意的是,音频扩展名(.mp3/.wav/.m4a/.ogg)与视频扩展名共用同一分类,因此纯音频播客、录音也能走同一条转写路径。
策略核心:用 god nodes 自拟一句话领域提示词,而非额外 API 调用
参考文档给出的策略(Strategy)是全文最有设计感的部分:
Read the god nodes from
graphify-out/.graphify_detect.json(or the analysis file if it exists from a previous run). You are already a language model — write a one-sentence domain hint yourself from those labels. Then pass it to Whisper as the initial prompt. No separate API call needed.
其要点是:
- 数据来源:读取 detect 输出或上一次运行留下的分析文件中的 god node 标签;
- 提示词生成:正在执行管线的编码代理本身就是语言模型,由它根据 god node 标签直接"脑内"写出一句话领域提示(domain hint),不需要为生成提示词单独调用一次 LLM API;
- 兜底规则:如果语料库只有视频文件、没有任何其他文档/代码,则没有 god nodes 可用,改用通用兜底提示词
"Use proper punctuation and paragraph breaks."。
Step 1 —— 亲手写出 Whisper 提示词
文档给出了两个标签到提示词的映射示例,可直接复制改写:
- 标签
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."
这一步的本质是给 Whisper 提供 initial_prompt——Whisper 模型会把该提示词的风格(标点习惯、分段落习惯、甚至领域术语偏好)延续到转写输出中,从而减少专有名词乱写、标点缺失等问题。
源码印证:build_whisper_prompt 的三级降级
transcribe.py 中有一个辅助函数 build_whisper_prompt,它的优先级顺序恰好印证了文档策略的三层结构:
def build_whisper_prompt(god_nodes: list[dict]) -> str:
if not god_nodes:
return _FALLBACK_PROMPT # 1) 无 god nodes → 兜底提示词
override = os.environ.get("GRAPHIFY_WHISPER_PROMPT")
if override:
return override # 2) 环境变量 → 代理自拟的提示词
labels = [n.get("label", "") for n in god_nodes[:10] if n.get("label")]
if not labels:
return _FALLBACK_PROMPT
topics = ", ".join(labels[:5])
return f"Technical discussion about {topics}. Use proper punctuation and paragraph breaks."
即:环境变量 GRAPHIFY_WHISPER_PROMPT 优先于 god node 自动拼接;god node 为空或标签缺失时回落到 _FALLBACK_PROMPT(transcribe.py 中定义为 "Use proper punctuation and paragraph breaks.")。tests/test_transcribe.py 中的 test_build_whisper_prompt_env_override 与 test_build_whisper_prompt_returns_topic_string 分别验证了这两条路径。
为什么必须 export
文档对导出环境变量特别强调了两遍(原文加粗):
Export it as
GRAPHIFY_WHISPER_PROMPT(the exact name the transcriber reads — and it must beexported so the child Python process sees it)
原因是转写命令以 $(cat graphify-out/.graphify_python) -c "..." 形式启动子进程 Python 解释器;未 export 的 shell 局部变量不会进入子进程环境,os.environ.get("GRAPHIFY_WHISPER_PROMPT") 会读到空值。这是本文档最容易被忽略的实操陷阱。
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 已把解析出的正确 Python 解释器路径写入该 sidecar,后续所有命令都复用它,避免 uv tool / pipx / venv 环境下解释器错位;detect.get('files', {}).get('video', []):只处理 detect 分类为video的文件,与触发条件闭环;- JSON 由 Python 写出而非 shell
>重定向:文档注释明确说明transcribe_all/Whisper 会向 stdout 打印进度,若用> file.json重定向,进度文本会混入 JSON 把文件搞坏(引用 issue #1392);因此把Transcribed N file(s)摘要打到 stderr,让 stdout 只留给 Whisper 自身的进度显示。
源码印证:transcribe_all 的批量与容错行为
transcribe.py 中 transcribe_all 的行为与文档要求逐条对应:
def transcribe_all(video_files, output_dir=None, initial_prompt=None):
"""initial_prompt is shared across all files — built once from corpus god nodes."""
if not video_files:
return []
transcript_paths = []
for vf in video_files:
try:
t = transcribe(vf, output_dir, initial_prompt=initial_prompt)
transcript_paths.append(str(t))
except Exception as exc:
print(f" warning: could not transcribe {vf}: {exc}")
return transcript_paths
- 空输入直接返回空列表;
- 单文件失败不中断整体:捕获异常、打印 warning 后继续下一个文件——正对应文档"如果某个文件转写失败,打印警告并继续其余文件"的要求;tests/test_transcribe.py 的
test_transcribe_all_skips_failed验证了失败文件被跳过、结果列表不含它。
单文件转写的底层细节
深入 transcribe 函数(transcribe.py)可以看到文档未展开的几个实现事实:
| 配置项 | 源码取值 | 说明 |
|---|---|---|
| 模型名 | os.environ.get("GRAPHIFY_WHISPER_MODEL", "base") |
默认 base;用户传 --whisper-model <name> 时必须 export 该变量 |
| 设备 | device="cpu" |
固定 CPU 推理,compute_type="int8" 量化 |
| 解码 | beam_size=5 |
beam search 解码 |
| 输出目录 | graphify-out/transcripts/ |
由 out_path("transcripts") 决定 |
| 缓存 | transcript_path = out_dir / (audio_path.stem + ".txt") |
已存在即直接返回,除非 force=True |
| 输出内容 | 各 segment 文本去空后用换行拼接 | 另打印检测到的语言与 segment 数 |
缓存行为有专门测试覆盖:test_transcribe_uses_cache(存在缓存时不调用 Whisper,直接返回缓存路径)与 test_transcribe_force_reruns(force=True 时即使缓存存在也重新转写,见 tests/test_transcribe.py)。
依赖安装:video 额外依赖
转写依赖 faster-whisper(转写引擎)与 yt-dlp(URL 音频下载),二者均属于 video 可选依赖组。从 pyproject.toml 可见:
video = ["faster-whisper; python_version >= '3.11'", "yt-dlp>=2026.6.9"]
安装方式即 pip install 'graphifyy[video]'(或 uv tool install 'graphifyy[video]')。这与源码中 _get_whisper() 的 ImportError 提示一致(transcribe.py):
raise ImportError(
"Video transcription requires faster-whisper. "
"Run: pip install 'graphifyy[video]'"
)
注意 faster-whisper 带 python_version >= '3.11' 标记——在 Python 3.11 以下环境中即使安装了 video 依赖组也不会引入 faster-whisper,转写会因 ImportError 失败。此外,转写固定使用 CPU + int8(WhisperModel(model_name, device="cpu", compute_type="int8")),长视频的转写时间应据此预期,可通过 --whisper-model 在模型大小(如 medium,见 skill-pi.md Usage 中的 --whisper-model medium 示例)与精度之间权衡。
转写完成后的接入:转写稿作为文档进入 Step 3
参考文档"After transcription"小节规定了四条收尾规则:
- 从
graphify-out/.graphify_transcripts.json读回转写稿路径列表; - 在 Step 3B 分发语义子代理(semantic subagents)之前,把这些路径加入 docs 列表——转写稿自此完全按文档处理,走既有的语义抽取缓存、chunking 与合并流程;
- 打印数量摘要:
Transcribed N video file(s) -> treating as docs; - 单文件失败打印警告并继续(源码层已保证,见上文
transcribe_all)。
这一"转写→文档"的接力在主 skill 中也有呼应:skill-pi.md 的 Part B Step B0 注释写明 "Video is transcribed to a document in Step 2.5 first",即语义抽取阶段的文件集合由 detect 的 document/paper/image 三类加上转写稿构成,代码文件仍只由 AST 路径覆盖。
附:URL 语料的前置下载与安全防护
虽然 transcribe.md 参考文档聚焦本地文件,但源码中 transcribe() 对 URL 输入有完整支持(transcribe.py):is_url() 判定为 URL 时先经 download_audio() 用 yt-dlp 下载纯音频流。实现上做了两点值得了解的工程处理:
- 稳定命名与缓存:下载文件名基于 URL 的 SHA-1 前 12 位(
yt_<hash>.<ext>),避免 yt-dlp%(title)s带来的长文件名;已下载文件直接复用并打印cached audio:(transcribe.py); - SSRF 防护:下载前先调用
validate_url(url)拦截私有 IP 与异常 scheme,且postprocessors: []意味着不需要 ffmpeg,直接取原生音频流。
从源码结构看,这为语料中直接混入视频链接(而非本地文件)的场景提供了兜底,但在本文的 skill 流程中,常规路径仍是本地音视频文件。
小结:参数与行为速查
| 项 | 取值 / 行为 | 依据 |
|---|---|---|
| 触发条件 | detect 的 files.video 非空 |
transcribe.md、detect.py |
| 支持扩展名 | .mp4 .mov .webm .mkv .avi .m4v .mp3 .wav .m4a .ogg | transcribe.py |
| Whisper 模型 | 默认 base,export GRAPHIFY_WHISPER_MODEL=<name> 覆盖(必须 export) |
transcribe.py |
| 提示词 | 优先 export GRAPHIFY_WHISPER_PROMPT=...;无 god nodes 时用 "Use proper punctuation and paragraph breaks." |
transcribe.py |
| 推理配置 | CPU + int8,beam_size=5 | transcribe.py |
| 输出 | graphify-out/transcripts/<stem>.txt,同 stem 缓存命中直接返回 |
transcribe.py |
| 清单 | graphify-out/.graphify_transcripts.json,由 Python 写出(防 stdout 污染,#1392) |
transcribe.md |
| 容错 | 单文件失败打印 warning 并继续 | transcribe.py |
| 后续接入 | 转写稿并入 docs 列表进入 Step 3B 语义抽取 | skill-pi.md |
掌握以上内容后,你就能在含音视频语料的 graphify 构建中:正确判断转写是否触发、自拟并导出领域提示词、执行批量转写、并把转写稿无缝接入后续的知识图抽取流程。
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 StartedRust0625
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