动态规则的 Agent 实践:用确定性工程驾驭 LLM 的资产智能验收

0 阅读24分钟

一、需求背景:为什么不用传统规则引擎?

先看一个真实的验收场景:财务同学收到一张验收单,点开附件是一份25页的PDF合同、几份扫描件,要核对合同里的约定数量、甲方主体、落款公章、费用发生期间——每一单都是如此,且月均验收单审批量数百单。

抽象一下,这个环节有三个天然难点:

费用类型多、流程各异: 资产验收涉及几十种费用类型,不同费用类型的验收流程与规则各不相同。规则非固定,持续调整: 业务调整、风控要求变化、审计口径更新,带来规则的增删改,规则是「活」的。单据核对数据量大: 验收单头、多条明细、关联合同、发票、各类附件,人工逐条比对费时费力。

常规思路是「把规则翻译成代码」,但在这里走不通:

规则频繁变化: 每次调整都要走「开发—测试—发布」全流程,响应成本高。规则作用对象是非结构化材料: 大量规则需要核对PDF合同、扫描件、图片附件、印章签字,传统程序「读不懂」这些材料。规则表达是自然语言: 「合同甲方主体需与验收单公司主体一致」「附件需加盖供应商公章」,本身就是语义判定而非数值计算,这些恰恰是大语言模型擅长的事。

于是思路定了:让LLM负责「读懂材料、按规则比对」,工程系统负责「流程编排、质量保障、可追溯」: 规则以文本形式配置化下发,不再硬编码进代码。

二、整体架构: 线上流量整体处理流程

系统由两个服务组成:

线上流量整体处理流程

线上流量处理时序图

链路上有几个关键设计。

异步受理 + 回调交付: 一次LLM验收要解析多份材料、执行多条规则,耗时可达分钟级,不能阻塞审批主流程。资产Agent系统收到请求后秒级返回「已受理」,后台执行完成后回调资产管理系统。回调所需的上下文由消息自带,而非服务端保存。

幂等与兜底: 以「审批实例 + 财务节点轮次」构造幂等批次号。加签、重试、退回再提交,都不会重复触发。提交失败同步落整明细失败行,查询侧懒超时兜底,保证任务不会「挂起不动」。

一套通道,三种流量: 线上流量、试跑流量和回测流量,共用同一套提交与回调链路,仅靠流量类型+上下文区分,新的流量模式零成本复用全部基建。不同流量完全隔离,避免 试跑/回测 流量污染线上流量,影响业务。

图片和文本双通道处理: Agent对图片和文本使用独立通道进行处理,既可以降低token消耗,又能保证识别的准确性。

三、质量体系:反馈、试跑、回测与发布门禁

验收规则本质上是一段提示词文本,改规则就等于改线上行为。规则既然非固定、调整会经常发生,就必须有一套轻量的「测试—回归—发布」机制——否则没人敢动规则。而要回答「新规则判得对不对」,前提是有一份标准答案。我们的标准答案不靠人工出题,而是从日常复核中自然长出来,所以先讲裁判,再讲考试。

用户反馈:系统的「标准答案」从哪里来

审批人在复核AI判定结果时,可以对每条判定做轻量标注——认可/不认可;不认可时必须填写文字原因。系统在反馈时记录两个快照:当时AI的判定结果、当时生效的规则版本。标准答案由此自动推导:认可→标准答案=AI原判定;不认可→标准答案=AI判定的反面(通过↔拒绝)。

反馈数据有两个去向:一是作为回测的比对基准,二是进入试跑侧,让系统对照反馈计算新旧版本的一致率。

试跑与回测

目的: 规则正式发布前,用真实的验收单对草稿版本规则演练,完全不影响线上真实验收单与审批流;可以理解成是业务同学debug规则的入口,让业务同学每次创建新的规则版本都可以实时感知当前版本的效果。实现: 管理端选择若干历史/在途满足条件(时间范围,费用类型一致等)的验收单号发起试跑;系统实时重建验收上下文(头、明细、附件、合同),「草稿版本」与「当前生效版本」各跑一遍,结果落在独立的旁路任务中——不回写验收单状态、不触达审批。试跑结果支持新旧版本并排对比,自动计算判定一致率,并对照用户反馈给出建议通过结论。

