Claude API PDF 文档问答实战:从原生解析到分页引用的完整方案

Claude API PDF 文档问答实战:从原生解析到分页引用的完整方案

拿到一份 60 页的供应商合同,老板要你"半小时内告诉我违约条款怎么写、付款节点几个、有没有自动续约"。你打开 PDF 想用 Ctrl+F,发现合同里关键条款都用了"该方"、"前述事项"这种含糊指代------搜索失效。

这种场景用 Claude API 是甜区。Claude 原生支持 PDF 输入,不需要先跑 OCR、不需要先切 chunk、不需要自建 RAG。但很多人接进来后还是踩坑:100 页的硬上限、32MB 请求体限制、citations 用了但定位不到原页、同一份文档每次都重新上传烧 token。

本文用一份合同问答的真实流程串起来,把 PDF 上传的四种方式、citations 分页引用、prompt caching 复用、超长文档切分都讲清楚,给出可以直接复制运行的代码。


一、Claude 的 PDF 支持到底是怎么回事

很多人以为 Claude 的 PDF 处理是"后台跑 OCR 提取文字再喂给模型"------不是。Claude 把 PDF 当作视觉 + 文本的混合输入:每一页既被作为图像理解(保留版式、表格、签章、手写笔迹),也提取出文本层(可选择性引用具体文字段落)。这一点对合同、财报、论文这种"版式承载语义"的文档至关重要------表头、签字栏、脚注、跨页表格都能被读懂。

代价是单页消耗比纯文本高得多。一份 100 页的 PDF,文本量可能只有 3 万 token,但当成 PDF 输入会消耗 7-10 万 token(每页约 700-1000 token,含图像 token)。所以不要为了"省事"就把已经是纯文本的内容塞成 PDF,能用 text/markdown 喂的就别用 PDF 喂。

硬限制(截至 2026-05)

限制项 数值 来源
单份 PDF 最大页数 100 页 Anthropic 官方文档
Messages API 请求体上限 32 MB 标准同步接口
Batch API 请求体上限 256 MB 异步批处理
Files API 单文件上限 500 MB 文件持久化接口
组织存储总上限 100 GB Files API 全局配额

100 页是模型层的硬约束,不是网关的限制------换 endpoint 解决不了。超过 100 页必须自己切分,本文第四节给出处理方案。

数据来源:Anthropic PDF support 官方文档Files API docs


二、四种 PDF 上传方式怎么选

Claude API 提供四种把 PDF 喂进模型的姿势,差异主要在单次开销复用成本适用规模

方式 适用场景 单文件上限 跨请求复用 推荐度
Base64 内联 一次性问答、文档 < 10 MB 32 MB(含整个请求体) 不能 临时验证
URL 引用 PDF 已托管在可公网访问的地址 32 MB 不能 一次性场景
Files API + file_id 同一份文档反复问答、多用户共享 500 MB 生产首选
Batch API 批量处理多份文档、可接受异步 256 MB 不能 离线分析

90% 的生产场景应该用 Files API :先上传一次拿 file_id,后续每次问答只引用 ID,省掉重复的网络传输和 base64 编码开销。下面分别给出最常用的两种姿势。

2.1 base64 内联(适合一次性问答)

python 复制代码
import anthropic
import base64

client = anthropic.Anthropic(
    api_key="sk-你的密钥",
    base_url="https://gw.claudeapi.com"
)

with open("contract.pdf", "rb") as f:
    pdf_data = base64.standard_b64encode(f.read()).decode("utf-8")

response = client.messages.create(
    model="claude-opus-4-7",
    max_tokens=4096,
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "document",
                    "source": {
                        "type": "base64",
                        "media_type": "application/pdf",
                        "data": pdf_data,
                    },
                },
                {
                    "type": "text",
                    "text": "这份合同的违约责任在第几条?逐条列出关键义务。",
                },
            ],
        }
    ],
)

print(response.content[0].text)

注意:base64 编码会让数据体积膨胀约 33%,所以原始 PDF 接近 24 MB 时就要小心 32 MB 请求体上限。

2.2 Files API 上传 + file_id 复用(生产首选)

第一步:上传文件,拿到 file_id

python 复制代码
import anthropic

client = anthropic.Anthropic(
    api_key="sk-你的密钥",
    base_url="https://gw.claudeapi.com",
    default_headers={"anthropic-beta": "files-api-2025-04-14"},
)

with open("contract.pdf", "rb") as f:
    uploaded = client.beta.files.upload(
        file=("contract.pdf", f, "application/pdf")
    )

print(uploaded.id)
# 形如 file_011CNha8iCJcU1wXNR6q4V8w

第二步:后续所有问答都引用这个 ID,不再传文件本身。

