尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Flowable集成大模型:External Worker+Spring AI实现智能流程节点

发布时间:2026/9/28 16:43:14

资讯中心
01
ARTICLE

Flowable集成大模型:External Worker+Spring AI实现智能流程节点

Flowable集成大模型:External Worker+Spring AI实现智能流程节点
1. 为什么要在 Flowable 里塞进一个大模型节点先说清楚这件事的背景。Flowable 是一套成熟的 BPMN 流程引擎擅长的是确定性编排——谁审批、走哪个网关、超时几天催办这些规则写死在流程图里跑一万次结果都一样。但现实业务里总有一些环节是模糊判断合同条款风险初筛、工单自动分类、客户意图识别、审批意见摘要生成。这类活儿用传统规则引擎写要么规则爆炸要么准确率感人。于是就有了把 LLM 接进流程节点的需求。核心思路不是让大模型去驱动整个流程而是把 LLM 当成流程里的一个智能服务节点流程走到这里把上下文丢给模型模型返回结构化结果流程再根据结果继续往下走。这样既保留了 BPMN 的确定性骨架又在关键节点注入了语义理解能力。我这次做的场景是合同审批流法务初审之前加一个 LLM 节点自动识别合同里的风险条款并给出风险等级高风险直接走加签分支低风险走快速通道。整套东西基于Flowable Spring Boot Spring AI模型侧对接的是兼容 OpenAI 协议的服务。下面把踩过的坑和最终跑通的方案完整拆一遍。适合谁看已经用过 Flowable 做过基础审批流、想往流程里加 AI 能力的后端同学或者正在评估到底用 Spring AI 还是 LangGraph4j的架构选型人。如果你连 BPMN 的排他网关都没画过建议先把 Flowable 快速入门过一遍再回来。2. 整体方案设计与选型思路拆解2.1 三种接入姿势的取舍把 LLM 接进 Flowable业内常见三条路我挨个试过说说真实感受。第一种是Service Task Java 委托类。在 BPMN 里画一个 Service Task指定flowable:class或flowable:delegateExpression委托类里直接调 Spring AI 的 ChatClient。优点是简单直接流程图一眼能看懂缺点是模型调用是同步阻塞的一个合同跑十几秒流程实例就卡在那儿并发一上来线程池直接打满。第二种是External Worker 模式。Flowable 提供 External Worker 任务类型流程走到这个节点会往ACT_RU_EXT_TASK表里插一条待办外部的一个独立 Worker 服务轮询拉取、处理、回填结果。这是我最推荐的方案原因后面细说。第三种是HTTP Task 直连模型网关。BPMN 里用 HTTP Task 直接 POST 到模型服务。看着最省事但鉴权、重试、结构化解析全得在流程定义里硬编码维护起来是灾难不推荐。我最终选的是External Worker Spring AI的组合。核心考量是解耦流程引擎只管编排模型调用这种耗时且可能失败的操作放到独立 Worker 里可以独立扩缩容、独立重试、独立限流。流程实例不会因为模型服务抖动而被拖死。2.2 为什么是 External Worker 而不是异步 Service Task有人会问Flowable 的 Service Task 也能配成flowable:asynctrue交给异步执行器跑不也解耦了吗理论上是的但实际用下来有几个硬伤。异步执行器用的是引擎内部的线程池默认配置下核心线程数有限模型调用动辄 5 到 30 秒很容易把执行器的线程占满导致其他流程的定时任务、异步任务全部排队。而且异步 Service Task 的重试是引擎级别的失败后按固定策略重试你很难针对模型限流和模型返回格式错误做差异化处理。External Worker 就不一样了。Worker 是独立进程你可以起 10 个实例专门处理 LLM 任务用消息队列的思路做背压。任务拉取用fetchAndLock带锁超时处理失败可以unlock让别人重试也可以complete时带上错误码走 BPMN 的错误边界事件。这套机制天然适配外部依赖不稳定的场景。2.3 数据契约设计让模型输出可被流程消费这是整个方案里最容易被低估的一环。LLM 输出的是自然语言流程需要的是确定性的变量。中间必须有一层结构化契约。我的做法是定义一个LlmTaskResult的 JSON Schema强制模型按这个格式返回{ riskLevel: HIGH | MEDIUM | LOW, riskPoints: [条款编号或描述], summary: 一句话摘要, confidence: 0.0 }然后在 Spring AI 侧用BeanOutputConverter把模型输出直接映射成 Java 对象。这样流程里的排他网关就能用${llmResult.riskLevel HIGH}这种表达式做判断干净利落。注意不要指望模型 100% 按格式返回。必须做兜底——解析失败时给一个默认的MEDIUM等级并标记parseErrortrue让流程走人工复核分支而不是直接抛异常中断流程。3. 核心细节解析与实操要点3.1 BPMN 流程定义的关键配置先看流程定义里 LLM 节点长什么样。我用的是 External Worker 的 Service TaskXML 片段如下serviceTask idllmRiskCheck name大模型风险初筛 flowable:typeexternal-worker flowable:topicllm-risk-check extensionElements flowable:field namepromptTemplate stringValuecontract-risk-v2/ flowable:field namemodelName stringValueqwen-plus/ /extensionElements /serviceTask这里flowable:topic是 Worker 拉取任务的标识多个 Worker 可以订阅同一个 topic 做水平扩展。field里塞的是业务参数比如用哪个 prompt 模板、调哪个模型这样同一套 Worker 代码能服务多个流程节点。节点后面接一个排他网关根据riskLevel分流exclusiveGateway idriskGateway/ sequenceFlow sourceRefriskGateway targetRefmanualReview conditionExpression xsi:typetFormalExpression ![CDATA[${llmResult.riskLevel HIGH}]] /conditionExpression /sequenceFlow提示条件表达式里访问的变量名必须和 Worker 回填时用的变量名完全一致。我踩过一次坑Worker 里写的是resultBPMN 里写的是llmResult流程直接报Unknown property排查了半小时。3.2 Spring AI 侧的模型调用封装Spring AI 的ChatClient用起来很顺手关键是 prompt 模板和输出转换。我的封装大致是这样Service public class LlmRiskService { private final ChatClient chatClient; public LlmRiskService(ChatClient.Builder builder) { this.chatClient builder .defaultSystem(你是合同风险审查专家只输出JSON不要任何解释性文字。) .build(); } public LlmTaskResult checkRisk(String contractText) { BeanOutputConverterLlmTaskResult converter new BeanOutputConverter(LlmTaskResult.class); String prompt 请审查以下合同文本识别风险条款。 输出格式要求 %s 合同文本 %s .formatted(converter.getFormat(), contractText); String raw chatClient.prompt() .user(prompt) .options(ChatOptions.builder() .model(qwen-plus) .temperature(0.1) .build()) .call() .content(); return converter.convert(raw); } }几个关键参数的选择理由temperature设成 0.1 而不是 0是因为完全为 0 时部分模型会出现重复输出的退化现象0.1 在稳定性和多样性之间比较平衡。model走的是配置中心方便按流程节点切换不同模型。3.3 External Worker 的拉取与回填逻辑Worker 的核心循环用 Flowable 的ExternalWorkerClientScheduled(fixedDelay 1000) public void pollAndProcess() { ListLockedExternalTask tasks client.fetchAndLock( llm-risk-check, worker-1, 10, 60000); for (LockedExternalTask task : tasks) { try { String contractText (String) task.getVariables().get(contractText); LlmTaskResult result llmRiskService.checkRisk(contractText); MapString, Object vars new HashMap(); vars.put(llmResult, result); client.complete(task.getId(), worker-1, vars); } catch (Exception e) { // 不 complete让锁超时后重新入队 log.error(LLM task failed: {}, task.getId(), e); } } }fetchAndLock的第三个参数是单次拉取数量第四个是锁超时时间毫秒。锁超时设 60 秒是因为模型调用最坏情况可能到 30 秒留一倍余量。如果设太短任务还在处理中锁就过期了会被另一个 Worker 重复拉取导致重复调用模型、浪费 token。注意complete时回填的变量会合并进流程实例的变量池。如果变量名和已有变量冲突会覆盖。建议给 LLM 相关变量统一加前缀比如llm_。4. 完整实操流程与关键环节实现4.1 环境准备与依赖清单先把依赖理清楚版本不匹配是新手最容易卡住的地方。组件版本说明Spring Boot3.2.xSpring AI 要求 3.2 以上Flowable7.0.x7.x 对 Spring Boot 3 支持更好Spring AI1.0.0-M1 及以上早期版本 API 变动大建议用较新里程碑版JDK17Spring Boot 3 硬性要求Maven 依赖核心就三块flowable-spring-boot-starter、spring-ai-openai-spring-boot-starter或对应厂商的 starter、以及数据库驱动。Flowable 默认用 H2生产环境记得换成 MySQL 或 PostgreSQL并且把flowable.database-schema-update设成true让它自动建表。4.2 数据库表与流程变量的对应关系理解 Flowable 的表结构对排查问题帮助极大。跟 External Worker 相关的核心表是ACT_RU_EXT_TASK任务拉取时这里会插入记录LOCK_OWNER_字段记录是谁锁的LOCK_EXP_TIME_记录锁过期时间。流程变量存在ACT_RU_VARIABLENAME_是变量名TEXT_存字符串值复杂对象会被序列化成 JSON 或字节数组。我遇到过一次诡异的问题LLM 返回的riskPoints是个 List回填后流程里取出来变成了字符串。原因是 Flowable 默认的变量类型对复杂对象处理有限需要显式指定类型或者干脆序列化成 JSON 字符串存。后来我统一把复杂结构转成 JSON 字符串回填流程里用表达式解析反而更稳。4.3 从流程启动到结果落库的完整链路把整条链路串一遍方便你对照自己的场景。流程启动时业务系统调用runtimeService.startProcessInstanceByKey把合同文本、合同 ID 等作为变量传入。流程走到 LLM 节点引擎往ACT_RU_EXT_TASK插一条 topic 为llm-risk-check的任务。Worker 轮询拉到任务调用 Spring AI拿到结构化结果complete回填。引擎继续往下走排他网关根据riskLevel分流。高风险走人工复核低风险走快速通道两条路最终都汇到归档节点。整个过程中模型调用是唯一的不确定环节所以我在 Worker 里加了三级防护调用超时设 30 秒、失败重试最多 3 次、重试仍失败则回填一个riskLevelMEDIUM加llmFailedtrue的兜底结果让流程走人工分支而不是卡死。4.4 参数计算并发量与 Worker 数量的估算这块很多人拍脑袋我给个可落地的算法。假设日均合同量 2000 单集中在 8 小时工作时间内平均每单模型调用耗时 8 秒。那么峰值 QPS 大约是2000 / (8 * 3600) * 峰值系数峰值系数取 3约等于 0.21 QPS。单个 Worker 串行处理能力是1 / 8 0.125 QPS所以至少需要 2 个 Worker 实例才能扛住峰值。但实际不能只按平均值算因为模型服务本身有并发上限。如果模型网关限制单账号 5 并发那 Worker 数量再多也没用反而会触发限流。这时候要么申请更高配额要么在 Worker 侧加信号量做本地限流把并发控制在配额以内。5. 常见问题与排查技巧实录5.1 模型返回格式错乱的三种典型情况第一种是模型在 JSON 外面包了 markdown 代码块标记比如json ... 。Spring AI 的BeanOutputConverter对这种情况有一定容错但不是万能的。我的处理是在转换前先做一次清洗用正则把代码块标记剥掉。第二种是模型自作主张加了字段说明比如在 JSON 后面跟一句以上是审查结果。这种要在 system prompt 里明确禁止并且用temperature压低随机性。第三种是模型返回的枚举值不在预期范围内比如riskLevel返回了UNKNOWN。这种必须在 Java 侧做校验非法值一律归到MEDIUM并记录告警。5.2 任务重复执行的排查思路External Worker 最烦的问题就是任务被重复处理。排查顺序是这样的先看ACT_RU_EXT_TASK的LOCK_EXP_TIME_如果锁超时时间早于当前时间说明 Worker 处理太慢导致锁过期。再看 Worker 日志确认是不是模型调用卡住了。最后检查fetchAndLock的锁超时参数是不是设得太短。我最终的配置是锁超时 60 秒、模型调用超时 30 秒、Worker 单次拉取 5 条。这样即使模型偶尔慢到 40 秒锁也不会过期。另外 Worker 处理逻辑要做幂等用任务 ID 做去重防止极端情况下重复调用。5.3 常见问题速查表现象可能原因解决方向流程卡在 LLM 节点不动Worker 没启动或 topic 不匹配检查 Worker 订阅的 topic 与 BPMN 是否一致变量取不到值变量名不一致或类型不匹配统一命名规范复杂对象转 JSON 字符串模型调用超时网络或模型服务负载高加超时、重试、降级兜底任务重复执行锁超时太短调大锁超时处理逻辑做幂等网关条件不生效表达式语法错误用${}包裹字符串比较加单引号5.4 独家避坑经验说几个文档里不会写的。第一Spring AI 的ChatClient默认是阻塞的如果你在 Worker 里用虚拟线程或者响应式要注意线程模型的适配别把阻塞调用放到事件循环线程里。第二Flowable 的变量序列化对中文支持没问题但如果你的合同文本特别长超过几万字建议存到对象存储流程变量里只存引用 ID否则ACT_RU_VARIABLE表会被撑爆。第三模型返回的confidence字段别太当真它和实际准确率的相关性有限我一般只用来做日志分析不用来做流程判断。6. 关于 Spring AI 与 LangGraph4j 的选型补充热词里有人问现在到底用 Spring AI 还是 LangGraph4j结合这个项目说说我的看法。Spring AI 的定位是Spring 生态里的模型调用抽象层它擅长的是把模型调用、RAG、工具调用这些能力以 Spring 的方式集成进来和 Flowable 这种流程引擎配合天然顺畅因为大家都在 Spring 容器里。LangGraph4j 的定位是用图的方式编排 Agent 逻辑它自己就是一套编排框架。如果你的场景是纯 Agent 自主决策、多轮工具调用、状态机式的复杂推理LangGraph4j 更合适。但如果你已经有 Flowable 在做业务编排再引入 LangGraph4j 就是两套编排体系打架职责边界会非常模糊。我的结论是流程编排交给 Flowable模型能力交给 Spring AI两者用 External Worker 解耦。这样各司其职谁也不用迁就谁。真要做复杂的 Agent 推理可以把它封装成一个独立的 Worker 服务对流程来说它就是一个普通的任务处理器内部怎么折腾是它自己的事。这套方案我在生产环境跑了三个月日均处理合同两千多份模型调用成功率稳定在 99% 以上剩下的 1% 走人工兜底分支没有出现过流程卡死的情况。唯一还在持续调优的是 prompt 模板风险识别的准确率从最初的 78% 提到了现在的 91%这块没有捷径就是拿真实数据反复迭代。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。