秒级切分混合文档流:Jerry Liu 开源 DocJev 的离散裁决架构与边界评测实录

秒级切分混合文档流:Jerry Liu 开源 DocJev 的离散裁决架构与边界评测实录

在当下的企业级知识库、智能问答(RAG)、财务票据审核与合同生命周期管理流水线中,非结构化文档的摄取(Ingestion)往往是整个系统最脆弱的阿喀琉斯之踵。现实世界中的文档流绝少以“一份文件一页 PDF”的理想形态出现,企业日常接收到的通常是一个数十甚至上百页的 “混合数据包”(Packet)——例如一次性扫描交付的文件袋中,前两页是 IRS Form 1040 税表,紧接着是两份属于不同交易的财政部拍卖结果,其后又附带一份长达十页且充斥密集统计图表的经济分析公报,最后以两页公共安全指南收尾。

面对这种高度杂糅的文档流,传统的工程方案往往直接调用大型自回归语言模型(如 GPT-4o 或 Claude 3.5 Sonnet)进行长上下文抽取与切分。然而,这种“大模型一把抓”的范式不仅带来了动辄 1.5 到 2 秒的漫长等待与高昂的 Token 账单,更深陷“同类相邻单据粘连”与“公文附件过度切割”的逻辑死角。近日,知名开源数据框架 LlamaIndex 创始人 Jerry Liu 正式开源了全新项目 DocJev(jerryjliu/docjev)。该项目依托 TypeSafe Jev 毫秒级非自回归决策引擎,结合本地零成本解析 LiteParse 与自适应云端 LlamaParse,在 40 份真实美国政府公文的严苛基准下,以 138ms 分类、209ms 切分 的惊艳速度打破了传统 LLM 的性能天花板。

本文将立足 DocJev 的完整开源代码库、工程白皮书与 40 份真实公文评测基准,全面解构这一轻量级文档智能架构的底层运行机制。


一、 核心痛点:为什么自回归大模型在文档切分上频频翻车?

长期以来,AI 工程师在处理文档分割(Document Splitting)与文档分类(Document Classification)时,习惯性地依赖 LLM 的结构化输出能力(Structured Outputs / JSON Mode)。典型的 Prompt 往往要求模型“阅读以下全部页面,并输出每个文档的起始页与终止页”。但在高并发生产环境中,这种机制存在四大结构性硬伤:

flowchart TD
    subgraph TraditionalLLM["传统自回归 LLM 方案缺陷"]
        T1["高延迟与算力浪费<br/>(逐 Token 生成 JSON 耗时 1.5s+)"]
        T2["同类相邻粘连盲区<br/>(两份相同类型的发票被粗暴合二为一)"]
        T3["公文附件过度撕裂<br/>(文末 Attachment 触发假阳性切割)"]
        T4["长上下文窗口膨胀<br/>(多页文档导致注意力稀释与显存剧增)"]
    end

    subgraph DocJevSolution["DocJev 离散裁决破局之道"]
        S1["System 1 非自回归裁决<br/>(Jev 引擎并发评估,时延降至 130-200ms)"]
        S2["类别与边界正交解耦<br/>(Choice 选型 + Noul 边界二元判决)"]
        S3["单所有权滑动窗口<br/>(PageWindow 携带前后上下文且绝不重叠)"]
        S4["四重风控审核机制<br/>(review_reasons 标出临界与语义冲突)"]
    end

    T1 -.-> S1
    T2 -.-> S2
    T3 -.-> S4
    T4 -.-> S3

1. 自回归生成的时延惩罚与算力错配

文档分类与切分本质上是 离散决策问题(Discrete Categorical & Boundary Decision),而非发散性生成任务。让一个拥有千亿参数的大模型,在显卡集群上以每秒数十个 Token 的自回归速度吐出 {"segments": [{"id": 1, "start": 1, "end": 3}]},是在极度低效地挥霍算力。在生产级实时发票审验、海关单据分流等场景中,1.5 秒与 0.2 秒的延迟差距直接决定了系统能否支持用户端无感响应。