回测数据集: 线上流量在每次验收时自动沉淀重放快照(验收上下文+当时的规则版本),构成可重放的历史样本池。结合用户反馈,样本按「AI 判定 × 人工标准答案」划分为四类,并分层抽样:

分层抽样的目的:如果只用随机抽样,集合很容易被简单样本主导,测出的高分没有说服力。假阳/假阴恰恰是规则调整最该盯的地方,重点关注新规则有没有修复历史误判,有没有又引入新的误判。

回测通过条件(双阈值)需要两个条件同时满足才算通过:

  • 数量阈值: 四类有效样本(带人工标准答案)数量达到配置下限,避免小样本下结论。
  • 质量阈值: 回测结果与标准答案的一致率超过配置阈值(如≥90%)。

规则版本的发布被回测状态门禁卡控:回测状态为 「回测通过」 或 「试跑替代回测通过」,允许常规发布,版本指针切换即生效;其他状态(待回测/回测不通过),只能走强制发布通道,必须是权限用户才能操作,并全程留痕。另外,任何对草稿的编辑都会自动重置回测状态——防止用旧版本的回测结论,放行新版本的规则。

用试跑替代回测:

回测门禁冷启动死锁

「回测通过才能发布」变成了一道永远过不去的门。为打破死锁,我们允许权限用户在试跑完成后人工审阅试跑结果(逐条判定、新旧版本对比、判定依据与材料出处),确认无误后将回测状态手动更新为通过——用人工对试跑结果的判断,替代回测对历史标准答案的自动比对。该通道严格限定在冷启动场景:权限控制、全程留痕、草稿一经编辑即重置回测状态;样本池充实后,规则调整回到标准回测通道。

试跑替代回测示例图

三种流量的数据隔离

试跑/回测与线上流量共用同一套提交、执行、回调链路,前提是严格的隔离——既不能影响线上验收,也不能污染回测样本池。隔离设在四道闸门:

三种流量的四道隔离闸门

由此,试跑/回测完整复用了生产级执行链路,又互不干扰:既不影响线上审批,也不污染发布门禁所依赖的数据集。把这些环节连起来,就是一个闭环:

数据飞轮闭环

规则调整不再是「拍脑袋上线」,而是每一次修改都能被历史标准答案检验。

四、Agent 设计:确定性 Workflow 如何驾驭 LLM

前面是业务侧的质量体系;这一章深入资产Agent系统内部——它是「规则即配置」能成立的技术底座。下文的技术细节均来自实际代码与实测数据。

总体思路:让 LLM 只做它最擅长的两件事

我们没有做对话式的多Agent架构,而是采用确定性工作流编排的pipeline式设计,把一次验收拆成固定阶段:

LLM只出现在两个绿色环节: 从材料中提取事实、基于事实与规则做比对判定。其余环节:文件解析、印章检测、数值核对、结论校验、超时兜底,全部由确定性代码完成。

收益是:行为可预期、环节可测试、问题可定位。对生产系统而言,这比「看起来很智能」重要得多。顺带回答一个常见问题:为什么不用自主循环的 Agent? 因为这个任务是方向明确的工作流,不是探索性质的问题,阶段划分、输入输出、成功标准在设计时就已确定。探索型任务里循环反思有价值,但方向明确的流程里,循环引入的是不可预测的步数与行为,恰好与「验收结论必须可预期、可回归」的要求相反。我们把「提升单次执行质量」的力气花在拆解阶段、压缩判定输入、关键判定投票上,而不是让模型自己决定下一步去哪。这与前端场景常见的取舍一致:能用TypeScript确定性表达的流程,不交给运行时自适应。

分层架构及处理流程

