教程Claude Code系列第 15 / 21 篇

给 Claude 看图片和 PDF:token 怎么算、坐标怎么对上,以及文件、批量和向量

图片和 PDF 怎么发、各占多少 token、让它返回的坐标怎么对回原图,本文通过中转站逐项实测。Files API、批量处理和向量这三样这家中转站没有开,按官方文档整理用法、限制和价格。

发布 · 约 15 分钟读完 · 经人工审核()· 本站的内容怎么来的

本站已实测,测试日期:2026-10-13。

本页目录(11 节)
  1. 一、发图片
  2. 二、一张图多少 token
  3. 三、要它给坐标的时候
  4. 四、发 PDF
  5. 五、Files API:传一次,反复用(按文档整理)
  6. 六、批量处理:不着急的任务半价跑(按文档整理)
  7. 七、向量(按文档整理)
  8. 八、通过中转站实测到的
  9. 和 Claude Code 的关系
  10. 花费
  11. 这次实测没有覆盖到的

这篇教程解决一组相关的问题:自己写程序调 Claude,要让它看截图、读 PDF,怎么发?一张图要多少钱?它说“按钮在 (462, 653)”,这个坐标是相对哪张图的? 后半篇讲三样配套的东西:把文件传一次反复用、不着急的任务半价批量跑、做检索要用的向量。

实测环境
日期 2026-10-13
方式 Python:anthropic SDK 1.13.0,部分实验直接发 HTTP 请求
模型 Sonnet 5.5,对比用 Opus 4.6
测试素材 用代码生成的纯色图、带色块的图,和一份两页的英文 PDF
连接方式 通过酷站AI 中转站,不是直连官方

先说实测的结论:

  • 用 base64 发图片和 PDF,通过这家中转站能用,占的 token 数和官方的算法分毫不差。
  • 图没有被缩小时,它返回的坐标一个像素都不差;图被缩小了,返回的是缩小后的坐标,要自己换算回去。
  • 用网址给图片被拒绝;Files API 和批量接口没有开。所以第五、六、七节是按文档整理的,没有跑过。

一、发图片

图片是 image 类型的内容块,和文字块一起放在用户消息的 content 里。来源有三种:

source 的 type 内容 说明
base64 图片的 base64 编码 最通用
url 一个公网能访问的图片地址 接口替你去取
file Files API 返回的 file_id 传一次,反复引用,见第五节
{"type": "image", "source": {"type": "base64", "media_type": "image/png", "data": "<base64 字符串>"}}

要点:

  • 图片放在文字前面。 官方说放后面也行,但先图后文效果最好。
  • 支持 JPEG、PNG、GIF、WebP。 动图只看第一帧。
  • 多张图一起发时,给每张加一个文字标签(“图 1:”“图 2:”),后面好指代。实测两张纯色图各加标签,它分别说对了颜色。
  • 多轮对话里,后面的轮次能看到前面的图,不用重发到新的那条消息里。但每次请求都要带完整的历史,base64 的图片会一轮一轮地重发,请求越来越大。图多的时候用 Files API。

上限:

项目 上限
单张尺寸 8000×8000 像素
单张大小 10 MB(base64 之后)
一次请求的图片数 600 张;上下文窗口是 20 万 token 的模型为 100 张
整个请求 32 MB

一次请求超过 20 张图时,每张图适用一个更严的尺寸上限,历史里重发的图和工具结果里的图都算在内。

它不能做的事:不会指认图里的人是谁;判断不了一张图是不是 AI 生成的;数很多小东西时只能给大概的数;空间位置是近似的。文档还说它不接收图片的元数据。

二、一张图多少 token

Claude 把图片切成 28×28 像素的小块来看,一块算一个 token。所以一张图的 token 数是:

向上取整(宽 / 28) × 向上取整(高 / 28)

图太大时会先被缩小。上限按模型分两档:

档位 模型 长边上限 token 上限
高分辨率 Claude 4.7 及之后的模型 2576 像素 4784
标准 其余模型 1568 像素 1568

两个上限同时生效,哪个先到算哪个。高分辨率是自动的,不用开关。

本站用纯色图实测了四种尺寸,量的是“带图比不带图多出的输入 token”:

图片尺寸 按公式算 Sonnet 5.5 实测 Opus 4.6 实测
200×200 64 67 67
1000×1000 1,296 1,299 1,299
1920×1080 高分辨率 2,691;标准 1,560 2,694 1,563
3840×2160 高分辨率 4,784;标准 1,560 4,787 1,563

每一格都是公式的结果加 3,这 3 个应该是内容块本身的开销。Opus 4.6 属于标准档,大图被缩到了 1,560 个 token 就封顶。

换算成钱,按 Sonnet 5.5 输入每百万 token 2 美元的官方标价:一张 1000×1000 的图约 0.0026 美元,一千张 2.6 美元;一张 4K 截图约 0.0096 美元,一千张 9.6 美元。

同一张大图,在新模型上最多是老模型的三倍 token。 不需要那么高的清晰度(比如只是让它判断图里是什么),先自己把图缩小再发,省钱也省传输时间。需要它读小字、看密集的界面时再用原图。

三、要它给坐标的时候

让 Claude 标出图里某个东西的位置(表格的边界框、按钮的点击位置),有三条规则。

第一,要绝对像素坐标。 在提示词里明说,例如“给出每个表格的边界框,用像素坐标,左上角和右下角”。文档特别说,不要让它给 0 到 1000 之间的归一化坐标,效果不好。坐标原点在图的左上角,x 向右、y 向下。

第二,坐标是相对“它看到的图”的。 图没被缩小,它看到的就是你发的;被缩小了,坐标就是缩小之后那张图上的。

第三,缩小的规则不是“长边缩到上限”那么简单。 两个上限里通常是 token 上限先到。文档的例子:1920×1080 的截图在标准档会被缩到 1456×819,不是 1568×882。一张 A4 纸按 130 DPI 扫描是 1075×1520,两条边都没超过 1568,但它要 2,145 个 token,超了,所以在标准档会被缩到 924×1307。按边长去猜,所有坐标都会偏。

本站的实测。白底上画红、绿、蓝三个矩形,让 Sonnet 5.5 给边界框:

图片 结果
1200×800(不会被缩小) 三个矩形的 12 个坐标值全部和真实值相同,偏差 0 像素
3000×2000(会被缩小) 每个值都是真实值的 0.784 倍,最大差了 594 像素

0.784 正好是 2352÷3000:这张图被缩到了 2352×1568。它给的坐标没有错,只是在另一张图上。

把坐标对回原图

下面这段代码原样跑过。resized_size 是照官方文档的参考实现写的,算出 Claude 实际看到的尺寸:

import base64
import json
import math
import anthropic
from mkfiles import png          # 本文用来生成测试图的小函数,换成你自己的图片文件即可

client = anthropic.Anthropic()


def resized_size(width, height, max_edge=2576, max_tokens=4784):
    """Claude 实际看到的尺寸。默认值是 Claude 4.7 及之后模型的上限;更早的模型两个数都用 1568。"""
    def fits(w, h):
        return (math.ceil(w / 28) * 28 <= max_edge and math.ceil(h / 28) * 28 <= max_edge
                and math.ceil(w / 28) * math.ceil(h / 28) <= max_tokens)
    if fits(width, height):
        return width, height
    if height > width:
        h, w = resized_size(height, width, max_edge, max_tokens)
        return w, h
    ratio, lo, hi = width / height, 1, width
    while lo + 1 < hi:
        mid = (lo + hi) // 2
        lo, hi = (mid, hi) if fits(mid, max(round(mid / ratio), 1)) else (lo, mid)
    return lo, max(round(lo / ratio), 1)


W, H = 3000, 2000
image = png(W, H, [(1500, 375, 2750, 650, (30, 160, 60))])     # 白底上一个绿色矩形
seen_w, seen_h = resized_size(W, H)
print("原图", (W, H), "→ Claude 看到的", (seen_w, seen_h))

reply = client.messages.create(
    model="claude-sonnet-5-5",
    max_tokens=300,
    output_config={"format": {"type": "json_schema", "schema": {
        "type": "object", "additionalProperties": False, "required": ["x1", "y1", "x2", "y2"],
        "properties": {k: {"type": "integer"} for k in ("x1", "y1", "x2", "y2")}}}},
    messages=[{"role": "user", "content": [
        {"type": "image", "source": {"type": "base64", "media_type": "image/png",
                                     "data": base64.b64encode(image).decode()}},      # 图片放在文字前面
        {"type": "text", "text": "给出绿色矩形的边界框,用像素坐标:左上角 (x1, y1),右下角 (x2, y2)。"},
    ]}],
)
box = json.loads(reply.content[0].text)
print("Claude 返回的", box)
sx, sy = W / seen_w, H / seen_h                               # 换算回原图
print("换算回原图", {k: round(v * (sx if k[0] == "x" else sy)) for k, v in box.items()}, "真实位置 (1500, 375, 2750, 650)")
print("这张图占的输入 token 约", math.ceil(seen_w / 28) * math.ceil(seen_h / 28))