2. 同类相邻粘连盲区(Same-Category Adjacency Blind Spot)

这是几乎所有朴素“按类别聚类”算法的致命弱点:

  • 假设一个扫描包中包含两份连续的 采购订单(Purchase Order),Page 1~2 是 PO #1001,Page 3~4 是 PO #1002;
  • 如果模型只判断每一页的类别,得到的结果是 [PO, PO, PO, PO];
  • 朴素的状态机只能看到类别没有变化,于是将两份独立的法律订单合并为一份 4 页的错误文档。区分它们必须依赖微观的版面线索(如页脚独立编号、标题重置、合同主体变动),这需要独立的 “边界判定” 维度。

3. 公文附录与补充说明的假阳性切割

在官方公文、研报与合同中,主体内容结束后往往紧跟带有明显独立版头(如“Decisions Regarding Monetary Policy Implementation”)的附录材料(Attachment)。传统模型极易将这种附录误判为全新的独立公文,造成法律文档的断章取义。

4. 上下文窗口的截断与缝隙丢失

面对 50 页甚至上百页的超长数据包,一次性塞入上下文会导致处理成本飙升且边缘注意力衰减;而如果采用固定大小的切片分块,位于切片交界处的跨页公文往往会因为缺少前后呼应而彻底丢失连贯性。


二、 架构全景:DocJev 的离散裁决与数据管线

针对上述痛点,Jerry Liu 在 DocJev 中摒弃了传统的自回归流水线,转向了以 TypeSafe Jev 为核心的“System 1 快决策系统”。Jev 并非通过自回归预测下一个词,而是通过预训练的高效判定头,直接计算用户定义的离散选项(Choice)与布尔/边界(Noul)的后验概率。

flowchart LR
    Input["输入原始文档<br/>(PDF / DOCX / PPTX)"] --> Parser["解析适配层"]

    subgraph ParserLayer["双轨制解析层"]
        Parser -->|默认本地零成本| P1["LiteParse 原生提取"]
        Parser -->|复杂扫描件/图表| P2["LlamaParse 云端 OCR"]
    end

    P1 --> State["标准化页面状态<br/>(page_state: 编号/文本/空白判定)"]
    P2 --> State

    subgraph Windowing["滑动窗口管理器 (windows.py)"]
        State --> W["PageWindow 边界映射<br/>(独占目标页 targets + 上下文邻居)"]
    end

    subgraph JevEngine["TypeSafe Jev 离散裁决引擎"]
        W --> Q1["单页类别提问 (Choice)"]
        W --> Q2["起始边界提问 (Noul)"]
        Q1 --> SystemOne["client.system_one() 并行运算"]
        Q2 --> SystemOne
    end

    subgraph Assembler["段落组装与风控引擎 (split.py)"]
        SystemOne --> Seg["assemble_segments()<br/>(状态机整合 / 冲突仲裁)"]
        Seg --> Rev["四重 review_reasons 标定"]
    end

    Seg --> Out["输出切分结果 & 物理 PDF 导出"]

1. 核心数据抽象:自然语言规则集

DocJev 的规则系统定义在 src/jev_docs/schemas.py 中,采用严格的 Pydantic V2 校验。用户通过简洁的 YAML 文件定义业务类别,DocJev 会自动补齐名为 other 的兜底类别:

categories:
  - id: tax_form
    description: 官方税务报表,包含 IRS 1040/940 等标准纳税申请与申报表单。
  - id: financial_report
    description: 机构与政府财务公报、拍卖结果记录及资产负债统计。
  - id: press_release
    description: 新闻通稿、统计局经济数据发布声明与公开说明纪要。
  - id: legal_notice
    description: 法定公告、联邦公报征求意见书与监管合规通告。
instructions: 根据全文主要目的进行整份或单页归类。
splitting_instructions: 保持同一公文的续页与附录完整。相邻即便同属财务报告,若为独立发布主体亦须切分。

