用视觉嵌入 + Agent 自动生成 IDP 文档 Schema

2026-05-12 16 预计阅读时间:1 分钟
来源:aws.amazon.com AI 摘要 原文链接

免责声明:本文为 AI 摘要整理,建议结合原文阅读。摘要可能省略上下文、版本差异或边界条件,不作为官方说明。

预计阅读时间:12 分钟

面对一堆来源未知的文档——发票、合同、报关单、体检报告混在一起——传统 IDP 流程的第一步就卡住了:你得先人工识别文档类型,再逐个手写提取 Schema。文档种类多、格式杂,这一步往往比后续抽取本身更耗时。

本文介绍的多文档发现(Multi-Document Discovery)功能,把这个前置步骤自动化了:先用视觉嵌入把文档按类型聚类,再让 Agent 为每个簇生成可直接用于 IDP Accelerator 的 Schema。下面拆解它的运作方式,并给出可复用的实践代码。

从"未知文档堆"到"可处理 Schema"的全流程

整体分三个阶段:

  1. 文档摄入——把 PDF/图片丢进系统,不做任何人工标注。
  2. 视觉聚类——对每份文档提取视觉嵌入向量,用聚类算法自动分组,同一类型的发票、合同自然归到同一簇。
  3. Agent 生成 Schema——对每个簇采样几份代表文档,Agent 读取内容后输出结构化 Schema(字段名、类型、必填、示例值),直接对接 IDP Accelerator。

关键点在于:聚类靠的是文档的视觉特征而非文本内容,所以即使同一类型的文档排版差异大(不同供应商的发票模板),只要视觉结构相似就能归到一起。

视觉嵌入:为什么不用 OCR 先抽文本?

传统做法是先 OCR 再按文本特征聚类,但这一步有几个实际问题:

  • OCR 对低质量扫描件、手写体、表格边框的识别不稳定,文本本身就有噪声,聚类结果跟着抖。
  • 有些文档类型(比如带印章的合同 vs 无印章的协议)的区别更多在视觉布局而非文字内容。
  • OCR 是重计算步骤,在"只是想先分个类"的阶段跑全量 OCR 性价比低。

视觉嵌入的做法是:把文档页面当作图片,过一个视觉编码器(如基于 ViT 的模型),拿到一个固定维度的向量。这个向量编码了页面的布局、字体大小分布、图表/表格区域等视觉信号,两张视觉相似的页面在向量空间中距离就近。

下面是一个用 Hugging Face 视觉模型提取文档嵌入并做聚类的最小可运行示例:

# 依赖:pip install transformers torch scikit-learn pillow pdf2image
# 还需安装 poppler(pdf2image 的底层):brew install poppler  (macOS) 或 apt install poppler-utils (Linux)

import os
import numpy as np
from pdf2image import convert_from_path
from PIL import Image
from transformers import AutoModel, AutoProcessor
from sklearn.cluster import AgglomerativeClustering

# 1. 把 PDF 转成图片并提取视觉嵌入
model_name = "google/siglip-so400m-patch14-384"  # 视觉编码器,也可换用其他 ViT 模型
processor = AutoProcessor.from_pretrained(model_name)
model = AutoModel.from_pretrained(model_name)

def pdf_to_images(pdf_path: str, dpi: int = 200) -> list[Image.Image]:
    """将 PDF 每页转为 PIL Image,只取第一页做代表。"""
    images = convert_from_path(pdf_path, dpi=dpi, first_page=1, last_page=1)
    return images[0]

def get_visual_embedding(image: Image.Image) -> np.ndarray:
    """单页文档 -> 视觉嵌入向量。"""
    inputs = processor(images=image, return_tensors="pt")
    with torch.no_grad():
        embeddings = model.get_image_features(**inputs)
    # 归一化,方便后续用余弦距离聚类
    emb = embeddings.cpu().numpy().flatten()
    emb = emb / np.linalg.norm(emb)
    return emb

# 假设你的文档都在这个目录下
doc_dir = "./raw_docs"
pdf_files = [f for f in os.listdir(doc_dir) if f.lower().endswith(".pdf")]

embeddings = []
for pdf in pdf_files:
    img = pdf_to_images(os.path.join(doc_dir, pdf))
    emb = get_visual_embedding(img)
    embeddings.append(emb)

X = np.stack(embeddings)  # shape: (num_docs, emb_dim)

# 2. 聚类——不预设簇数,用距离阈值自动决定
clusterer = AgglomerativeClustering(
    metric="cosine",      # 视觉嵌入用余弦距离更合理
    linkage="average",
    distance_threshold=0.35,  # 阈值需根据你的文档集微调
    n_clusters=None       # 不固定簇数,由阈值决定
)
labels = clusterer.fit_predict(X)

# 3. 输出聚类结果
for cluster_id in set(labels):
    members = [pdf_files[i] for i in range(len(labels)) if labels[i] == cluster_id]
    print(f"簇 {cluster_id}{len(members)} 份文档): {members}")

运行前需要准备 ./raw_docs 目录并放入若干 PDF。distance_threshold 是最需要调的参数——阈值越小簇越多(分类更细),阈值越大簇越少(合并更激进)。建议先用 0.3–0.5 跑一遍,看输出是否把明显不同类型的文档分开了,再微调。

Agent 生成 Schema:从簇到可用的提取定义

聚类完成后,每个簇对应一种文档类型。下一步是为每类文档生成 Schema——字段名、数据类型、是否必填、取值示例。

