结构化提取

发票和收据共用入口,怎样避免两种结构混在一起

一个接口同时接收发票和收据,很容易写出“所有字段都可选”的大对象。这样的对象什么都能装,却很难说明收到的究竟是什么。更清楚的做法是保留两份独立契约,用明确类型字段选择其中一份。

阅读文本 / Reading text
本文使用合成样本演示处理方法。代码已通过隔离环境中的示例检查;这不代表对所有真实文件或产品效果的保证。处理重要文件前,请保留原件,并按文中的边界与验收项复核。 一个接口同时接收发票和收据,很容易写出“所有字段都可选”的大对象。这样的对象什么都能装,却很难说明收到的究竟是什么。更清楚的做法是保留两份独立契约,用明确类型字段选择其中一份。 oneOf 不是任意一个就行 oneOf 要求恰好匹配一个分支。两个分支同时通过也算失败。若分支只有几个可选字段,它们可能大量重叠,合法输入反而无法通过。用必填的 kind 加 const ,能把这类重叠提前消除。 一个可执行的分流协议 from jsonschema import Draft202012Validator def branch(kind, identity): return { "type": "object", "additionalProperties": False, "required": ["kind", identity], "properties": {"kind": {"const": kind}, identity: {"type": "string", "minLength": 1}} } schema = {"oneOf": [branch("invoice", "invoice_number"), branch("receipt", "receipt_number")]} v = Draft202012Validator(schema) valid = lambda x: not list(v.iter_errors(x)) assert valid({"kind": "invoice", "invoice_number": "I-1"}) assert valid({"kind": "receipt", "receipt_number": "R-1"}) assert not valid({"kind": "invoice", "receipt_number": "R-1"}) assert not valid({"kind": "receipt", "receipt_number": ""}) assert not valid({"kind": "invoice", "invoice_number": "I-1", "receipt_number": "R-1"}) assert not valid({"kind": "unknown"}) 这是人工构造的最小记录,不是现实发票的完整字段模型。代码演示分支选择与多余字段拒绝,税率、抬头和金额需要另行定义。业务若允许附加字段,也应明确它们的命名空间,而不是悄悄取消所有约束。 错误信息别只显示“不匹配” 输入特征 实际疑点 建议反馈 kind 缺失 无法选择协议 请提供单据类型 类型与编号字段冲突 两种结构被混合 展示冲突字段及来源 编号空字符串 键存在但无值 回看编号区域 未知类型 当前协议未覆盖 进入独立待分类队列 不确定类型也需要一个出口 如果分类器无法区分发票与收据,应保留未知状态,而不是随机选一个分支凑出“成功”。分类置信度、结构校验和业务真实性是三个不同层次。结构通过不能反向证明分类正确,更不能证明单据真实。 翻译流程里也要保留单据类型 同一字段在不同单据中可能需要不同术语。使用 OmniDoc(产品入口) 的术语与双语审查工作流时,把单据类型随交付清单一起提供,有助于审校理解编号和金额所处的语境。不要让一个宽泛 JSON 对象抹掉这些差别。 参考: JSON Schema 组合规则 。 - 1 -
更新于 2026-10-08