票据图片看似清晰,字段提取仍可能漏项、日期格式混乱,甚至混淆含税与未税金额;单靠更强的识别模型,并不能消除这些风险。文章从保留完整原图、预先定义字段与状态,到约束模型输出、用程序校验结构和格式,再接入人工复核,梳理一条可追溯的整理流程。怎样让识别结果便于核对,却不被误当成可靠凭证?
— 此摘要由AI分析文章内容生成,仅供参考。
票据图片看起来很清楚,提取出来的字段却可能出现漏项、日期格式不一致,甚至把含税金额当成未税金额。解决办法不是只换一个“识别更准”的模型,而是先定义好数据结构,再把缺失处理、格式校验和人工复核串成一条流程。视觉语言模型可以协助读取和整理信息,但输出不能直接当作财务凭证或无误结果。

先准备图片:让模型看见完整信息
拍摄或扫描时,尽量让票据平整、主体完整,避免阴影、反光和明显倾斜。不要为了突出某个字段而把边缘裁掉:商户名称、日期、金额和票据编号有时分布在不同位置,过度裁切会丢失上下文。多页文件应按页拆分或明确页序,并保留原始图片,方便回查。
如果图片质量不佳,先处理旋转、透视和裁切,再交给模型;处理后的图片仍应与原件对应。不要把模型“看不清”的字段通过猜测补上。对同一批文件,尽量统一图片命名和编号,并用独立记录关联图片与识别结果。
先定义字段,再调用模型
不要只说“识别这张发票”。先决定下游实际需要什么字段,以及每个字段采用什么格式。例如,日期统一为 YYYY-MM-DD,金额以字符串保存,避免不同软件对小数的处理差异;币种单独记录,不要默认所有金额都是人民币。
建议至少为每个字段保留三类信息:识别值、原图依据、识别状态。状态用于区分“已读出”“看不清或有歧义”“票据上没有该字段”。这样,空值就不会被误当成模型漏答,也不会让系统把猜测当成事实。
{
"merchant": {
"value": "示例商户",
"evidence": "票据顶部的商户名称",
"status": "read"
},
"invoice_date": {
"value": "2026-03-08",
"evidence": "开票日期:2026年3月8日",
"status": "read"
},
"total_amount": {
"value": "128.50",
"evidence": "价税合计:128.50元",
"status": "read"
},
"currency": {
"value": "CNY",
"evidence": "金额旁标注人民币元",
"status": "read"
},
"invoice_number": {
"value": null,
"evidence": null,
"status": "missing"
}
}
字段名和状态值应在整批数据中保持一致。收据、发票和普通表单的字段并不完全相同,可按业务需要设计不同的结构,但不要让模型随意新增字段或改变字段含义。
提示词要约束“如何回答”
可以先用以下模板测试,再根据票据类型调整:
请从图片中提取 merchant、invoice_date、total_amount、currency、invoice_number。
只返回符合约定字段结构的 JSON,不要添加 Markdown 或其他说明。
规则:
- 只填写图片中能直接读到的信息,不推测、不补全。
- 日期统一为 YYYY-MM-DD;金额使用字符串,不带千位分隔符。
- 每个字段都返回 value、evidence、status。
- status 只能是 read、uncertain、missing。
- 图片没有该字段时,value 和 evidence 返回 null,status 为 missing。
- 内容模糊或存在多个可能值时,不要猜,status 返回 uncertain,
并在 evidence 中说明看到的原始片段。
这里的 evidence 应是图片上的文字或简短位置说明,而不是模型对内容的解释。若所用模型支持返回文字区域或坐标,可一并保存用于复核;不支持时,也应保留原图和文件编号。模型自报的置信度不等于准确率,不宜单独用它决定是否入账。
用程序检查结构和格式
模型返回的 JSON 即使语法正确,也可能把日期写成“3月8日”、把金额写成带货币符号的文本,或漏掉必需字段。接收结果后,先做确定性的格式检查,再进入人工复核。下面的示例只检查字段结构、日期和金额格式,不会判断票据内容真假:
import re
from datetime import date
from decimal import Decimal, InvalidOperation
FIELDS = {
"merchant": "text",
"invoice_date": "date",
"total_amount": "amount",
"currency": "text",
"invoice_number": "text",
}
STATUSES = {"read", "uncertain", "missing"}
def validate(record):
errors = []
if set(record) != set(FIELDS):
errors.append("字段集合与约定不一致")
for name, kind in FIELDS.items():
item = record.get(name)
if not isinstance(item, dict):
errors.append(f"{name}: 必须是对象")
continue
if set(item) != {"value", "evidence", "status"}:
errors.append(f"{name}: value、evidence、status 结构不完整")
continue
value, evidence, status = (
item["value"], item["evidence"], item["status"]
)
if status not in STATUSES:
errors.append(f"{name}: status 不合法")
continue
if status == "missing" and value is not None:
errors.append(f"{name}: missing 状态的 value 应为 null")
if status == "read" and value is None:
errors.append(f"{name}: read 状态不能没有 value")
if evidence is not None and not isinstance(evidence, str):
errors.append(f"{name}: evidence 必须是字符串或 null")
if value is None:
continue
if not isinstance(value, str):
errors.append(f"{name}: value 应为字符串或 null")
continue
if kind == "date":
try:
date.fromisoformat(value)
except ValueError:
errors.append(f"{name}: 日期应为 YYYY-MM-DD")
elif kind == "amount":
try:
Decimal(value)
except InvalidOperation:
errors.append(f"{name}: 金额不是有效数字")
return errors
实际使用时,可把校验失败的记录放入待复核队列,而不是直接丢弃。币种代码、必填字段、金额小数位等规则,应根据你接收的票据种类和后续系统要求设定;不要在不确认业务规则时强行补默认值。
人工抽查:优先看不确定和高风险字段
刚开始处理一批新格式票据时,先逐条复核,确认字段定义和提示词符合实际需要。流程稳定后,仍应优先检查状态为 uncertain 的字段、图片模糊的记录、金额较大的记录,以及模型提取结果与业务规则不一致的记录。即使状态为 read,也可定期抽查,尤其是金额、日期和票据编号。
金额之间的关系可以作为复核线索,但不应一律当作硬性判错规则:折扣、舍入、税额展示方式和票据类型都可能影响合计。发现不一致时,回到原图确认,并记录是图片质量、字段定义、模型读取还是校验规则导致的问题。
敏感资料先确认数据流向
上传前先确认图片是否含有个人信息、账号信息或其他不应外传的内容,并查看所用服务对数据传输、保存、访问权限和模型训练的说明。只提取任务所需字段;如果无需长期保存原图,应制定明确的保留和删除规则。使用本地模型也不代表数据自动安全,还需检查运行环境、日志、备份和文件权限。
上线前,用一小批具有代表性的图片比较模型输出与人工结果,重点统计漏字段、格式错误和无法判断的比例。只有在结构稳定、异常能被拦截、复核责任明确后,才适合把它接入后续表格或自动化流程。

评论列表 (2条):
加载更多评论 Loading...