这一步由 Agent 完成:它从簇中采样 2–3 份代表文档,读取其内容(此时才做 OCR/文本抽取),然后基于内容推理出合理的 Schema。Agent 的优势在于:

  • 能理解语义上下文,区分"金额"和"税额"这类相近字段。
  • 能根据文档内容推断字段类型(日期、数字、枚举)。
  • 能处理同一字段在不同文档中名称不一致的情况("发票号" vs "Invoice No")。

下面是用 OpenAI API 实现 Schema 生成 Agent 的示例,可直接改造接入其他 LLM:

# 依赖:pip install openai pytesseract pillow pdf2image
# 还需安装 Tesseract OCR:brew install tesseract (macOS) 或 apt install tesseract-ocr (Linux)

import json
import os
import pytesseract
from pdf2image import convert_from_path
from openai import OpenAI

client = OpenAI()  # 默认读取环境变量 OPENAI_API_KEY

def extract_text_from_pdf(pdf_path: str) -> str:
    """OCR 抽取 PDF 第一页文本,供 Agent 阅读。"""
    images = convert_from_path(pdf_path, dpi=200, first_page=1, last_page=1)
    text = pytesseract.image_to_string(images[0], lang="chi_sim+eng")
    return text

def generate_schema(sample_texts: list[str], cluster_label: str) -> dict:
    """让 Agent 根据采样文档文本生成提取 Schema。"""
    combined = "\n---\n".join(sample_texts)
    prompt = f"""你是一个文档处理专家。以下是同一类文档的 {len(sample_texts)} 份样本内容:

{combined}

请为这类文档生成一个 JSON Schema,用于后续结构化提取。要求:
1. 字段名用英文 snake_case,但每个字段加中文注释(description)。
2. 标明字段类型(string / number / date / enum 等)。
3. 标明是否必填(required)。
4. 如果能推断出枚举值,列在 enum 中。
5. 只输出 JSON,不要额外解释。

输出格式示例:
{
  "schema_name": "invoice",
  "fields": [
    {"name": "invoice_number", "type": "string", "required": true, "description": "发票号码"},
    {"name": "total_amount", "type": "number", "required": true, "description": "总金额"}
  ]
}"""

    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": prompt}],
        temperature=0.1  # 低温度保证输出稳定
    )
    return json.loads(response.choices[0].message.content)

# 假设上一步聚类结果已存为 dict: cluster_id -> [pdf_filenames]
cluster_map = {
    0: ["invoice_001.pdf", "invoice_002.pdf", "invoice_003.pdf"],
    1: ["contract_001.pdf", "contract_002.pdf"],
}

doc_dir = "./raw_docs"
schemas = {}

for cluster_id, members in cluster_map.items():
    # 每簇采样 2 份
    samples = members[:2]
    texts = [extract_text_from_pdf(os.path.join(doc_dir, f)) for f in samples]
    schema = generate_schema(texts, str(cluster_id))
    schemas[cluster_id] = schema
    print(f"簇 {cluster_id} Schema:")
    print(json.dumps(schema, indent=2, ensure_ascii=False))

生成的 Schema 可以直接喂给 IDP Accelerator 或任何文档抽取框架。如果你的 Accelerator 有特定 Schema 格式要求,只需调整 prompt 中的输出格式描述即可。

实际部署时的几个取舍

决策点 选项 建议
视觉编码器选型 通用 ViT vs 专用文档模型 文档场景优先试 LayoutLMv3 / DiT 等文档预训练模型,通用 ViT 是快速起步的备选
聚类算法 Agglomerative vs KMeans vs DBSCAN 文档数量未知、簇数不定时,Agglomerative + 距离阈值最灵活;已知类型数可用 KMeans
OCR 时机 聚类前全量 OCR vs 聚类后仅采样 OCR 先聚类后 OCR 算力省一半以上,除非你的文档文本特征比视觉特征区分度更高
Agent 模型 GPT-4o vs Claude vs 本地模型 Schema 生成对推理能力要求中等,GPT-4o 稳定性好;成本敏感可试 Claude Haiku 或 Qwen2.5-7B
聚类阈值 固定值 vs 自适应 先固定值跑基准,后续可按簇内平均距离自动调整(距离 = 簇内均值 / 簇间均值 < 0.5 算合理)

跑通后的检查清单

在把自动生成的 Schema 接入生产前,逐项确认:

  • 聚类质量:随机抽几个簇,人工看成员是否确实同类型。如果发票和收据混在一簇,调低距离阈值或换更细粒度的视觉模型。
  • Schema 完整性:拿 5 份未参与采样的文档,逐字段核对——有没有遗漏字段?有没有字段类型标错(数字标成字符串)?
  • 边界文档:单页 vs 多页、高清 vs 低清扫描件,是否被正确聚类?视觉嵌入对分辨率敏感,建议摄入前统一 DPI。
  • 增量更新:新文档类型出现时,它会自成新簇还是被错误归入已有簇?定期重跑聚类并对比簇数变化。

多文档发现功能本质上把 IDP 流程中最枯燥的"看文档、分类型、写 Schema"三步压缩成了一次自动化预处理。视觉聚类让你不必先 OCR 全量文档,Agent 让你不必手写每个字段定义。投入半小时调好聚类阈值和 Agent prompt,后续面对新文档集时就能一键跑通从分类到 Schema 的全流程。


相关推荐