用 Solon ReActAgent 落地发票识别与智能报销

某中型企业财务部每月处理大量报销单,团队疲于奔命:

痛点 具体表现 后果
发票录入耗时 每张发票手工录入 财务时间被重复劳动占满
合规审核靠人眼 真伪、抬头、连号靠抽检 漏检风险
重复报销难查 同一张票被报两次偶发出现 资金损失
报销周期长 提交到到账周期长 员工体验差

一、业务方案

1.1 目标

  • 拍照后自动识别并填单
  • 常见合规规则自动校验
  • 重复报销可拦截
  • 异常单附上下文转人工

1.2 流程改造

text 复制代码
改造前:
员工贴发票 → 填写报销单 → 主管审批 → 财务初审 → 财务复审 → 出纳打款

改造后:
员工拍照上传 → Agent 识别+填单+合规检查 → 主管审批 → 通过后打款
                   ↓
           异常 → 人工介入(附检查摘要)

二、技术落地(ReActAgent 实现)

说明 :这是典型业务闭环,正确入口是 ReActAgent + 领域工具 + HITL

不要使用虚构的 implements ToolHarness.call()AiInterceptormemory.search()

2.1 模块结构

text 复制代码
com.example.finance/
├── tools/
│   ├── OcrTools.java                 # 发票 OCR
│   ├── ComplianceTools.java          # 合规与重复报销检查
│   └── PaymentTools.java             # 打款执行
├── hitl/
│   └── FinanceHitlController.java    # 超标/异常审批
└── ReimbursementAgentApp.java

2.2 工具定义(真实 API)

java 复制代码
import org.noear.solon.ai.annotation.ToolMapping;
import org.noear.solon.ai.chat.tool.AbsToolProvider;
import org.noear.solon.annotation.Param;

/** 发票 OCR 工具 */
public class OcrTools extends AbsToolProvider {
    @ToolMapping(description = "识别发票图片,返回发票代码、号码、日期、金额、购买方、销售方等字段")
    public String ocr_invoice(
            @Param(description = "发票图片 base64 或文件 ID") String imageRef) {
        // 生产环境调用 OCR 服务
        return """
            {
              "code":"044001900111",
              "number":"12345678",
              "date":"2026-06-18",
              "amount":1200.00,
              "tax":72.00,
              "total":1272.00,
              "buyer":"某某科技有限公司",
              "seller":"某某酒店",
              "category":"住宿"
            }
            """;
    }
}

/** 合规检查工具 */
public class ComplianceTools extends AbsToolProvider {
    @ToolMapping(description = "检查发票真伪、抬头、是否重复报销,返回合规结论")
    public String check_invoice(
            @Param(description = "发票号码") String invoiceNo,
            @Param(description = "购买方名称") String buyer,
            @Param(description = "价税合计") double total) {
        // 生产环境:税务查验接口 + 已报销库查询 + 公司抬头校验
        if ("12345678".equals(invoiceNo) && total > 0) {
            return """
                {
                  "invoiceNo":"12345678",
                  "titleOk":true,
                  "duplicate":false,
                  "taxVerified":true,
                  "result":"PASS",
                  "message":"合规检查通过"
                }
                """;
        }
        return """
            {
              "result":"REJECT",
              "message":"发票校验失败,请转人工"
            }
            """;
    }

    @ToolMapping(description = "按费用类别检查是否超过公司报销标准")
    public String check_policy(
            @Param(description = "费用类别,如住宿/交通/餐饮") String category,
            @Param(description = "金额") double amount) {
        if ("餐饮".equals(category) && amount > 400) {
            return "{\"result\":\"OVER_LIMIT\",\"limit\":400,\"amount\":"
                    + amount + ",\"message\":\"餐饮超标准,需主管确认\"}";
        }
        return "{\"result\":\"OK\",\"message\":\"符合标准\"}";
    }
}

/** 打款工具(敏感操作) */
public class PaymentTools extends AbsToolProvider {
    @ToolMapping(description = "提交报销打款。仅在合规通过且审批完成后调用")
    public String submit_payment(
            @Param(description = "报销单号") String reportId,
            @Param(description = "收款人") String employee,
            @Param(description = "打款金额") double amount) {
        return "{\"status\":\"submitted\",\"reportId\":\"" + reportId
                + "\",\"employee\":\"" + employee + "\",\"amount\":" + amount + "}";
    }
}

2.3 组装报销 Agent

java 复制代码
import org.noear.solon.ai.agent.AgentSession;
import org.noear.solon.ai.agent.react.ReActAgent;
import org.noear.solon.ai.agent.react.intercept.HITLInterceptor;
import org.noear.solon.ai.agent.session.InMemoryAgentSession;
import org.noear.solon.ai.chat.ChatModel;

ChatModel chatModel = LlmUtil.getChatModel();

HITLInterceptor hitl = new HITLInterceptor()
        .onTool("submit_payment", (trace, args) -> {
            // 打款一律人工确认;也可按金额阈值判断
            return "报销打款需财务确认";
        });

