cd ../返回博客
$Guide//2026年6月4日//7 min read

视觉与文档理解:用一套 OpenAI 接口读图、识字、解析文档

通过 Brievio 用同一套 OpenAI image_url 结构,让 Claude 与 Gemini 原生读图:URL、base64、多图扫描件,OCR/图表/表单一网打尽。

相当多所谓的「文档 AI」,其实不过是把一张图片发给聊天模型,再读回它的 回答。一张收据、一张仪表板截图、一页扫描的 PDF、一张白板照片 —— 如今的 Claude 和 Gemini 模型都能原生读懂这一切,不需要单独的 OCR 引擎。麻烦 通常出在管道这一层:每家供应商都有自己一套附加图片的方式,把代码从一家 移植到另一家很烦人。

通过 Brievio,你只用一套请求结构 —— OpenAI Chat Completions 的 content 数组,配上一个 image_url 部分 —— 它在 Claude Opus 4.7、Sonnet 4.6、Haiku 4.5 以及 Gemini 2.5 Pro / Flash 上的表现完全一致。这些都是货真价实的第一方模型,原生视觉能力如实 保留,所以同一张 Claude 读得好的 JPEG,Claude 是真的在读。本文讲的是 图像输入(理解),不是图像生成:URL、base64、多图提示,以及真实工作中 会遇到的 OCR / 图表 / 扫描文档等场景。

最简单的情形:通过 URL 发图

如果你的图片已经放在一个公开的 HTTPS URL 上,就把它作为一个 image_url 部分,紧挨着文字附上。把 claude-sonnet-4-6 换成 gemini-2.5-pro,请求体 一个字都不用改 —— 这种可移植性正是关键所在:

image_url.py
# 通过 URL 发送一张图片。沿用同一套 OpenAI chat 结构,打到真模型上。
from openai import OpenAI

client = OpenAI(
    api_key="sk-brievio-...",
    base_url="https://api.brievio.com/v1",
)

resp = client.chat.completions.create(
    model="claude-sonnet-4-6",     # 或换成 gemini-2.5-pro —— 请求结构完全一致
    max_tokens=500,
    messages=[
        {
            "role": "user",
            "content": [
                {"type": "text", "text": "What does this chart show? Give the trend in one sentence."},
                {
                    "type": "image_url",
                    "image_url": {"url": "https://example.com/q3-revenue.png"},
                },
            ],
        }
    ],
)

print(resp.choices[0].message.content)
# 图片按【输入】token 计费 —— 读 resp.usage.prompt_tokens 就能看到这部分成本。

有一点要趁早记牢:图片要花输入 token。模型并不是免费看 像素的 —— 它会把图片切成小块,每一块都像文本一样计费。Brievio 上报的是 诚实的 token 数,所以图片成本会原样体现在 resp.usage.prompt_tokens 里,与上游供应商的收费完全一致。 一张全屏截图视分辨率而定,可能消耗几百到两千多个输入 token。把它当成 一段上下文那样去预算,而不是当成白送的。

base64:你真正会用到的情形

在生产环境里,图片很少是公开 URL —— 它往往是用户刚上传的文件、扫描仪 出来的缓冲区、一个私有的 S3 对象。对这些情况,就把字节内联成一个 base64 的 data: URL。模型分辨不出区别;你的字节也无需对外可达:

base64_upload.py
# 生产环境里的图片大多不是公开 URL。把它们内联成 base64 的 data URL。
import base64
from openai import OpenAI

client = OpenAI(api_key="sk-brievio-...", base_url="https://api.brievio.com/v1")

def data_url(path: str, media_type: str = "image/png") -> str:
    with open(path, "rb") as f:
        b64 = base64.standard_b64encode(f.read()).decode("utf-8")
    return f"data:{media_type};base64,{b64}"

resp = client.chat.completions.create(
    model="gemini-2.5-flash",      # OCR / 收据场景,便宜又快
    max_tokens=800,
    messages=[
        {
            "role": "user",
            "content": [
                {"type": "text", "text": "Extract every line item and total as JSON. Keys: items[], total."},
                {"type": "image_url", "image_url": {"url": data_url("receipt.jpg", "image/jpeg")}},
            ],
        }
    ],
)

print(resp.choices[0].message.content)
# 提示:data URL 会让请求体比原始文件膨胀约 33%。把图片尺寸控制在
# 合理范围 —— 识别文字时 2-3MP 的截图就绰绰有余,几乎用不到 12MP。

