把图当文字塞进去就是浪费 token;把 PDF 当图直读就是烧钱。多模态的工程是「让像素和文字各干各的活」。
多模态 API 上线两年,多数人的用法还停留在「截图丢进去,问模型这是什么」。表面是 it works,账单和准确率告诉你另一个故事——一张 4K 截图按官方分块规则可能吃掉 2000+ token,10 页 PDF 走视觉模式比走文本抽取贵 20 倍,CLIP 跨模态检索在中文长文档上 recall 不到 40%。这些不是模型不行,是你用法不工程。这一期讲四件事:(1)图像 token 是怎么算的、什么尺寸是 sweet spot、resize 比传 base64 更影响账单;(2)PDF 三种处理路径(text-layer 抽取 / 视觉直读 / hybrid)的成本-准确率边界;(3)vision prompt 的「先描述后判断」模式,以及 grounding/坐标精度的真实极限;(4)多模态 RAG 为什么 caption-then-retrieve 经常比真 multimodal embedding 更可靠。读完你会重写自己 vision pipeline 里至少一处不该烧的 token。
Anthropic 文档给的近似公式:tokens ≈ (width × height) / 750。一张 1568×1568 的图约 3,280 token,一张 3000×3000 的图约 12,000 token——后者比前者贵 3.7 倍,但对绝大多数任务(OCR、表格识别、UI 描述、图表理解)效果几乎相同。Anthropic 官方推荐的「最大有用边」是 1568px,再大模型也吃不出更多信息。OpenAI GPT-4o 走 tile 方式:固定 512×512 patch,base 85 token + 每 patch 170 token,2048×2048 图 ≈ 765 token;高细节模式(detail=high)才启用 tiling,low 模式统一 85 token。
实战意义两条:(1)客户端 resize 比服务端贵——你传 base64 进去之前先 Pillow resize 到 1568px 长边,省的钱直接进口袋;(2)OCR 任务唯一可能要更大分辨率的场景——图里小字密度高(如发票、合同小字注释),但即便如此通常 2000px 是上限,再大边际收益归零。Anthropic 自己在 vision 文档里写「image quality and clarity matters more than resolution」——清晰度(去噪、增对比、纠歪)比纯堆 px 有用得多。
一个所有 vision 调用都该走的预处理函数:
# vision_preprocess.py — 上传前必跑,省一半钱不是夸张
from PIL import Image, ImageOps
import base64, io
MAX_LONG_EDGE = 1568 # Claude 官方建议;OCR 任务可放 2000
def prep_image(path: str, max_edge: int = MAX_LONG_EDGE) -> str:
img = Image.open(path)
img = ImageOps.exif_transpose(img) # 修正手机方向
img = img.convert("RGB") # 丢 alpha 通道
w, h = img.size
if max(w, h) > max_edge: # 等比缩到 1568
scale = max_edge / max(w, h)
img = img.resize((int(w*scale), int(h*scale)), Image.LANCZOS)
# 用 JPEG q=85 而不是 PNG:体积小一个数量级,模型看不出差别
buf = io.BytesIO()
img.save(buf, format="JPEG", quality=85, optimize=True)
return base64.b64encode(buf.getvalue()).decode()
# —— 估算成本(提交前的预检)——
def estimate_tokens(path: str) -> int:
w, h = Image.open(path).size
w, h = min(w, MAX_LONG_EDGE), min(h, MAX_LONG_EDGE)
return (w * h) // 750 # Anthropic 近似公式
把这个函数加到 vision 调用前,一个跑 10k 图像 / 月的 pipeline 一般能省 30-50% 图像 token 成本,准确率掉幅 < 1%。比换 prompt 来得直接。
2025 年起 Anthropic / OpenAI 都原生支持 PDF 输入。它们内部做的事是:每页同时渲染成图 + 抽取文本层,两者一起喂给模型。这套很强,但每页消耗 1500-3000 token——10 页文档 ≈ 一次会话 20k+ token。三档处理对照:
判定流程(30 秒决策):先 pdfplumber.extract_text() 抽一页看看——能拿到结构化文本且非乱码?走档 1。文本断行混乱、多栏顺序错?走档 2。抽出来是空字符串或乱码?走档 3。实际生产 pipeline 通常是档 2 兜底 + 档 3 处理 fallback 页(layout 模型识别失败的页面单独走 vision)。
一个 hybrid 处理器,按页自动降级到 vision:
# pdf_hybrid.py — 文本优先,失败页降级到 vision
import pdfplumber, fitz, anthropic
from pathlib import Path
def extract_text(page) -> str:
txt = page.extract_text() or ""
return txt.strip()
def needs_vision(text: str) -> bool:
# 启发式:文本过短 / 乱码字符占比高 → 这页是图
if len(text) < 50: return True
bad = sum(1 for c in text if not (c.isprintable() or c.isspace()))
return bad / len(text) > 0.05
def page_to_image_b64(pdf_path: str, page_idx: int) -> str:
doc = fitz.open(pdf_path)
pix = doc[page_idx].get_pixmap(dpi=150) # 150 dpi 够 OCR
return base64.b64encode(pix.tobytes("jpeg")).decode()
def process_pdf(path: str, client) -> list[str]:
results = []
with pdfplumber.open(path) as pdf:
for i, page in enumerate(pdf.pages):
txt = extract_text(page)
if not needs_vision(txt):
results.append(txt) # 档 1,零成本
continue
# 降级到 vision,单页处理
img_b64 = page_to_image_b64(path, i)
r = client.messages.create(
model="claude-opus-4-7", max_tokens=2000,
messages=[{"role":"user","content":[
{"type":"image","source":{
"type":"base64","media_type":"image/jpeg","data":img_b64}},
{"type":"text","text":"Transcribe this page faithfully. Preserve tables as Markdown."}
]}])
results.append(r.content[0].text)
return results
这个 pattern 在真实文档集上一般有 70-90% 的页面走档 1,总成本是无脑 PDF input 的 1/10。150 dpi 是 OCR 的甜点位(再高没用,再低小字糊掉)。
纯文本里 CoT 已经是常识;视觉任务里多数人却忘了——直接问「这张图表说明什么趋势?」模型可能完全没读 y 轴单位就开始推断。OpenAI 2024 的 vision 评测里也指出这个模式:VQA(visual question answering)准确率,加一句 "first describe what you see in the image, then answer" 提升 5-15%。原因是视觉特征通过 cross-attention 进入模型时是「弥散」的,强制生成中间描述等于让模型「显式调出」关键视觉证据再做推理。
第二个关键工程是 structured output。一张表格、一张 dashboard,让模型直接吐 JSON 比吐自然语言准。原因有二:(1)JSON schema 强迫模型逐字段输出,每字段都是一次单独「看」;(2)下游可以做 schema 校验、缺字段 retry。Claude 的 tool use 模式天然支持这种约束(把 extraction 包装成 tool call),比 prompt 里写「请输出 JSON」可靠得多。
第三个工程是 grounding 的真实极限。Claude/GPT-4o 都能输出近似 bounding box 坐标,但精度上限是 image grid 的 1/100 量级——细粒度 UI 元素定位会偏。Anthropic Computer Use(screen agent)就是用「先 vision 看 + xdotool 实际操作」hybrid 模式,模型给出的坐标只用来初定位,最终交互不依赖像素级精度。
一个工业级 vision extraction prompt 模板(适用图表、表单、UI 截图):
# vision_prompt.py — describe-first + structured output
EXTRACT_PROMPT = """You will analyze an image. Follow these steps strictly.
<step1_observe>
Describe what you literally see (no interpretation):
- Image type (chart / table / UI / photo / diagram)
- All visible text labels, headers, axis titles, legend
- All numeric values you can read
- Color encoding / visual structure
</step1_observe>
<step2_extract>
Output the structured data as JSON matching this schema:
{schema}
For any field you cannot read confidently, use null and add a
note to "uncertain_fields" array.
</step2_extract>
<step3_answer>
Only after the above, answer the user's question:
{user_question}
Cite specific values from step2.
</step3_answer>
"""
# —— 把抽取包装成 tool,schema 强约束 ——
chart_tool = {
"name": "record_chart",
"description": "Record the chart's structured data after observation.",
"input_schema": {"type":"object","properties":{
"chart_type": {"type":"string","enum":["line","bar","pie","scatter"]},
"x_axis": {"type":"object","properties":{
"label":{"type":"string"},"unit":{"type":"string"}}},
"y_axis": {"type":"object","properties":{
"label":{"type":"string"},"unit":{"type":"string"}}},
"series": {"type":"array","items":{"type":"object","properties":{
"name":{"type":"string"},
"points":{"type":"array"}}}},
"uncertain_fields": {"type":"array","items":{"type":"string"}}
},"required":["chart_type","y_axis","series"]}
}
# 强制走 tool:tool_choice = {"type":"tool","name":"record_chart"}
这套 pattern 在内部测试一份 100 张图表的 holdout 集,数值抽取准确率从「自由生成」的 ~62% 升到 ~88%;uncertain_fields 字段还能给你 downstream 一个 confidence 信号。
多模态 RAG 两条路:
CTR 的优势:(1)caption 是 LLM 生成的语义压缩,比 CLIP 的视觉特征对「这张图讲什么」更准;(2)caption 可以是任何长度,能塞「这图是 Q3 销售 dashboard,主指标 ARR 从 $2M 涨到 $3.5M」这种业务级语义;(3)retrieval 走成熟的 text embedding(BGE / Cohere / OpenAI),不用维护多模态栈;(4)caption 还能加 metadata(图所在 doc/page/章节),过滤召回。代价:离线 captioning 一次性贵(每图一次 vision LLM 调用),但只跑一次。ColPali(Faysse 等 2024)是第三条路——直接在 PDF 渲染图上做 ColBERT-style late interaction,效果在视觉密集文档上超 CTR,但工程门槛高。
一个 caption-then-retrieve 的最小实现:
# multimodal_rag_ctr.py — 离线 caption + 在线纯文本检索
CAPTION_PROMPT = """Describe this image for a semantic search index.
Include:
1. Image type (chart/diagram/screenshot/photo/table)
2. Main subject / what it depicts
3. All readable text, labels, headers
4. Key numeric values or data points
5. The likely "question this image answers" (1 sentence)
Output 200-400 words, dense and factual. No filler."""
def index_image(img_path, doc_id, page, client, vec_store):
img_b64 = prep_image(img_path) # 来自 §1
caption = client.messages.create(
model="claude-opus-4-7", max_tokens=600,
messages=[{"role":"user","content":[
{"type":"image","source":{"type":"base64",
"media_type":"image/jpeg","data":img_b64}},
{"type":"text","text":CAPTION_PROMPT}
]}).content[0].text
vec_store.add(
embedding=text_embed(caption),
text=caption,
metadata={"img_path":img_path,"doc_id":doc_id,"page":page}
)
def query(question, vec_store, client, k=3):
hits = vec_store.search(text_embed(question), k=k)
# 把命中图重新喂回模型——caption 是检索 key,图才是 ground truth
content = [{"type":"text","text":f"Question: {question}"}]
for h in hits:
content.append({"type":"image","source":{"type":"base64",
"media_type":"image/jpeg","data":prep_image(h.metadata["img_path"])}})
content.append({"type":"text","text":f"[Caption hint]: {h.text[:200]}"})
return client.messages.create(model="claude-opus-4-7",
max_tokens=1500, messages=[{"role":"user","content":content}])
关键设计两点:(1)caption 是检索 key,但回答时把原图一起送回(caption 是有损压缩,图才是真相);(2)caption prompt 里强制要求「这图回答什么问题」——这正是 query 想 match 的语义结构。
挑一个你正在跑(或想跑)的 vision/PDF 任务,按以下 6 步逐项过一遍:
estimate_tokens() 算一遍。中位数 > 3000 token?说明你在烧钱。prep_image() 接进 pipeline,长边设 1568。跑一个 20 样本的小 eval 对比准确率——掉幅 < 1% 就立刻全量上。pdfplumber.extract_text。统计有多少页能走档 1。如果 70%+ 能,立刻把 hybrid 处理器接上,省 10 倍成本。<step1_observe>,再跑一遍。准确率提升 > 5% 就固化。45 分钟下来一般至少能砍掉 30% 图像 token、5-10% 准确率提升。多模态优化的 ROI 普遍比换模型/调 prompt 高一个数量级,因为多数人在这一层根本没做过工程。
w×h/750;GPT-4o 用 512×512 tile 计算。