ReActAgent agent = ReActAgent.of(chatModel)
        .role("""
            你是企业智能报销助手。处理步骤:
            1. 先 OCR 识别发票
            2. 再做合规检查(真伪/抬头/重复报销)
            3. 再检查费用标准
            4. 全部通过后,生成报销摘要;需要打款时调用 submit_payment
            5. 任一检查失败,明确列出原因并建议转人工
            """)
        .defaultToolAdd(new OcrTools())
        .defaultToolAdd(new ComplianceTools())
        .defaultToolAdd(new PaymentTools())
        .defaultInterceptorAdd(hitl)
        .modelOptions(o -> o.temperature(0.0))
        .maxTurns(12)
        .autoRethink(true)
        .build();

AgentSession session = InMemoryAgentSession.of("expense_report_001");

String prompt = """
    员工张三提交差旅报销,部门销售部。
    已上传 1 张住宿发票图片,文件 ID=img_001。
    请完成识别、合规检查,并在可打款时提交支付。
    """;

String result = agent.prompt(prompt)
        .session(session)
        .call()
        .getContent();

2.4 重复报销怎么做?

不要 虚构 Memory memory; memory.search(...)

正确做法:

  1. check_invoice 工具内部查"已报销发票库"(DB/Redis)
  2. 报销成功后,由支付/归档服务把发票号写入已报销库
  3. 下次再报同一票号时,工具直接返回 duplicate=true
java 复制代码
// check_invoice 内部伪代码
if (reimbursedInvoiceRepository.exists(invoiceNo)) {
    return "{\"result\":\"REJECT\",\"message\":\"该发票已被报销过\"}";
}

2.5 超标场景

check_policy 返回 OVER_LIMIT 时:

  • Agent 不应直接打款
  • 应生成摘要给主管
  • 若你希望"超标也允许打款但必须审批",可在 HITL 策略里根据参数返回审批原因
java 复制代码
HITLInterceptor hitl = new HITLInterceptor()
        .onTool("submit_payment", (trace, args) -> {
            double amount = Double.parseDouble(args.get("amount").toString());
            if (amount > 2000) {
                return "单笔报销超过 2000,需财务负责人审批";
            }
            return "常规报销打款确认";
        });

三、业务效果(定性)

  • 录入从手工填单变成 OCR + 自动填字段
  • 合规检查可覆盖全量票据,而不是抽检
  • 重复报销可在工具层硬拦截
  • 财务从"逐张核对"转向"异常复核"

真实处理示例

员工上传:差旅住宿发票

Agent 链路

  1. ocr_invoice(img_001) → 金额 1,272,类别住宿
  2. check_invoice → PASS
  3. check_policy(住宿, 1272) → OK
  4. submit_payment → HITL 挂起
  5. 财务批准后续传,完成打款提交

若餐饮票 500 元、标准 400 元:

  1. OCR 成功
  2. 合规通过
  3. check_policy 返回超标准
  4. Agent 输出"需主管确认超标 100 元",不直接打款

四、同类场景扩展

业务场景 核心工具 关键规则 业务价值
采购发票校验 发票识别 + 订单匹配 三单匹配 对账效率提升
备用金核销 发票识别 + 预算查询 超标预警 核销周期缩短
客户开票 开票 API + 商品匹配 额度控制 降低开票错误
凭证生成 发票识别 + 科目映射 借贷平衡 制证提速
进项税认证 发票识别 + 税务接口 可抵扣判断 减少人工认证

五、落地建议

  1. 先做自动填单 + 人工确认,准确率稳定后再放开自动提交
  2. 优先覆盖高频票种(专票/普票)
  3. 重复报销检查必须做在工具层,不要只靠 prompt
  4. 打款必须 HITL,资金动作不能全自动裸奔
相关推荐
罗超驿2 小时前
15.Java异常处理:从束手无策到从容应对
java·开发语言
半夜修仙2 小时前
RabbitMQ的推模式和拉模式
java·分布式·中间件·rabbitmq·github·java-rabbitmq
掉鱼的猫2 小时前
用 Solon ReActAgent 落地发票识别与智能报销
java·llm·agent
_kerneler2 小时前
cgroup :限制cpu原理
java·服务器·前端
千桐科技2 小时前
🚀 qModel 算法模型平台v1.2.0 开源版发布!Python 模型接入链路全面升级,告别“能跑但上不了线”的尴尬
后端·算法·llm
唐青枫2 小时前
Java Jakarta Validation 实战指南:从参数校验到分组、自定义注解和全局异常处理
java
jvmind_dev2 小时前
第一章:AI Agent 如何重新定义 Java 性能分析
java·agent
从此以后自律3 小时前
Java 并发编程完整讲解
java·开发语言
Sylvia33.3 小时前
足球数据接口开发实战:如何用火星数据API盘活赛事应用
java·服务器·开发语言·数据库·python