2. 类别识别与边界判定的正交解耦

在 src/jev_docs/engines/jev.py 中,Jerry Liu 设计了两种截然不同但协同工作的提问算子:

  1. 类别算子(Choice):针对当前页面,计算其属于配置规则中哪一个类别的后验概率分布;
  2. 边界算子(Noul):针对从第 2 页开始的每个页面,发出布尔型离散判定:“当前第 X 页是否作为独立新文档的开头,而非对前一页 X-1 的延续?”
# 摘录自 src/jev_docs/engines/jev.py
@staticmethod
def split_questions(window: PageWindow, rules: RuleSet) -> dict:
    questions: dict = {}
    for page in window.targets:
        if page.blank:
            continue
        # 1. 目标单页的类别多选判决
        questions[f"category_{page.number}"] = Choice(
            instructions=(
                UNTRUSTED + f"What document category does page number {page.number} belong to? "
                "Use the neighboring pages to identify continuation pages whose title is absent. "
                + rules.instructions
            ),
            criteria=rules.criteria,
        )
        # 2. 目标单页与前页的独立边界二元判决
        if page.number > 1:
            questions[f"boundary_{page.number}"] = Noul(
                instructions=(
                    UNTRUSTED + BOUNDARY_POLICY + f"Does page number {page.number} start a new "
                    f"source document, rather than continue page number {page.number - 1}? "
                    + rules.splitting_instructions
                )
            )
    return questions

通过这一解耦,DocJev 彻底击碎了“同类相邻粘连”问题:即便 Page 4 和 Page 5 都被 Choice 算子判定为 financial_report,只要 Page 5 上的 starts_document(Noul 判定)返回 True,系统就会将其干净利落地截断为两个独立的公文段落。


三、 工程精粹:滑动窗口、组装状态机与风控矩阵

要将这一算法落地到真实生产环境,必须解决超长文档的显存控制、类别冲突解决以及低置信度下的风控提示。DocJev 在这些细节上的工程设计极为扎实。

1. 带单所有权的重叠上下文窗口(PageWindow)

在处理长达上百页的大型混合文档包时,模型不能脱离前序页面孤立判断当前页,但又不能引入重复决策。src/jev_docs/windows.py 给出了优雅的解答:

@dataclass
class PageWindow:
    pages: list[Page]    # 包含前后邻近上下文的完整输入切片
    targets: list[Page]  # 本轮请求中唯一拥有判决产出所有权的页面切片

def window_for(pages: list[Page], start: int, end: int) -> PageWindow:
    """start:end 页面拥有输出;相邻边界页面仅作为只读上下文,绝不产生二次输出。"""
    return PageWindow(
        pages[max(0, start - 1) : min(len(pages), end + 1)],
        pages[start:end]
    )
  • 每个窗口仅对 targets(如默认大小为 8 页)发起 Choice 与 Noul 提问;
  • 但提供给模型的输入 pages 会自动向左延伸 1 页(start - 1)并向右延伸 1 页(end + 1);
  • 这使得模型在评估第 8 页是否是第 7 页的延续时,能完整观测到上下文转折,同时保证每个物理页码在全局有且仅有一个归属决策者,彻底消除了拼接冲突与遗漏;
  • 配合内置严格的字节预检(单状态提问不超过 95KB,总请求体不超过 190KB),一旦超出安全预算会自动下调窗口尺寸,而绝不粗暴截断正文文字。

2. 段落组装状态机(Segment Assembler)

在拿到所有页面的决策流之后,src/jev_docs/split.py 中的状态机开始按时序构建段落。触发新段落(Segment)的判定条件非常清晰:

New Segment  ⟺  is_first_page∨category_changed∨starts_document∨after_blank\text{New Segment} \iff \text{is\_first\_page} \lor \text{category\_changed} \lor \text{starts\_document} \lor \text{after\_blank}