输出:

原图 (3000, 2000) → Claude 看到的 (2352, 1568)
Claude 返回的 {'x1': 1176, 'y1': 294, 'x2': 2156, 'y2': 510}
换算回原图 {'x1': 1500, 'y1': 375, 'x2': 2750, 'y2': 651} 真实位置 (1500, 375, 2750, 650)
这张图占的输入 token 约 4704

换算以后只差 1 个像素。这里顺手用了结构化输出来拿坐标,免得自己从文字里抠数字,用法见上一篇。

还有一个细节:Claude 会把图的右边和下边补到 28 的整数倍。补的部分没有内容,原点不动,所以换算时除以缩小后的尺寸,不要除以补齐后的尺寸。

更稳的两个办法

自己先缩好再发。 用上面的函数算出目标尺寸,用图片库缩到那个尺寸,再发出去。这样你手里的图就是它看到的图,坐标直接能用。

让“会被缩小”变成报错。 换了图片来源或者换了模型档位,都可能悄悄重新引入缩放。给图片块加一个字段,接口发现这张图需要缩小时就直接拒绝:

{"type": "image", "source": {...}, "transformations": {"oversized_image": "error"}}

实测对 3000×2000 的图加上它,返回 400,报错里直接给出了该缩到多大:

image dimensions 3000x2000 exceed the maximum image size of a model named on this request
and would be downsized to 2352x1568; scale the image to at most 2352x1568 or set the
image's oversized_image setting to "downsize"

这个字段是按图片设的,同一个请求里可以只给要点坐标的截图加,别的图照常自动缩。

坐标的精度有限。目标很小时,缩小会损失精度,文档建议把那一块裁出来单独发,再把返回的坐标加上裁剪的偏移。大批量处理之前,先抽几张画出来看看对不对。

四、发 PDF

PDF 用 document 类型的内容块,来源同样是 base64、网址、file_id 三种。

限制 数值
整个请求 32 MB
页数 600 页;请求的上下文窗口不到 100 万 token 时是 100 页
格式 标准 PDF,不能有密码或加密

它是怎么读的:每一页被转成一张图片,同时把这一页的文字提取出来,图和文字一起给模型。所以它既能读文字,也能看懂图表、版式这些文字里没有的东西。代价是每页既算文字的 token,也算图片的 token。文档给的估计是每页文字 1,500 到 3,000 个 token,另加图片。

下面这段原样跑过。report.pdf 是一份两页的英文报告:第 1 页是几行文字,第 2 页有一张只画了柱子和月份、没写数字的柱状图。

import base64
import anthropic

client = anthropic.Anthropic()

with open("report.pdf", "rb") as f:                 # 一份两页的英文报告,第 2 页有一张柱状图
    pdf_data = base64.b64encode(f.read()).decode()

reply = client.messages.create(
    model="claude-sonnet-5-5",
    max_tokens=600,
    messages=[{"role": "user", "content": [
        {"type": "document",
         "source": {"type": "base64", "media_type": "application/pdf", "data": pdf_data},
         "title": "Q3 report",
         "citations": {"enabled": True}},            # 不要引用就去掉这一行
        {"type": "text", "text": "API 的可用率是多少?第 2 页的图里哪个月花得最多?"},
    ]}],
)
print("输入 token:", reply.usage.input_tokens + (reply.usage.cache_creation_input_tokens or 0)
      + (reply.usage.cache_read_input_tokens or 0))
for block in reply.content:
    if block.type == "text":
        print(block.text, end="")
        for c in block.citations or []:
            print(f"〔第 {c.start_page_number} 页〕", end="")
print()

输出:

输入 token: 4356
**API 可用率:** 公共 API 的可用率为 99.97%。〔第 1 页〕

