AI数字员工:RPA与大模型融合的办公自动化

AI数字员工:RPA与大模型融合的办公自动化

RPA(机器人流程自动化)这个行业存在十几年了,靠的是模拟人在界面上的点击、输入、复制粘贴,把重复性的办公流程交给软件机器人执行。财务对账、发票录入、跨系统数据搬运,都是它的经典场景。但传统 RPA 有个致命弱点:太脆。界面改个按钮位置,脚本就挂;遇到一份格式没见过的单据,直接卡死等人来处理。大模型的出现恰好补上了这块短板——它擅长理解非结构化内容、处理例外情况。两者结合,就是这两年被反复讨论的"AI 数字员工"。本文聊聊这种融合架构怎么设计、落地时有哪些坑。

传统 RPA 的天花板在哪

传统 RPA 的本质是"录制与回放":开发者用 UiPath、影刀这类工具把人工操作流程录成脚本,靠元素选择器(selector)定位按钮和输入框,按固定规则流转数据。它在流程标准化、界面稳定的场景里很好用,比如每天固定从 ERP 导出报表发邮件。

但真实办公环境充满了意外:供应商发来的发票版式五花八门,邮件里的需求描述千奇百怪,目标系统升级后 DOM 结构全变了。规则引擎面对这些非结构化、非确定性的输入力不从心。行业里的经验数据是,传统 RPA 项目里例外处理的人工介入率常常超过 20%,维护脚本的成本逐年上升。这就是为什么纯 RPA 的 ROI 故事讲到最后总是差点意思。

大模型补上的三块能力

大模型给自动化带来的不是简单的"更聪明",而是三类过去做不了的能力:

  1. 非结构化理解:从邮件正文、PDF 合同、扫描件里抽取关键字段。以前要为每种单据写模板或训练专门的抽取模型,现在一个 LLM 加结构化输出提示词就能覆盖大部分版式;
  2. 意图识别与任务规划:用户用自然语言说"把上个月的差旅报销单都审一遍,超过 5000 的标出来",模型能把这句话拆成一串可执行的子任务;
  3. 异常时的自主决策:弹出一个没见过的对话框、页面加载失败,LLM 可以读屏幕内容判断该点哪个按钮、该不该重试,而不是直接报错退出。

融合架构:感知、决策、执行三层

把 RPA 和大模型缝在一起,比较成熟的架构是分三层:

  • 感知层:负责把环境状态变成模型能理解的输入。GUI 场景下是截图 + 控件树(通过 UI Automation API 获取),文档场景下是 OCR 结果 + 版面分析,数据场景下是 API 返回的 JSON。多模态模型可以直接看截图,但结合控件树给出带坐标的结构化元素列表,定位精度会高很多;
  • 决策层:LLM 扮演大脑,根据感知输入和任务目标输出下一步动作。关键是约束输出格式——用 JSON Schema 限定动作空间(click、type、extract、call_api、done、ask_human),模型只许在这个集合里选择,避免自由发挥;
  • 执行层:传统 RPA 的老本行,把决策层的结构化动作翻译成真实的鼠标键盘操作或 API 调用。这一层必须保持确定性,并加幂等和回滚设计——同一个动作执行两次不能把数据录重。

一个最小化的决策循环长这样:

import json from openai import OpenAI ACTION_SCHEMA = { "type": "object", "properties": { "action": {"enum": ["click", "type", "extract", "done", "ask_human"]}, "target": {"type": "string", "description": "目标控件描述或字段名"}, "value": {"type": "string", "description": "输入文本或抽取结果"}, "reason": {"type": "string"}, }, "required": ["action", "reason"], } def decide(client: OpenAI, task: str, history: list, ui_state: str) -> dict: resp = client.chat.completions.create( model="gpt-4o", messages=[ {"role": "system", "content": "你是办公自动化机器人的决策模块。根据界面状态和任务目标," "输出下一步动作,只允许 click/type/extract/done/ask_human。"}, *history, {"role": "user", "content": f"任务: {task}\n当前界面元素:\n{ui_state}"}, ], response_format={"type": "json_object"}, ) return json.loads(resp.choices[0].message.content) def run_loop(task: str, max_steps: int = 30): client = OpenAI() history = [] for _ in