值得注意的是对 语义冲突(Conflict)的处理:如果当前页的类别发生了变动(如由 tax_form 变为 press_release),但模型的边界判定却声称它“继续了前一页”(starts_document = False),系统会优先以类别断裂为准强制切分,同时在结果中记录警告并在段落上挂载 category_boundary_conflict 标签,提交人工复核。

3. 四重边界风控审核机制(Boundary Review Signals)

在自动化流水线中,不能给业务系统直接抛出一个黑盒结果。DocJev 为每一个输出的 Segment 注入了 needs_review 标识与 review_reasons 诊断列表:

风控标识代码 触发场景说明 工程干预机制
boundary_near_threshold 边界得分与判定门限(0.5)差距在容限内(如处于 [0.4, 0.6] 灰度区间) 双向挂起:同时将当前段落与前一相邻段落均标记为待审
category_uncertain 胜出类别的预测概率低于最低置信门限(min_probability < 0.7) 标记当前段落类别存疑,提示人工校验
category_boundary_conflict 页面类别发生显式突变,但边界模型给出延续判定 强行切分并标记冲突,防范同类误判引起的异常截断
other_category 系统落入了兜底类别(other) 标记异常件,提示可能存在新单据类型需要扩充规则库

四、 真实公文基准评测:Jev 1.13.0 对决 GPT-5.6 Luna

为了拒绝“合成虚构数据”的花架子评测,DocJev 随代码库开源了一组高度严肃的基准测试集:从美国国税局(IRS)、财政部(Treasury)、经济分析局(BEA)、证券交易委员会(SEC)、联邦公开市场委员会(FOMC)以及疾控中心(CDC)下载的 40 份原始官方 PDF(共 116 个独立页面),并进一步组合成 8 个包含 5 份混合文档的复杂数据包。

测试环境保持严格对齐:两套引擎共享同一份由本地 LiteParse 预提取的页面文本哈希,决策计时严格剔除 OCR 阶段的磁盘与网络开销,并发数设为 1,禁用重试机制。

1. 核心指标全面对撞

下表整理自官方公布的基准运行报表(real-small-v1-run01):

评测维度 任务类别 TypeSafe Jev 1.13.0 GPT-5.6 Luna(基线) 性能与成本对比
准确率 (Quality) 单文档分类 (40 份) 40 / 40 (100.0%) 40 / 40 (100.0%) 均达完美分类
复杂数据包切分 (8 包) 7 / 8 (87.5%) 8 / 8 (100.0%) Jev 召回 32/32 处真实边界,仅多切 1 处附录
中位时延 (p50) 单文档分类 138.6 ms 794.3 ms Jev 提速 5.73 倍
复杂数据包切分 209.6 ms 1,352.3 ms Jev 提速 6.45 倍
95 分位时延 (p95) 单文档分类 184.5 ms 1,208.8 ms Jev 抖动控制极佳,维持在 200ms 以内
复杂数据包切分 290.0 ms 1,862.7 ms Luna 尾部时延逼近 1.9 秒
决策 API 费用 40 次分类总支出 0.005028 美元 0.024406 美元 Jev 成本约为大模型的 20.6%
8 次切分总支出 0.006635 美元 0.022488 美元 Jev 成本约为大模型的 29.5%
全流程总计 包含预热调用 0.011663 美元 0.046894 美元 整体开销直降 75.1%
xychart-beta
    title "决策中位耗时对比 (ms) - 越低越好"
    x-axis ["单文档分类任务", "多文档数据包切分任务"]
    y-axis "毫秒 (ms)" 0 --> 1600
    bar [138.6, 209.6]
    bar [794.3, 1352.3]

2. 唯一样本失分深度复盘:FOMC 公文附录切割争议

