直接回答
可靠的文档工作流是一台版本化状态机:
接收 -> 隔离 -> 校验 -> 标准化 -> 提取
-> 业务检查 -> 必要时人工复核
-> 提交 -> 按策略留存或删除
每个输出都必须能够追溯到不可变原件,以及生成它的精确处理配置。OCR、多模态模型或 LLM 可以提出文本和字段;只有确定性代码与获得授权的 Reviewer 才能决定这些字段是否可以改变业务系统。
“拆分 PDF、调用 AI、保存 JSON”不是端到端设计。它遗漏了不可信文件处理、来源链、重试、不确定性、复核、授权和纠错。
定义文档契约
从一种文档和一个业务结果开始。以发票为例:
document_type: supplier-invoice
accepted_inputs: [pdf]
source:
immutable: true
max_bytes: policy-defined
required_fields:
- supplier_id
- invoice_number
- currency
- total_minor_units
business_oracles:
- supplier_exists
- purchase_order_matches
- line_sum_matches_total
- invoice_not_previously_committed
human_review:
- missing_required_field
- conflicting_evidence
- high-impact_policy
commit:
idempotent: true
authorized_principal: required
这份契约区分了语法提取与业务验收。模型可能正确读出“1,000.00”,却把它关联到错误供应商或币种。此时提取成功,业务仍然失败。
建模 Artifact 与状态
原件保持不可变,每个派生制品拥有独立身份:
| Artifact | 用途 | 必需来源信息 |
|---|---|---|
| 原始字节 | 证据与重处理来源 | 来源渠道、Owner、Digest |
| 清理或标准化文件 | 更安全的解析器输入 | Parent Digest、Sanitizer 版本 |
| 页面图像 | OCR 或视觉模型输入 | Parent、页码、渲染配置 |
| OCR 文本与布局 | 可搜索文本与坐标 | Engine、Model、Language、Page |
| 提取字段 | 候选业务数据 | Schema、Evidence Span、Confidence |
| 已复核记录 | 人工决定 | Reviewer、原因、修改字段 |
| 已提交记录 | 下游副作用 | Idempotency Key、Target Version |
W3C PROV 模型提供了 Entity、Activity、Agent,以及 Usage、Generation、Derivation 等可复用概念。工作流不必采用 RDF 存储,但应该回答同一组问题:使用了什么、产生了什么、由哪个过程生成、谁负责、何时发生。
状态转换应显式定义:
不能因“最后一个函数返回”就推断流程完成。每次状态变化及其证据都要持久化,使中断 Worker 可以恢复,又不重复执行副作用。
解析前先保护接收边界
文档是不可信输入。OWASP File Upload Cheat Sheet强调分层控制,因为没有任何单一校验足够可靠。
接收阶段需要:
- 验证上传者身份与权限;
- 只允许业务必需的扩展名;
- 规范化原文件名,再生成服务端存储名;
- 比较扩展名、声明 MIME、检测类型和文件签名;
- 限制压缩前后大小、页数、尺寸和处理时间;
- 在 Web Root 之外隔离存储;
- 按场景执行 Malware Scan 或 CDR;
- 在没有生产凭证的隔离 Worker 中解析;
- 按策略拒绝加密、损坏、Active Content 或不支持的文件。
Content-Type: application/pdf 是用户可控元数据。%PDF- 前缀也不能证明所有 Embedded Object 安全。Malware Scan 同样不是语义校验器。
处理压缩包时,应单独限制 Entry 数量、嵌套深度、解压总字节与 Path Traversal。5 MB 上传文件可能解压为远大于原大小的内容。
分离标准化、OCR 与语义理解
这些阶段有不同输出和失败模式:
- 标准化:在不改变权威原件的前提下生成解析器可用版本。
- OCR:把像素映射为文字与布局坐标。
- 文档解析:提取原生文本、表格、表单、附件和结构。
- 分类:选择适用的文档契约。
- 语义提取:提出带类型的业务字段与证据位置。
- 验证:将候选与源证据和外部系统对照。
Digital PDF 可能已有文本层,但阅读顺序或隐藏层仍可能错误;扫描 PDF 需要 OCR;混合文档可能同时需要原生解析和页面渲染。不要假设所有 PDF 都是一类输入。
AI 辅助提取时,应保留:
- 每个字段的页码与 Bounding Box;
- 原文 Evidence Span;
- OCR 与模型版本;
- Prompt 与 Schema 版本;
- 源语言与标准化方式;
- 备选值或不确定性;
- Validator 与 Reviewer 决策。
坐标和证据管理可继续阅读AI 图像理解:OCR、解析与 VQA。
校验字段,而不只是 JSON
Structured Output 只证明语法,不证明语义。
发票可以执行以下确定性检查:
currency 属于支持的代码集合
total_minor_units >= 0
sum(line_amounts) + tax 按规定舍入后等于 total
supplier_id 在当前 Tenant 中存在
purchase_order 处于可用状态且属于该 Supplier
invoice_number 在该 Supplier 下尚未提交
date 与 payment terms 位于允许范围
Validation 应返回类型化原因。“Confidence 低于 0.8”还不够,系统应区分证据缺失、解析失败、跨系统冲突、Duplicate、Policy Denial 和模型不确定。
Confidence 只是路由信号
模型 Confidence 只有在代表性数据上完成 Calibration 后才有实际含义。应按 Confidence Bucket、Document Class、Source、Language 与 Field 测量真实错误率。
高影响字段即使 Confidence 很高,也可能仍需复核;低影响字段即使阈值较低,只要容易纠错,也可能可以自动接收。路由应基于预期错误成本,而不是统一全局数字。
把人工复核作为正式状态
Review Queue 需要展示:
- 原始页面和候选字段;
- 高亮证据与坐标;
- 旧值和冲突原因;
- 允许的修正与 Reviewer Identity;
- 必要时的职责分离;
- 禁止由模型生成的文字自行批准;
- 持久化 Decision 与 Audit Event。
测量 Reviewer Agreement 与 Correction Rate。若 Reviewer 也经常分歧,问题可能来自模糊政策或源文档,而不是模型能力。
人工修正不能自动成为训练数据。复用前仍需 Consent、Provenance、质量审查、去重和防泄漏切分。
让重试与提交具备幂等性
阶段幂等身份可以采用:
source_digest + stage + processor_revision + configuration_digest
相同输入和配置的同一阶段可以返回既有 Artifact。OCR 模型或 Schema 发生变化时,应创建新 Artifact,而不是覆盖历史。
下游写入可以使用:
approved_record_digest + target_system + operation
请求发送后超时,应将结果记为 Unknown,并先与目标系统对账。盲目重试可能重复创建发票、Case、通知或支付。
可运行的 Go 状态机
下面的无依赖示例创建 Content-addressed Artifact,并拒绝非法状态转换。它只演示编排;真实 Parser 仍应放在隔离 Worker 中。
package main
import (
"crypto/sha256"
"encoding/hex"
"fmt"
)
type State string
const (
Received State = "received"
Quarantined State = "quarantined"
Validated State = "validated"
Extracted State = "extracted"
Reviewed State = "reviewed"
Committed State = "committed"
Rejected State = "rejected"
)
var transitions = map[State]map[State]bool{
Received: {Quarantined: true},
Quarantined: {Validated: true, Rejected: true},
Validated: {Extracted: true, Rejected: true},
Extracted: {Reviewed: true, Rejected: true},
Reviewed: {Committed: true, Rejected: true},
}
type Document struct {
ID string
SourceSHA string
State State
Revision int
CommittedID string
}
func digest(data []byte) string {
sum := sha256.Sum256(data)
return hex.EncodeToString(sum[:])
}
func advance(document Document, next State) (Document, error) {
if !transitions[document.State][next] {
return document, fmt.Errorf(
"invalid transition %s -> %s",
document.State,
next,
)
}
document.State = next
document.Revision++
return document, nil
}
func commitKey(document Document, target string) (string, error) {
if document.State != Reviewed || target == "" {
return "", fmt.Errorf("reviewed document and target are required")
}
return digest([]byte(document.SourceSHA + "|" + target)), nil
}
func main() {
source := []byte("%PDF-1.7 fixture")
document := Document{
ID: "doc-42",
SourceSHA: digest(source),
State: Received,
Revision: 1,
}
var err error
for _, next := range []State{Quarantined, Validated, Extracted, Reviewed} {
document, err = advance(document, next)
if err != nil {
panic(err)
}
}
document.CommittedID, err = commitKey(document, "erp.invoice.create")
if err != nil {
panic(err)
}
document, err = advance(document, Committed)
if err != nil {
panic(err)
}
fmt.Printf(
"id=%s state=%s revision=%d source=%s commit=%s\n",
document.ID,
document.State,
document.Revision,
document.SourceSHA[:12],
document.CommittedID[:12],
)
}
预期输出以此开头:
id=doc-42 state=committed revision=6 source=
Source 与 Commit 标识是确定性的。相同已批准操作再次执行时,可以先读取已有结果,而不是制造第二次副作用。
评测完整工作流
按 Document Class、Source、Language、Layout、Quality 和 Risk 分切片:
| 层次 | 有用指标 |
|---|---|
| Intake | 拒绝原因、恶意样本、解压大小失败 |
| OCR | 字符/词错误、阅读顺序、坐标覆盖 |
| Classification | 每类 Precision、Recall、Abstention |
| Extraction | 字段 Precision/Recall、精确值、证据位置 |
| Validation | 冲突检出、False Acceptance、False Rejection |
| Review | Queue Age、Correction Rate、Reviewer Agreement |
| Commit | 重复率、Unknown Outcome、下游修正 |
| Operations | p50/p95 时延、吞吐、重试、每个合格文档成本 |
| Governance | 删除完成、来源覆盖、越权访问 |
不要发布单一“文档准确率”平均数。发票总额错误与缺少一个可选备注的代价完全不同。
运行和演进
版本化 Parser、OCR Engine、Model、Prompt、Schema、Validator 与 Routing Policy。让新版本在既有文档上 Shadow,对比字段级回归,并保留旧路径用于回滚。
只要提取文本会进入 LLM,就应把所有外部文档视为潜在 Prompt Injection。AWS 摄取安全指南说明了为什么知识库摄取前和推理时都需要控制。
原件、页面图像、OCR 文本、Embedding、提取记录、Prompt、日志、Review Snapshot 与训练数据需要分别定义 Retention。只有策略要求的副本与索引都被处理后,删除才算完成。
常见问题
什么是智能文档处理工作流?
它是一条把不可信文件变成已验证业务记录的受治理管线,覆盖接收、隔离、解析或 OCR、提取、确定性检查、人工复核和授权提交,同时负责来源、重试、留存、删除与恢复。
Confidence 低时都应该转人工吗?
不应该。Confidence 必须先根据真实错误完成校准,路由还要考虑字段影响、业务规则冲突、源质量、不同解析器分歧和修正成本。部分高影响字段即使 Confidence 很高,也仍应复核。
MIME 与 Malware Check 通过后文件就安全吗?
不安全。每项检查只降低一种风险,Parser 仍可能存在漏洞,文件也可能包含 Active Content。系统仍需分层校验、隔离执行、资源限制、不可变原件,并对任何下游副作用单独授权。
文档工作流应该怎样重试?
Source、Stage、Processor 与 Configuration 完全相同时复用已有 Artifact,只重试已分类的瞬时故障。写操作结果未知时,必须先向目标系统对账,再决定是否重试。
哪个指标最能代表工作流质量?
没有单一指标。应按切片报告字段正确率和业务验收、False Acceptance、Review 负载、下游修正、时延、每个合格文档成本、重复副作用,以及恢复和删除完成度。
常见失败模式
- 信任扩展名或浏览器
Content-Type; - 在特权应用进程中解析文档;
- 用转换后文件覆盖原件;
- 所有字段共用一个 Confidence Threshold;
- JSON 有效便直接写入业务系统;
- 只用正则表达式脱敏敏感数据;
- 超时后不对账便重试写操作;
- 日志没有 Source 和 Processor Revision;
- 未经来源和 Consent 审核就用人工修正训练;
- 只看 OCR 平均质量,不看财务或安全错误;
- 宣称“零人工”却让 Exception Queue 持续堆积;
- 推荐一批库,却没有 Support 与补丁负责人。
生产检查清单
- [ ] 定义一种文档、一个 Schema、一个业务结果和失败策略。
- [ ] 上传后先隔离,并在解析前执行分层校验。
- [ ] 保持原件不可变,为每个派生 Artifact 建立身份。
- [ ] 记录 Parser、OCR、Model、Prompt、Schema 与配置版本。
- [ ] 为提取字段保留页码、坐标与 Evidence Span。
- [ ] 提交前与权威业务系统校验。
- [ ] 按校准风险和错误成本路由,而不是只看 Confidence。
- [ ] 让阶段与下游副作用具备幂等性。
- [ ] 重试写操作前先对账 Unknown Outcome。
- [ ] 测试质量、安全、复核负载、时延、成本、恢复与删除。
相关资源
一手资料
总结
只有保留证据并控制副作用,文档自动化才可靠。隔离原件,创建版本化 Artifact,分离 OCR 与语义理解,对照业务事实验证字段,把不确定结果送入复核,并用幂等方式提交。先让系统能够解释每份文档发生了什么,并在任一阶段失败后安全恢复,再优化速度和成本。