路由层                        薄路由:读请求原文 / 上下文标识日志 / 错误码映射,零业务
  └─ 异步编排层                受理校验 / 背压槽位 / 后台任务 / 失败重试
       └─ 执行内核             并发信号量 / 预算 / 材料准备 / 分阶段调度 / 超时兜底
            ├─ 解析模块        纯函数:入参解析 + 金额精度处理 + 契约兼容
            ├─ 映射模块        纯函数:规则翻译 / 提示词模板 / 结果映射
            └─ 复用既有引擎:
                 ├─ 审核引擎    并发执行、多值结果协议、多模型投票
                 └─ 材料处理    下载、归类、抽取、渲染、要素检测

路由零业务: 职责仅四项——读raw body、四级校验漏斗、背压占位、后台注册。业务编排全部下沉 async.ts;错误码映射统一收口在入口,网关接入不感知内部结构。

内核单一职责: 执行内核只认任务不认请求——只管并发信号量、预算、材料准备、分阶段调度四件事。受理与回调全部上提至异步编排层,同一内核可被同步、异步、重试三种入口复用。

规则与引擎解耦: 解析、映射两模块全为纯函数——入参解析、金额精度、契约兼容、规则翻译、提示词模板、结果映射,无IO、无副作用,业务规则的任何变化都收敛为可单测的函数改动;底层审核与材料能力抽象成引擎服务,多模型投票、要素检测等能力只实现一次,上层编排迭代不触碰引擎代码。

一次请求的生命周期:

受理、执行、回调三阶段生命周期

时序图第一行的「入参校验 + 回调块校验 + 背压占位」值得展开,受理入口其实是全链路设计密度最高的一段,它是一个多级串行的 fail-fast 漏斗,任一级失败立即拒绝,不注册后台任务、不回调,部分失败码和上游动作举例:

这样设计的好处在于,一错误码即重试策略: 多失败码设计让上游用代码就能区分「改了也没用的失败」与「稍后再试即可成功」,从而上游可以根据返回 code 采取不同的操作;二受理校验全通过才受理: 受理校验失败直接前置拒绝,代价远低于让任务跑满超时时间后被懒超时作废。

材料准备流水线

材料准备流水线示例图

核心原则是「每份材料只下载一次,各取最适合的形态」,实现的要点包括:

  • 一个buffer复用到底,下载是重IO操作,同一份文件只下载一次,分页提取、触发页渲染、盖章检测共享。

  • PDF分型智能降级,按单页字符阈值自动分流——电子合同(文本型)零成本直取文本层,扫描件(图片型)才走渲染+OCR,两条路径输出统一结构。

  • 按需渲染,只有存在盖章/签字类视觉规则时才渲染页面截图,纯文本规则不为此买单。

  • OCR与Vision各司其职,OCR只做它擅长的「文字关键词定位」——判断哪些页面可能涉及盖章/签字、把扫描件文本回填进文本槽;Vision只做OCR干不了的「图形判定」——这页有没有章、有没有手写签字。没有用最弱的手段(纯 OCR 判印章)去做最关键的判定,也没让视觉模型去啃整篇文本找触发词。工具分工正确,是这套混合方案能成立的地基。

双制品模型:提取与判定分离

每条上游规则被翻译为两条引擎制品:

以「验收单数量需与合同约定数量一致」这条规则为例,两段制品的真实输出:

Extract制品(事实,逐字摘录带溯源):

{
"needed_fields": ["合同约定数量", "验收单数量"],
"extracted": [
    { "field": "合同约定数量", "value": "20", "source_file": "合同.pdf", "source_page": 3, "source_method": "ocr" },
    { "field": "验收单数量", "value": "20", "source_file": null, "source_page": null, "source_method": "extraContext" }
  ],
"judgment": "NULL",
"reason": "合同数量取自 p3;验收数量取自单据结构化字段"
}

Aggregate制品(结构化比对项,无自由文本):

{
"checkResult": 1,
"comparisons": [{
"leftName": "合同约定数量", "leftValue": "20", "leftSource": "《合同.pdf》p3",
"rightName": "验收单数量", "rightValue": "20", "rightSource": "补充上下文",
"relation": "一致"
  }]
}

单一职责带来四个好处:

  • 可审计: 判断理由必须引用具体值与来源(禁「均一致」这类笼统表述)每个值可回溯到页码。

  • 控成本: 判定段只看事实摘要,不重读原始材料,token与耗时可控。

  • 可短路: 确定性判断(如盖章)可以在提取段整段跳过LLM(见 4.7)。

  • 可校验: 两段均为 JSON输出,模型输出四值→规则判定二值映射可以做成纯函数校验。

