mcpField
D
document-workflows

document-workflows

github.com/andrewyng文档处理14.0k49 次安装21 周前更新
代码仓库

关于 document-workflows

这是一组将 LandingAI ADE 操作组合成文档处理工作流的模式,面向构建批处理、分类、数据库和 RAG 流程的开发者,并强调先检查样本文档及验证视觉输出。

主要功能

  1. 01处理前检查文档
  2. 02批量解析与提取模式
  3. 03先分类再提取工作流
  4. 04RAG 与数据库处理流程
  5. 05章节提取与表格拼接
  6. 06视觉裁剪结果核验

适用场景

  1. 01构建批量文档处理流程
  2. 02将混合文档路由至提取模式
  3. 03准备 RAG 文档分块
  4. 04将提取结果导出到数据库

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/andrewyng/context-hub --skill document-workflows

安装后在 Claude Code 中输入/document-workflows确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
batch-processing.md
database-integration.md
rag-pipelines.md
schema-catalog.md
table-stitching.md
visualization.md

详细介绍

文档工作流 — ADE 流水线模式

概述

此技能提供了可复用的构建模块,用于将 LandingAI ADE 基础功能(解析、提取、拆分)组合成可用于生产环境的文档处理流水线。它是对 document-extraction 技能的补充:

关注点document-extractiondocument-workflows
范围ADE SDK API:解析、提取、拆分、依据定位端到端工作流:批处理、RAG、数据库、分类路由
适用场景需要调用单个 ADE 操作需要将多个操作组合成工作流
代码带参数的 SDK 方法调用包含错误处理和并行处理的完整函数
依赖项仅 landingai-ade+ 工作流专用库(pandas、chromadb 等)

理念: 按工作流模式(批处理、RAG、数据库插入)组织,而不是按文档类型组织。无论文档是发票、公用事业账单还是医疗表单,都适用同一种模式。


