ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Python接入Gemini 3.8 Flash实现票据视觉抽取与规则校验

Python接入Gemini 3.8 Flash实现票据视觉抽取与规则校验 把票据识别成一段文字并不等于能自动报销。真正麻烦的是总额和税额是否看反、币种是否缺失、合计能否对上、低清图片是否应该转人工。本教程用Python调用Gemini 3.8 Flash读取票据再用结构化Schema和本地金额规则做双重检查。你会得到一个“模型负责看本地代码负责判”的最小闭环而不是把财务决定交给一段自然语言。基础概念视觉理解不只是OCROCROptical Character Recognition光学字符识别关注“图里写了什么字”视觉语言模型还会结合版面、标签和相邻关系判断哪个数字是小计、税额或总额。它对复杂布局更灵活但也可能看错小数点、把折扣当费用或在模糊区域补出看似合理的内容。因此可靠流程需要三层约束输入层检查格式和大小模型层用Schema固定字段业务层重新计算金额并设置转人工条件。Google官方说明小图片可以以内联Base64数据提交整个请求小于20MB时最方便大文件或重复使用的图片应走Files API。技术流程是否本地票据图片格式与大小检查Base64内联给GeminiPydantic Schema约束输出Decimal金额复算差额小于0.01且字段完整?进入待审批队列标记原因并转人工注意结果进入的是“待审批队列”不是“自动打款”。即使金额数学上吻合也可能存在重复票据、伪造图片、超预算或不合规品类。环境准备使用Python 3.10或更高版本。官方google-genai仓库提醒当前大版本仍应固定在3.0以下示例选择2.24系列同时安装Pydantic。密钥从GEMINI_API_KEY读取。python-mvenv .venvsource.venv/bin/activate pipinstallgoogle-genai2.24,3pydantic2.8,3exportGEMINI_API_KEY你的密钥exportRECEIPT_PATHreceipt.jpgpython receipt_check.py完整代码importbase64importmimetypesimportosfromdecimalimportDecimal,InvalidOperationfrompathlibimportPathfromtypingimportLiteralfromgoogleimportgenaifromgoogle.genaiimporttypesfrompydanticimportBaseModel,Field,ValidationErrorclassReceipt(BaseModel):merchant:strField(description商户名称无法识别时为空字符串)currency:strField(descriptionISO币种如CNY无法判断时写UNKNOWN)subtotal:strField(description税前或小计金额十进制字符串)tax:strField(description税额没有时写0)total:strField(description最终应付总额十进制字符串)image_quality:Literal[clear,uncertain,unreadable]notes:list[str]defto_money(value:str)-Decimal:try:returnDecimal(value).quantize(Decimal(0.01))exceptInvalidOperationasexc:raiseValueError(f非法金额:{value})fromexcdefmain()-None:api_keyos.getenv(GEMINI_API_KEY)image_pathPath(os.getenv(RECEIPT_PATH,receipt.jpg))ifnotapi_key:raiseSystemExit(请先设置 GEMINI_API_KEY)ifnotimage_path.is_file():raiseSystemExit(f图片不存在:{image_path})ifimage_path.stat().st_size15*1024*1024:raiseSystemExit(示例只接受15MB以内图片更大文件请使用Files API)mime,_mimetypes.guess_type(image_path.name)ifmimenotin{image/jpeg,image/png,image/webp}:raiseSystemExit(f不支持的图片格式:{mime})image_b64base64.b64encode(image_path.read_bytes()).decode(ascii)clientgenai.Client(api_keyapi_key,http_optionstypes.HttpOptions(timeout30_000),)try:responseclient.interactions.create(modelgemini-3.8-flash,input[{type:text,text:(读取票据可见内容。不要猜测被遮挡字段金额保留两位小数。若画面不足以确认降低image_quality并在notes说明。)},{type:image,data:image_b64,mime_type:mime},],response_format{type:text,mime_type:application/json,schema:Receipt.model_json_schema(),},)receiptReceipt.model_validate_json(response.output_text)subtotal,tax,totalmap(to_money,[receipt.subtotal,receipt.tax,receipt.total])deltaabs(subtotaltax-total)needs_review(receipt.image_quality!clearorreceipt.currencyUNKNOWNordeltaDecimal(0.01))print(receipt.model_dump_json(indent2))print(fcalculation_delta{delta}; needs_human_review{needs_review})except(ValidationError,ValueError)asexc:raiseSystemExit(f模型结果未通过本地校验:{exc})fromexcexceptExceptionasexc:raiseSystemExit(fAPI调用失败请检查网络、配额和权限:{exc})fromexcif__name____main__:main()逐段解释Receipt把模型输出限制为七个字段。金额故意用字符串而非浮点数因为二进制浮点会产生0.1 0.2一类精度问题进入业务代码后再转成Decimal。image_quality是三选一枚举让“看不清”成为显式状态而不是逼模型编造答案。输入检查把大小控制在15MB给官方20MB的整个内联请求上限留出提示词和编码余量。Base64会比原文件更大所以不要把20MB原图直接塞进去。对于大图、PDF或同一图片多次询问应上传到Files API后引用URI。模型返回后Pydantic先验证结构本地代码再计算subtotal tax - total。差额超过0.01、币种未知或图像不清晰时一律转人工。这里没有要求模型输出“置信度百分比”因为未经校准的自报分数容易制造虚假确定性。图片送入模型前先做哪些处理预处理的目标不是把票据“修得更像真的”而是让可见证据更稳定。移动端可以在本地检测四角、纠正旋转并提示用户补光不要过度锐化或涂抹因为这可能改变小数点和数字边缘。原图与处理后图片应使用不同哈希并建立关联审核人员需要能够回到原始证据。分辨率也不是越高越好。超大图片增加上传时间和成本却未必改善小字体过度压缩又会让“6”和“8”混在一起。最实用的方法是用自己的票据集合做分档实验按短边像素、压缩质量和拍摄条件分组观察关键字段完全匹配率而不是只看“模型给了答案”的比例。如果一张图片含多张票据不要让模型自行猜边界后合并总额。先做页面或票据分割为每个裁剪区域生成独立ID再分别抽取和复算。多页发票则应保留页码与文档ID防止第一页的小计与最后一页的总额被错误组合。预期输出清晰样例可能得到{merchant:示例咖啡,currency:CNY,subtotal:46.00,tax:0.00,total:46.00,image_quality:clear,notes:[]} calculation_delta0.00; needs_human_reviewFalse本次任务所在机器只有Python 3.9.6而官方最新SDK要求Python 3.10。我对示例做了py_compile语法检查但没有安装依赖、没有提供密钥也没有调用线上API或识别真实票据这不等于模型效果验证。常见错误把20MB当原图上限内联限制覆盖整个请求Base64还会膨胀应预留空间。用float处理金额可能产生精度误差使用Decimal并统一两位小数。提示模型“必须给答案”模糊图片会诱发猜测要允许uncertain和unreadable。只校验JSON结构结构正确不代表合计正确还要做本地复算和业务规则。忽略图片隐私票据可能含姓名、地址和卡号上传前应脱敏并确认数据政策。适用与不适用场景适合低风险报销预审、收据归档、商品标签抽取和人工审核辅助。不适合单凭图片自动打款、判断票据真伪或处理高额异常交易。视觉模型能读内容却不能替代发票查验平台、重复报销检测和财务授权。工程化改进生产环境应保存图片哈希而非随意复制原图建立不同拍摄角度、低光、折痕和多语言票据的评测集分别统计字段准确率、金额完全匹配率和人工转交率。对高频商户可增加模板规则但不要让模板覆盖模型原始证据。模型或SDK升级时先做影子流量对比再调整自动通过阈值。评测时应按字段赋予不同风险权重商户名错一个字可能仍可搜索总额错一位小数却不能接受。可以同时记录字符准确率、字段完全匹配率、金额差错率与“应该转人工却自动通过”的漏拦率。最后一个指标最关键因为系统价值不在于少点几次鼠标而在于把高风险错误挡在付款之前。还要加入重复报销检查。模型抽取出的票号、日期、金额和商户可以生成候选键但最终去重应结合原图哈希、感知哈希与历史记录相似并不等于重复阈值附近仍需人工确认。删除原图或执行付款都属于高风险写操作不应由这个只读识别流程触发。若业务覆盖多币种还要保存汇率来源和生效时间不能让视觉模型自行换算。含服务费、折扣、预授权或小费的票据也不能只套“小计加税等于总额”这一条公式应先按票据类型选择规则。无法识别类型时宁可转人工也不要为了提高自动通过率而放宽金额差额。最终审批页面应同时展示原图裁剪、模型字段、本地复算和触发的风险规则让审核者看到证据而不是只看到一个绿色结果。5分钟实践题给Schema增加invoice_date和receipt_number再在本地加入一条规则日期晚于今天或票号为空时必须转人工。用一张清晰票据和一张故意裁掉日期的图片比较结果。你更愿意让视觉模型自动通过哪类低风险单据又会把哪类永远留给人工关注「蜗牛聊AI」一起看懂技术变化背后的真正机会。本文首发于 java4u.cn转载请注明出处。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进