**第 2 页图表中花费最多的月份:** 9 月(Sep)。图中 9 月的柱子最高,高于 7 月和 8 月,其中 8 月最低。这与报告摘要中的说明一致:9 月的费用高峰是由一次性数据迁移造成的。〔第 1 页〕

几个实测到的数字:

  • 这份 PDF 只有 1.6 KB、两页、每页十来行字,输入是 3,364 个 token(不开引用时),平均每页约 1,680 个。页面再空也有图片那一份。开了引用是 4,356 个。
  • 图里的信息它是看得懂的。 另一次单独问三根柱子的比例,它量出来是 1 : 0.94 : 1.28。画图时用的数值是 412、389、527,比例是 1 : 0.944 : 1.279。
  • 引用只能指向文字。 上面关于图表的那句话,引用挂的是第 1 页的一句文字,不是图。PDF 里的图片不能被引用。

官方的几条建议:PDF 放在文字前面;页面要正着放,别歪别倒;提问时用 PDF 阅读器里显示的页码;文件大就拆开;要反复问同一份文档就配上缓存。扫描得很密、图很多的 PDF,可能没到页数上限就先把上下文占满了。

别的格式:.txt、.csv、.md 这类纯文本,可以直接把内容放进消息,或者作为纯文本文档给。.docx、.xlsx 不能直接放进文档块,先转成文本或 PDF;里面有图的 Word 文档转成 PDF 再发。

五、Files API:传一次,反复用(按文档整理)

通过这家中转站,文件接口返回 404,在消息里引用 file_id 返回 400。下面是文档的内容。

Files API 让你把文件上传到 Anthropic 那边,拿到一个 file_id,之后在消息里引用它,不用每次重发内容:

uploaded = client.files.upload(
    file=("document.pdf", open("/path/to/document.pdf", "rb"), "application/pdf"),
)

reply = client.messages.create(
    model="claude-sonnet-5-5", max_tokens=1024,
    messages=[{"role": "user", "content": [
        {"type": "document", "source": {"type": "file", "file_id": uploaded.id}},
        {"type": "text", "text": "总结这份文档。"},
    ]}],
)
文件类型 用哪种内容块
PDF、纯文本 document
图片 image
数据集等其他文件 container_upload,交给代码执行工具用

要知道的事:

  • 文件是整个工作区共享的。 同一个工作区里任何一把 key 都能用任何一个文件,它不按最终用户、对话或会话隔离。做多租户的应用,文档建议一个租户一个工作区;不要接受来源不可信的 file_id。
  • 你上传的文件不能下载。 只有技能或代码执行工具生成的文件才能下载。
  • 不能改名,不能改内容。 要改就传一个新的,删掉旧的。
  • 单个文件最大 500 MB,一个组织合计 1 TB。
  • 文件一直保留到你删除,或者到它的过期时间。删了不能恢复。
  • 这个接口已经不是 beta,不用带 beta 头。

六、批量处理:不着急的任务半价跑(按文档整理)

通过这家中转站,批量接口返回 404。下面是文档的内容。

批量接口让你一次提交一大批请求,异步处理,所有用量按标准价的五折计费。大多数批次不到一小时跑完。适合不需要马上要结果的活:跑评测、给大量内容分类、批量生成摘要。

import time

batch = client.messages.batches.create(requests=[
    {"custom_id": "review-0001",                    # 自己起的编号,用来对结果
     "params": {"model": "claude-sonnet-5-5", "max_tokens": 300,
                "messages": [{"role": "user", "content": "给这条评论分类:……"}]}},
    {"custom_id": "review-0002",
     "params": {"model": "claude-sonnet-5-5", "max_tokens": 300,
                "messages": [{"role": "user", "content": "给这条评论分类:……"}]}},
])

while client.messages.batches.retrieve(batch.id).processing_status != "ended":
    time.sleep(60)                                  # 轮询,直到整批结束

for item in client.messages.batches.results(batch.id):
    if item.result.type == "succeeded":
        print(item.custom_id, item.result.message.content[0].text)
    else:
        print(item.custom_id, "没有成功:", item.result.type)

限制和规则:

项目 内容
一批的大小 最多 10 万个请求,或 256 MB,先到为准
处理时间 多数一小时内;24 小时没跑完的请求算过期
结果保留 创建后 29 天内可以下载
custom_id 1 到 64 个字符,只能用字母、数字、连字符、下划线,一批里不能重复
结果的顺序 不保证和提交的顺序一致,一定用 custom_id 对
不能用的参数 stream、快速模式的 speed、max_tokens: 0

