Skip to content

个人报销智能审批:Java 本地计算引擎与大模型 Agentic 混合架构指南

归类:Java / 智能审计 状态:✅ 已验证

一、 架构背景与演进动机

在原有架构 (V1) 中,大模型需要同时处理非结构化的语义分析(如“判断是否为市内交通”、“是否存在敏感词”)与高精度的确定性计算(如“核对行程金额与发票总额是否一致”、“判断行程时间是否属于非工作日或超窗时间段”、“判断行程日期是否超过3个月时效限制”)。

由于大模型在处理数字运算和日期推算时存在天然的不确定性(LLM Hallucination),V1 架构面临以下痛点:

  1. 逻辑幻觉频繁:大模型难以精确计算非工作日重叠,特别是在面对逗号分隔的多日期多行程单时,经常算错月份或金额。
  2. 性能低下(响应迟钝):为防算错,必须迫使大模型开启“思考模式(enable_thinking)”,导致 API 延迟攀升了 3至5倍,极大影响了前端审批流转时效。

升级后的 Agentic 架构 (V2) 将数学计算/时间比对等确定性规则下沉到 Java 本地计算引擎(LocalAuditEngine) 预先执行,并将产出的结构化事实(Fact)注入大模型上下文,大模型仅需要做单纯的语义判断与润色输出,不仅保证了 100% 正确性,还能关闭思考模式使响应速度提升 90% 左右。


二、 服务端核心改动及结构设计

本次升级在 erp_wf_expenseExternal 服务端工程中新增了以下三个核心文件,无任何对现有生产业务代码的破坏性修改。

text
erp_wf_expenseExternal/
├── src/main/java/com/bj58/expenseExternal/vo/chatling/LocalAuditResult.java  # 预计算结果传输 VO
├── src/main/java/com/bj58/expenseExternal/utils/chatling/LocalAuditEngine.java  # 本地核心计算引擎
└── src/test/java/com/bj58/expenseExternal/utils/LocalAuditEngineTest.java       # 规则边界 JUnit 单元测试

1. 数据传输模型:LocalAuditResult.java

承载 Java 本地计算产出的预核对结论:

java
package com.bj58.expenseExternal.vo.chatling;

import java.io.Serializable;
import java.util.List;
import lombok.Data;

@Data
public class LocalAuditResult implements Serializable {
    private static final long serialVersionUID = 1L;

    /** 是否命中工作日超窗违规(工作日非09:00~18:00时间段出行) */
    private boolean timeWindowViolation;
    
    /** 违规的具体时间列表,方便大模型在输出意见时引用叙述 */
    private List<String> violatingTimes;

    /** 是否命中休息日违规(出行日期在restDays中) */
    private boolean restDayViolation;
    
    /** 违规的具体休息日出行日期列表 */
    private List<String> violatingDates;

    /** 是否超期违规(行程出行时间距离报销申请月超过3个月) */
    private boolean dateExpiredViolation;
    
    /** 违规的超期行程日期列表 */
    private List<String> expiredDates;

    /** 发票总金额与行程单总金额是否一致(求和后进行完全等值匹配) */
    private boolean amountMatched;
}

2. 工具引擎类:LocalAuditEngine.java

这是计算引擎的主体,使用了纯函数的设计思想。输入包含行程单、发票、休息日和提单时间,输出封装好的核对结论。

  • Rule 1 (金额撮合):对 invoicesitineraries 的总额进行 BigDecimal 累加比较。
  • Rule 2 (非工作日/工作日超窗):优先校验休息日,若是休息日则计入违规日期且不再进行超窗校验(防重复报告);否则校验出行小时/分钟是否超出 [09:00, 18:00] 窗口。
  • Rule 4 (超期校验):计算行程月度距提单申请月度偏离度,偏移量超 [0, 3] 视为超期。

三、 面向全栈/新同学的 Java 基础与工程规范解读

为了帮助前端或转全栈的同学快速上手后端工程代码,这里对其常见的 Java 头部定义及工程习惯进行拆解:

1. 包声明与导入(package & import

java
package com.bj58.expenseExternal.utils.chatling;
  • package:相当于 Node.js 中的目录结构。它是 Java 的命名空间定义,指明当前文件在项目的逻辑层级。Java 的包名通常与文件物理路径完全一致。
java
import com.bj58.expenseExternal.vo.ItineraryInfoVo;
import com.bj58.expenseExternal.vo.chatling.LocalAuditResult;
import org.springframework.util.CollectionUtils;
import java.math.BigDecimal;
import java.util.*;
  • import:类似于 JS 的 importrequire。Java 用它导入当前文件需要引用的其他类或第三方库。
    • CollectionUtils:来自 Spring 框架,用于防空指针的集合操作。
    • BigDecimal:Java 用于处理金融、金钱等绝对不能丢失精度的浮点数类型。

2. 什么是 VO?

import com.bj58.expenseExternal.vo 包中,VO(Value Object - 值对象) 指的是无业务逻辑、纯粹用于结构化承载和在层级间传递数据的实体对象。 通常只包含私有变量(如 private String boardingDate)和自动生成的读取/写入方法(Getter/Setter,通过类头部的 @Data 自动提供)。

3. 函数还是静态方法?

Java 不存在孤立的全局函数。所有的执行逻辑必须嵌套在类中。 为实现类似 JS 工具函数的效果,Java 设计了静态方法(public static

java
// 本地引擎的主入口方法,使用 public static 修饰
public static LocalAuditResult performCalculations(
        List<ItineraryInfoVo> itineraries, 
        List<TicketInfoVo> invoices, 
        List<String> restDays, 
        String applyDate) {
    ...
}

调用时直接用 LocalAuditEngine.performCalculations(...),无需先 new LocalAuditEngine(),属于典型的线程安全纯函数设计。


四、 本地与大模型调用关系拓扑

1. 业务全链路调用拓扑图

2. 为什么该混合架构可成倍降低大模型延迟?

  • 计算下沉:LLM 擅长阅读理解和语言表达,但不擅长复杂的逻辑分支判断和数据计算。在计算下沉到 Java 后,Prompt(提示词资产)的大小可减小约 80%
  • 参数裁剪:大模型无需开启 enable_thinking 进行冗长推理,可以直接根据 localAuditResult 中的事实字段生成直接结论。ChatLing 接口端到端耗时可从 ~15s 降低至 ~1.5s,极大地保护了核心 API 吞吐量。