用 Nova 2 Lite 和 Claude 搭一条低成本扫描件数字化流水线

2026-06-30 27 预计阅读时间: 1 分钟
来源: aws.amazon.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:10 分钟

扫描件数字化最容易被低估的地方,不是 OCR 本身,而是“页面上这些文字到底对应哪张脸、哪个表格、哪个区域”。这篇方案的核心变化是把任务拆给两个模型:Amazon Nova 2 Lite 先做原生多模态抽取,在一次调用里识别照片、可见姓名、坐标和页面级元数据;Claude Sonnet 4.6 再根据版面做空间推理,把名字和人脸配对。这样既控制成本,也把复杂推理留给更适合的模型。

为什么不是一个模型从头干到尾

如果把扫描年鉴页直接丢给一个强推理模型,让它完成检测、OCR、坐标抽取、关系判断,工程上当然省事,但规模化时成本会很快变硬约束。

两模型流水线的思路更像传统文档处理系统,只是把几个脆弱的规则模块换成了多模态模型:

  • Nova 2 Lite 负责“看见了什么”:照片框、姓名文本、坐标、页码、版式等结构化信息。
  • Claude Sonnet 4.6 负责“这些东西如何关联”:根据上下左右、网格、标题、姓名靠近关系,把姓名匹配到头像。
  • 中间结果用 JSON 传递,便于审计、重试、缓存和人工校验。

关键点在于,不要让第二个模型重新阅读整张扫描图。它需要的是 Nova 2 Lite 已经抽出来的结构化布局,而不是原始像素。这样 Claude 的输入更短、更稳定,推理成本也更可控。

中间 JSON 是整条流水线的合同

这类系统最怕“模型说得像对,但无法落库”。建议把 Nova 的输出约束成一个明确 schema,例如:

{
  "page": {
    "page_number": 12,
    "width": 2480,
    "height": 3508,
    "document_type": "yearbook_page"
  },
  "photos": [
    {"id": "photo_1", "bbox": [120, 320, 420, 720]},
    {"id": "photo_2", "bbox": [520, 320, 820, 720]}
  ],
  "names": [
    {"id": "name_1", "text": "Alice Chen", "bbox": [130, 735, 410, 780]},
    {"id": "name_2", "text": "Marcus Lee", "bbox": [535, 735, 805, 780]}
  ]
}

坐标最好统一成像素坐标,并明确 bbox 顺序,比如 [x1, y1, x2, y2]。如果页面在进入模型前做了缩放、裁剪或旋转,也要把这些转换记录在 metadata 里,否则后续人工复核和可视化会对不上。

Claude 这一步不需要“猜页面内容”,而是接收结构化对象并输出匹配结果:

{
  "matches": [
    {
      "photo_id": "photo_1",
      "name_id": "name_1",
      "name": "Alice Chen",
      "confidence": 0.92,
      "reason": "Name is directly below the photo in the same column."
    }
  ],
  "unmatched_photos": [],
  "unmatched_names": []
}

这个合同越清楚,系统越容易做批处理、质量抽样和回归测试。

可以这样实践:用 Bedrock 串起两次调用

下面示例展示一个最小 Python 流程:读取一张扫描页图片,先调用 Nova 2 Lite 抽取布局,再把布局 JSON 发给 Claude Sonnet 4.6 做空间匹配。

运行前需要修改三处:AWS_REGION、两个 modelId,以及本地图片路径。不同账号和区域可用的 Bedrock model ID 可能不同,请以你控制台里启用的模型为准。

python -m venv .venv
source .venv/bin/activate
pip install boto3
export AWS_REGION=us-east-1
python process_yearbook_page.py ./page-001.jpg

process_yearbook_page.py

import base64
import json
import os
import sys
from pathlib import Path

import boto3

REGION = os.getenv("AWS_REGION", "us-east-1")

# Replace these with the exact model IDs enabled in your Bedrock account/region.
NOVA_MODEL_ID = os.getenv("NOVA_MODEL_ID", "amazon.nova-2-lite-v1:0")
CLAUDE_MODEL_ID = os.getenv("CLAUDE_MODEL_ID", "anthropic.claude-sonnet-4-6-v1:0")

bedrock = boto3.client("bedrock-runtime", region_name=REGION)


def image_format(path: Path) -> str:
    suffix = path.suffix.lower()
    if suffix in {".jpg", ".jpeg"}:
        return "jpeg"
    if suffix == ".png":
        return "png"
    raise ValueError("Use a .jpg, .jpeg, or .png scan for this example")