python 复制代码
response = client.messages.create(
    model="claude-opus-4-7",
    max_tokens=4096,
    extra_headers={"anthropic-beta": "files-api-2025-04-14"},
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "document",
                    "source": {
                        "type": "file",
                        "file_id": "file_011CNha8iCJcU1wXNR6q4V8w",
                    },
                },
                {
                    "type": "text",
                    "text": "付款节点有几个?分别是哪一条款?",
                },
            ],
        }
    ],
)

关键提醒 :Files API 仍在 beta 阶段,必须带上 anthropic-beta: files-api-2025-04-14 头部。文件持久保留直到调用 DELETE /v1/files/{file_id} 显式删除,不用了记得清理释放 100 GB 配额。

2.3 Node.js 版本

typescript 复制代码
import Anthropic, { toFile } from "@anthropic-ai/sdk";
import { createReadStream } from "node:fs";

const client = new Anthropic({
  apiKey: "sk-你的密钥",
  baseURL: "https://gw.claudeapi.com",
  defaultHeaders: { "anthropic-beta": "files-api-2025-04-14" },
});

const uploaded = await client.beta.files.upload({
  file: await toFile(createReadStream("contract.pdf"), "contract.pdf", {
    type: "application/pdf",
  }),
});

const response = await client.messages.create({
  model: "claude-opus-4-7",
  max_tokens: 4096,
  messages: [
    {
      role: "user",
      content: [
        { type: "document", source: { type: "file", file_id: uploaded.id } },
        { type: "text", text: "这份合同的违约责任在第几条?" },
      ],
    },
  ],
});

console.log(response.content[0].text);

2.4 cURL 验证

bash 复制代码
# 上传
curl https://gw.claudeapi.com/v1/files \
  -H "x-api-key: sk-你的密钥" \
  -H "anthropic-version: 2023-06-01" \
  -H "anthropic-beta: files-api-2025-04-14" \
  -F "file=@contract.pdf;type=application/pdf"

# 引用 file_id 提问
curl https://gw.claudeapi.com/v1/messages \
  -H "x-api-key: sk-你的密钥" \
  -H "anthropic-version: 2023-06-01" \
  -H "anthropic-beta: files-api-2025-04-14" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-opus-4-7",
    "max_tokens": 1024,
    "messages": [{
      "role": "user",
      "content": [
        {"type": "document", "source": {"type": "file", "file_id": "file_xxx"}},
        {"type": "text", "text": "概述本文档的核心条款"}
      ]
    }]
  }'

三、开启 citations:让答案带页码出处

合同问答最怕"模型说有就有"------读者无法核验。Claude 的 citations 功能强制让答案中的每个事实片段附带原文出处(页码 + 文本块),把"信我"变成"看原文"。

只需要在 document 块加一个 citations: {"enabled": true}:

python 复制代码
response = client.messages.create(
    model="claude-opus-4-7",
    max_tokens=4096,
    extra_headers={"anthropic-beta": "files-api-2025-04-14"},
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "document",
                    "source": {"type": "file", "file_id": "file_xxx"},
                    "citations": {"enabled": True},
                },
                {
                    "type": "text",
                    "text": "请逐条列出付款节点,并标注原文位置。",
                },
            ],
        }
    ],
)

for block in response.content:
    if block.type == "text":
        print(block.text)
        if block.citations:
            for c in block.citations:
                print(f"  ↳ 出处: 第 {c.start_page_number}-{c.end_page_number} 页")
                print(f"    原文片段: {c.cited_text[:80]}...")

输出会形如:

复制代码
首付款节点为合同签署后 7 个工作日内,金额为合同总价的 30%。
  ↳ 出处: 第 12-12 页
    原文片段: 甲方应于本合同签订之日起七(7)个工作日内,向乙方支付合同总金额的百分之三十...

业务侧的好处:前端可以直接把页码渲染成跳转链接,用户点击就翻到原文位置------这套体验在法律、金融、医疗文档问答里属于标配。


四、超过 100 页怎么办

100 页是模型硬上限,但实战中合同附件、年报、技术规范动辄两三百页。处理思路有三种,按优先级排序:

思路一:语义切分,而不是机械切 100 页

简单粗暴地按页切会把同一条款劈成两半,导致问答时上下文断裂。更好的做法是按章节、附件、目录边界切------先用一次便宜的调用让 Sonnet 输出"目录结构 + 每章起止页",再按章节切。代价是多一次调用,但单文档只切一次,后续都按 file_id 复用。

思路二:Batch API + 索引文件

把 PDF 切成多份(每份 ≤ 100 页),都通过 Files API 上传,维护一个 {section_name: file_id} 的本地索引。提问时先用一次轻量调用让 Haiku 4.5 判断"这个问题落在哪一章",再只调用对应章节的 file_id。这种"路由 + 精确召回"的方式比把整文档塞给模型省 70% 以上 token。

