ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Flowable集成Spring AI:打造智能审批与动态流程决策引擎

Flowable集成Spring AI:打造智能审批与动态流程决策引擎 1. 从一张流程图说起为什么Flowable需要AI工作流引擎这个领域说实话已经沉寂很久了。从Activiti到Flowable再到Camunda大家卷来卷去比的都是性能、BPMN规范覆盖率、集成便利性这些东西。但有一个问题始终绕不过去流程定义是死的而业务场景是活的。我做过不少审批流的项目最头疼的就是需求方隔三差五来提需求——“这个单子金额超过50万得走副总裁审批”“这个客户是老客户能不能信用审批自动通过”“这个类别比较特殊如果只有部门主管审批的话风险有点大”。每次遇到这种情况本质上是业务规则在变而流程引擎里的逻辑是写死的。改流程定义要重新部署。写网关条件复杂一点的规则直接挤爆表达式。到最后流程是跑起来了但维护成本高到想骂人。这就是我开始尝试把AI塞进Flowable的初衷。AI的价值其实不在“自动化”本身——流程引擎本来就是干自动化这件事的——而是在“识别变化”和“动态决策”上。当流程遇到一个没有预设规则的分支节点时让AI根据上下文数据做出判断把结果回传给流程引擎继续执行。这就是“智能流程决策”的基本形态。这个项目适合谁看如果你在用Flowable做企业级流程开发手里有审批、工单、任务分配这类场景而且已经被复杂条件表达式折磨过那你一定能从这篇文章里找到不少可落地的思路。我尽量用实际项目的角度来拆解不讲虚的直接讲怎么设计、怎么落地、踩过哪些坑。顺便说一句我用的技术栈是Spring Boot 3 Flowable 7 Spring AI模型接的是本地部署的大模型。这套组合的好处是流程引擎和AI之间没有强耦合所有智能节点的决策都通过独立的接口打通算是一个比较稳妥的起步姿势。2. 整体架构设计流程引擎与AI的分工边界2.1 智能决策节点放在哪个位置先看一个典型的审批流程。用户提交申请系统先做规则校验然后根据条件分支走不同层级的审批最后归档。传统做法里分支条件的判断靠的是Flowable的网关表达式比如${amount 100000 ? senior_approval : manager_approval}这种写法本身没毛病问题在于“amount 100000”这个规则是拍脑袋定的。真实业务里风控要看信用分、历史订单、黑名单记录、节假日特征甚至要看审批人当前的负荷量。这些因素加在一起多条件组合的复杂度是呈指数增长的。所以在我的设计里智能决策节点承担的是“规则引擎不够用时的兜底判断”。Flowable还是负责流程的推进、任务的分配、历史的记录但在需要真正做主观判断的地方挂一个服务任务调用AI接口拿回结果后决定后续走向。这个节点的位置很关键。我的建议是放在规则引擎完全无法覆盖的分支入口不要一股脑替换掉原有的决策逻辑。原因很现实AI有不确定性即使现在的大模型能力再强也难免出现判断偏差而审批流这种场景一旦出错代价是实打实的。保留原有的硬规则作为底线让AI去做模糊判断两边互补这个设计思路目前跑下来最顺。2.2 三个核心模块流程引擎、决策路由、AI服务整个系统拆成三个独立的模块模块之间通过接口通信互不侵入。流程引擎模块是纯Flowable负责流程定义的管理、实例的推进、任务的分配。这个模块保持“传统”不掺入任何AI逻辑所有智能判断都封装成服务任务对外只暴露执行入口。决策路由模块负责判断当前节点是否需要AI参与。它读取流程上下文中的数据先跑一遍硬规则如果硬规则能确定走向直接返回如果拿不准就构造Prompt调用AI服务。这个模块本质上是一个策略分发器好处是后续想换AI服务商或者改成规则优先都不影响流程引擎本身。AI服务模块是独立部署的封装了对大模型的调用。它接收结构化的输入数据返回结构化的JSON结果。我在项目里用的是本地部署的Qwen模型通过Spring AI来统一管理模型调用的抽象层后面想换成GPT或者GLM改动成本会低很多。这三个模块独立部署、独立扩容。尤其是在高并发场景下流程引擎和AI服务的负载特性完全不同前者是短平快的事务操作后者是长耗时的推理调用。如果不拆分AI一慢整个流程引擎都得跟着等这是设计上最容易踩的坑。2.3 为什么用Spring AI而不是直接调HTTP接口这个问题当时纠结了很久。直接用HTTP调用大模型接口代码也就几十行凭什么要多引入一个Spring AI我的答案很简单后续要接的能力不止是“生成文本”。在流程决策这个场景里我需要的不只是模型对话而是结构化的输出解析、多模型的切换、以及可观测的调用链路。Spring AI的ChatClient接口能把“调模型”这件事的复杂度挡在外面比如自动处理流式输出、支持函数调用、还能方便地接入各种向量数据库。更关键的是Spring AI定义了相对统一的API从OpenAI到阿里云的通义千问从Ollama到HuggingFace上的开源模型切换时只需要改配置和服务实现业务代码不用动。对于企业项目来说这种低耦合带来的便利远比省掉一个依赖更重要。这里给个建议如果你的项目只是临时做个小DEMO直接用HTTP调用没问题简单粗暴。但如果是要长期维护的产品级方案Spring AI这类抽象层值得引入省下来的维护成本远超引入依赖的成本。3. 核心流程定义与BPMN模型设计3.1 带智能节点的工作流定义在Flowable里定义流程建议直接用Flowable Modeler画BPMN图。以下是我在项目里用到的一个简化版定义一个包含“AI判断”分支的审批流程。process idsmartApproval name智能审批流程 isExecutabletrue startEvent idstartEvent / userTask idapplyTask name提交申请 flowable:assignee${applyUser} / serviceTask idaiDecisionTask nameAI智能决策 flowable:classcom.example.workflow.task.AIDecisionTaskDelegate / exclusiveGateway iddecisionGateway / userTask idmanagerApproveTask name经理审批 flowable:assignee${manager} / userTask idseniorApproveTask name总监审批 flowable:assignee${seniorManager} / endEvent idendEvent / sequenceFlow idflow1 sourceRefstartEvent targetRefapplyTask / sequenceFlow idflow2 sourceRefapplyTask targetRefaiDecisionTask / sequenceFlow idflow3 sourceRefaiDecisionTask targetRefdecisionGateway / sequenceFlow idflow4 sourceRefdecisionGateway targetRefmanagerApproveTask conditionExpression xsi:typetFormalExpression ![CDATA[${decision manager}]] /conditionExpression /sequenceFlow sequenceFlow idflow5 sourceRefdecisionGateway targetRefseniorApproveTask conditionExpression xsi:typetFormalExpression ![CDATA[${decision senior}]] /conditionExpression /sequenceFlow sequenceFlow idflow6 sourceRefmanagerApproveTask targetRefendEvent / sequenceFlow idflow7 sourceRefseniorApproveTask targetRefendEvent / /process这个流程的核心就在aiDecisionTask这个服务任务上。Flowable执行到这里的时候会通过JavaDelegate把流程数据传给AI服务拿回一个decision变量然后由排他网关根据这个变量来决定走哪条分支。实际的项目里不会只有这么简单的分支但模式是一样的在需要智能判断的位置插入服务任务把AI决策结果作为流程变量输出后续网关根据变量路由。这个模式能覆盖绝大多数场景。3.2 服务任务实现JavaDelegate里跑AI调用Flowable的服务任务有两种实现方式一种是委托表达式一种是全类名。我习惯用全类名显式指定一个JavaDelegate实现类。Component public class AIDecisionTaskDelegate implements JavaDelegate { Autowired private AIDecisionService decisionService; Override public void execute(DelegateExecution execution) { // 从流程上下文中获取业务数据 BigDecimal amount execution.getVariable(amount, BigDecimal.class); String customerLevel execution.getVariable(customerLevel, String.class); Integer creditScore execution.getVariable(creditScore, Integer.class); Integer historyOrderCount execution.getVariable(historyOrderCount, Integer.class); // 构造决策输入 MapString, Object bizData new HashMap(); bizData.put(amount, amount); bizData.put(customerLevel, customerLevel); bizData.put(creditScore, creditScore); bizData.put(historyOrderCount, historyOrderCount); // 调用AI决策服务 DecisionResult result decisionService.decide(bizData); // 将决策结果写入流程上下文 execution.setVariable(decision, result.getRouteKey()); execution.setVariable(decisionReason, result.getReason()); execution.setVariable(decisionConfidence, result.getConfidence()); } }这一段代码看起来简单但有几个很关键的细节。第一AI服务调用是同步还是异步必须想清楚。我一开始直接在主线程里同步调用结果就是流程实例卡在服务任务上动不了。后来给AI决策加了超时控制默认3秒超时后走兜底策略——交给默认的负责人处理。这一点非常重要流程引擎可以被慢调用拖死超时熔断必须有。第二所有传给AI的字段必须先从流程上下文里取出来组装成一个结构清晰的参数对象。不要让AI服务直接去拿流程变量那样会让两个模块的依赖关系变复杂不好维护。第三决策结果不只是一个路由值我还会把理由和置信度一起存进流程变量。这样后续排障时能清楚地看到AI为什么做出这个决策回头审计也有据可查。3.3 网关条件里怎么做兜底AI判断毕竟不是100%可靠所以在排他网关的条件表达式中我做了兜底设计。${decision senior decisionConfidence 0.7}如果AI返回的路由值是senior但置信度只有0.5这个条件就不成立流程会走到默认分支由默认的审批人兜底处理。这个设计很实用。AI模型对某些边缘案例的把握度就是低与其硬着头皮让AI扛不如设置一个置信度阈值把人放进来兜底。等模型迭代变好了再慢慢调高阈值整个系统会越来越聪明但永远不会出现失控的情况。4. AI决策服务的实现提示词工程与结构化输出4.1 提示词设计让大模型输出稳定的JSON这个环节是整个项目的灵魂。AI模型不是用来聊天的是要在流程里干活。要想让模型稳定输出可供程序解析的结构化数据Prompt的设计至关重要。我踩了不少坑才找到比较稳定的写法。早期我是直接把业务数据拼接成一句话丢给模型结果返回的东西五花八门——有时候是散文式的分析有时候是半截JSON有时候是只给一个结论什么解释都没有。后来我总结出两个原则第一必须明确告诉模型你的输出格式是什么最好直接给它JSON模板。第二把决策规则和判断维度写清楚让模型有据可依。一个比较成熟的Prompt范例如下你是企业流程管理系统中的智能审批决策助手。 请根据以下业务数据进行审批路由判断判断该申请应该由部门经理审批还是由总监审批。 业务数据 - 申请金额{amount}元 - 客户等级{customerLevel} - 信用评分{creditScore} - 历史订单数{historyOrderCount} - 订单所属季度{quarter} 判断维度 1. 金额因素金额超过30万元应考虑总监审批 2. 客户因素VIP客户可适当放宽金额阈值 3. 风险因素信用评分低于600分建议总监审批 4. 综合判断结合以上维度给出最终路由建议 请严格按照以下JSON格式输出不要输出任何额外内容 { routeKey: manager 或 senior, confidence: 0.0到1.0之间的数字, reason: 不超过50字的判断理由 }我把判断维度写得很具体并且给了示例JSON框架。实测下来模型输出的稳定性能达到95%以上偶尔会有JSON格式错误但我后面做了解析兜底也能扛住。4.2 结构化输出的解析与兜底大模型输出不稳定是常态所以解析层一定要有容错机制。我在Spring AI里用了structured output的能力可以要求模型直接返回Java对象。但即便如此我还是加了双重保险——先尝试直接映射失败就手动修复JSON。public DecisionResult parseAIResult(String aiOutput) { try { // 尝试直接解析 return objectMapper.readValue(aiOutput, DecisionResult.class); } catch (JsonProcessingException e) { // 尝试截取JSON片段 String jsonFragment extractJsonFragment(aiOutput); try { return objectMapper.readValue(jsonFragment, DecisionResult.class); } catch (JsonProcessingException ex) { // 兜底返回默认决策 return DecisionResult.fallback(); } } }extractJsonFragment做的事情很简单从模型输出里找到第一个{和最后一个}之间的内容然后尝试解析。如果连这一步都失败了就直接返回一个fallback结果——路由到默认审批人置信度置为0。这个兜底逻辑非常重要它能保证即使AI服务出现极端异常流程也不会因为这个节点卡死。在企业系统里稳定大于一切。4.3 从Flowable里安全调用AI服务考虑到AI服务调用是阻塞操作而且延迟不确定我强烈建议不要直接在Flowable的工作线程里做同步调用。Flowable内部的CommandExecutor是同步的执行模型如果某个命令执行时间特别长它会占用一个工作线程线程池耗尽之后整个引擎的处理能力都会受到影响。我的做法是单独建了一个线程池来处理AI调用服务任务里通过Future来获取结果并设置超时。这样即使AI挂掉也只是那一次调用超时不会拖垮流程引擎。private static final ExecutorService AI_EXECUTOR Executors.newFixedThreadPool(8, new ThreadFactoryBuilder() .setNameFormat(ai-decision-pool-%d) .build()); public DecisionResult decideWithTimeout(MapString, Object bizData, long timeout, TimeUnit unit) { FutureDecisionResult future AI_EXECUTOR.submit(() - decideInternal(bizData)); try { return future.get(timeout, unit); } catch (TimeoutException e) { future.cancel(true); log.warn(AI决策超时默认走兜底策略); return DecisionResult.fallback(); } catch (Exception e) { log.error(AI决策异常, e); return DecisionResult.fallback(); } }这段话值得单独拿出来讲Flowable官方其实不推荐在服务任务里做长时间的阻塞操作这会对引擎的并发能力造成很大影响。如果你只是在学习阶段简单同步调用问题不大但准备上生产的话这个隔离线程池的方案建议尽早引入。5. 实操过程Spring Boot整合Flowable与Spring AI5.1 依赖引入与基础配置Maven的pom.xml里核心依赖是三件套Flowable的Spring Boot Starter、Spring AI的Qwen模块以及Flowable的JSON转换支持。dependency groupIdorg.flowable/groupId artifactIdflowable-spring-boot-starter/artifactId version7.0.1/version /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-alibaba-starter/artifactId version1.0.0-M6/version /dependencyFlowable 7和Spring Boot 3的兼容性是目前最好的。如果还在用Spring Boot 2.x建议用Flowable 6.x的版本否则会出现一些底层依赖冲突处理起来很蛋疼。配置方面Flowable默认会使用项目配置的数据源在application.yml里需要指定spring: datasource: url: jdbc:mysql://localhost:3306/flowable_ai?useUnicodetruecharacterEncodingutf8nullCatalogMeansCurrenttrue username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver flowable: database-schema-update: true async-executor-activate: true history-level: full spring: ai: alibaba: tongyi: base-url: http://localhost:11434 chat: options: model: qwen2.5:7bdatabase-schema-update: true意味着Flowable会在启动时自动建表或升级表结构非常方便。生产环境建议改成false用专门的迁移脚本管理。5.2 启动项目并验证流程定义部署启动项目后Flowable会自动部署resources/processes目录下的BPMN文件。启动日志里如果能看到类似Deployment created的字样说明流程定义已经成功部署了。可以通过REST接口来验证。Flowable自带了/repository/deployments等接口也可以用自定义接口查询RestController RequestMapping(/flow) public class FlowController { Autowired private RuntimeService runtimeService; GetMapping(/start/{processKey}) public String startProcess(PathVariable String processKey) { MapString, Object vars new HashMap(); vars.put(amount, 350000); vars.put(customerLevel, VIP); vars.put(creditScore, 720); vars.put(historyOrderCount, 56); vars.put(applyUser, zhangsan); ProcessInstance instance runtimeService .startProcessInstanceByKey(processKey, vars); return 流程已启动实例ID: instance.getId(); } }我这里传入了完整的业务变量流程跑到aiDecisionTask时服务任务会自动读取这些变量组装成AI决策的输入。5.3 测试智能决策是否生效启动流程后去查ACT_RU_EXECUTION表可以看到当前流程实例停在了哪个节点。如果一切正常decision变量会被写入流程上下文。写一个查询接口来验证GetMapping(/task/current/{processInstanceId}) public ListString getCurrentTasks(PathVariable String processInstanceId) { return taskService.createTaskQuery() .processInstanceId(processInstanceId) .list() .stream() .map(Task::getName) .collect(Collectors.toList()); }如果AI判断需要总监审批查询结果会返回“总监审批”这个任务节点。如果AI判断只需要经理审批返回的就是“经理审批”。用这个接口就能直观地看到AI决策的结果。这里给新手一个提醒跑测试的时候先去Flowable自带的前端页面看一眼流程走到哪里了比单纯看数据库表要直观得多。Flowable 7自带的UI模块单独打包可以做成一个独立的管理后台开发调试的时候很有用。6. 常见问题与排坑实录6.1 AI判断不准怎么调优这是所有用大模型做决策的人都会遇到的问题。模型给出的路由结果不是每次都很准有时会偏离业务直觉。我的经验是先把模型“当傻子看”。不要指望模型自动理解业务背景所有判断维度和规则都必须写清楚。比如“金额超过30万走总监审批”这种规则直接在Prompt里给出来。模型不做推理它只是在“套用规则”。当然在表述上要适度留出空间给AI整合多因素也就是说规则写成“应当重点考虑的因素”而不是“一票决定的条件”。另外Prompt里写多少规则合适我的测试是4到6条最好。太少模型容易只关注一两个维度太多模型又会无所适从。如果调了Prompt还是不准就要看是不是数据的问题。模型只能根据你给的信息做判断如果业务数据本身有缺失比如没有传信用评分它就只能根据金额和客户等级来猜误差自然大。所以做好输入数据的治理比调Prompt更根本。6.2 模型调用超时或不可用大模型推理是很耗时的本地部署的模型稍微好一点但也要看显卡。我用的是Qwen 2.5 7B在单张消费级显卡上跑一次推理大概是1到3秒。并发上来后排队时间和推理时间加在一起很容易超过我设置的3秒超时。解决思路有两个层面。第一个是模型层面可以用vLLM或者Ollama做并发批处理提高推理吞吐。第二个是应用层面做好流量控制——给AI决策服务加一个Semaphore限制同时进入的推理请求数防止突发流量打爆模型服务。private final Semaphore semaphore new Semaphore(4); public DecisionResult decideInternal(MapString, Object bizData) { if (!semaphore.tryAcquire()) { return DecisionResult.fallback(); } try { // 调用模型接口 } finally { semaphore.release(); } }核心思路是宁可让部分请求走兜底也不能让AI服务拖死流程引擎。对于审批场景来说一次判断晚几分钟没关系但系统整体不可用是不能接受的。6.3 热词里提到的“Flowable前端集成”很多人搜索Flowable前端集成是因为Flowable本身的UI比较朴素不适合直接嵌入业务系统。我的建议是用BPMN-JS来做前端流程设计器集成到Vue或React项目里后端仍然用Flowable的REST API。BPMN-JS是一个很成熟的开源库支持在线绘制BPMN图、校验、导出XML。它的API设计比较友好一个基本的流程设计器大概几百行代码就能搞定。如果你只是需要一个查看流程进度的管理后台Flowable自带REST API配合前端框架做个只读展示就可以不用每次都重新设计流程。前端集成这块的细节真要展开讲又是一篇文章。我这里只提醒一个坑BPMN-JS生成的XML和Flowable的Modeler生成的XML存在少量差异可能在部署到Flowable时解析失败。解决办法是在前端保存XML之前用Flowable的模型服务转换一下格式两边对不上这个问题我排查了整整一天才找到根源。6.4 关于“降AI率”和“AI生成内容”的杂音最近网上关于“降AI率工具”“无审核AI聊天”这些东西热度很高。我的态度很直接在自己手头这个场景里AI是用来做结构化决策判断的不是用来生成自由文本的所以不会涉及什么AI率的问题。企业系统里使用AI应该追求确定性、稳定性和可解释性而不是追求“看起来不像AI写的”。也正是因为自己做了这个项目我越来越觉得AI在工作流里的最佳位置是“辅助决策”而不是“替代流程”。让AI负责那些需要经验判断但又不能完全靠硬性规则覆盖的环节让流程引擎继续保证执行的确定性这才是符合现实需求的演进路径。7. 后续演进思路这个项目做完之后我想到的几个后续方向供你参考。第一个方向是做基于历史数据的动态决策模型。目前的Prompt规则是固定的比如“金额超过30万建议总监审批”。但这些规则本身是不是最优的不一定。后续可以把所有历史决策数据和最终审批结果收集起来用算法分析哪些规则应该被调整甚至可以训练一个专用的轻量分类模型来代替大模型。第二个方向是把AI能力从“决策”扩展到“异常处理”。流程执行过程中经常遇到审批人长时间不处理、节点执行失败、条件分支无匹配等异常情况。传统做法是定时任务扫描人工干预。现在可以尝试让AI根据流程上下文自行判断怎么处理比如自动改派给其他审批人、自动催办、把流程回退给发起人修改。第三个方向是引入知识库和RAG。审批规则往往来自制度文档、历史案例、行业规范把这些非结构化知识导入向量数据库让AI在做决策时能参考这些知识而不是只靠Prompt里的几个字段。这一块做起来会更复杂但价值也更大。不过我要泼一盆冷水涉及审批流程的项目AI介入越深就需要越多的审批数据和决策记录沉淀。前期先跑通小场景、建立信任、让人工介入兜底比一步到位全部交给AI要稳妥得多。这个节奏把握好项目就能持续演进。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进