在数据包切分任务中,Jev 取得了 7/8 的满分表现,唯一被判错的 Case 位于数据包 p001 的第 8~9 页交界处。DocJev 在 benchmarks/results/real-small-v1-run01/error-analysis.md 中以极高的学术严谨度公开了该 Case 的完整细节:

  • 争议公文背景:源文件为美联储 FOMC 官方声明(编号 e017),源文件共 4 页;
  • 物理版面事实:第 2 页(数据包第 8 页)末尾明确打印了 “Attachment.” 字样;第 3 页(数据包第 9 页)以显眼的大标题开启 “Decisions Regarding Monetary Policy Implementation”,并重新冠以当天的发布日期;
  • 模型的判决行为:Jev 敏锐捕捉到了大标题、重置日期与“Attachment”标记,给出了 0.76 的新文档启动概率,从而将后两页切分为一个独立的 press_release 段落;
  • 基准标注的原则:在预设的评测协议中,由于美联储官网将这 4 页作为同一份公文发布,标注规则明确规定“保留公文附件,不因章节变更而拆分”;
  • 启示:这一失分恰恰印证了 Jev 对版面微结构的高灵敏度。在严格遵照出版物维度的协议下它多切了一刀,但在需要提取细粒度决议条款的业务场景中,这种识别能力反而具有高度实用性。

五、 双轨 OCR 哲学:本地轻量 LiteParse 与云端 LlamaParse

文档智能系统的整体吞吐时延不仅取决于推理阶段,还取决于前端的版面解析。DocJev 采取了务实的 “双轨混合解析架构”:

graph TD
    In["传入输入源文件"] --> FormatCheck{"文件类型判定"}
    FormatCheck -->|DOCX / PPTX| Conv["LibreOffice 无头转换<br/>(保留规范 PDF 页面映射)"]
    FormatCheck -->|PDF| Direct["原生 PDF 输入"]

    Conv --> Mode{"用户配置的 OCR 模式"}
    Direct --> Mode

    Mode -->|--ocr liteparse (默认)| L["LiteParse 本地轻量引擎"]
    L --> L1["纯本地 Python 快速提取 (PyMuPDF/pdfplumber)"]
    L1 --> L2["零 API 成本 / 零外发隐私泄漏 / 毫秒级提取"]

    Mode -->|--ocr llamaparse| Cloud["LlamaParse 云端高级解析"]
    Cloud --> C1["Parse v2 深度模型<br/>(Cost-effective / Agentic / Agentic-plus)"]
    Cloud --> C2["攻克手写、密集表格、复杂倾斜扫描件"]
  1. LiteParse(默认首选):全本地运行的 Python 原生解析器,对原生数字 PDF 执行直接文本层提取,对扫描页自动下载本地轻量 OCR 权重。API 调用成本为零,处理时间控制在数十毫秒级,且保证了企业核心财务数据绝不离开本地环境;
  2. LlamaParse 插件集成:针对被严重污染的纸质扫描件或需要深层版面理解(如复杂嵌套财务附表)的特殊场景,DocJev 支持无缝挂接 LlamaParse 的三大服务层级(Cost Effective、Agentic、Agentic Plus),并内置了 24 小时本地 Hash 缓存机制,避免重复计费。

六、 实战演练:从 CLI 到 Python 生产级集成

DocJev 现已发布在 GitHub,依托现代化包管理工具 uv,开发者可以在三分钟内跑通完整的分类与切片流程。

1. 命令行极速体验

# 1. 检出仓库并同步依赖
git clone https://github.com/jerryjliu/docjev.git
cd docjev
uv sync

# 2. 配置 TypeSafe 凭证
export TYPESAFE_API_KEY="your-typesafe-api-key"

# 3. 对 10 页 BEA 经济公报执行分类
uv run docjev classify examples/real/originals/r04.pdf \
  --rules examples/real/classify/rules.yaml

# 4. 对 15 页混合公文包执行切分,并直接按公文物理导出拆分后的独立 PDF
uv run docjev split examples/real/public-finance-packet.pdf \
  --rules examples/real/split/rules.yaml \
  --export-dir output/real-segments \
  --output output/real-split.json

