用Agent Skill给Claude补上视频理解能力:从抽帧到事实表
去年年底我被一个需求搞得有点崩溃:用户往Agent里扔了一个产品演示视频,让Agent回答“视频里我们的新功能到底演示了哪几个步骤”,Agent当场卡壳。文本、PDF、图片都能处理,唯独视频是个硬伤——模型看不到视频流,用户又不想自己手动转文字。当时Claude刚好在推Agent Skills的机制,我意识到与其等模型原生支持视频,不如自己写一个Skill,把“看懂视频”这个能力拆成抽帧、转写、理解、对齐四步,挂到Agent身上。项目名字就叫claude-video。
这篇把整个项目的设计思路、SKILL.md的写法、核心脚本和实测翻车记录全部摊开讲。适合正在做Agent开发,或者想给智能体补上视频感知能力的同学参考,不管你是想直接抄作业,还是想理解Agent Skill到底怎么设计,都能找到对应的内容。
1. 视频是Agent最大的感知盲区:为什么“看视频”必须靠Skill补齐
1.1 大模型“看不见”视频的真正原因
很多刚接触Agent开发的人会有一个错觉:多模态大模型不是能看图吗?那给它视频应该也能看懂吧?实际完全不是这回事。视频不是一张图的“放大版”,它本质上是一个复合容器,里面装着图像序列、音频轨道、字幕流、时间轴信息。模型一次能看几张图,但没法直接“播放”一个视频文件。
更深层的问题在于,视频没有一个统一的token表示。文本有token,图像有视觉token,但视频是一段时间维度上的状态流,现有的上下文窗口根本装不下完整视频的原始表达。一个10分钟的视频按30fps算是18000帧,就算按1fps抽帧也有600帧,这远超视觉模型的单次处理能力。所以“给Agent视频能力”本质上是在做一个降维转换:从高维的视频流里提取出值得被推理的关键信息,压缩成语言模型能消费的符号表达。
这个思路其实和人类看视频的过程很像。人不会记住每一帧画面的像素,而是眼睛在看画面,耳朵在听声音,大脑把视觉信息、语音信息整合成一段连续的理解,最终记住的是“发生了什么”而不是“第几分几秒的像素是什么颜色”。机器要复现这个流程,就需要三样东西:视觉采样器、语音转写器、时间轴对齐器。这三样合起来,就是一个完整的视频理解管道。
1.2 为什么非要用Skill的形式而不是硬编码
可能有人会问:我把视频处理的逻辑写成普通的Python脚本,挂在Agent的工具列表里不也行吗?为什么一定要做成Skill?
这里必须讲清楚Agent Skill和普通工具脚本的区别。普通工具函数是被动执行的:用户显式说“调用视频处理工具”,或者Agent在tool list里机械地选择它。而Skill是一套“方法+工具”的包,它包含一段自然语言写的操作指南、一组可执行脚本、以及一个让Agent自动判断何时调用的描述信息。Skill不是常驻上下文,而是在Agent判断“这个问题需要处理视频”时,才把整包指令注入进来。
这个“按需加载”的设计非常关键。Agent的上下文窗口是稀缺资源,如果把视频处理的所有命令、参数、注意事项都常驻在系统提示词里,会挤占大量空间。Skill机制相当于给Agent配了一个工具箱,平时工具不占地方,等需要拧螺丝的时候再打开对应的那一格。claude-video能跑通的底层逻辑,就是充分利用了这种按需注入的机制。
2. Agent Skills的触发机制:SKILL.md、frontmatter与上下文注入
2.1 SKILL.md的三层结构
一个标准Skill在文件系统里的形态,通常是一个目录,里面最关键的文件叫SKILL.md。这个文件分三段:YAML格式的frontmatter、自然语言指令正文、以及可选的可执行脚本目录。
frontmatter里的name和description是Agent判断“什么时候该用这个Skill”的依据。description写得好不好,直接决定了Agent会不会在合适的场景调用它。我见过不少Skill效果差,问题不在脚本,而在description写得模棱两可,Agent根本不知道这个Skill是干嘛的。
正文部分是给Agent读的“教科书”,需要一步步说明这个Skill能处理什么输入、输出什么格式、遇到边界情况怎么办。而scripts目录里的脚本,是Agent可以实际调用的工具,比如ffmpeg命令、Python脚本、shell脚本。这三层结构合在一起,才构成一个完整的Skill:有判断依据、有操作指南、有落地工具。
2.2 Agent是怎么决定“该用这个Skill”的
这个机制理解起来其实不复杂。Agent在收到用户消息后,会根据当前对话内容和可用Skill列表做匹配。匹配的依据就是frontmatter里的description字段。如果用户说“帮我看一下这个视频讲了什么”,Agent会把这个请求和description做语义匹配,发现claude-video的description里写了“分析视频文件、总结视频内容、回答视频相关问题”,于是决定加载这个Skill。
加载之后,SKILL.md的正文会进入上下文,相当于Agent读了一遍“使用手册”,然后按照手册里的步骤去执行脚本、读取结果、组织回答。这里有两点值得注意:第一,description的设计应该以“用户意图”为中心,而不是以“技术实现”为中心。不要写“这个Skill调用ffmpeg和whisper”,而要写“当用户需要理解视频内容、提取视频信息、基于视频回答问题”。第二,一个Skill的加载是一次性注入,如果指令写得又长又乱,Agent执行时会迷失方向,所以SKILL.md正文必须简短、精确、步骤化。
2.3 Skill和Agent的关系:方法包和智能体的分工
Skill和Agent经常被放在一起讨论,不少人分不清。简单说,Agent是决策和执行的主体,Skill是Agent可以随时取用的“方法包”。Agent决定“做什么”,Skill提供“怎么做”。同一个Agent可以装多个Skill,同一个Skill也可以被多个Agent使用,两者是解耦的。
在做claude-video的过程中,我越来越觉得这种分工是有道理的。视频处理涉及很多领域知识:ffmpeg的参数、抽帧间隔的选择、语音转写模型的使用、多模态API的调用格式,这些知识如果全部塞给Agent,Agent会被细节淹没。但封装成Skill之后,Agent只需要知道“遇到视频问题就加载claude-video”,剩下的专业操作全部由Skill内部处理。这也让Skill具备很强的可复用性:换个Agent项目,只要目录拷贝过去,描述信息一致,就能直接用。
3. claude-video的管道设计:帧采样、音频转写与时间轴融合
3.1 从“眼睛+耳朵”到“视觉流+音频流”的并行管线
claude-video的核心是一条并行处理管线。视频文件进来之后,第一步是拆分成两条支线:视觉支线和音频支线。
视觉支线做的事情是抽帧。用ffmpeg把视频按一定时间间隔截成多张JPEG图片,然后交给多模态模型逐张理解,输出每张图的画面描述。音频支线做的事情是转写。先用ffmpeg从视频里提取出音频轨道,再用语音识别模型把语音转成带时间戳的文字稿。两条支线的产出会汇合到一个融合层,把“这一帧的画面内容”和“这一段时间的语音内容”按时间戳对齐,生成一个结构化的视频事实表。这个事实表才是Agent最终用来推理的依据。
这里有个非常容易犯的错误,就是只做音频转写,忽略画面信息。很多会议录播、课程视频确实靠语音转写就能覆盖大部分内容,但产品演示视频、操作教学视频恰恰相反,关键信息全在画面上:按钮点在哪里、界面弹出了什么、鼠标做了哪个操作,这些语音里根本不会详细描述。只转写音频,Agent能回答“说了什么”,但回答不了“做了什么”。
3.2 帧采样策略:均匀抽帧、关键帧与弹性的平衡
抽帧是整个管线里最需要拿捏的部分。抽得太密,Token消耗爆炸;抽得太稀,关键画面容易漏掉。我的实测经验是,大多数场景下1秒1帧(fps=1)是一个不错的起点,既能覆盖主要画面变化,又不会让后续视觉理解的成本失控。
ffmpeg里的命令很直接:
ffmpeg -i input.mp4 -vf fps=1 frame_%04d.jpg -q:v 2
这里的
-q:v 2
表示JPEG质量参数,数值越低质量越好,但我建议不要低于2,否则图片体积过大会拖慢后续处理。对于画面变化特别快的视频,比如游戏录屏、UI操作演示,可以临时调到fps=2甚至fps=5;对于PPT讲解这类画面长时间静止的视频,fps=0.5就够了,还能省一半Token。
也有一种更聪明的方案是直接用ffmpeg的关键帧提取:
-vf select='eq(pict_type,I)'
,只保留视频编码里的关键帧。这个方案在压缩率上很漂亮,但实际用了两次我就弃了:不同编码器的关键帧间隔差异太大,有的视频两秒一个关键帧,有的十秒才一个,提取结果完全不可控。均匀抽样虽然“笨”,但胜在稳定可预期,对Agent来说稳定性比聪明更重要。
3.3 时间戳对齐:把画面与字幕拼成一个可以被推理的事实表
两支流都处理完之后,最核心的融合环节来了。如果只是把一堆帧描述和一段转写文本扔给Agent,Agent很难回答“视频第5分钟发生了什么”这种时间敏感问题,因为两边的信息没有关联起来。
我的做法是生成一个JSON格式的事实表,每一个条目包含:起始时间、结束时间、画面描述、语音内容。融合逻辑是:拿音频转写的segments时间戳作为主轴,把落在同一时间段内的帧描述拼接进去。比如转写文本显示“第3分02秒到3分20秒,主讲人说我们要引入自动保存功能”,同时那段时间内抽到的画面描述是“界面上出现一个设置弹窗,自动保存开关被打开”,那这个时间槽位就是一个完整的、可被推理的事件单元。
这个事实表的价值在于,它把原本分离的视觉信息和语音信息绑定到了统一的时间坐标上,Agent拿到之后既可以回答内容性问题(“视频讲了什么”),也可以回答时间性问题(“自动保存功能是在第几分钟提到的”)。我强烈建议在SKILL.md正文里明确告诉Agent:回答问题时优先引用事实表中的对应时间片段,这样最终答案质量会高很多。
4. 手写一个可用的claude-video:从目录结构到完整脚本
4.1 目录设计:让Agent一眼看懂怎么用
一个Skill能不能被Agent顺畅使用,目录设计占了很大比重。claude-video的目录结构我调整过好几版,最终稳定成下面这个样子:
claude-video/
├── SKILL.md
└── scripts/
├── extract_frames.py
├── transcribe_audio.py
└── build_fact_table.py
设计原则只有一个:每个脚本做一个独立的原子操作,SKILL.md负责描述它们的组合方式。不要写一个超大脚本包揽所有事情,Agent在执行过程中如果某个环节失败了,小而独立的脚本更容易局部重试。这也是我踩过坑之后总结出来的教训,一开始我把抽帧、转写、融合全写在一个
process_video.py
里,结果转写出了问题就得从头跑一遍,浪费大量时间。
4.2 SKILL.md完整示例
SKILL.md是整个Skill的灵魂,它决定了Agent是否理解这个工具、能否正确使用。我贴一份可以直接用的版本:
---
name: claude-video
description: Use this skill when the user asks to analyze, summarize, extract information from, or answer questions about a video file. Handles local video files and video URLs. Covers visual content, audio content, and timestamped events in the video.
---
# Video Analysis Skill
You are helping the user understand a video. Follow these steps:
1. First, identify the video file path or download the video from the provided URL.
2. Run `scripts/extract_frames.py <video_path> <output_dir>` to sample frames every second.
3. Run `scripts/transcribe_audio.py <video_path> <output_dir>` to get timestamped speech transcription.
4. Run `scripts/build_fact_table.py <frames_dir> <transcript_dir> <output_dir>` to merge visual and audio information.
5. Read the generated fact table JSON and answer the user's question based on it.
## Important notes
- If the video has no audio track, skip transcription and rely on frame descriptions only.
- If the video is longer than 15 minutes, sample frames every 2 seconds instead of every second to control token usage.
- When answering timing-sensitive questions, always reference the start/end timestamps from the fact table.
我想特别提醒description的写法。很多人会写“Used for video processing”,这太笼统,Agent没法做语义匹配。要写清楚“什么情况下用”——分析视频、总结视频、提取视频信息、回答视频相关问题——并且暗示能处理的内容类型。写清楚边界也很重要,比如“如果视频没有音轨就跳过转写”,这能帮Agent在异常场景下做出正确判断。
4.3 核心脚本:extract_frames.py 的解析
抽帧脚本是整个管线里最基础的环节,代码其实很短,核心就一段:
#!/usr/bin/env python3
import subprocess
import sys
from pathlib import Path
def extract_frames(video_path: str, output_dir: str, fps: float = 1.0):
out = Path(output_dir)
out.mkdir(parents=True, exist_ok=True)
pattern = str(out / "frame_%04d.jpg")
cmd = [
"ffmpeg", "-y",
"-i", video_path,
"-vf", f"fps={fps}",
"-q:v", "2",
pattern
]
subprocess.run(cmd, check=True)
print(f"Frames saved to {out}")
if __name__ == "__main__":
extract_frames(sys.argv[1], sys.argv[2])
这里唯一需要解释的是fps参数用什么形式传进去。我用的是
-vf fps=1
,也就是每秒抽1帧,这个参数是经过权衡的:1秒1帧能覆盖大多数操作演示,同时控制在合理成本内。如果你想支持长视频,可以在脚本里加一个按视频时长自动调整fps的逻辑,比如视频超过15分钟就自动降为每2秒1帧。这个策略我在实测中发现对成本和覆盖率都有明显帮助。
4.4 转写、融合脚本与一次完整调用过程
转写脚本我直接用whisper的命令行封装:
#!/usr/bin/env python3
import subprocess
import sys
from pathlib import Path
def transcribe(video_path: str, output_dir: str):
out = Path(output_dir)
out.mkdir(parents=True, exist_ok=True)
audio_path = out / "audio.wav"
# extract audio first
subprocess.run([
"ffmpeg", "-y", "-i", video_path,
"-vn", "-acodec", "pcm_s16le",
"-ar", "16000", "-ac", "1",
str(audio_path)
], check=True)
# transcribe with whisper
subprocess.run([
"whisper", str(audio_path),
"--model", "base",
"--output_format", "json",
"--output_dir", str(out)
], check=True)
if __name__ == "__main__":
transcribe(sys.argv[1], sys.argv[2])
融合脚本做的事情是读取帧文件的创建时间顺序、读取转写JSON里的segments,按时间轴合并,输出事实表。这一步不涉及复杂AI能力,就是纯粹的JSON处理,可以用任何你熟悉的语言写。我运行完完整管线的经验是:一个5分钟的视频,在普通笔记本上,抽帧50秒、转写30秒、融合几秒,整个过程Agent执行起来非常顺手。如果是GPU机器,转写速度可以提到秒级,体验和“看视频”已经很接近了。
5. 实测边界与翻车现场:哪些视频能看、哪些看不了
5.1 十分钟视频的Token账本
做这个Skill时,我最关心的问题就是Token花销。给Agent用东西,如果成本不可控,再好的能力也没法落地。我拿一个10分钟的实操演示视频跑了一次完整的Token账本:以fps=1抽帧,10分钟产生600帧,但视觉模型理解这600帧,如果一帧接一帧全量分析,Token开销很惊人,单视觉部分就可能让上下文爆掉。
所以实际落地时,我做了两个调整。第一是抽帧降频,10分钟以上视频自动降到每2秒1帧,600帧变成300帧;第二是分组理解,不是逐帧独立分析,而是把相邻的5帧拼成一张联系表,让模型一次看一小段时间窗,输出这段集中发生了什么。这样既保留了时间连续性,又把单帧重复描述大量压缩。实测下来一个10分钟视频的视觉Token消耗降了将近一半,而且输出质量没有明显下降。
反直觉的一点是,真正吃掉Token的大头反而不是视觉部分,而是画面描述里的重复信息。连续几帧画面变化不大时,逐帧描述会产生大量冗余。分组理解对消除这种冗余非常有效。
5.2 画面灰暗、PPT翻页快、没有音轨:三类典型翻车
翻车案例能让人学到比成功案例更多的东西。我第一次把claude-video投入到真实场景里,就遇到了三个问题。
第一个是录屏视频画面灰暗,尤其是一些会议软件的录屏默认压低亮度,抽出来的帧整体偏黑,多模态模型把不少细节认错了。后来我在抽帧命令里加了亮度增强参数
-eq brightness=0.05:contrast=1.02
,效果好了不少。但更稳妥的方案是直接建议用户在录制时注意画面质量,AI能做的修复始终有限。
第二个是PPT切换很快的讲课视频。这类视频的元素大量变化,1秒1帧还是会有漏掉的页,导致Agent在总结时漏掉某些要点。我试过提高抽帧率到fps=2,漏页问题基本解决,但Token成本也涨了。一个折中是先用作用于讲座/PPT场景的附加参数,对画面做去重后再分析,只保留内容有实质变化的帧。
第三个更常见,就是视频本身没有音轨或音轨是纯音乐。这种情况下,转写环节直接跳过,整条水管线只剩下视觉支线。如果你的视频是动画短片、剪辑作品、或者背景音乐配字幕,那Agent基本只能回答画面层面的内容,语音信息为零。这个边界我觉得不应该硬撑,直接在SKILL.md里说明即可,让Agent在缺一条信息时主动降低问题回答的置信度。
5.3 长视频超时与中断的应急预案
还有个必须解决的问题是运行超时。Agent在执行Skill脚本时,如果视频太长,比如1个小时的课程回放,抽帧和转写都可能跑很久,而Agent执行任务通常有超时机制,脚本跑太久会被中断。
我的做法是把管线拆得更细,加一个分阶段产物检查的机制。每个脚本执行前先检查输出目录里是否有缓存结果,如果有就直接跳过。这样即使某个环节超时,修复问题后重新执行,已经算好的部分不会被浪费。另外,对超过30分钟的视频,我建议在SKILL.md里明示Agent先做“分段处理+分层摘要”,不要试图一次把整段视频塞进上下文,这个策略才是长视频问题的治本之策。
6. 让这套Skill更好用的进阶优化:缓存、分层摘要与Skill组合
6.1 帧缓存与转写缓存:第二次调用直接秒回
做过处理管线的朋友都知道,抽帧和转写是重活。尤其转写,一次要跑几十秒到几分钟。实际使用中,同一个视频可能被反复询问,今天问“讲了什么”,明天问“某功能在第几分钟出现”,如果每次都要重新抽帧转写,不仅慢,而且浪费。
所以我在Skill里加了缓存机制:所有中间产物都按视频文件的哈希值存在一个缓存目录里。第二次遇到同一个视频时,脚本检查到缓存存在,直接跳过抽帧和转写,只做融合和推理。实测下来,重复调用几乎可以秒回。这个优化对日常体验的提升非常明显,因为用户面对同一个视频往往会有多个后续问题,缓存让“看第二遍”的成本趋近于零。
6.2 分层摘要:先全局后局部,避免一次塞爆上下文
长视频是Agent上下文最大的敌人。我的处理方案是“分层摘要”,也就是不让Agent一次读完整个视频的事实表,而是先用轻量模型对整个事实表做一个粗粒度摘要,输出“这个视频大致讲了什么、主要分几个阶段”。当用户问某个具体问题时,再定位到对应时间段,读取那一部分的细节。
这个过程有点像先翻书的目录,再决定精读哪一章。粗摘要阶段的Token成本很低,但它给Agent提供了一张“地图”,让后续的精读定位变得非常精准。很多视频问答效果不好,都是因为Agent在没有任何先验知识的情况下扎进细节里,结果被海量信息淹没。分层摘要能有效避免这个问题。
6.3 把claude-video作为其他Skill的上游能力
Skill之间可以组合,这是我后来才发现的好玩之处。claude-video本质上是一个“把非结构化视频转成结构化文本”的能力,而结构化文本是所有Agent下游任务的通用输入。
比如我把它和笔记整理类Skill组合,视频理解完成后自动生成带时间戳的课程笔记;和代码生成类Skill组合,可以让Agent看完一个开发教程视频后,直接按照视频里的方案生成项目骨架代码;和数据分析类Skill组合,能以“视频中出现的所有数据点”为依据生成可视化报表。组合使用时的接入成本非常低,因为claude-video的输出是标准JSON事实表,其他任何Skill都能直接消费。
随着这个Skill被我用得越来越顺手,我的感受是:给Agent补能力这件事,方向比数量重要。与其一次给它装一堆花哨但用不上的技能,不如先把“读懂真实世界输入”这个底座打好。视频、音频、网页、PDF,这些非结构化信息是Agent能力的天花板,每打通一种,Agent能处理的任务范围就拓宽一大块。claude-video只是这个方向上的一次探索,但它让我确信,未来Agent的标准配置里,一定会有“读取视频”这一项。
最后分享一个实际踩坑后的心得:做Skill,最好先从一个具体的、让你自己难受过的场景切入,比如“我不想手动把课程视频转文字”,然后围绕这个场景做深做透。不要一上来就想做一个全能的视频理解工具,那种设计最后通常只能演示,没法在真实任务里稳定运行。claude-video之所以能留在我自己的Agent配置里持续使用,就是因为它解决的问题很具体,管道每一步都有明确的产出和兜底。
更多推荐
所有评论(0)