步骤 0(必做)— 预检文档探索 {#pre-flight}

**在编写任何流水线代码前,**只要当前会话中尚未检查过文档的内部结构,就先执行此步骤。

规则:在先对样本文档运行工具 2(诊断解析)之前,绝不编写章节检测、标题匹配或文本搜索代码。 标题格式因文档而异,无法仅根据任务描述或文档类型推断;要确定标题格式,唯一可靠的方法就是查看实际的 ADE 输出。

常见意外情况:论文中的“Introduction”标题可能显示为 1. Introduction(纯文本,没有 #)、## Introduction、INTRODUCTION(全大写),或嵌入正文文本块中。如果判断有误,就会无声地匹配失败(匹配到 0 个文本块),而排查问题需要重新完整解析。

编写任何代码之前,先对 1–3 份有代表性的样本文档运行工具 1(视觉渲染)和工具 2(诊断解析)。这不到一分钟,就能避免原本通过预检即可避免的调试迭代。

工具 1 — 页面视觉渲染

将 1–2 页渲染为 PNG,并将其作为视觉上下文阅读。不会消耗 ADE 额度,但每张 PNG 都会占用上下文 token。布局不明确或文档来源不明(手写?扫描件?表单?)时使用。

.venv/bin/python - << 'EOF'
import pymupdf
from pathlib import Path
from PIL import Image

pdf = Path('path/to/sample.pdf')
out_dir = Path('/tmp/ade_preflight'); out_dir.mkdir(exist_ok=True)
doc = pymupdf.open(pdf)
for pg in range(min(2, len(doc))):   # first 2 pages only
    pix = doc[pg].get_pixmap(matrix=pymupdf.Matrix(1.5, 1.5))   # 108 DPI
    img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
    out = out_dir / f"{pdf.stem}_page{pg + 1}.png"
    img.save(out)
    print(out)
doc.close()
EOF

然后阅读保存的 PNG。可立即解答以下问题:

  • 标题是否为粗体文本(→ ADE 可能输出纯文本标题,而非 # Heading)
  • 文档是手写的还是扫描件?→ 需要 Tesseract OCR,而不是 PyMuPDF
  • 是单栏还是双栏布局?
  • 是否存在噪声:页眉、页码、水印、印章?
工具 2 — ADE 诊断解析

解析 1 份样本并打印 Markdown 结构和文本块清单。此操作会消耗 ADE 额度,最多只解析 1–3 份样本,绝不要解析整个语料库。

.venv/bin/python - << 'EOF'
import os
from pathlib import Path
from collections import Counter
from dotenv import load_dotenv

# Load API key: prefer existing env var, then .env file lookup
load_dotenv()  # Load API key from .env. Add a path to the .env if needed.

from landingai_ade import LandingAIADE
client = LandingAIADE()
pr = client.parse(document=Path('path/to/sample.pdf'))

print("=== MARKDOWN (first 80 lines) ===")
for i, ln in enumerate(pr.markdown.splitlines()[:80], 1):
    print(f"{i:3}: {ln}")

print("\n=== CHUNKS ===")
for ch in pr.chunks:
    txt = (ch.markdown or '').replace('\n', ' ')[:70]
    b = ch.grounding.box
    print(f"p{ch.grounding.page} {ch.type:12} "
          f"l={b.left:.2f} t={b.top:.2f} r={b.right:.2f} b={b.bottom:.2f} | {txt}")

print(f"\nPages: {pr.metadata.page_count}  "
      f"Chunks: {len(pr.chunks)}  "
      f"Types: {dict(Counter(ch.type for ch in pr.chunks))}")
EOF

**费用说明:**首次运行后,用 pr.model_dump() 将解析结果保存为 JSON 文件。后续开发时加载该文件,而不要再次调用 client.parse()。只有文档集发生变化时才重新解析。

需要检查的内容
观察结果影响
标题为 1. Introduction(纯文本,没有 #)ADE Markdown 不会使用 ATX 标题 → 使用 ADE 提取,而不是正则表达式
不同文档的标题格式不一致(一份中是 # INTRO,另一份中是 1. Intro)正则表达式会在某些文档中失效 → 使用 ADE 提取以提高稳健性
每个 ch.markdown 都以 <a id='...'></a> 开头字符串匹配或显示前先移除锚点
双栏:同一页上的文本块分别有 l=0.07 和 l=0.50文本顺序是先左栏后右栏;章节可能横跨两栏
文本块在分页处被截断,单词只剩一半章节跨页;需要收集多个页面上的文本块
marginalia 文本块位于 t<0.08 或 t>0.90页眉 / 页码 → 从内容提取中排除
页面图像中可见扫描或手写内容PyMuPDF 文本提取无法奏效 → 使用 Tesseract OCR
工具 3 — 裁剪后视觉验证(边界框工作流必需) {#post-crop-verification}

生成任何边界框裁剪图或叠加图(提取图形、裁剪分块、提取表格单元格、单词级定位)后,至少将一张输出 PNG 作为图像读回,并描述你看到的内容。将描述与用户的请求进行比较。这有助于发现:

  • 页面错误 — ADE 页码从 0 开始;索引偏差 1 会使裁剪落在相邻页面上,而该页内容可能完全不同
  • 区域错误 — 坐标系不匹配,导致裁剪出空白区域或无关部分

规则:在目视读取至少一张输出 PNG 并确认其内容与用户请求相符之前,绝不要宣布裁剪工作流已完成。

验证步骤
  1. 将第一张裁剪图保存为 PNG(工作流本身已完成此操作)
  2. 将 PNG 文件作为图像读取(对 PNG 路径使用 read_file 工具)
  3. 描述你看到的内容:出现了什么内容、表格、图形或文本?
  4. 与用户的请求进行比较:
    • 用户要求“Events 表格” → 裁剪图是否显示了 Events 表格?
    • 用户要求“Figure 3” → 裁剪图是否显示了图表/示意图?
    • 用户要求“Introduction 部分” → 裁剪图是否显示了引言文本?
  5. 如果描述不符 → 在继续之前检查页面索引和边界框坐标
  6. 验证第一张裁剪图后,再继续处理其余裁剪图
为什么使用 LLM 视觉能力,而不是启发式方法

空白检查启发式方法(例如“平均亮度 > 250 → 空白”)只能捕捉最明显的问题。Agent 自身的视觉能力可以进行语义验证:“这张裁剪图显示的是柱状图”,而“用户要求的是数据表格”。即使裁剪图包含了错误部分中的有效内容,这也能发现页面错误。


快速参考 — 构建模块

#模块模式参考
0前置检查(必需)构建前渲染页面 + 诊断解析上文
1解析 + 保存单个文档 → JSON + Markdown下文
2解析 + 提取 + 保存单个文档 → 结构化数据下文
3批处理(同步)ThreadPoolExecutor + tqdmbatch-processing.md
4批处理(异步)AsyncLandingAIADE + aiolimiterbatch-processing.md
5大文件解析任务 API(异步轮询)batch-processing.md
6分类 → 提取枚举分类 + 架构路由下文
7结果 → DataFrame将嵌套提取结果展平为表格database-integration.md
8结果 → CSV摘要 + 按文档导出database-integration.md
9结果 → Snowflake4 张规范化表 + COPY 上传database-integration.md
10分块 → RAG CSV19 列分块数据集rag-pipelines.md
11分块 → ChromaDBOpenAI 嵌入 + 持久化存储rag-pipelines.md
12分块 → FAISSLangChain Documents + FAISS 索引rag-pipelines.md
13RAG 查询带来源的 RetrievalQA 链rag-pipelines.md
14分块图像将页面中的分块裁剪为 PNGvisualization.md
15定位叠加层在页面上显示彩色边界框visualization.md
16单词级定位OCR + 模糊匹配高亮visualization.md
17章节提取从 Markdown 中提取命名章节(正则表达式或 ADE 提取)下文
18嵌入计算本地(FastEmbed)或 API(OpenAI),包含最佳实践rag-pipelines.md
19层次分块将 ADE 分块归组为语义单元以生成嵌入rag-pipelines.md
20多粒度 RAG分块、层次或文档级策略rag-pipelines.md
21表格拼接仅解析或解析 + 提取,合并跨页表格table-stitching.md
—架构目录可直接使用的 Pydantic 模型schema-catalog.md

核心工作流:解析 + 提取 + 保存

ADE 的基本两步模式。其他所有工作流都基于此构建。

import io
from pathlib import Path
from typing import Any, Tuple, Type

from landingai_ade import LandingAIADE
from landingai_ade.lib import pydantic_to_json_schema


def parse_extract_save(
    doc_path: Path,
    client: LandingAIADE,
    schema_cls: Type[Any],
    output_dir: str = "./ade_results",
) -> Tuple[Any, Any]:
    """Parse a document, extract structured data, save both
    as JSON via save_to. Returns (parse_result, extract_result)."""
    # Step 1 — Parse (auto-saves {stem}_parse_output.json)
    parse_result = client.parse(
        document=doc_path, save_to=output_dir,
    )

    # Step 2 — Extract (auto-saves {stem}_extract_output.json)
    extract_result = client.extract(
        schema=pydantic_to_json_schema(schema_cls),
        markdown=io.BytesIO(
            parse_result.markdown.encode("utf-8")
        ),
        save_to=output_dir,
    )
    return parse_result, extract_result

save_to 参数: 可用于 parse()、extract() 和 split()。 如果文件夹不存在则创建,并写入 {input_filename}_{method}_output.json。 这是客户端侧的便捷功能 — API 调用后会将完整响应保存在本地。

仅解析(不提取)
def parse_and_save(
    doc_path: Path,
    client: LandingAIADE,
    output_dir: str = "./ade_results",
) -> Any:
    return client.parse(
        document=doc_path, save_to=output_dir,
    )

架构: 请参阅 schema-catalog.md,获取 可直接使用的 Pydantic 模型(发票、水电费账单、银行对账单、 工资单、食品标签、CME 证书、文档分类器)。 有关架构设计规则,请参阅 document-extraction 技能。


先分类再提取

先分类,再应用相应架构,以处理混合类型的文档。有两种方法:

方法 1:分类提取(适用于任意文档组合)
from typing import Literal
from pydantic import BaseModel, Field


class DocType(BaseModel):
    type: Literal[
        "invoice", "bank_statement", "pay_stub",
        "utility_bill",
    ] = Field(description="The type of the document.")


# Map types to schemas (from schema-catalog.md)
SCHEMA_MAP: dict[str, type] = {
    "invoice": InvoiceSchema,
    "bank_statement": BankStatementSchema,
    "pay_stub": PayStubSchema,
    "utility_bill": UtilityBillSchema,
}


def classify_and_extract(
    doc_path: Path,
    client: LandingAIADE,
) -> dict:
    """Classify a document then extract with the matching
    schema."""
    pr = client.parse(document=doc_path)

    # Classify using first page
    cls = client.extract(
        schema=pydantic_to_json_schema(DocType),
        markdown=pr.markdown,
    )
    doc_type: str = cls.extraction["type"]

    # Extract with type-specific schema
    schema_cls = SCHEMA_MAP[doc_type]
    er = client.extract(
        schema=pydantic_to_json_schema(schema_cls),
        markdown=pr.markdown,
    )
    return {
        "type": doc_type,
        "extraction": er.extraction,
        "parse_result": pr,
        "extract_result": er,
    }
方法 2:Split API(多文档 PDF)

当单个 PDF 中包含多种文档类型(例如,包含发票和收据的文件包)时,请先使用 Split API:

def split_classify_extract(
    pdf_path: Path,
    client: LandingAIADE,
    split_classes: list[dict],
) -> list[dict]:
    """Split a multi-doc PDF, classify each split, extract."""
    pr = client.parse(document=pdf_path, split="page")

    # Split into sub-documents
    split_result = client.split(
        markdown=pr.markdown,
        split_class=split_classes,
    )

    results = []
    for split_doc in split_result.splits:
        # Classify
        cls = client.extract(
            schema=pydantic_to_json_schema(DocType),
            markdown=split_doc.markdowns[0],
        )
        doc_type = cls.extraction["type"]

        # Extract
        schema_cls = SCHEMA_MAP[doc_type]
        er = client.extract(
            schema=pydantic_to_json_schema(schema_cls),
            markdown=split_doc.markdowns[0],
        )
        results.append({
            "type": doc_type,
            "extraction": er.extraction,
            "pages": split_doc.pages,
        })
    return results

Split API 参数: 使用 split_class(包含 name、description、identifier 键的字典列表)。 完整的 Split API 参考请参阅 document-extraction 技能。

何时使用 Split API,何时使用分类:

  • Split API:一个 PDF 包含多个独立文档
  • 分类提取:每个文件都是一个文档,但文档类型各不相同

章节提取

从已解析文档的 Markdown 中提取指定章节(例如“引言”“摘要”)。有两种方法——根据文档的多样性以及额外的 API 成本是否值得来选择。

**决策:**如果诊断性解析(工具 2)显示所有文档都使用一致的 ATX 标题(## Introduction、## 2. Methods),请使用方法 A。如果发现任何纯文本编号标题(1. Introduction)或文档间格式有所不同,请完全跳过方法 A,直接使用方法 B。

方法适用场景
A — 正则表达式结构统一、格式规范的文档(学术论文、报告)。免费、快速。
B — ADE 提取格式混杂或难以预测的文档(幻灯片、扫描件、模板各异的文档)。每份文档需额外消耗一次提取额度。
方法 A — 基于规则的正则表达式(免费、快速、脆弱)

即使在同一文档中,ADE 也可能将标题输出为 ATX Markdown(## 2. Related Work)或纯文本(1. Introduction)。请同时处理这两种模式:

import re

def find_section(markdown: str, name: str) -> str | None:
    """Extract a named section from ADE markdown, handling both ATX
    headers (## Introduction) and plain-text numbered headings
    (1. Introduction) which ADE may emit inconsistently."""

    # Pattern 1: ATX header  (# Introduction, ## 1. Introduction …)
    m = re.search(
        r"^(#{1,6})\s+(?:\d+\.?\s+)?" + re.escape(name) + r"\b.*$",
        markdown, re.IGNORECASE | re.MULTILINE,
    )
    if m:
        level = len(m.group(1))
        end = re.search(r"^#{1," + str(level) + r"}\s",
                        markdown[m.end():], re.MULTILINE)
        end_pos = m.end() + (end.start() if end else len(markdown[m.end():]))
        return markdown[m.start():m.end() + end_pos].strip()

    # Pattern 2: plain-text numbered heading  (1. Introduction)
    m2 = re.search(r"^(?:\d+\.?\s+)?" + re.escape(name) + r"\s*$",
                   markdown, re.IGNORECASE | re.MULTILINE)
    if m2:
        end2 = re.search(
            r"^#{1,6}\s|^(?:\d+\.?\s+)[A-Z][a-zA-Z ]{3,}\s*$",
            markdown[m2.end():], re.MULTILINE,
        )
        end_pos = m2.end() + (end2.start() if end2 else len(markdown[m2.end():]))
        return markdown[m2.start():end_pos].strip()
    return None
方法 B — ADE 提取(稳健,可处理多样的文档)

使用 ADE 自带的提取功能,通过语义定位章节——无需正则表达式。 即使格式不一致,LLM 也能理解章节含义:

from pydantic import BaseModel, Field
from landingai_ade import LandingAIADE
from landingai_ade.lib import pydantic_to_json_schema
from pathlib import Path


class PaperSections(BaseModel):
    abstract: str = Field(
        description="The abstract section, plain text only, "
                    "no markdown formatting or anchor tags."
    )
    introduction: str = Field(
        description="The introduction section, plain text only, "
                    "no markdown formatting or anchor tags."
    )


client = LandingAIADE()
pr = client.parse(document=Path("paper.pdf"))
er = client.extract(
    schema=pydantic_to_json_schema(PaperSections),
    markdown=pr.markdown,
)
intro_text = er.extraction["introduction"]

**成本说明:**每次 extract() 调用都会在 parse() 的基础上额外消耗额度。对于文档类型统一的高吞吐量管道,方法 A 可以避免这项成本。对于多样或难以预测的文档,准确性的提升值得额外消耗额度。


多页表格拼接 {#table-stitching}

当表格跨越多页时,ADE 可能会将其按页输出为多个独立的表格块——也可能将某些页面输出为纯文本,而不是表格块。这种不一致可能出现在任何页面,而不仅仅是最后一页。

有三种方法可以处理这种情况,各自在成本、准确性和脆弱性方面有所取舍:

方法ADE 调用次数可处理非表格块脆弱性
A — Parse + Extract2✓ LLM 读取完整 Markdown低——无需自定义解析
B — HTML 表格解析1✓ 可使用备用正则表达式高——要求行结构一致
C — pandas read_html1✗ 会漏掉非表格块中

选择指南:

  • 当准确性至关重要、成本次之时,使用方法 A
  • 当行结构高度统一、文档结构可预测,且节省成本值得承担基于正则表达式解析的脆弱性时,使用方法 B
  • 对于快速原型开发,或可以接受漏掉部分行的情况,使用方法 C
表格拼接的预检补充项

选择方法之前,运行诊断性解析(工具 2)并检查:

检查项方法原因
每页的块类型按页统计 type == "table" 与 "text" 的数量任何页面的块类型都可能不一致
列数一致性比较各表格块的列数列数不一致可能表示这些表格并非同一张表
是否存在表头行检查每个表格块的首行检测表格和筛选行时需要
非目标表格查找列数相同的汇总表或元数据表必须与目标表格区分开
行结构一致性比较各页的行结构一致性低会使方法 B 更脆弱
领域专用语义检查

拼接后,添加利用领域知识的验证检查:

  • **金融:**余额递增,列合计 = 各行之和
  • **库存:**各行之间的数量守恒
  • **时间序列:**按时间顺序排列,序列无间隔
  • **科学:**单位一致,ID 单调递增

这些检查既可用于验证(确认正确性),也可用于消歧(解决解析输出中的结构歧义)。

所有三种方法的完整代码及可复用模式: 请参阅 table-stitching.md。


批量处理

根据规模可选择两种模式。两者都包含逐文档错误处理。

快速:ThreadPoolExecutor(同步)
from concurrent.futures import ThreadPoolExecutor, as_completed
from tqdm import tqdm


def batch_process(
    files: list[Path],
    schema_cls: type,
    max_workers: int = 4,
) -> list[tuple[Path, Any, Any]]:
    client = LandingAIADE()
    results: list[tuple[Path, Any, Any]] = []
    with ThreadPoolExecutor(max_workers=max_workers) as pool:
        futures = {
            pool.submit(
                parse_extract_save, fp, client, schema_cls
            ): fp
            for fp in files
        }
        for fut in tqdm(
            as_completed(futures), total=len(futures)
        ):
            fp = futures[fut]
            try:
                results.append((fp, *fut.result()))
            except Exception as e:
                print(f"FAILED {fp.name}: {e}")
    return results
可扩展:AsyncLandingAIADE(异步)
import asyncio
from aiolimiter import AsyncLimiter
from landingai_ade import AsyncLandingAIADE


async def batch_parse_async(
    files: list[Path],
    rate_limit: int = 30,
) -> list[dict]:
    client = AsyncLandingAIADE()
    limiter = AsyncLimiter(rate_limit, 60)

    async def _process(fp: Path) -> dict | None:
        try:
            async with limiter:
                return {
                    "path": fp,
                    "result": await client.parse(document=fp),
                }
        except Exception as e:
            print(f"FAILED {fp.name}: {e}")
            return None

    raw = await asyncio.gather(*[_process(fp) for fp in files])
    return [r for r in raw if r]

完整代码,包含输出目录组织、CSV 导出和分块图像保存: 请参阅 batch-processing.md。


将结果转换为 DataFrame 和 CSV

将嵌套的 ADE 提取结果展平为 4 个规范化表:

import uuid
from datetime import datetime, timezone


def rows_from_doc(
    file_path: str,
    parse_result: Any,
    extract_result: Any,
    run_id: str = "",
) -> tuple[dict, list[dict], list[dict], dict]:
    """Returns (main_row, line_rows, chunk_rows, md_record).

    - main_row: flattened top-level fields (nested__field)
    - line_rows: one per list item (line items, transactions)
    - chunk_rows: one per parsed chunk with bounding boxes
    - md_record: full markdown for traceability
    """
    doc_uuid = str(uuid.uuid4())
    f = extract_result.extraction

    # Flatten top-level fields
    main_row = {"doc_uuid": doc_uuid, "document_name": Path(file_path).name}
    for k, v in f.items():
        if isinstance(v, dict):
            for sk, sv in v.items():
                main_row[f"{k}__{sk}"] = sv
        elif not isinstance(v, list):
            main_row[k] = v

    # Extract list fields as line rows
    line_rows = [
        {"doc_uuid": doc_uuid, "list_field": k, "line_index": i, **item}
        for k, v in f.items() if isinstance(v, list)
        for i, item in enumerate(v) if isinstance(item, dict)
    ]

    # Chunk rows from parse result
    chunk_rows = [
        {
            "doc_uuid": doc_uuid,
            "chunk_id": getattr(ch, "id", None),
            "chunk_type": getattr(ch, "type", None),
            "page": ch.grounding.page if hasattr(ch, "grounding") else None,
        }
        for ch in (parse_result.chunks or [])
    ]

    md_record = {
        "doc_uuid": doc_uuid,
        "markdown": parse_result.markdown,
    }
    return main_row, line_rows, chunk_rows, md_record

完整代码,包含 Snowflake 上传、UUID 可追溯性和边界框列: 请参阅 database-integration.md。


RAG 准备

将解析后的文档快速构建为可查询的 RAG 系统。提供两种嵌入选项:本地(免费、离线)或 API(质量更高)。

选项 A — 使用 FastEmbed 进行本地嵌入(免费)
import re
from fastembed import TextEmbedding


def ade_to_embeddings_local(
    parse_results: list[dict],
    model: str = "BAAI/bge-small-en-v1.5",
) -> list[dict]:
    """Embed ADE chunks locally. Returns list of dicts with
    text, vector, and grounding metadata."""
    embedder = TextEmbedding(model_name=model)
    items: list[dict] = []
    for pr in parse_results:
        for ch in (pr["parse_result"].chunks or []):
            text = re.sub(
                r"<a id='[^']*'>\s*</a>", "", ch.markdown,
            ).strip()
            if not text:
                continue
            items.append({
                "text": text,
                "source": pr["name"],
                "page": ch.grounding.page,
                "box": {
                    "l": ch.grounding.box.left,
                    "t": ch.grounding.box.top,
                    "r": ch.grounding.box.right,
                    "b": ch.grounding.box.bottom,
                },
            })
    vecs = list(embedder.embed([i["text"] for i in items]))
    for item, vec in zip(items, vecs):
        item["vector"] = vec.tolist()
    return items
选项 B — 使用 OpenAI 进行 API 嵌入
from langchain.docstore.document import Document
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings


def ade_to_rag(
    parse_results: list[dict],
    embedding_model: str = "text-embedding-3-small",
) -> FAISS:
    """Convert ADE parse results to a FAISS vector store.

    Args:
        parse_results: list of {"name": str, "parse_result": ParseResponse}
    """
    docs = [
        Document(
            page_content=ch.markdown,
            metadata={
                "source": item["name"],
                "chunk_type": getattr(ch, "type", ""),
                "page": ch.grounding.page if hasattr(ch, "grounding") else -1,
            },
        )
        for item in parse_results
        for ch in (item["parse_result"].chunks or [])
        if ch.markdown.strip()
    ]
    return FAISS.from_documents(
        docs, OpenAIEmbeddings(model=embedding_model)
    )

完整代码,包含嵌入最佳实践、分层分块、多粒度策略、ChromaDB、LangChain RetrievalQA 和 CSV 导出: 请参阅 rag-pipelines.md。

rag-pipelines.md 中的高级 RAG 模式:

  • 嵌入计算(区块 18–19)——在本地嵌入(FastEmbed,免费)和 API 嵌入(OpenAI,质量更高)之间进行选择,包括批次大小和速率限制
  • 分层分块(区块 20)——在多个粒度级别(分块、章节、文档)进行嵌入,以支持混合检索
  • 多粒度 RAG(区块 21)——结合分块级精度和文档级上下文,根据查询范围将其路由到合适的嵌入级别

可视化

用于在解析后的页面上叠加边界框的快速代码片段:

from PIL import Image, ImageDraw
import pymupdf

CHUNK_COLORS = {
    "text": (40, 167, 69),
    "table": (0, 123, 255),
    "figure": (255, 0, 255),
    "marginalia": (111, 66, 193),
}

def annotate_page(
    img: Image.Image, chunks: list, page: int,
) -> Image.Image:
    annotated = img.copy()
    draw = ImageDraw.Draw(annotated)
    w, h = img.size
    for ch in chunks:
        if not hasattr(ch, "grounding") or ch.grounding.page != page:
            continue
        box = ch.grounding.box
        color = CHUNK_COLORS.get(getattr(ch, "type", ""), (200, 200, 200))
        draw.rectangle(
            [int(box.left * w), int(box.top * h),
             int(box.right * w), int(box.bottom * h)],
            outline=color, width=3,
        )
    return annotated

完整代码,包含分块图像裁剪、仅提取内容的叠加层和词级 OCR 对齐: 请参阅 visualization.md。


Streamlit UI 模式

用于交互式文档处理的快速 Streamlit 应用:

import streamlit as st
from pathlib import Path
from landingai_ade import LandingAIADE
from landingai_ade.lib import pydantic_to_json_schema

st.title("Document Processor")

uploaded = st.file_uploader(
    "Upload document", type=["pdf", "png", "jpg"]
)
if uploaded:
    # Save temp file
    tmp = Path(f"/tmp/{uploaded.name}")
    tmp.write_bytes(uploaded.read())

    client = LandingAIADE()

    with st.spinner("Parsing..."):
        pr = client.parse(document=tmp)

    st.subheader("Markdown Preview")
    st.markdown(pr.markdown[:2000])

    st.subheader("Chunks")
    for ch in pr.chunks:
        with st.expander(
            f"{ch.type} (page {ch.grounding.page})"
        ):
            st.text(ch.markdown[:500])

完整的 Streamlit 应用,包含批量上传、提取结果显示和可视化选项卡:根据 batch-processing.md 和 visualization.md 中的模式进行调整。


依赖项指南

工作流安装
核心(解析 + 提取)pip install landingai-ade
批量同步pip install landingai-ade tqdm
批量异步pip install landingai-ade aiolimiter
DataFrame / CSVpip install landingai-ade pandas
Snowflakepip install landingai-ade pandas snowflake-connector-python[pandas]
RAG(本地嵌入)pip install landingai-ade fastembed
RAG(ChromaDB)pip install landingai-ade chromadb openai
RAG(FAISS + LangChain)pip install landingai-ade langchain langchain-openai langchain-community faiss-cpu
可视化pip install landingai-ade Pillow pymupdf
词级对齐pip install landingai-ade Pillow pymupdf pytesseract fuzzywuzzy + tesseract 二进制文件
Streamlit UIpip install landingai-ade streamlit
架构转换from landingai_ade.lib import pydantic_to_json_schema(已包含在 landingai-ade 中)

参考文件

针对特定工作流构建时,请阅读以下文件以了解完整实现:

  • schema-catalog.md — 可直接使用的 Pydantic 架构,适用于发票、水电费账单、银行对账单、工资单、食品标签、CME 证书和文档分类
  • batch-processing.md — ThreadPoolExecutor、AsyncLandingAIADE 和 Parse Jobs API 模式,包含完整的错误处理
  • rag-pipelines.md — 将分块转换为 CSV、ChromaDB 导入、FAISS + LangChain,以及 RAG 查询链
  • database-integration.md — DataFrame 规范化、Snowflake 上传和 CSV 导出模式
  • visualization.md — 分块图像裁剪、边界框叠加和词级 OCR 定位
  • table-stitching.md — Parse+Extract(稳健)、HTML 解析(脆弱)和 pandas 方法,用于将跨页表格合并为单一输出

评论

支持 Markdown