监控拍下一段十分钟的走廊录像,你想让 AI 一句话告诉你「几点几分有人进来、拿走了什么」;财务甩给你一沓 PDF 发票,你想让 AI 自动填进表格。这两件事,019 教的「看单张图」够用吗?不够——视频是会动的,文档是成沓的、还带着复杂版面。但别慌:进阶多模态没有另起炉灶,它只是在「看图」外面套了一层「怎么把视频和文档,变成模型能看的一串图片」。想通这一层,剩下的全是你熟悉的老动作。这一期,我们就把这层窗户纸捅破,再各写一段能跑的代码。
01从「看一张图」到「看懂视频、读懂文档」
回顾一下 019:我们把一张图片编码成 base64、塞进 content,模型就能读图表、认截图、做 OCR。那是多模态的「起步档」——一张、静态、内容不太复杂的图。
可一旦走进真实业务,你会立刻撞上两堵墙:
- 会动的画面
:监控录像、操作教程、短视频……信息不在某一帧里,而在帧与帧之间的变化——谁先进门、球往哪飞、进度条走到哪。单看一张图,等于让人只瞄一眼电影剧照就复述整部片子。 - 成沓的文档
:合同、发票、论文、财报……它们看似是「文字」,其实是图文混排——两栏排版、跨页表格、盖章、图表、公式。你以为复制粘贴就行,一试就知道版面全乱了。
这一期干的,就是把「看图」这门手艺,往视频和文档两个方向各推进一档。方法出奇地一致,我们先看那把「总钥匙」。
02一把总钥匙:都拍平成「图片序列」
别被「视频理解」「文档解析」这些词唬住。它们背后是同一把钥匙:
- 视频 = 一叠按时间排好的图片
。电影本来就是一张张画面快速翻页骗过眼睛的把戏(每秒 24~30 张)。把它按时间切开,就是一本连环画。 - 文档 = 一页页图文混排的图片
。与其纠结怎么把版面「读」成文字,不如干脆把每一页当成一张图片整个看进去——版面、表格、盖章,一个都不丢。
于是进阶多模态就塌缩成了一句话:想办法把视频和文档拆成「一串图片」,剩下的就是 019 的看图力。而这些图片进了模型,又会像 019 说的那样,被切成 patch、变成「视觉 token」,和文字 token 混进同一条序列,用 001 那套注意力机制统一处理。