思路三:RAG 作为兜底

如果文档结构完全无规律(扫描件、混合文档),或者要做跨文档检索,还是要回到经典 RAG------把每页跑 embedding 入向量库,问答时召回 top-k 页再让 Claude 阅读。Files API 不替代 RAG,而是让"小规模文档问答"省掉建索引的成本。


五、Prompt Caching:把重复读取的成本砍到 1/10

如果同一份合同会被反复提问(法务团队场景),每次都把 PDF 内容塞进上下文会非常烧钱。开启 prompt caching 后,文档内容的 token 只在首次问答时全价计费,后续 5 分钟内的复用按 1/10 计价。

python 复制代码
response = client.messages.create(
    model="claude-opus-4-7",
    max_tokens=4096,
    extra_headers={"anthropic-beta": "files-api-2025-04-14"},
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "document",
                    "source": {"type": "file", "file_id": "file_xxx"},
                    "cache_control": {"type": "ephemeral"},
                },
                {
                    "type": "text",
                    "text": "违约责任条款在第几页?",
                },
            ],
        }
    ],
)

注意:cache_control 放在 document 块上,不是 text 上。cache 的最小命中粒度是 1024 token,普通 PDF 一页几百 token,放心整页缓存。


六、5 个高频踩坑

坑 1:把 PDF 里的扫描件当文字 PDF 处理

如果 PDF 是扫描件(纯图片,无文本层),Claude 仍能读懂(用视觉能力),但页面 token 消耗会更高,且对小字体、低分辨率扫描件准确率下降。生产环境建议先用 pdftotext -layout 试一下,确认文本层质量再决定是用 PDF 输入还是先 OCR 再喂文本。

坑 2:base64 编码后忘了算 33% 膨胀

24 MB 的 PDF 经过 base64 编码变成约 32 MB,正好顶到 Messages API 上限。文件超过 20 MB 就直接走 Files API,不要纠结。

坑 3:Files API 没带 beta header

复制代码
{"error": {"type": "invalid_request_error", "message": "..."}}

90% 的概率是漏了 anthropic-beta: files-api-2025-04-14 头部。Python SDK 用 default_headersextra_headers,Node.js 用 defaultHeaders

坑 4:同一个 PDF 反复上传导致存储爆炸

100 GB 配额看着大,但每个用户每次问答都上传一次的话很快就爆。生产环境应该用文件 hash(SHA-256)做去重,本地维护 {file_hash: file_id} 映射,同一份文件只上传一次。

坑 5:citations 启用但前端不渲染

模型已经返回了 citations,但接业务侧只取 text 字段,等于白花 token。citations 一旦启用就要在前端把"段落 ↔ 页码"的可视化做出来,否则关掉省钱。


七、模型选型建议

场景 推荐模型 理由
合同/法律/财报问答 Opus 4.7 长上下文推理强,容错低
普通报告/论文/手册问答 Sonnet 4.6 性价比最高,90% 场景够用
简单抽取(发票/表单) Haiku 4.5 极速且便宜,适合结构化抽取
跨章节路由判断 Haiku 4.5 第四节"思路二"里的路由调用

小结

PDF 问答的工程要点就三条:用 Files API 做持久化 (避免重复上传)、开启 citations (让答案可核验)、对超 100 页文档做语义切分(而不是机械切片)。剩下的 prompt caching、模型选型都是在这三个骨架上做优化。

国内访问 api.anthropic.com 直连会被防火长城拦截,代码示例里 base_url 指向的国内接入点见 claudeapi.com

相关推荐
CAE虚拟与现实4 小时前
重置系统后,Postgresql不用重装
数据库·redis·postgresql·kafka
彦为君4 小时前
JavaSE-03-集合框架(详细版)
java·开发语言·python
sakiko_4 小时前
Swift学习笔记31-网络请求
网络·笔记·学习·swift
丿小王同学4 小时前
快速集群安装mysql
数据库·mysql
老王谈企服4 小时前
2026企业数字化转型:从规则脚本到实在Agent智能体进化全解析
人工智能·ai
Dicky-_-zhang4 小时前
API接口签名验证实战
java·jvm
java1234_小锋4 小时前
Redis 支持哪些数据类型?请分别说明它们的使用场景
java·数据库·redis
一路往蓝-Anbo4 小时前
第六章:RTOS 任务 —— 任务逻辑与并发的 TDD 路径
网络·stm32·单片机·嵌入式硬件·tdd
半臻(火白)4 小时前
脑机接口的未来:技术突破、应用场景与伦理挑战
人工智能