每个请求的结果是四种之一:succeeded(成功)、errored(出错)、canceled(被取消)、expired(过期)。后三种不收费。一个请求失败不影响同一批里的其他请求。

容易踩的地方:

  • 参数写错不会在提交时报出来。 每个请求的参数是异步校验的,整批结束时才告诉你。先拿一个请求用普通接口试通,再批量提交。
  • 和缓存可以叠加,五折之上再享受缓存的折扣。但批次里的请求是并发处理的,能不能命中只能尽力而为。文档建议每个请求带完全相同的缓存断点,并且用 1 小时的缓存时长,因为批次常常跑过 5 分钟。
  • 批量跑得快,花费可能略微超过工作区设的消费上限。

除此之外,普通接口能发的几乎都能放进批次:图片、工具、系统提示词、多轮对话、思考、服务器端工具。批量接口还有一个独有的 beta 功能:带上 output-300k-2026-03-24 这个头,单个请求的输出上限可以提到 30 万 token,普通接口上没有。

七、向量(按文档整理)

做检索增强要先把文本变成向量。Anthropic 自己不提供向量模型。 官方文档这一页介绍的是另一家公司的服务(现在属于 MongoDB 的 Voyage AI),并且说明这只是一个例子,你应该多比较几家。

这意味着它和本系列别的内容不一样:是另一个接口、另一把 key、另一套计费,和 Claude 的接口没有关系。本站没有测。

文档里的用法是这样的:

import voyageai

vo = voyageai.Client()                    # 读环境变量 VOYAGE_API_KEY
docs = vo.embed(["文档一……", "文档二……"], model="voyage-4", input_type="document").embeddings
query = vo.embed(["用户的问题"], model="voyage-4", input_type="query").embeddings[0]

值得记的只有一条:做检索时,文档和查询要分别标明 input_type(document 和 query),不要省略,两边会被加上不同的前缀再算向量。

文档列的模型分几类:通用的文本向量模型、针对代码检索的、能同时处理图文的多模态模型、带上下文的分块向量模型,以及对检索结果重新排序的模型。具体型号和价格以那一家自己的文档为准。

向量检索出来的内容怎么交给 Claude 并带上出处,见上一篇讲的搜索结果块。

八、通过中转站实测到的

项目 官方文档 通过中转站实测
base64 图片 可用 可用,token 数和公式一致
多张图 可用 可用
transformations 的 oversized_image 需要缩小时 400 400,一致,报错给出目标尺寸
用网址给图片 可用 400:URL sources are not supported.
base64 的 PDF 可用 可用,带引用也可用
Files API 可用 404
消息里引用 file_id 可用 400
批量接口 可用 404

通过这家中转站,图片和 PDF 都用 base64 发。多轮对话里图片要每轮重发,注意请求的大小。

和 Claude Code 的关系

在 Claude Code 里贴进去的截图,同样是作为图片发给模型的。按本文的计费规则推算,一张 4K 的全屏截图在新模型上是四千七百多个输入 token,并且会留在这一段会话的历史里;只需要它看一小块时,先裁一下。这一条是按规则推的,本站没有在 Claude Code 里单独量过。

花费

这一篇一共发了约 25 次请求,token 的大头是图片和 PDF。按官方标价粗算约 0.1 美元。

这次实测没有覆盖到的

  • 官方接口没有测。
  • Files API、批量处理、向量都没有跑,第五、六、七节的代码没有运行过,是按文档的示例改写的。
  • 图片只用了代码生成的纯色图和色块图。 真实的照片、截图、表单、手写内容没有试;坐标只测了大的矩形,小目标的精度没有量。
  • PDF 只用了一份自己生成的两页英文文件。 中文 PDF、扫描件、大文件、页数上限都没有试。
  • 用网址给图片和 PDF 在这家中转站上不可用,没法验证。
  • JPEG、GIF、WebP 没有试,只用了 PNG。
  • 各项只跑了一两次。
  • 只测了一家中转站、一天。

来源

通过 API 中转站使用这些模型?到 Veridrop 免费检测中转站给你的是不是它声称的模型,或者看看中转站排行榜前 100 名。

相关文章