日均 400+ 工单、准确率 98%:拆解一个高速稽核 AI Agent 的工程实现
摘要:高速稽核是个典型的"规则清晰但操作地狱"的场景------路径要给逃费车辆还原出来,证据要按格式打包,工单要赶在系统放单的几秒窗口里提交。人工干不了,传统 RPA 脚本又脆得一碰就碎。本文讲讲我们是怎么用 Agent 的思路重做这套流程的:感知层做多源取证,推理层做时空路径还原,执行层做带事务补偿的自动化填报,最终实现 3 秒创单、日均 400+ 工单、98%+ 准确率。
关键词:AI Agent、RPA、路径还原、图搜索、工作流编排、收费稽核、自动化
一、先理解业务:稽核到底在干什么
外行听"高速稽核"容易想成审计,其实它非常具体------找出逃费的车辆,还原它真实的行驶路径,把差额追回来。
常见逃费手法包括:倒卡换牌、甩挂拖挂、大车小标、屏蔽 ETC 设备等。稽核人员要做的是:
- 从海量门架交易流水里,识别异常车辆(比如入口出口时间矛盾、轴数与车型不匹配);
- 根据该车的门架流水序列,还原出它实际走过的路;
- 按真实路径计算应缴金额,与实收金额比对,得出差额;
- 打包证据(图片、流水、路径图),在规定的时效内向部级平台发起协查工单。
痛点非常集中:
| 痛点 | 具体表现 |
|---|---|
| 时效压力 | 部级平台定期放单,工单要"抢",人工经常得夜间值守 |
| 路径还原难 | 一辆车的门架流水可能有几十条,且存在缺失、噪声、时序抖动 |
| 人工成本高 | 单条协查人工耗时约 15 分钟,白天做不完,夜里还得蹲 |
| 系统对接难 | 部级平台不提供开放 API,传统集成方案周期长、风险高 |
最后一条最要命:拿不到 API,就意味着一切自动化都必须走 UI 层。
二、为什么传统 RPA 脚本在这儿活不下来
我们一开始也试过录制脚本。上线三天,崩了。崩法是花样百出的:
- 平台灰度改版,某个按钮的层级变了,选择器全失效;
- 页面偶发加载缓慢,脚本没等到元素就往下走,数据填错字段;
- 网络抖动导致提交请求发出但本地没收到响应,脚本误判失败又提交了一次,重复工单;
- 验证码/风控识别偶发失败,整个队列卡死,没人知道。
归纳起来是三个根因:缺乏感知容错、缺乏业务理解、缺乏事务语义。
所以我们换了个思路------不要"脚本",要 Agent:能看懂页面、能自己做决策、能从失败里恢复的执行体。
三、Agent 的三层架构
text
┌──────────────────────────────────────────────────────┐
│ 决策编排层 Orchestrator(DAG 状态机 + 重试策略) │
├──────────────────────────────────────────────────────┤
│ 推理层:① 嫌疑车辆筛查 ② 时空路径还原 ③ 差额核算 │
│ 规则引擎 + 模型 + 路网图算法 │
├──────────────────────────────────────────────────────┤
│ 执行层:RPA Worker(元素感知 + 事务补偿 + 幂等控制) │
├──────────────────────────────────────────────────────┤
│ 感知层:多源数据接入(交易流水 / 抓拍图片 / 门架数据) │
└──────────────────────────────────────────────────────┘
3.1 编排层:用 DAG 代替线性脚本
线性脚本最大的问题是不知道自己走到哪儿了。我们把每个任务建模成一个带持久化的状态机,任何中断都能从断点续跑。
python
# agent/orchestrator.py
from enum import Enum
from typing import Callable, Dict, List
class NodeState(Enum):
PENDING = "pending"
RUNNING = "running"
SUCCEEDED = "succeeded"
FAILED = "failed"
COMPENSATING = "compensating"
class DutyGraph:
"""支持断点续跑与事务补偿的任务 DAG"""
def __init__(self, nodes: Dict[str, Callable], edges: Dict[str, List[str]],
compensations: Dict[str, Callable]):
self.nodes = nodes
self.edges = edges
self.compensations = compensations
self.state: Dict[str, NodeState] = {n: NodeState.PENDING for n in nodes}
def run(self, ticket_id: str, resume: bool = False):
self._load_checkpoint(ticket_id) if resume else None
for node in self._topo_order():
if self.state[node] == NodeState.SUCCEEDED:
continue
self.state[node] = NodeState.RUNNING
try:
self.nodes[node]()
self.state[node] = NodeState.SUCCEEDED
except Exception as e:
self.state[node] = NodeState.FAILED
self._compensate_upstream(node)
raise
finally:
self._save_checkpoint(ticket_id)
def _compensate_upstream(self, failed_node: str):
"""逆序回滚已完成的上游节点,避免产生脏数据/重复工单"""
for node in reversed(self._upstream_of(failed_node)):
if self.state[node] == NodeState.SUCCEEDED and node in self.compensations:
self.state[node] = NodeState.COMPENSATING
self.compensations[node]()
self.state[node] = NodeState.PENDING
幂等控制是这里的重点:提交前先查重。每一个外部提交动作都要带一个本地生成的幂等键,重复执行不会产生第二条记录。
四、推理层核心:门架流水上的时空路径还原
这是最有技术含量也最有业务价值的一环。
4.1 问题建模
门架是高速公路上的龙门架,每经过一次会记录一笔交易:(车牌, 门架ID, 时间戳)。若干轻微 Issue:
- 门架识别偶有错误(车牌识别不准导致序列断裂);
- 部分门架数据延迟入库,导致序列时序抖动;
- 车辆可能真的存在多次出行,序列需要切分。
抽象出来就是一个在路网图上做时序约束的最佳路径搜索问题。
4.2 算法实现
python
# reasoning/path_recovery.py
import heapq
from typing import List, Tuple
class PathRecovery:
"""
在路网图上搜索满足时空约束的最优路径。
代价函数同时考虑:空间距离偏离、时间不可行性、门架漏识惩罚。
"""
def __init__(self, road_graph, max_speed_kmh: int = 140):
self.g = road_graph
self.max_speed = max_speed_kmh / 3.6 # m/s
def recover(self, gantry_seq: List[Tuple[str, float]]) -> List[str]:
# 1. 时序纠偏:允许小幅度乱序,用单调栈重整
seq = self._temporal_reorder(gantry_seq)
heap = [(0.0, seq[0][0], [seq[0][0]])]
visited = set()
while heap:
cost, current, path = heapq.heappop(heap)
idx = len(path) - 1
if idx == len(seq) - 1:
return path
for nxt in self.g.neighbors(current):
key = (nxt, idx + 1)
if key in visited:
continue
visited.add(key)
spatial_cost = self.g.distance(current, nxt)
t_gap = seq[idx + 1][1] - seq[idx][1]
# 时间不可行惩罚:按最高限速推算,到不了就重罚
infeasible = max(0.0, spatial_cost - self.max_speed * max(t_gap, 1e-3))
time_penalty = infeasible * 0.5
new_cost = cost + spatial_cost + time_penalty
heapq.heappush(heap, (new_cost, nxt, path + [nxt]))
return []
@staticmethod
def _temporal_reorder(seq):
"""用单调栈处理延迟入库导致的小规模时序错位"""
stack = []
for item in sorted(seq, key=lambda x: x[1]):
while stack and stack[-1][1] > item[1] and len(stack) > 1:
stack.pop()
stack.append(item)
return stack
实际业务里还要叠加:
- 车牌相似度兜底:识别错误时用编辑距离 + 车牌规则(省份简称、字符位)做候选合流;
- 多模型判别:车型判别用独立模型与轴数、座位数等数据交叉验证,避免单一识别错误漏判;
- 路网先验:不同省份的路网拓扑数据要能热更新,否则新通车路段会直接导致还原失败。
我们最终在这块的准确率做到了 98%+,且对嫌疑车辆保持零漏判------这在稽核这种"宁可多查不可放过"的场景里,比单纯追求精度更重要。
五、执行层:RPA 稳定性的四个狠招
因为没有 API,UI 自动化是唯一出路。稳定性完全靠工程细节堆出来。
5.1 多策略元素定位
单一选择器必挂。我们对同一个控件维护多重定位策略依次降级:
python
# rpa/locator.py
class RobustLocator:
"""多重降级策略定位 UI 元素"""
STRATEGIES = ["id", "xpath_anchor", "text_semantic", "visual_template"]
def locate(self, driver, target: dict):
errors = []
for strategy in self.STRATEGIES:
try:
el = self._locate_by(driver, strategy, target)
if el and el.is_displayed():
return el, strategy
except Exception as e:
errors.append((strategy, str(e)))
raise ElementNotFoundError(target, errors)
其中 text_semantic 这一层是用小模型做的语义匹配------即使改版把按钮文案从"确认提交"改成"确定发送",也能命中。这是应对灰度改版最有效的一招,救了我们非常多次。
5.2 显式等待 + 业务态断言
不要 sleep(3)。要等待业务状态而非时间:
python
# rpa/waits.py
def wait_until_submitted(driver, timeout=10):
"""等待真正成功的状态信号,而非某个元素出现"""
WebDriverWait(driver, timeout).until(
lambda d: d.execute_script(
"return window.__submitState && window.__submitState.done === true"
) or d.find_element(By.CSS_SELECTOR, ".result-table tbody tr").is_displayed()
)
5.3 任务队列与反压
7×24 小时运行意味着必须有流量控制。我们用带优先级的队列 + 令牌桶限速,避免请求过于密集触发平台风控。抢单窗口期内自动提高并发,平时低速运行做存量清理。
5.4 一小时一次的健康巡检
长跑系统的敌人是静默失败。我们加了巡检任务:每小时用一条影子用例跑完整链路,一旦失败立刻告警------这比等真正丢单后发现要便宜得多。
六、7 天预填报:把"抢单"难题换了个解法
部级平台的工单有时限要求,人工模式下必须熬夜抢,这是稽核人员最痛苦的点。
我们的做法是把工作前移:
text
日常低峰期 放单窗口期
─────────────────────► ─────────────────►
筛查 → 路径还原 → 证据打包 3 秒提交
生成"预填报草稿"(可提前 7 天) (仅剩最后一步)
所有耗时的工作在放单前就做完了,只剩最后一次提交动作。3 秒创单就是这么来的------它不是"手速快",而是把 99% 的工作挪到了时间不敏感的阶段。
这是个很通用的思路:当某个环节存在严格时效约束时,先去看看约束前面的部分能不能全部前置。
七、实测数据
| 指标 | 改造前(人工) | 改造后(Agent) |
|---|---|---|
| 单条协查耗时 | ~15 分钟 | ~3 分钟(提效 5 倍) |
| 单条工单发起 | ~18 秒 | 3 秒(提效 6 倍) |
| 日均处理量 | 波动大 | 400+ 单 |
| 准确率 | 受人员状态影响 | 98%+ |
| 人力投入 | 需夜间值守 | 日均释放约 5 人 |
| 系统对接周期 | 数周至数月 | 分钟级(仅需账号) |
最后一行值得多说一句:免系统对接意味着不需要改动甲方现有任何系统,不需要协调部级平台的接口开放。这在政企项目里往往比技术先进性更能决定项目能不能成。
八、几点复盘
- Agent 的价值不在"聪明",在"抗造"。 真实业务里模型精度提升带来的收益,远不如"不再静默失败"来得实在。把状态持久化、幂等、补偿、巡检做到位,比堆模型有用。
- 没有 API 不代表不能自动化。 UI 自动化做好了同样能跑 7×24,前提是把它当分布式系统来设计,而不是当宏来写。
- 时效约束优先用"前置"解决,而不是"加速"解决。 3 秒创单的本质是把 15 分钟的活搬到前一天去做。
- 不要一上来就做全自动。 我们保留了人工复核开关,低置信度案例自动转人工------既控制了风险,也让业务方安心。
这套"AI 识别 + 路径还原 + 自动化执行"的能力,我们已经沉淀为产品 AI 稽核精灵,面向高速路段公司与省级稽核中心,协查审核、工单发起、嫌疑车筛查、积压清零全覆盖,独立部署免对接,一体机交付。
如果你也在做类似的"规则清晰但操作地狱"的业务自动化,欢迎评论区聊聊你们的做法。
本文基于真实项目实践整理。文中涉及产品为易构软件「AI 稽核精灵」。