def extract_layout_with_nova(image_path: Path) -> dict:
    prompt = """
You are digitizing a scanned yearbook page.
Return strict JSON only with this shape:
{
  "page": {"width": number, "height": number, "document_type": string},
  "photos": [{"id": string, "bbox": [x1, y1, x2, y2]}],
  "names": [{"id": string, "text": string, "bbox": [x1, y1, x2, y2]}],
  "notes": [string]
}
Detect portrait photos and visible person names. Use pixel coordinates if available.
""".strip()

    image_bytes = image_path.read_bytes()
    response = bedrock.converse(
        modelId=NOVA_MODEL_ID,
        messages=[
            {
                "role": "user",
                "content": [
                    {"text": prompt},
                    {
                        "image": {
                            "format": image_format(image_path),
                            "source": {"bytes": image_bytes},
                        }
                    },
                ],
            }
        ],
        inferenceConfig={"temperature": 0, "maxTokens": 3000},
    )
    text = response["output"]["message"]["content"][0]["text"]
    return json.loads(text)


def match_names_with_claude(layout: dict) -> dict:
    prompt = f"""
You match names to portrait photos using page layout.
Input JSON contains photo boxes and name boxes in [x1, y1, x2, y2] order.
Return strict JSON only:
{{
  "matches": [
    {{"photo_id": string, "name_id": string, "name": string, "confidence": number, "reason": string}}
  ],
  "unmatched_photos": [string],
  "unmatched_names": [string]
}}
Prefer direct vertical or grid-column relationships. Mark ambiguous cases with lower confidence.

Layout JSON:
{json.dumps(layout, ensure_ascii=False)}
""".strip()

    response = bedrock.converse(
        modelId=CLAUDE_MODEL_ID,
        messages=[{"role": "user", "content": [{"text": prompt}]}],
        inferenceConfig={"temperature": 0, "maxTokens": 3000},
    )
    text = response["output"]["message"]["content"][0]["text"]
    return json.loads(text)


def main() -> None:
    if len(sys.argv) != 2:
        raise SystemExit("Usage: python process_yearbook_page.py ./page-001.jpg")

    image_path = Path(sys.argv[1])
    layout = extract_layout_with_nova(image_path)
    matches = match_names_with_claude(layout)

    print(json.dumps({"layout": layout, "matches": matches}, indent=2, ensure_ascii=False))


if __name__ == "__main__":
    main()

这个示例刻意把两步分开。生产环境里你应该把 layout 存下来,例如写入 S3 或数据库。这样 Claude 匹配提示词调整后,可以只重跑第二步,不必重新做图像抽取。

批处理时要把成本控制点显式化

这类文档处理系统一旦进入“几万页、几十万页”,成本和质量问题会同时出现。建议把以下控制点做成配置,而不是散落在代码里:

pipeline:
  input_bucket: scanned-yearbook-pages
  output_bucket: digitized-yearbook-results
  nova_extraction:
    model_id: amazon.nova-2-lite-v1:0
    temperature: 0
    max_tokens: 3000
    cache_layout_json: true
  claude_matching:
    model_id: anthropic.claude-sonnet-4-6-v1:0
    temperature: 0
    max_tokens: 3000
    only_send_layout_json: true
  quality_gate:
    min_confidence: 0.80
    send_ambiguous_to_review: true
    sample_rate_for_manual_audit: 0.02

配置里的 only_send_layout_json 是一个很重要的工程约束:第二步只处理结构化布局,避免把大图重复发给强推理模型。min_confidence 则决定哪些结果进入人工复核队列。年鉴页、证件页、档案页这类材料经常有老照片、歪斜扫描、遮挡、重名和跨栏排版,完全自动化并不总是值得追求。

落地建议:从可观测的小闭环开始

这套 Nova 2 Lite + Claude 的组合适合“先抽取、再推理”的文档场景,尤其是页面里存在空间关系的问题:头像和姓名、表格单元和字段、图片说明和图片对象。

上线前可以按这个清单推进:

  • 固定中间 JSON schema,并对模型输出做 JSON 解析失败重试。
  • 保存原图、Nova 输出、Claude 输出和最终人工修正结果。
  • 对低置信度、重叠 bbox、未匹配姓名建立复核队列。
  • 用一小批标注页做回归集,每次改 prompt 或 modelId 都跑一遍。
  • 不要只看平均准确率,重点看“错误是否可发现、可修正、可回放”。

边界也要说清楚:如果扫描质量极差、姓名离头像很远、页面设计高度不规则,空间推理仍然会出错。把便宜模型用于大规模视觉抽取,把强模型用于明确的布局推理,是一个务实的成本优化方向,但它不是免人工校验的魔法按钮。


相关推荐