
让 AI 当流程节点的执行者Spring AI Flowable 工作流深度整合前两篇我们把 Agent 做出来了也让多个 Agent 协作起来了。但企业里还有一个绕不开的现实流程已经在跑了。OA 审批、工单系统、合同管理背后大多是 Flowable 或 Activiti 驱动的工作流引擎。本文解决的问题是怎么把 Agent 嵌进去而不是推倒重来。配套阅读第一篇《从 ReAct 到 MCP》、第二篇《多 Agent 协作》。一、两种错误的整合思路在动手之前先说两条弯路省得你走。错误一用 Agent 替换整个工作流引擎。有人看完 Agent 演示第一反应是那我还要 Flowable 干嘛。这个想法很危险。工作流引擎解决的是流程持久化、状态恢复、超时重试、审计日志这些问题——这些 Agent 一个都不擅长。Agent 擅长的是非结构化判断读一段文字、理解意图、调工具。两者是互补关系不是替代关系。错误二在 Agent 里硬编码流程逻辑。上一篇的ExpenseSupervisor是 Java 硬编码的 Pipeline。这在流程固定时没问题。但一旦业务说这个客户走特殊通道、“节假日前后审批人不同”你就得改代码重新部署。工作流引擎存在的意义就是让这些变化不需要改代码。正确姿势Flowable 管流程骨架Agent 管节点内的智能判断。Flowable 工作流 ├── 开始事件 ├── [ServiceTask] → OcrAgent.extract() ← AI 节点 ├── [UserTask] → 人工复核金额 1万 ├── [ServiceTask] → PolicyAgent.check() ← AI 节点 ├── [ExclusiveGateway] → 合规 / 不合规 ├── [ServiceTask] → BookingAgent.book() ← AI 节点 └── 结束事件流程图在 Flowable Designer 里画AI 节点在 Java 里实现。业务改流程改 BPMN 文件就够了Agent 代码不动。二、Flowable 基础三个概念够用不熟悉 Flowable 的读者三个概念就能上手ProcessDefinition流程定义就是那张 BPMN 图描述流程长什么样。ProcessInstance流程实例一次具体的流程执行比如张伟 2026-04-10 提交的报销单。Task流程里的一个节点可以是人工任务UserTask或自动任务ServiceTask。Agent 要做的事就是实现ServiceTask背后的 Java 逻辑。三、项目搭建3.1 依赖dependencies!-- Flowable Spring Boot Starter --dependencygroupIdorg.flowable/groupIdartifactIdflowable-spring-boot-starter/artifactIdversion7.1.0/version/dependency!-- Spring AI同前两篇 --dependencygroupIdorg.springframework.ai/groupIdartifactIdspring-ai-starter-model-openai/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-jpa/artifactId/dependencydependencygroupIdcom.h2database/groupIdartifactIdh2/artifactIdscoperuntime/scope/dependency/dependencies3.2 配置spring:datasource:url:jdbc:h2:mem:flowable;DB_CLOSE_DELAY-1driver-class-name:org.h2.Driverai:openai:base-url:https://api.deepseek.comapi-key:${DEEPSEEK_API_KEY}chat:options:model:deepseek-chattemperature:0.1flowable:database-schema-update:trueasync-executor-activate:true四、核心把 Agent 包装成 JavaDelegateFlowable 的ServiceTask通过JavaDelegate接口执行 Java 逻辑。把每个 Agent 包一层JavaDelegate就能挂进流程节点。4.1 OcrDelegateComponent(ocrDelegate)publicclassOcrDelegateimplementsJavaDelegate{privatefinalOcrAgentocrAgent;publicOcrDelegate(OcrAgentocrAgent){this.ocrAgentocrAgent;}Overridepublicvoidexecute(DelegateExecutionexecution){StringrawText(String)execution.getVariable(rawText);InvoiceInfoinvoiceocrAgent.extract(rawText);// 把结果写回流程变量供后续节点使用execution.setVariable(invoiceAmount,invoice.amount());execution.setVariable(invoiceMerchant,invoice.merchant());execution.setVariable(invoiceDate,invoice.date());execution.setVariable(invoiceNo,invoice.invoiceNo());}}关键点流程变量ProcessVariable就是多 Agent 之间的黑板。上一篇我们用ExpenseContextRecord 传递状态这里换成 Flowable 的变量存储——它会持久化到数据库进程重启也不丢。4.2 PolicyDelegateComponent(policyDelegate)publicclassPolicyDelegateimplementsJavaDelegate{privatefinalPolicyAgentpolicyAgent;publicPolicyDelegate(PolicyAgentpolicyAgent){this.policyAgentpolicyAgent;}Overridepublicvoidexecute(DelegateExecutionexecution){varinfoInvoiceInfo.from(execution);// 从流程变量重建对象StringuserId(String)execution.getVariable(userId);PolicyCheckcheckpolicyAgent.check(info,userId);execution.setVariable(policyPassed,check.passed());execution.setVariable(policyReason,check.reason());}}4.3 BookingDelegateComponent(bookingDelegate)publicclassBookingDelegateimplementsJavaDelegate{privatefinalBookingAgentbookingAgent;publicBookingDelegate(BookingAgentbookingAgent){this.bookingAgentbookingAgent;}Overridepublicvoidexecute(DelegateExecutionexecution){varctxExpenseContext.from(execution);StringvoucherNobookingAgent.book(ctx);execution.setVariable(voucherNo,voucherNo);}}五、BPMN 流程定义在src/main/resources/processes/expense-approval.bpmn20.xml创建流程文件?xml version1.0 encodingUTF-8?definitionsxmlnshttp://www.omg.org/spec/BPMN/20100524/MODELxmlns:flowablehttp://flowable.org/bpmntargetNamespaceexpenseprocessidexpenseApprovalname报销审批流isExecutabletruestartEventidstart/!-- AI 节点 1OCR 识别 --serviceTaskidocrTaskname发票识别flowable:delegateExpression${ocrDelegate}/!-- AI 节点 2合规校验 --serviceTaskidpolicyTaskname合规校验flowable:delegateExpression${policyDelegate}/!-- 网关合规与否 --exclusiveGatewayidpolicyGatewayname是否合规/!-- 人工节点金额超限复核 --userTaskidmanualReviewname人工复核flowable:assignee${approver}/!-- AI 节点 3入账 --serviceTaskidbookingTaskname财务入账flowable:delegateExpression${bookingDelegate}/!-- 结束 --endEventidapproved/endEventidrejected/!-- 连线 --sequenceFlowsourceRefstarttargetRefocrTask/sequenceFlowsourceRefocrTasktargetRefpolicyTask/sequenceFlowsourceRefpolicyTasktargetRefpolicyGateway/sequenceFlowidpassFlowsourceRefpolicyGatewaytargetRefmanualReviewconditionExpression${policyPassed true}/conditionExpression/sequenceFlowsequenceFlowidrejectFlowsourceRefpolicyGatewaytargetRefrejectedconditionExpression${policyPassed false}/conditionExpression/sequenceFlowsequenceFlowsourceRefmanualReviewtargetRefbookingTask/sequenceFlowsourceRefbookingTasktargetRefapproved//process/definitions注意flowable:delegateExpression${ocrDelegate}这里的ocrDelegate就是 Spring Bean 的名字Flowable 会自动从 Spring 容器里找。六、启动和推进流程ServiceRequiredArgsConstructorpublicclassExpenseWorkflowService{privatefinalRuntimeServiceruntimeService;privatefinalTaskServicetaskService;publicStringstartExpense(StringuserId,StringrawText){MapString,ObjectvarsnewHashMap();vars.put(userId,userId);vars.put(rawText,rawText);vars.put(approver,resolveApprover(userId));// 查审批人ProcessInstanceinstanceruntimeService.startProcessInstanceByKey(expenseApproval,vars);returninstance.getId();}publicStringgetStatus(StringinstanceId){// 查当前停在哪个节点vartaskstaskService.createTaskQuery().processInstanceId(instanceId).list();if(tasks.isEmpty()){// 流程已结束查结果变量varvarsruntimeService.getVariables(instanceId);return(String)vars.getOrDefault(voucherNo,rejected);}returnwaiting:tasks.get(0).getName();}publicvoidcompleteManualReview(StringtaskId,booleanapproved){taskService.complete(taskId,Map.of(manualApproved,approved));}privateStringresolveApprover(StringuserId){// 查组织架构返回直属主管工号returnmanager_userId;}}接口层RestControllerRequestMapping(/api/expense)RequiredArgsConstructorpublicclassExpenseController{privatefinalExpenseWorkflowServiceworkflowService;PostMapping(/submit)publicMapString,Stringsubmit(RequestBodySubmitRequestreq){StringinstanceIdworkflowService.startExpense(req.userId(),req.rawText());returnMap.of(instanceId,instanceId);}GetMapping(/{instanceId}/status)publicMapString,Stringstatus(PathVariableStringinstanceId){returnMap.of(status,workflowService.getStatus(instanceId));}PostMapping(/task/{taskId}/complete)publicvoidcomplete(PathVariableStringtaskId,RequestBodyMapString,Booleanbody){workflowService.completeManualReview(taskId,body.get(approved));}recordSubmitRequest(StringuserId,StringrawText){}}调用示例# 提交报销curl-XPOST http://localhost:8080/api/expense/submit\-HContent-Type: application/json\-d{userId:zhangwei,rawText:[发票内容] 金额3860元 上海出行 2026-04-10}# 返回{instanceId:abc-123}# 查状态curlhttp://localhost:8080/api/expense/abc-123/status# 返回{status:waiting:人工复核}# 主管审批通过curl-XPOST http://localhost:8080/api/expense/task/task-456/complete\-HContent-Type: application/json\-d{approved:true}七、三个让整合更稳的工程细节7.1 Agent 异常不能让流程卡死JavaDelegate里抛出未捕获异常Flowable 会把流程实例标记为 ERROR 状态卡在那个节点。正确做法是捕获异常写入流程变量让网关决定走人工还是重试Overridepublicvoidexecute(DelegateExecutionexecution){try{InvoiceInfoinvoiceocrAgent.extract(rawText);execution.setVariable(ocrSuccess,true);execution.setVariable(invoiceAmount,invoice.amount());}catch(Exceptione){log.error(OCR failed for instance {},execution.getProcessInstanceId(),e);execution.setVariable(ocrSuccess,false);execution.setVariable(ocrError,e.getMessage());// 流程继续由网关判断走人工补录}}7.2 异步 Agent 调用LLM 调用耗时 3–10 秒同步阻塞 Flowable 线程池不合适。用flowable:asynctrue让节点异步执行serviceTaskidocrTaskname发票识别flowable:asynctrueflowable:delegateExpression${ocrDelegate}/Flowable 会把任务放进异步执行器队列不阻塞主线程。7.3 流程变量别存大对象Flowable 的流程变量会序列化存数据库。把整个InvoiceInfo对象塞进去序列化反序列化容易出问题而且查询慢。原则流程变量只存基础类型String、Long、Boolean、Double复杂对象存业务数据库流程变量只存 ID// 不推荐execution.setVariable(invoice,invoiceInfo);// 整个对象// 推荐StringinvoiceIdinvoiceRepo.save(invoiceInfo).getId();execution.setVariable(invoiceId,invoiceId);// 只存 ID八、整合后的架构全貌用户 / 前端 │ ▼ ExpenseController │ ▼ Flowable 工作流引擎 ├── ServiceTask → OcrDelegate → OcrAgent → [OCR 工具] ├── ServiceTask → PolicyDelegate → PolicyAgent → [制度库 / 预算 API] ├── UserTask → 人工审批钉钉 / 企微 通知 ├── ServiceTask → BookingDelegate → BookingAgent → [ERP API] └── 流程变量持久化 → MySQL每一层职责清晰Flowable流程状态、持久化、超时、审计Delegate胶水层连接 Flowable 和 AgentAgent智能判断调工具处理非结构化输入工具真实业务系统 API九、写在最后AI Agent 不是要替换企业现有系统而是要嵌入它。工作流引擎管流程走到哪了Agent 管这一步该怎么判断。两者结合才是企业 AI 落地的正确姿势——既保留了流程的可审计性和可配置性又获得了 AI 的理解能力和自动化能力。