两点实务提醒。第一,base64 会给请求体增加约 33% 的开销,而且单张图片 有大小上限(Anthropic 在 API 上把单图限制在约 5 MB;Gemini 也有 自己的上限)。如果一张大扫描件返回 413,就把它缩小 —— 文字在远低于你 想象的分辨率下依然清晰可读。第二,要发送正确的 media_typeimage/pngimage/jpeg image/webp);类型不匹配是导致静默解码失败的常见原因。当一个 请求确实在 Brievio 上以 4xx 或 5xx 失败时,你不会为它付费 —— 失败的调用 免费,所以你可以重试一张缩小后的图片,而不用付两次钱。

多图提示与扫描文档

content 数组想要多少 image_url 部分都行,并能 与文字交错排列。这就解锁了那些真正有用的工作流:对比一张前后截图、读一份 多页扫描文档,或喂进一组图表然后让它找出贯穿其中的脉络。在两大模型家族 上都见效的小技巧,是给每张图配一个简短的文字锚点,让模型能引用它:

multi_image.py
# 一个提示里塞多张图 —— 对比两张截图,或读一份 4 页的扫描件。
content = [
    {"type": "text", "text": "These are pages 1-3 of a scanned contract. Summarize the parties, term, and termination clause. Cite the page number for each."},
]
for i, path in enumerate(["page1.png", "page2.png", "page3.png"], start=1):
    content.append({"type": "text", "text": f"--- Page {i} ---"})
    content.append({"type": "image_url", "image_url": {"url": data_url(path)}})

resp = client.chat.completions.create(
    model="claude-opus-4-7",       # 处理密集文档时推理能力最强
    max_tokens=1200,
    messages=[{"role": "user", "content": content}],
)

print(resp.choices[0].message.content)
# 在每张图前插一个文字标签("--- Page 2 ---"),相当于给模型一个可引用的
# 锚点,能明显改善两大模型家族在多图场景下的定位准确度。

对于长文档,存在一个选模型的取舍。Gemini 2.5 Flash 又便宜又快,是 高吞吐 OCR、收据和表单提取场景下极好的默认选择。Claude Opus 4.7 在密集的 多页材料上推理更深 —— 合同、财务报表,凡是需要它把好几页内容同时纳入 视野并交叉比对的场景。Sonnet 4.6 和 Gemini 2.5 Pro 居于两者之间。除了那个 model 字符串,你不用改任何代码就能按任务路由;实时列表见 /models

这些模型擅长(和不擅长)什么

原生视觉能很好地胜任一大批真实任务:

  • OCR 与转录 —— 印刷体文字,乃至意外地相当不错的手写体。 不必维护一套 Tesseract 管道。
  • 图表与仪表板 —— 从柱状图 / 折线图上读取数值、总结一段 趋势、对截图里的某个指标做合理性核对。
  • 结构化提取 —— 把收据、发票、表单、证件转成 JSON。在 提示里配上一份严格的 schema,输出会很干净。
  • 界面与图示理解 —— 描述一个界面、读懂一个报错弹窗、 讲解一张架构图。

也有一些诚实的局限。模型仍会偶尔读错某一个数字或一格密集的表格, 所以凡是「数字错了代价很大」的场景,都要对照一份 schema 或一个校验和来 验证(比如各条明细应当加总等于注明的合计)。低分辨率图片里的小字是最 常见的失败点 —— 给它一张更高分辨率的裁剪图。而且各模型的表现确实有 差异:一种布局某个模型能轻松搞定,另一个却可能栽跟头,这正是为什么能够 在一套 API 背后切换模型 并用你自己的文档做 A/B,要比任何单一的基准测试都更有价值。

更进一步:视觉加工具

视觉能力可以和 API 的其余部分组合起来。你可以同时把一张图片一组工具交给模型,让它读完一张截图后再用提取到的内容去调用一个函数 —— 「读这张发票,然后调用 create_expense(amount, vendor, date)。」这一层工具调用在 Brievio 上对 Claude 和 Gemini 同样是统一的; 工具调用指南 讲了这套共用结构。把它和视觉结合起来,一个请求就几乎搭起了大半条文档 处理流水线。

要点总结

做图像理解,你并不需要单独的 OCR 服务,也不需要各家专属的 SDK。在你 平常的聊天请求里附上一个 image_url 部分 —— URL 或 base64 的 data URL —— 给多张图打上标签让模型能引用它们,再路由到适合该任务的 模型:高吞吐的廉价识别用 Gemini Flash,难啃的文档用 Claude Opus。记住 图片按输入 token 计费(在 usage 里如实展示),把分辨率保持在合理范围,验证提取出的 数字,并在一张大扫描件被弹回时依靠免费的失败重试。完整的请求参考和 各模型的限制都在 文档里 —— 从那里上手,一个下午你就能让 文档在 Claude 或 Gemini 之间顺畅流转起来。