Java标准HTTP调用多模态模型的票据抽取前置校验

视觉模型能读票据,不代表每张图片都该发给它。上传错文件、超大图片和"申请金额比识别金额高很多"才是报销自动化最常见的事故入口。答案很朴素:Java 先校验文件,再让模型抽取字段,最后用 BigDecimal 做金额比对。你会拿到一个可运行的 Java 17+ 最小程序;它只演示请求构造与本地门禁,不把模型的文字当成付款指令。

Gemini 的图像理解文档说明可将图像作为多模态输入。这里采用 REST(Representational State Transfer,基于HTTP的接口风格)而非假定某个 Java SDK 的版本;请求超时、密钥和响应状态都显式处理。示例未在本次任务中实际调用线上 API;本机的 javac 缺少 Java 运行时,未能编译,已完成静态审阅。

flowchart LR A[上传票据] --> B{类型、大小校验} B -->|失败| X[拒绝并提示] B -->|通过| C[视觉模型抽取] C --> D[人工/规则解析金额] D --> E{金额容差校验} E -->|通过| F[待审批] E -->|不通过| G[人工复核]

环境与运行

需要 JDK 17+,不需要 Maven 依赖。设置 export GEMINI_API_KEY='你的密钥',把示例保存为 ReceiptGate.java,编译运行:javac ReceiptGate.java && java ReceiptGate receipt.jpg 128.50。当前端点与模型名请以你的账号和官方图像理解文档为准。真实项目不应把原始票据长期写入日志。

java 复制代码
import java.io.*;
import java.math.BigDecimal;
import java.net.URI;
import java.net.http.*;
import java.nio.file.*;
import java.time.Duration;
import java.util.Base64;

public class ReceiptGate {
  static void check(Path file) throws IOException {
    String n = file.getFileName().toString().toLowerCase();
    if (!(n.endsWith(".jpg") || n.endsWith(".jpeg") || n.endsWith(".png")))
      throw new IllegalArgumentException("只接受JPG/PNG");
    if (Files.size(file) > 5 * 1024 * 1024) throw new IllegalArgumentException("图片超过5MB");
  }
  static boolean within(BigDecimal declared, BigDecimal extracted) {
    return declared.subtract(extracted).abs().compareTo(new BigDecimal("0.01")) <= 0;
  }
  public static void main(String[] args) throws Exception {
    if (args.length != 2) throw new IllegalArgumentException("用法: 图片路径 申报金额");
    String key = System.getenv("GEMINI_API_KEY");
    if (key == null || key.isBlank()) throw new IllegalStateException("缺少GEMINI_API_KEY");
    Path file = Paths.get(args[0]); check(file);
    String b64 = Base64.getEncoder().encodeToString(Files.readAllBytes(file));
    String body = "{\"contents\":[{\"parts\":[{\"text\":\"仅返回票据总金额\"},"
        + "{\"inlineData\":{\"mimeType\":\"image/jpeg\",\"data\":\"" + b64 + "\"}}]}]}";
    HttpRequest req = HttpRequest.newBuilder(URI.create(
        "https://generativelanguage.googleapis.com/v1beta/models/gemini-3.8-flash:generateContent"))
        .timeout(Duration.ofSeconds(25)).header("x-goog-api-key", key)
        .header("Content-Type", "application/json").POST(HttpRequest.BodyPublishers.ofString(body)).build();
    HttpResponse<String> res = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build()
        .send(req, HttpResponse.BodyHandlers.ofString());
    if (res.statusCode() / 100 != 2) throw new IOException("API状态=" + res.statusCode());
    System.out.println("模型原始响应请交由受控JSON解析器提取金额,HTTP=" + res.statusCode());
  }
}

check 在上传前拒绝类型和大小,避免把任意文件或巨图带入模型成本;within 使用 BigDecimal,不能用 double 比较货币。请求对连接设置5秒、整体设置25秒超时;非2xx响应直接失败。为了保持示例只用 JDK,代码没有脆弱地用字符串"猜"模型金额:生产中应要求模型按 JSON Schema 返回 currency、amount 和 confidence,再用 Jackson 等成熟 JSON 库解析。

预期是成功时打印 HTTP 状态,缺密钥、文件类型不符、超过5MB或网络/API失败时抛出清晰异常。常见错误:图片实际为PNG却硬写JPEG MIME;把模型输出中的逗号金额直接转数值;将0.01容差用于不含税/含税不同口径;失败后无限重试造成重复上传。适合做"进入审批前的辅助核验",不适合自动付款或替代税务规则。

工程化时需加入病毒扫描、对象存储短期签名URL、JSON响应校验、票据哈希去重、人工复核与保留期限。5分钟实践:把 within 的容差改为按币种表配置,并为 128.50 与 128.52 写两条单元测试。

如果票据金额与员工填写金额不一致,你的流程会自动拒绝还是进入复核?

关注「蜗牛聊AI」,一起看懂技术变化背后的真正机会。


本文首发于 java4u.cn,转载请注明出处。

相关推荐
智驭未来掌门人1 小时前
AI 的“手和眼”:一场关于感知与行动的静默革命
人工智能
柯南46681 小时前
【AI工程师精讲】05:FlashAttention:它不是算得更快,是搬得更少
人工智能·ai编程
C++ 老炮儿的技术栈1 小时前
char (*csConnectName)[256]; 和 char csConnectName [256] 有什么区别
java·c语言·开发语言·c++·人工智能·算法·c
Rocky Ding*1 小时前
VideoChat3 深度解析:视频理解的效率,来自时空压缩与主动感知的共同设计
论文阅读·人工智能·深度学习·机器学习·aigc·ai-native·视频理解
禹凕1 小时前
深度学习之激活函数(Deep Learning about Activation function)
人工智能·深度学习
loulanyue_1 小时前
脑机接口:意动·芯动·行动——读同济医院副院长廖家智2026云栖演讲
人工智能·深度学习·云栖大会
Dcr_stephen1 小时前
电商 Agent 实践:为什么运营分析与产品调研,需要两套相反的工作流?
人工智能
Omics Pro1 小时前
全新可重分析!代谢组质谱专用
数据库·人工智能·算法·机器学习·自然语言处理
一只废狗狗狗狗狗狗狗狗狗1 小时前
Network架构1——卷积神经网络CNN
人工智能·算法·cnn