多模型投票:把单点误判压在关键判定上

验收是AND语义,一个关键事实判错,整单结论就错。而LLM对同一输入的判定存在噪声:扫描件模糊、电子签章不是传统圆形红章、骑缝章跨页,这些都会让单一模型「看走眼」。尤其视觉判定(该页有无章)的准确率是整套方案的下限:单页把「有章」误判成「无章」,一份实际合规的投标就被整份否决,单点误判的代价太高,必须用机制对冲。

我们的做法是多数表决(majority vote):每个关键判定不交给单一模型,而是主模型+一组投票模型并发作答,取得票最多的答案。投票的适用范围是有边界的——只在关键判定上付费:

投票机制在两处发挥了不可替代的作用:一是降低输出漂移,把一个长问题拆解成多个子问题(对应多个function),每个function返回值固定在一个小合法集内(是/否),模型只在小闭集上作答,再经多数表决收敛——同一输入反复调用的结果稳定性显著高于让模型自由发挥一段长答案。二是盖章确认(见4.7 Stage 3),Vision二分类「是否印章」走三模型投票,把「单模型看图判章」的噪声压住后,前两级确定性过滤才敢放心把不检出的页面直接判「无章」。

盖章检测三级流水线:确定性优先,LLM 兜底

「附件需加盖供应商公章」是验收高频规则。如果直接把整份合同扔给Vision模型问「盖章了吗」,又贵又不稳。我们的做法是三级流水线,前两级零LLM成本:

盖章检测三级流水

每个阈值都不是拍脑袋,而是来自对真实合同的逐页实测(以一份25页文本型合同为验证样本):

防幻觉组合拳

财务场景容不得「看起来对」。容易出幻觉的点,逐一用工程手段封死:

上表中的每一条手段都有对应的代码落点,不依赖模型「自觉」。其中逻辑最绕的两条——回答值域的收敛、金额从报文到结论——各用一张图进一步讲解,**四值闭集契约:把模型的回答收敛进确定值域。**所有 LLM 调用都经统一的 ask 引擎,回答值域先在类型层面钉死。

export type CheckVal = 'true' | 'false' | 'NULL' | 'N/A'
// true=通过;false=不通过;NULL=无需判断;N/A=无法判断(如 .zip/.pptx 等无法解析的材料)

闭集是双重落地的:提示词侧声明「只返回是、否、NULL或N/A」,代码侧对任何越界回答强制回落到合法值末位——模型无论输出什么,都产不出四值之外的结论。从原始回答到最终结论的完整收敛路径见下图,兜底方向永远保守:最坏是「无法判断、交人工」,而不是编造一个 true/false。

金额链路:从原始报文到最终结论,每一环都不信任模型。BigDecimal字面量预提取、数值双向仲裁、结论派生对账三个手段串成一条流水线,下图即一笔金额的完整旅程——模型只出现在第③环,其余全是确定性代码:

可解释的结构化输出

LLM不写自由文本的判定理由,而是输出结构化比对项(左值/右值/各自来源/关系),由代码按固定模板拼装成富文本。字段抽不到时走缺材料形态:

{ "leftName": "合同约定数量", "relation": "缺失", "expectedSource": "采购合同正文" }

拼装为「合同约定数量缺失(应出自采购合同正文),该部分无法判断」——而不是让 LLM 自由发挥一段「经查验未能获取相关信息」的模糊话术。最终呈现给审批人的每条判定都附带规则原文与材料出处(哪份合同、第几页),人工复核一目了然,也天然支撑了反馈标注。

以上是缺材料形态;常规形态是可比较项——除左右值与关系外,每项还必须携带各自来源(《文件名》+页码,或「补充上下文」)与 ruleRef(从子规则列表逐字引用的一条规则原文,供代码把该理由挂回规则)。以「各方主体名称应一致」为例,aggregate段输出的比对项:

{
  "leftName": "合同甲方主体",
  "leftValue": "XXX有限公司",
  "leftSource": "《xxx协议.pdf》p3",
  "rightName": "验收单公司主体",
  "rightValue": "XXX有限公司",
  "rightSource": "补充上下文",
  "relation": "一致",
  "ruleRef": "1、各方主体名称应一致"
}

relation是闭集,渲染形态共四种、各配一条专属模板(表中为剥去 标签层的纯文本):

程序按固定模板将其拼装为带标签的语句,并跟前端约定好展示规则,生成的结果里每个关键部位都由代码拼装,LLM全程不感知HTML。通过程序拼接的方式能保证返回结果的一致性,示例:

<judge verdict="通过" rule="各方****一致性"></judge><br/>来源: <sources><a href="https://xxxx.pdf">《合同A.pdf》</a>、<a href="https://xxxx.jpeg">《图片B.jpeg》</a></sources><br/>理由: 经比对,验收界面公司主体()与合同、补充协议、订单附件及验收附件中的我方主体名称完全一致;验收界面供应商主体()与上述各文件中的对方主体名称完全一致,满足主体一致性要求 因此整体判断为通过<br/><b>因此整体判断为通过。</b><br/>置信度: 85% 

前端展示结果示例图

面向生产的工程护栏与踩坑实录

背压与预算分层: 在途任务上限设置阈值,满则直接拒绝——静默接受一个注定超时作废的任务,不如让上游立即按失败落库,避免雪崩。

失败显性化: 材料下载失败会同时注入提示词与判定理由,明确告知「本次判定基于不完整材料」,禁止静默降级。

上下文工程: 文本预算按文件数均分,防止主合同挤掉关联合同;数值字面量在JSON解析前原文保留。

Prompt Cache 稳定前缀断点: system消息拆为两个part——稳定模板(角色设定 + 输出契约 + 防注入声明)打cache_control:ephemeral断点,规则指令、素材、图片全部后置。Prompt Cache是前缀匹配,这样组织后稳定模板可跨规则、跨请求、跨投票模型命中缓存。缓存的命中发生在远端LLM网关:网关把断点之前的内容tokenize后计算哈希,在后端KV Cache存储中查找匹配的缓存块,命中则预填充阶段直接从缓存读取KV,无需逐token重新计算——省的是真金白银的算力与首token延迟,而非本地缓存那种响应级别的复用;user段一律不打断点——它每规则都变,参与断点会使缓存永不命中。

配置驱动: 预算/背压/超时/重试全部走配置文件,调参不发版。

全链路可观测: 入口即打身份日志(含入参全文,问题可现场重放);受理、执行、回调三段日志同一 ID串联,一次grep定位全链路;受理成功与背压拒绝日志均携带pending水位,容量回溯无需监控预埋。

敏感信息脱敏: 合同与单据材料在进入LLM前先过一道脱敏——手机号、邮箱、打印人员信息直接抹除;经营指标(GMV/ROI/UV/PV/DAU)等敏感词按配置表替换为占位符。脱敏发生在提示词组装阶段,LLM拿到的材料永远是不含敏感信息的版本,判定所需的主体名、金额、日期等字段不受影响。敏感词表走配置驱动,新词条加入无需发版。

几条用真实教训换来的经验:

五、效果展示

以下为系统实际效果图,沿「审批复核 → 规则配置 → 版本管理 → 试跑/回测」的主线展示。

验收审批页 —— AI 审批结果详情抽屉页

ai批次处理结果汇总

验收审批明细

验收单流转到财务审批节点后,AI 判定结果同步透出,点击打开「AI 审批结果详情」抽屉:

  • 规则判定结果汇总: 按通过/不通过聚合本单规则数,审批人一眼掌握全单情况,并返回综合判定结果:通过/不通过/需人工复核。

  • 规则判定结果明细: 逐规则展示规则编号、AI 判定结果、规则原文与判定依据;判定依据中的数据值(费用类型、金额、合同编号等)以超链接呈现,点击可查看该数据的数据来源,便于逐项核实。

  • 来源: 点击文件链接,可以打开分析依据的材料;

  • 反馈: 每条判定底部提供「正确/错误」反馈按钮,审批人的标注沉淀为标准答案,反哺试跑/回测。