🔑 记住这把钥匙
进阶多模态 = 「拆」+「看」。难的不是「看」(019 已经会了),而是怎么把视频/文档拆成一串合适的图片——视频拆得太密会烧钱、太稀会漏戏;文档拆得不对会丢版面、错表格。所以本期真正的功夫,全在「怎么拆」这三个字上。
03视频理解的核心:抽帧(frame sampling)
先说视频。既然「视频 = 一叠图」,最笨的办法就是把每一帧都喂给模型——可这条路走不通。一段 10 分钟的视频,按每秒 30 帧算就有 18000 帧。全塞进去会怎样?
- 贵到离谱
:每张图都要吃掉成百上千个 token,上万帧直接把账单干爆(016 算过这笔账)。 - 装不下
:远远超出上下文窗口(004),模型根本收不下这么长的序列。 - 纯属浪费
:相邻两帧几乎一模一样,99% 是重复信息,喂进去也是白喂。
所以视频理解的第一课,就是抽帧(frame sampling):按一定策略,从成千上万帧里挑出有代表性的少数几帧,把一段视频压成一本薄薄的连环画,再喂给模型。
🧑🏫 就像看连环画猜剧情
你不需要看完电影的每一格胶片,只要翻几张关键剧照,就能脑补出「男主追车 → 撞了 → 下车吵架」。模型也一样:给它时间上均匀铺开的几张关键帧,它就能靠帧与帧之间的变化,推理出这段视频里发生了什么。抽帧,就是替模型挑出这几张「值得看的剧照」。
04动手:从视频里抽帧,让模型讲出剧情
理论说透,上代码。下面这段 videobot.py,用 OpenCV 把视频每隔几秒抽一帧,再把这串帧当成图片(一如 019)塞给模型。调用大模型的地方,照旧是官方 anthropic SDK:
# videobot.py —— 让模型「看懂」一段视频:抽帧 → 喂图 → 讲剧情from anthropic import Anthropicimport cv2, base64 # cv2 = OpenCV,负责把视频拆成一帧帧图片client = Anthropic() # 老朋友,自动读环境变量 ANTHROPIC_API_KEY(密钥别写进代码,见 025)defsample_frames(path, every_sec=2): # 每隔 every_sec 秒抽一帧,别一股脑全塞 cap = cv2.VideoCapture(path) fps = cap.get(cv2.CAP_PROP_FPS) # 这段视频每秒多少帧 frames, idx = [], 0whileTrue: ok, img = cap.read() ifnot ok: breakif idx % int(fps * every_sec) == 0: # 到点了才留这一帧 _, buf = cv2.imencode(".jpg", img) # 帧 → JPEG 字节 frames.append(base64.b64encode(buf).decode()) idx += 1 cap.release() return frames # 一串 base64 图片,就是视频的「连环画」defwatch(path, question): frames = sample_frames(path, every_sec=2) print(f"抽了 {len(frames)} 帧") # 第一块先交代「这是按时间顺序抽的帧」,模型才懂先后(关键!) content = [{"type": "text", "text": f"下面是一段视频按时间先后抽出的若干帧。{question}"}] for b64 in frames: # 把每一帧当成一张图片塞进去(回顾 019) content.append({ "type": "image", "source": {"type": "base64", "media_type": "image/jpeg", "data": b64}, }) resp = client.messages.create( model="claude-opus-4-8", max_tokens=500, messages=[{"role": "user", "content": content}], ) return"".join(b.text for b in resp.content if b.type == "text")print(watch("clip.mp4", "按时间顺序讲讲视频里发生了什么?"))
看清楚这里唯一的「新东西」了吗?只有 sample_frames()——把视频拆成帧。往下的「把图片塞进 content、问个问题、取 resp.content 里的 text」,和 019 看单张图一模一样,只不过这次一口气塞了好几张。
🔑 帧是「有序」的,一定要告诉模型
那句 "下面是一段视频按时间先后抽出的若干帧" 不是废话——它是让模型理解先后、因果、动作方向的命门。少了这句,模型只当收到一堆无关的散图,答不出「先……然后……」。要更精确,还可以在每帧前插一句「第 12 秒:」把时间戳也告诉它。时间信息不会自己长在图片里,得你亲手喂。
05抽帧的学问:抽多密,就是在花多少钱
上面偷懒用了「每 2 秒一帧」的均匀抽帧。它简单,但不总聪明:一段大部分时间静止、只在某几秒有动作的监控,均匀抽会抽出一堆废帧、又可能恰好错过那个关键瞬间。于是有了更讲究的抽法:
怎么选?一句话:抽帧的密度,直接换算成你的账单和延迟。抽得越密越不容易漏戏,但 token 和成本蹭蹭涨。来看一段 10 分钟视频在不同密度下的估算,感受一下这条曲线有多陡:

⚠️ 采样率就是那个「烧钱旋钮」
图片是 token 大户,视频更是「按帧数乘法计费」。上线前务必先算账:能降采样就降、能只在关键片段抽就别全程抽、固定不变的画面别重复喂。先用最稀的密度跑通,发现「漏戏」了再逐步加密——永远别默认「每秒一帧」,那是账单刺客。
06换个方向:为什么解析文档也是「多模态」问题
看完视频,转向文档。你可能第一反应是:「PDF 不就是文字吗?复制粘贴不就完事了?」——真动手你就会栽跟头:
- 扫描件根本没有文字
:很多合同、发票是打印后扫描的,整页就是一张图片,你一个字都复制不出来。 - 电子版也会「丢版面」
:就算能复制,双栏排版会被拉成一长串串行文字、表格的行列会错位、图表和公式直接消失——信息不只在文字里,更在「它排在哪」。
换句话说,读懂一份文档,靠的从来不只是认字,还有看懂版面:这个数字属于「价税合计」那一行、这段话在「免责条款」那一栏。而「看懂版面」本质是个视觉任务——这就是为什么文档解析是个不折不扣的多模态问题。既然如此,最省事的姿势往往就是:把每一页当成一张图,整个看进去。
🧑🏫 一张发票,一半信息在「排版」里
想象一张发票:金额「¥1,280」为什么是总价而不是单价?因为它排在「合计」那一栏、字号更大、在最下面一行。这些线索全藏在空间位置里,一旦拉成纯文本就荡然无存。把整页当图喂给多模态模型,等于让它像人一样连看带读,版面线索一个不落。
07两条路解析文档:OCR 流水线 vs 直接喂多模态
把文档变成能用的信息,业界有两条成熟路线,各有取舍:
怎么选?如今越来越多新项目直接从路线 B 起步——省掉一长串工程、还能顺手做语义理解和结构化抽取。追求极致成本或超大批量时,再用 A+B 混合:先用便宜的 OCR 抽出文字兜底,再把「原图 + OCR 文字」一起喂给模型,让它对照着理解版面,又快又准。
🔑 别把「解析」和「理解」混为一谈
路线 A 擅长「解析」(把字抠出来),却不懂「理解」(这字是什么意思)。多模态模型的杀手锏,是把这两步合成一步:看一眼整页,直接告诉你「开票方是谁、合计多少、有没有超期」。下一节就把这一步写成代码。
08动手:把 PDF 解析成结构化 JSON
光「看懂」还不够,业务要的是能入库的结构化数据。这时就该请出 020 的老招——用 Tool Use 强制模型「填表」:把想要的字段写进 input_schema、用 tool_choice 逼它调用,直接拿到解析好的 JSON。而这次的输入,是一整张 PDF——anthropic SDK 支持把 PDF 当成 document 块直接发,模型自己看版面:
# invoice_parser.py —— 把一张 PDF 发票解析成结构化 JSON(多模态「看」+ 020 的「填表」)from anthropic import Anthropicimport base64 client = Anthropic() pdf_b64 = base64.b64encode(open("invoice.pdf", "rb").read()).decode() INVOICE_TOOL = { # 想要的「表格」长什么样,写死在 schema 里(见 020)"name": "save_invoice", "description": "保存从发票中抽取的结构化字段", "input_schema": { "type": "object", "properties": { "vendor": {"type": "string", "description": "开票方名称"}, "date": {"type": "string", "description": "开票日期 YYYY-MM-DD"}, "total": {"type": "number", "description": "价税合计(元)"}, "items": {"type": "array", "items": {"type": "string"}, "description": "商品明细"}, }, "required": ["vendor", "date", "total"], }, } resp = client.messages.create( model="claude-opus-4-8", max_tokens=800, tools=[INVOICE_TOOL], tool_choice={"type": "tool", "name": "save_invoice"}, # 强制填表,别写作文 messages=[{"role": "user", "content": [ {"type": "document", # PDF 直接当「文档块」发,模型自己看版面"source": {"type": "base64", "media_type": "application/pdf", "data": pdf_b64}}, {"type": "text", "text": "把这张发票的关键字段抽出来,调用 save_invoice。"}, ]}], ) data = next(b.input for b in resp.content if b.type == "tool_use") # 直接拿到解析好的 dictprint(data["vendor"], data["total"], data["items"])
妙就妙在这是两招合体:多模态负责「看懂这张 PDF 的版面和内容」,Tool Use 负责「把看到的塞进规定好的格子」。跑完你拿到的不是一段描述文字,而是一个能直接 insert 进数据库的 dict。发票、合同、简历、报表——换个 schema,同一套代码全能干。
🧑🏫 长文档塞不下怎么办?
一份 200 页的年报,整个塞进去必然超窗(004)。老办法照样管用:要么分而治之——按页/章切块、每块各抽一次、最后汇总(Map-Reduce);要么先检索再看——把文档灌进 006 的 RAG,问什么就只取相关几页喂给模型。别指望一口吞下一头大象。
09六个必踩的坑
- 抽帧太稀漏关键、太密烧钱
:视频理解成败全在采样率。先稀后密、按内容自适应,别无脑「每秒一帧」(第 05 节)。 - 时间/顺序信息会丢
:多帧塞进去,一定要明说「按时间先后」,最好带上时间戳,否则模型分不清先后因果(第 04 节)。 - 看不清小字和密集表格
:分辨率不够或图缩太小,「8」会看成「3」、表格行列会串。解法:关键页提高分辨率、单页单发、别把大图硬压小。 - 幻视(hallucination)
:图糊、字缺时,模型会「脑补」一个看似合理的数字或条款。财务、医疗、法律场景务必人工复核,并让它标注出处/置信度(呼应 018 可观测、017 安全)。 - token 与成本爆炸
:图片和 PDF 都是 token 大户。先 count_tokens算账、能降采样降采样、固定前缀用提示缓存省钱(016)。 - 只测「漂亮样本」等于没测
:歪扫描、盖章遮字、手写批注、多语言混排才是照妖镜。上线前用这些「脏数据」做评估(024),别被 demo 的顺利骗了。
⚠️ 高风险字段,绝不盲信
合同金额、化验单数值、身份证号这类字段,模型看错一位就是大事故。铁律:让它「抽取 + 标出处」(这个数字来自第几页哪一行),再在代码侧做校验(金额加总对不对、日期格式合不合法),高风险的最后一定过人工。多模态是「超强的初筛」,不是「免检的终审」。
10今日小测验
答:总钥匙是「拆成一串图片,复用 019 的看图力」。视频=一叠按时间排好的图片,用抽帧把上万帧压成薄薄一本「连环画」;文档=一页页图文混排的图片,把每页整个当图看进去,版面/表格/盖章一个不丢。两者进了模型都被切成 patch、变成「视觉 token」,和文字 token 混进同一条序列,用 001 那套注意力机制统一处理——所以难点不在「看」,而在「怎么拆」。
答:10 分钟视频按 30fps 有 1.8 万帧,全喂会贵、超窗、且相邻帧高度冗余。抽帧就是挑出少数有代表性的关键帧。三种策略:① 均匀抽帧(每隔固定秒数)——最简单,适合内容平稳的讲座/教程;② 关键帧/场景切分(只在画面变化大处抽)——帧少而精,适合电影/剪辑过的短视频;③ 事件驱动(检测到特定事件才抽)——最省,适合监控/体育/直播。核心权衡:抽得越密越不漏戏,但 token 和成本蹭蹭涨,采样率就是烧钱旋钮。
答:因为读懂文档不只靠认字,还要看懂版面:扫描件整页就是图片、一个字都复制不出;电子版复制也会丢双栏/表格/图表的空间关系。而「一个数字属于合计那一行」这类线索全藏在排版里,是视觉任务。两条路:A=OCR+版面分析流水线,便宜快、可离线,但版面一花就崩、不懂语义;B=整页/PDF 直接喂多模态模型,抗乱版面、能出语义和结构化,但较贵、可能看错小字。多数新项目从 B 起步;追求极致成本或超大批量时用 A+B 混合(OCR 兜底文字 + 模型理解版面)。
答:是两招合体:019 的多模态负责「看」——用 document 块把整张 PDF 发进去,让模型连版面带内容一起看懂;020 的 Tool Use 结构化输出负责「填表」——把想要的字段写进 input_schema、用 tool_choice 强制模型调用,直接拿到解析好的 dict,而不是一段作文。合起来就是:看懂 + 填格子 = 能直接入库的结构化数据。换个 schema,发票/合同/简历/报表通吃。长文档超窗则用 Map-Reduce 分块或 006 的 RAG 先检索。
11小结 & 下一站
🎯 带走这一句
进阶多模态 = 「拆」+「看」:把视频拆成抽帧后的连环画、把文档拆成一页页图,剩下的就是 019 的看图力,全都拍平成同一种「视觉 token」用 001 的注意力处理。视频的功夫全在抽帧密度——它直接换算成账单,先稀后密、按内容自适应;文档的关键是把它当图看(版面才是信息),多数场景直接喂多模态模型、再叠上 020 的强制工具调用就能把 PDF 榨成结构化 JSON。当心六个坑:漏帧、丢顺序、看不清小字、幻视、token 爆炸、只测漂亮样本。记住:多模态是超强的「初筛」,高风险字段永远别免了人工终审。
🧪 动手作业:① 找一段十几秒的短视频,照 videobot.py每 2 秒抽一帧,让模型「按时间顺序讲讲发生了什么」,再把间隔改成 5 秒、10 秒,感受剧情细节和账单一起变化;② 拿一张发票/账单 PDF,用document块 + 强制工具调用抽成 JSON,故意换张歪扫描的试试它会不会「幻视」。📄 延伸阅读:回看 019 · 多模态看图 打底,再把 020 · 结构化输出 的强制填表接到文档解析上,一通百通。 🔁 回顾:001 · 注意力机制 · 004 · 上下文窗口 · 006 · RAG · 016 · 成本与限流 ⏭️ 下一期预告(第 029 期):上下文工程进阶:把有限的上下文窗口用到极致——该塞什么、怎么压缩历史、如何组织信息,让模型每次都「读到重点」而不是被噪音淹没。
夜雨聆风