导出的 real-split.json 结果极为纯粹,每个片段附带了页码范围、归属分类与审核标记:

{
  "segments": [
    {
      "id": "segment-001",
      "category": "tax_form",
      "pages": [1, 2, 3],
      "needs_review": false,
      "mean_category_probability": 0.985
    },
    {
      "id": "segment-002",
      "category": "financial_report",
      "pages": [4],
      "needs_review": false,
      "mean_category_probability": 0.962
    },
    {
      "id": "segment-003",
      "category": "financial_report",
      "pages": [5],
      "needs_review": false,
      "mean_category_probability": 0.941
    },
    {
      "id": "segment-004",
      "category": "press_release",
      "pages": [6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
      "needs_review": false,
      "mean_category_probability": 0.991
    }
  ]
}

注意细节:即便第 4 页与第 5 页均为 financial_report(两份不同的财政部拍卖报告),DocJev 依然精准将其拆分为 segment-002 与 segment-003,彻底克服了同类文档粘连。

2. Python 异步流水线集成

在后端服务中,可以直接通过异步 API 将 DocJev 嵌入高并发文档处理流水线:

import asyncio
from pathlib import Path
from jev_docs import parse_document, load_rules, asplit_document, export_segments

async def process_incoming_packet(pdf_path: Path, output_dir: Path):
    # 1. 载入切分规则与预解析文档
    rules = load_rules("examples/real/split/rules.yaml")
    doc = await asyncio.to_thread(parse_document, pdf_path)
    
    # 2. 毫秒级异步执行离散裁决
    result = await asplit_document(
        doc,
        rules,
        engine="jev",
        boundary_threshold=0.5,
        min_probability=0.7,
        boundary_review_margin=0.1
    )
    
    # 3. 拦截低置信度或边界存疑的段落
    for segment in result.segments:
        if segment.needs_review:
            print(f"[警告] 段落 {segment.id} (P{segment.pages[0]}-P{segment.pages[-1]}) "
                  f"存在风控疑点: {[r.code for r in segment.review_reasons]}")

    # 4. 物理切分并另存为独立 PDF 文件
    export_segments(doc, result, output_dir)
    print(f"成功将混合数据包切分为 {len(result.segments)} 份独立公文,耗时: {result.metrics.decision_ms:.1f}ms")

if __name__ == "__main__":
    asyncio.run(process_incoming_packet(
        Path("examples/real/public-finance-packet.pdf"),
        Path("output/split-docs")
    ))

七、 总结与前瞻:系统级重构下的文档智能演进

Jerry Liu 开源的 DocJev,为近年来逐渐走向“参数越大越好、上下文越长越好”的大模型应用生态注入了一剂清醒剂:

  1. 破除对自回归 LLM 的盲目依赖:在文档理解与数据摄取流水线中,大量任务(分类、去重、边界切分、路由)天然属于离散决策范畴。引入非自回归的 System 1 模型,能够在保持顶尖准确率的同时,斩获 5~6 倍的时延提升 与 75% 的成本压缩;
  2. 算法与工程架构的有机统一:通过“类别识别”与“边界判定”的正交解耦,DocJev 从底层算法机制上根治了相邻同类单据粘连的痼疾;而带单所有权的滑动窗口机制,则为超长多页文档的稳定处理奠定了坚实的工程基石;
  3. 迈向 “快慢协同” 的复合 AI 架构(Compound AI Systems):未来的智能文档处理管线,必然属于精细分工的混合系统——由 DocJev 这样的“快反射神经”在毫秒间完成大规模混合文档流的高速分流、清洗与边界切割,再将提纯后的精准单据交付给 Claude 或 GPT 等“深度思考模型”进行复杂内容推理与业务抽取。

对于正在构建下一代企业级 RAG、智能合同审查或端到端财务智能系统的技术团队而言,DocJev 展现出的精益架构与极速体验,无疑代表了一条极具启发性的演进范式。