构建视频理解Skill:让Agent真正“看懂”视频的完整实践
作为一个长期在Agent开发圈子里折腾的人,我太清楚一个痛点:你给Agent塞一篇文档、丢一个链接,它能给你整得明明白白;但你丢一个视频过去,它基本就傻眼了。文本世界和视频世界之间隔着一道沟,而大多数Skill都在填文本的坑,没人去填视频的坑。这篇文章想聊的,就是我自己从零搭出来的一个名叫 claude-video 的Skill——它的目的很简单,就是让我的Agent真正拥有“看视频”的能力,而不是只会读字幕文件或者看视频标题猜内容。
这个Skill解决的问题很具体:用户丢进来一个本地视频文件或者视频链接,Agent能像人一样看完,然后告诉你视频里发生了什么、讲了什么、关键时间点在哪、有哪些细节值得注意。它不是简单调用现成的视频理解API,而是把“拆视频—抽内容—喂给模型—生成结构化理解”整条链路做成了一个可以被Agent直接调用的能力包。适合谁看?如果你正在做Agent开发、想做多模态方向的Skill、或者手头有大量视频资料不知道怎么让模型去消化,这篇文章应该能给你省不少弯路。
我会把整个实现链路、踩坑过程、还有最后接入Agent工作流的思路都摊开来讲。先说清楚一点:这中间没有魔法,全是工程上的取舍。
1. Agent“看不见”视频的真正原因:缺的不是模型,是拆解流程
很多人误以为“让Agent看视频”=“找个多模态模型,把视频丢进去”。真这么简单,市面上早就有无数现成方案了。问题在于,视频本身不是模型能直接“吃”的格式。大模型能处理的是文本和单张图片,视频是连续帧、音频、还有可能存在的字幕轨道的复合体,直接塞进去既不符合模型输入的限制,也会产生爆炸性的token消耗。
Agent看不了视频的根本原因,是它缺少一套把视频“翻译”成自己能够理解的信息格式的工具链。这就像一个人视力正常但没学过盲文,你给他一本盲文书,他照样读不了——需要中间加一个翻译环节。而Skill这个机制,恰好就是干这个的:把一套可复用的工具和流程,包装成Agent可以随时调用的能力。
多模态模型当然能看懂单张图片,甚至能理解多张图片之间的逻辑关系,但如果把视频每隔一帧全抽出来,成本直接失控。一个10分钟的视频,如果按25fps算是15000帧,即便是按每秒1帧抽也有600帧,全部送进多模态模型,API账单会教做人。所以核心问题从来不是“模型能不能看”,而是“让模型看什么、看多少、以什么顺序看”。
我设计 claude-video 时定的原则就三条:
- 最小信息抽取:抽出来的每一帧、每一段文字都必须是“信息密度”最高的那一部分。
- 多通道融合:画面、语音、字幕三条信息通道并行抓取,再在最后汇总,而不是只靠某一条通道。
- 时间戳对齐:理解结果必须能对应回视频的某一段,这是视频理解区别于图片理解的关键。
这三条原则看起来朴素,但真正落地的时候,每一条都会让你踩出一堆坑。后面我展开细说。
2. 核心链路设计:三条信息通道的抽取与融合
把视频变成Agent能理解的信息,我把它拆成了三条通道:视觉通道、听觉通道、文本通道。对应到技术上就是:抽帧、语音转写、字幕/文本提取。三条通道各取所长,最后在汇总阶段融合成一份带时间轴的结构化内容。
2.1 视觉通道:抽帧不是“抽得越多越好”,而是要找关键帧
视觉通道是视频理解最直观的部分,也是最容易做错的部分。我在第一版实现里用的方案很简单粗暴:每秒钟抽一帧,直接把所有帧以base64形式塞给多模态模型。结果10分钟的视频抽了600帧,多模态API直接报错——一请求超长,二费用失控,三模型理解的连贯性也差,记住前面的忘了后面的。
后面换成了一套组合策略:
-
先用
ffprobe探测视频时长、分辨率和关键帧间隔。 - 对短视频(3分钟以内):均匀抽帧,每3秒一帧。
- 对长视频(超过10分钟):先做镜头切分,每个镜头内只抽最清晰的中间帧;再用场景检测找出画面变化剧烈的帧,确保关键转场不漏掉。
- 带人脸的视频:额外追加人脸清晰的特写帧,方便模型识别发言者表情和身份特征。
核心逻辑是: 模型需要看的不是“帧”,而是“变化” 。连续相似的内容抽100帧和抽1帧,对模型理解视频毫无区别,但token消耗差了100倍。所以抽帧阶段一定要做视觉去重。我后来实现的时候用了感知哈希(pHash)做相似度对比,相似度高于阈值的帧直接丢弃,实测下来能把所需帧数压缩到原先的1/5到1/10,而内容完整度几乎不降。
帧数控制的范围我一般卡在20到50帧之间。少于20帧关键细节可能丢,多于50帧模型的上下文窗口和成本都吃不消。每帧在送进模型之前,先用压缩工具压到768px宽、JPEG格式、质量85,单帧大小控制在100KB以内,这样才能保证base64编码后不浪费太多的输入token。
2.2 听觉通道:Whisper转写是成本最低的高收益方案
语音通道很多人会忽略,但它恰恰是视频理解里信息密度最高的一条。视频里大量核心信息是通过对话和解说表达的,光看画面经常要猜。
转写方案我用的是本地Whisper,主要考虑到三件事:一是视频的音频轨属于用户数据,我倾向不上传第三方云转写服务;二是音频转写这种重负载任务放本地可以控制成本,批量处理也不心疼;三是Whisper对中文、英文和混合语言的转写效果在开源方案里属于第一梯队。
转写的实操里有几个细节值得单独说:
-
先用
ffmpeg把音频提取成16kHz采样率、单声道、16bit的WAV格式。这是Whisper识别效果最稳定的参数,采样率太高没有意义,太低会损失音质导致识别率下降。 - 长音频必须分段。Whisper对长音频的处理虽然可以一次性传,但超过30秒的音频内部会自动切分并可能导致上下文割裂。我更倾向手动切成60秒一段,重叠4秒边界,尽量减少断句处丢词。
-
转写结果记得带回时间戳。Whisper的
word_timestamps=True参数能输出每个词的时间位置,后面做“视频回顾定位”的时候没有这个字段基本没法用。
转写完成之后,听觉通道产出的就是一份带时间轴的纯文本: 哪段时间有人在说话、说了什么 。这份材料单独看已经能支撑不少问答场景了,比如“视频里提到X概念是在什么时候”“这个博主对Y事件的评价是什么”。
2.3 文本通道:字幕轨道经常会白送,但格式坑多
视频里如果带着字幕轨道,那其实是白送的一份与时间轴严格对齐的文本。对这种字幕,我的做法是优先于Whisper转写直接提取。字幕轨道编码格式五花八门,有常见的SRT、ASS,也有封装在MKV里的PGS图形字幕。SRT和ASS这类文本字幕可以直接解析,PGS图形字幕需要OCR,就比较折腾了。
从坑里爬出来的经验是:能提取文本字幕就提,提不了就用Whisper跑的音频转写结果顶上,OCR是最后的选择,因为图形字幕的OCR对样式复杂的字幕(渐变、描边、滚动)识别率惨不忍睹。字幕提取后,解析SRT的时间轴格式(
00:00:01,000 --> 00:00:04,500
)是基本功,重点是把装饰性标记(比如
<i>
斜体标签、ASS样式代码)剥干净,只留纯文本和对应时间区间。
2.4 融合阶段:把三路信息按时间轴拼成“剪辑脚本”
三条通道抽完以后,最关键的一步是融合。这个环节不能简单地把抽帧描述、语音转写、字幕文本一股脑丢给模型,而是要按时间轴把它们对齐,形成一种类似“视频剪辑脚本”的结构化文本。
融合后的数据长这样:
-
00:00-00:03:画面为视频封面,字幕显示标题“XX产品上手体验” -
00:03-00:08:人物出镜介绍背景,语音转写“今天咱们来聊聊这个新出的设备” -
00:08-00:25:演示操作界面,字幕提及参数配置 - ……
这种对齐后的结构对模型来说几乎是“喂到嘴边”了,它看到了某一时间段有什么画面、什么语音、什么文字,理解起来远比自己从一堆零散信息里拼凑要准确。我在实测中对比过:直接丢全部抽帧+全部转写文本的“大锅烩”方案,和按时间轴对齐的“脚本化”方案,后者对细节问题的回答准确率明显更高,开销还更小。原因很好理解:大模型本身就有很强的上下文理解能力,但它需要一个清晰的叙事结构,时间轴对齐恰好供给这种结构。
3. 项目结构设计:Skill的边界感和可维护性
先前讲了整体思路,这节落回工程实现。一个Skill的骨架我在实践里总结下来包括几个部分:入口声明、执行逻辑、工具函数、缓存。很多人做Skill只写一句提示词“你是一个视频理解助手”,那遇到需要真正处理视频文件的时候就露馅了。 Skill的边界一定要清晰:告诉Agent你擅长什么、用什么工具、不做什么 ,剩下的交给Agent去编排。
3.1 SKILL.md:Agent的“岗位说明书”
先给不熟悉这个结构的朋友解释下:SKILL.md 是Skill的入口文件,Agent接受到任务后,会先读这个文件来决定“现在应该调用什么能力”。所以这个文件的开篇一定要写清楚三件事:这个Skill负责什么场景、使用的前提条件是什么、产出的内容形态是什么。
我写的 SKILL.md 开头是这样的:
# claude-video
把视频文件/链接转化为带时间轴的结构化理解报告。
适用的输入:本地视频文件路径、公开可访问的视频链接。
前置依赖:ffmpeg安装在系统PATH中。
输出格式:markdown报告,包含视频概要、时间轴事件列表、关键细节索引、话题分类。
这段描述的信息量看起来很朴素,但Agent能不能在正确时机调用它,全靠这几句话写得准不准确。比如“本地视频文件路径”这个输入格式的声明,就暗示了Agent需要先通过对话或文件上传拿到路径,而不是自己去网上找一个视频。
3.2 脚本层:把脏活累活交给命令行工具
我底下是三个Python脚本,各管一件事:
-
video_probe.py:封装ffprobe,获取视频元信息(时长、分辨率、编码格式、是否有音轨/字幕轨),输出JSON,供后续流程做决策。 -
media_extractor.py:负责抽帧和音频提取,内部调用ffmpeg子进程。抽帧策略、感知哈希去重、音频重采样都在这里实现。 -
transcriber.py:负责音频转写,封装Whisper模型加载与推理,输出带时间戳的JSON。
把逻辑拆成三个独立脚本而不是一个大文件,是刻意的设计。因为Agent在实际调用的时候,不一定每次都需要走完整流程。比如用户只想问“这个视频里有没有函数式编程的讨论”,那只需要跑
transcriber.py
,不必把抽帧也跑一遍。
Skill暴露给Agent的能力粒度越细,Agent的调度就越灵活,浪费的计算就越少
。
这个目录结构完全可以照搬:
claude-video/
├── SKILL.md # 入口声明
├── scripts/
│ ├── video_probe.py # 视频元信息探测
│ ├── media_extractor.py # 抽帧 + 音频提取
│ └── transcriber.py # 语音转写
├── cache/ # 中间产物缓存
└── output/ # 生成的结构化报告
缓存这一层千万别省。视频抽帧、音频转写都是重计算,同一段视频被问两次,如果每次都重新跑一遍,时间和成本都吃不消。我的做法是:以视频文件的MD5值为key做缓存目录,帧图片和转写结果都存在里面,下次直接命中。这属于花小钱省大钱的典型。
3.3 执行流程编排:Agent和Skill怎么配合
Skill脚本写好之后,Agent本身的编排逻辑也很重要。如果Agent只会老老实实按顺序跑一遍,遇到视频元信息探测失败还继续往下走,就会得到一堆没有意义的结果。我在SKILL.md里专门写过一段“执行建议”,核心是让Agent按需跳步:
- 先跑探测,确认视频有没有音轨和字幕轨。
- 有字幕轨且格式可解析,直接提取字幕作为主要文本通道;没有字幕再跑Whisper。
- 视觉抽帧永远执行,但帧数根据视频长度动态调整。
- 最终汇总时,如果视频里的信息高度依赖字幕(比如纯课程录屏),视觉通道可以只保留封面和幻灯片关键帧,减少模型输入。
- 如果用户只关心某个时间段的内容,先通过转写文本定位相关区间,只抽那个区间的帧,省掉全片抽帧。
这一步一步编排下来,整个流程就不再是“无脑全跑”,而是有动态选择性的。Agent主导流程判断、脚本来兜底执行,两边的优势都用上了。
4. 实测效果:拿三个真实视频验证Skill的边界
工程做完了总得实战检验。我拿了三个不同类型的视频测:一个是B站的技术分享(全程一个人对着屏幕聊,画面基本静止,高信息密度在语音和字幕),一个是户外实拍Vlog(画面变化剧烈、背景音嘈杂、无字幕),还有一个是电影解说剪辑(画面节奏快、解说语音配背景音乐)。三个视频的信息特征差异非常大,对Skill的抽取策略是很好的检验。
4.1 技术分享类:字幕通道完胜
第一个视频测试结果最理想。画面信息量极低(几乎全程是一张PPT),所以抽帧抽出来50张几乎是同样的内容,感知哈希去重后只剩下5张,全部是幻灯片切换时的新画面。字幕轨道完整,提取出来2万多字的转写文本,加上时间戳之后,模型对“这个视频讲了哪几个模块”“中间提到的某个概念出现在什么时候”这类问题的回答几乎全对。
这个测试给了一个明确结论: 当信息主要由语音和字幕承载时,视觉抽帧可以大幅削减,甚至只保留封面和标题帧就够用 。Skil的弹性调度在这里体现出了价值——如果固定抽帧数,等于白花钱。
4.2 户外Vlog类:音频失败时的兜底方案
第二个测试就翻车了。背景音嘈杂、人声和风噪混在一起,Whisper的转写置信度明显下降,字幕轨道不存在。这种情况下音频通道基本废了大半,大部分信息都只能靠视觉通道硬扛。我原来想象抽帧应该能捕捉到博主在做什么、去了哪里,实际效果是: 抽帧捕捉了场景的静态信息,但抓不住连续动作的因果逻辑 。相比文字转写,视觉通道对模型理解的要求更高,需要多帧之间的连续推理,模型在“博主先去A店再去B店最后去了C店”这种顺序性事件上会混淆。
这轮的救命稻草是音频转写里的时间戳信息和一些“标志性的声音事件”(比如关车门声、打招呼声)仍然能被识别出来。把这类声音事件的提示词补充进汇总材料里,模型推理的准确率回升了不少。但必须承认,在音画质量都拉胯的极端情况下,视频理解的准确率天花板比纯文字场景低不少,这是当前技术条件下的现实。
4.3 电影解说类:时间轴对齐的威力
第三个测试最有意思。电影解说剪辑的画面信息是碎片化的,但解说词构建了完整叙事。字幕和语音双通道都能抓到解说词,视觉通道则负责抓画面里的关键信息(角色表情、场景切换)。按时间轴对齐之后,模型能回答“解说中提到主角在第几分钟改变了态度”这种时间锚点非常明确的问题。
这个测试让我确认了时间轴对齐的意义:当信息来自多个通道时, 时间轴是天然的索引结构 ,它让模型能按时间顺序去组织故事线,也能精确地反查某一时刻的多模态状态。如果只是把抽帧和转写分开丢给模型,这种跨通道的时间关联根本不可能建立。
三个测试下来,Skill的能力边界基本摸清了: 文本和语音越丰富,视频理解越准;画面信息占比越高,难度就越大 。但这已经比“Agent完全看不懂视频”强了太多。
4.4 与单纯“丢链接”类方案的对比
市面上也有些工具号称“丢一个视频链接就能总结”,但实际用下来主要差在三点:一是对链接的依赖很强,本地文件处理支持差;二是处理流程完全黑盒,告诉你结果但不会告诉你“它是怎么从视频里得到这个结论的”,如果它抓错了片段,你完全没办法修正;三是对时间轴的还原能力弱,你可以问“视频里说了什么”,但很难问“第二分钟在讲什么”。而 claude-video 这个项目的定位从一开始就是 中间的每一层产物都要可追溯、可检查、可修正 ——抽了什么帧、转写出什么文字、哪些信息对应视频的哪个时间点,全部清晰可见。这种可控性对于我这种喜欢较真的开发者来说,是必须有的。
5. 踩坑实录:项目开发中必须绕开的五个深坑
任何一个工程性项目都是在“踩坑—填坑”中稳步推进的。这部分把开发过程中印象最深的五个问题写出来,每一个都直接对应到最后的代码实现上。
5.1 坑一:你以为视频有音频轨,其实它是静音轨
第一版
video_probe.py
只检查了“是否有音频流”,没检查音频流的实际内容。结果遇到一个视频,ffprobe显示有音频轨,但是抽出来的时间轴上全是静音,Whisper顺理成章转写出了一堆空字符串,浪费了整整一轮处理。
排查链路是这样的:我用
ffmpeg -af volumedetect -f null /dev/null
计算了音频的实际音量,发现最大音量都在-50dB以下,属于典型的静音轨。后来在探测脚本里加了音量检测这一步,静音轨直接被标记为“无有效音频”,流程自动跳过转写,省了一大截时间。
提示:判断“有没有语音内容”不能只靠流是否存在,要检测实际音量。
ffmpeg -af volumedetect是这条判断里成本最低的方法。
5.2 坑二:长视频的多模态输入爆掉上下文窗口
最初跑一个两小时讲座视频时,5秒一帧抽了1440帧,加上转写文本后直接超出模型上下文窗口,API返回400。当时的第一反应是抽稀疏一点,但后来仔细想了下,问题不是抽帧频率,而是抽出来的帧里有大量冗余信息要处理。
最终解决是把抽帧和汇总拆成两级:
- 第一级:每5秒抽的帧先交给视觉模型做“帧内描述”,产出结构化标签(画面内容、人物、文字、场景类型);
- 第二级:把描述后的标签序列与语音转写文本合并,再做一次全局理解。
这就像一个先压缩再分析的流程。第一级相当于图像内容的无损压缩,第二级才是高层的语义分析。第一级产物比原始图片小几十倍,但信息量够第二级使用了。
5.3 坑三:帧抽取的GPU/CPU占用导致整个机器卡死
ffmpeg抽帧在高码率4K视频上非常吃资源,不加限制地并发抽帧会把开发机的CPU吃满,连SSH都卡死。痛过一次之后,我在
media_extractor.py
里强制限制了三个参数:线程数
-threads 2
、抽帧速度
-preset ultrafast
、输出分辨率
scale=768:-2
。4K视频的抽帧速度提升了近一倍,系统资源占用也降到可接受范围。
5.4 坑四:Whisper模型加载一次要好几秒,频繁调用会把人逼疯
刚开始每段音频都重新加载模型,60秒一段的视频要切出10段,光模型加载的时间就占了总耗时的七八倍。后来我把模型加载做成了全局单例,进程内只加载一次,后续调用复用同一份模型权重。就这一个改动,处理一个10分钟视频的总耗时从将近4分钟压到1分钟出头。如果频繁处理视频,甚至可以干脆把Whisper单独起一个常驻服务,用HTTP接口调用,彻底避免Python子进程反复初始化GIL和GPU上下文。
5.5 坑五:时间戳偏移导致的“张冠李戴”
抽帧和转写的时间戳对不齐,是融合阶段最阴的问题。原因是抽帧时用了不同的起始偏移(比如某些工具默认从第0帧开始,Whisper分段加载音频时少了开头的0.2秒静音),导致画面时间轴和语音时间轴之间存在几百毫秒到几秒的系统性偏移,让事件对错了位置。
排查这问题的方法很土但有效:选一个中间有“明显动作变化”的时间点,同时对比画面变化和语音内容,手动调整两条时间轴的偏移量。最终我在
media_extractor.py
和
transcriber.py
里统一了时间基准——都以视频文件的绝对时间轴为基准,抽帧的起始偏移和音频切分的起始偏移全部置零,并在转写时保留偏移量,合并时再统一换算。
6. 接入Agent工作流的进阶玩法:知识库、回顾与批量处理
Skill跑通以后,我并没有满足于“单次视频提问”这一个场景,而是继续把它接进了更大的Agent工作流里。这部分的探索让我觉得,视频Skill的真正价值不在“看一次”,而在“看完之后沉淀下来的东西能被复用”。
6.1 视频理解结果沉淀为知识库条目
视频看完之后的产出,如果只是对话输出,用完就没了,非常可惜。我后来的做法是: 把视频理解的结论切分成带时间戳的事件片段,导入向量知识库 。这样用户在之后提问时,即使不是直接引用那条视频,Agent也能通过向量检索找到相关片段,并引用来源时间轴。
比如我用30多个产品测评视频建了一个小知识库,再让Agent回答“某某产品在续航方面口碑怎么样”,它能自动从某一条测评视频的特定时间段里找到对应的解说内容来支撑答案,而不是凭空归纳。这算是“看过的视频变成了长期记忆”。
6.2 基于转写文本的站内搜索与回顾
时间戳对齐后的转写文本,其实还能承担“站内搜索”的功能。我写了一个小脚本,能对同一文件夹下的所有视频建立索引,用户输入关键词时,先在所有转写文本里做检索,定位到命中的视频和时间段,随后再用原视频的帧和上下文做二次验证。这在处理课程录像、会议记录这类视频时特别实用——以往这种内容必须人工倍速听大量录音,现在让Agent做了初筛,人只需要看重点部分。
6.3 批量处理与增量更新
如果手头有大量视频需要消化,批量处理是绕不开的。我在
media_extractor.py
里加了一个队列模式,支持输入一堆路径后按顺序处理,并用任务数据库记录每个视频的处理状态(待处理/处理中/完成/失败),中途断掉可以从断点继续,不用从头跑起。
一个批量处理的目录结构可以是:
videos/
├── raw/ # 原始视频摆放位置
├── processed/ # 转写和缓存的中间产物
├── reports/ # 生成的结构化理解报告
└── index.db # 任务状态与索引
这实质上已经变成了一个轻量的“视频内容管理系统”,Skill只是其中最核心的那个理解引擎而已。
6.4 和现有Agent编排框架的配合
现在Agent框架有不少,Spring AI也好,自己搭的也好,本质上都解决“怎么把工具暴露给Agent”的问题。Skill模式的优势在于它的独立性和可移植性:SKILL.md + scripts 的目录结构不依赖特定框架,换个环境把目录拷过去就能用。接进Spring AI这类框架时,只需要把三个脚本包装成工具类,对外暴露为可调用的方法即可。
7. 关于项目后续演进的一些个人想法
视频理解这个方向,迭代速度极快。现在这套Skill的架构,我觉得短期内还有几个可以深挖的方向,给准备做类似项目的朋友参考:
一是把“理解结果”从文本摘要升级成结构化事件图谱——不只是“某时间段发生了什么”,而是“某事件由谁触发、影响了哪些后续事件”,这对复杂的纪录片、教程类视频价值极高。
二是加入主动“提问式采样”的抽帧策略。现在抽帧是均匀抽+去重,未来可以考虑借助Agent本身:先读转写文本找出疑问区,再针对疑问区做定向抽帧验证。这样视觉通道的利用率能再上一个台阶。
三是音频事件不只靠Whisper转写做文字识别,还能做声音事件分类(掌声、笑声、门响、警示音),这类非语言声音信息对场景理解是重要补充,特别是在没有清晰人声的实况类视频里。
做这个项目的过程里,我最大的体会是: Skill的复杂度不是越高越好,而是要有一个恰到好处的边界 。它既不能只是一个空空荡荡的提示词,也不能做成一个过度设计的大系统。把视频拆解、抽取、融合这套链路做扎实,Agent的理解能力就自然上来了。希望这篇文章能帮你跨过那些我已经踩过的坑,少走几步弯路。
更多推荐
所有评论(0)