AI验收规则配置页面及版本管理

规则配置: 配置页以列表形式展示所有规则维度的信息:规则编号、规则名称、费用类型、等级(阻断/严重/一般)、状态(启用/禁用)、创建人、创建时间等,顶部支持按费用类型、创建人、状态组合筛选,方便快速查询定位某条规则;操作列提供详情、编辑、版本管理、禁用、删除等入口。

版本管理: 每条规则都支持多个不同的版本管理:页面列出规则的全部版本(草稿、v1、…)及是否生效(「生效中」/-)。每个版本都支持试跑和回测,草稿版本额外提供编辑与回测入口;回测通过后方出现发布按钮,规则发布被回测状态门禁卡控。

试跑与回测结果

记录历史试跑结果;展示草稿版本与当前使用版本的结论差异;如果有人工反馈(金标),则展示金标结果。

回测结果列表、与标准答案的一致率、回测状态。

用户反馈入口

业务反馈入口

对于大模型基于每条规则的判断结果,用户可以点击判定底部的「正确」或「错误」按钮进行反馈:点击「正确」即认可AI判定;点击「错误」则弹出错误反馈弹窗,需要用户提交错误反馈的原因(不认可时必须填写,保证反馈信息量)。

反馈数据有两个去向: 一是推导标准答案、反哺试跑/回测门禁(见 3.1);二是错误原因文本本身——程序基于余弦相似度 将不同反馈归于不同的语义簇(Semantic Cluster),便于后续从不同的语义簇中抽取代表性数据生成few-shot,让模型从同类历史错误模式中学习,规则迭代越用越准。

六、数据结构设计

附上支撑「试跑/回测/反馈」的核心表结构,方便想落地的同学参考。五张表、一条主线:一切判定都挂到「规则版本」上,让历史可追溯、标准答案可推导。

设计思路:

  • 三种流量共享重放快照表,靠流量类型+批次编号逻辑隔离,不为试跑/回测单独建表,读侧按流量类型与批次号前缀各取所需——这是「一套通道三种流量」在存储层的直接映射,新流量模式零建表成本。

  • 规则版本号贯穿结果、快照、反馈三张表,每条历史判定都能回答「当时用的是哪个版本的规则」——没有这个外键,「反馈推导标准答案」「草稿编辑自动重置回测状态」都无从谈起。

  • 标准答案在写反馈时固定,设置为金标集,回测/试跑只需逐条比对模型反馈结果和金标即可算一致率,不需要在查询时做任何推导——把一次性的推导成本留在写入时,把高频的比对做成零逻辑。

七、小结

面对「费用类型多、规则非固定、核对量大」的验收场景,这套方案给出的答案是:

  • 规则配置化: 规则以文本下发、语义路由,改规则不改代码。

  • 判定 AI 化: LLM 负责材料理解与规则比对,确定性 workflow 保证流程可控。

  • 质量工程化: 试跑彩排、回测门禁、反馈标准答案,让每一次规则调整都可验证、可回归。

  • 人机协同: AI 提供判定与依据,人做最终决策并反哺标准答案,形成数据飞轮。

月均近千单的验收比对,从纯人工逐条核对,变成了「AI 预判+人工复核重点」。后续我们将继续扩充费用类型覆盖与反馈数据积累,让规则迭代跑得更快、更稳。

往期回顾

1.发一张图,它居然"跃"出屏幕了?一文读懂得物 3D 空间图片技术实践

2.别再只卷向量检索了,得物交易搜索如何用“生成式”实现召回范式跃迁?

3.指标平台:从语义底座到智能消费的实践路径|得物技术

4.企业级 MultiAgent 落地:Plan 模式与主子 Agent 协作|得物技术

5.得物小摊 AI Native 演进实录:用 Harness 构建可控 AI 交付

文 / 星珀

关注得物技术,每周四更新技术干货

要是觉得文章对你有帮助的话,欢迎评论转发点赞~

未经得物技术许可严禁转载,否则依法追究法律责任。