概述
Slickflow.NET 作为基于 .NET 的开源工作流引擎,除了支持传统的人工审批流程(用户任务、会签、加签等),还提供了流程自动化运行能力。与需要人工介入的审批流程不同,自动化运行流程在启动后可按预定义顺序自动执行所有节点,直至流程结束,无需人工参与。
本文面向 Slickflow 引擎开发人员,重点讲解:
- 流程自动化运行的底层逻辑与架构(相对于人工审批流程的差异)
- 代码方式定义流程:使用 Workflow 类在内存中构建 BPMN 流程
- 代码方式运行流程:使用 WorkflowExecutor 及 UseProcess(workflow) 执行
- 两个完整测试用例:LocalMethod 订单金额计算流程、AI 多轮问答智能客服流程
一、流程自动化运行 vs 人工审批流程
1.1 核心差异
维度人工审批流程流程自动化运行执行模式每执行一个节点后暂停,等待人工操作启动后自动顺序执行所有节点,直到结束节点类型用户任务(UserTask)为主,需要签收、办理服务任务(ServiceTask)、AI 任务、脚本任务等自动节点持久化依赖数据库存储流程实例、活动实例、任务可选用纯内存执行,不落库上下文ActivityForwardContext、IDbSessionAutoExecutionContext、内存变量字典典型场景请假审批、报销审批、合同会签数据处理流水线、AI 对话编排、ETL 流程1.2 自动化运行的底层逻辑
自动化运行流程的核心是:引擎在启动流程后,循环执行“获取下一可执行活动 → 执行活动 → 推进流程”,直到没有可执行活动(流程结束)或达到步数上限。
简化伪代码:- 启动流程 -> 创建流程实例
- while (存在可执行活动) {
- 获取下一可执行活动列表
- foreach (活动 in 活动列表)
- 执行活动(调用 Service / AI / Script 执行器)
- 推进流程(Run)-> 更新当前节点、流转到下一节点
- }
- 返回执行结果
复制代码 与人工审批流程的区别在于:
- 人工审批:执行完一个用户任务后,流程停在待办,需调用 WorkflowService.Run(runner) 时传入下一节点办理人,或由用户在前端签收后再继续。
- 自动化运行:服务任务、AI 任务、脚本任务执行完毕后,引擎自动推进到下一节点,无需人工介入。
二、架构设计
2.1 整体架构
- ┌─────────────────────────────────────────────────────────────────┐
- │ Workflow(流程定义层) │
- │ Slickflow.Graph.Model.Workflow │
- │ .Start() .ServiceTask() .RagService() .End() │
- └───────────────────────────┬─────────────────────────────────────┘
- │ BuildInMemory()
- ▼
- ┌─────────────────────────────────────────────────────────────────┐
- │ ProcessEntity(内存流程实体) │
- │ ProcessXmlBuilder 序列化为 BPMN XML,不写入数据库 │
- └───────────────────────────┬─────────────────────────────────────┘
- │ UseProcess(workflow)
- ▼
- ┌─────────────────────────────────────────────────────────────────┐
- │ WorkflowExecutor(执行器) │
- │ UseApp / UseProcess / AddVariable / Run │
- └───────────────────────────┬─────────────────────────────────────┘
- │
- ┌───────────────┼───────────────┐
- ▼ ▼ ▼
- ┌───────────────┐ ┌───────────────┐ ┌───────────────┐
- │ ServiceTask │ │ RagService │ │ LocalService │
- │ (LocalMethod) │ │ (AI/RAG) │ │ (服务类) │
- │ 委托注册表解析 │ │ 大模型调用 │ │ 反射实例化 │
- └───────────────┘ └───────────────┘ └───────────────┘
复制代码 2.2 关键组件
组件职责Workflow代码定义流程结构,支持 Start、Task、ServiceTask、RagService、LlmService、Agent、Parallels、Branch、End 等ProcessXmlBuilder将 Workflow 图结构序列化为 BPMN 2.0 XMLWorkflowExecutorExtensions.UseProcess(workflow)接收 Workflow 实例,调用 BuildInMemory() 生成 ProcessEntity,并按 ProcessId:Version 进行内存缓存,无需数据库ServiceTaskDelegateRegistryLocalMethod 委托注册表,按 key 解析并执行本地方法WorkflowActivityExecutor根据 ServiceDetail.Method 分发:LocalMethod / LocalService / WebAPI 等AutoExecutionContext内存执行上下文,Variables 字典存储输入输出变量三、代码方式定义流程
3.1 基本语法
- using Slickflow.Graph.Model;
- var wf = new Workflow("ProcessName", "ProcessCode");
- wf.Start("Start")
- .ServiceTask("服务节点名", "ActivityCode", "DelegateKey") // LocalMethod
- .RagService("RAG 回答", "RAG001") // RAG 节点
- .LlmService("LLM 智能节点", "LLM001") // 通用 LLM 节点
- .ServiceTask<MyService>("保存", "Save001") // LocalService 服务类
- .End("End");
复制代码
- new Workflow(string name, string code):name 为流程名称,code 为流程编码;内部会自动生成 ProcessId = "process_xxx" 与默认版本号 Version = "1"。
- Start("Start"):开始事件
- ServiceTask(name, code, delegateKey):绑定 LocalMethod,delegateKey 对应委托注册表中的 key
- RagService(name, code):RAG 增强的 AI 服务任务
- LlmService(name, code):通用大模型服务任务节点(如 DeepSeek、通义千问等)
- ServiceTask(name, code):绑定本地服务类
- End("End"):结束事件
3.2 并行与分支(Parallels / Branch)
最新的 Workflow 支持更简洁的并行与分支语法,例如:- // 简单并行:开始 -> 并行三个任务 -> 汇聚 -> 结束
- wf.Start("Start")
- .AndSplit("并行网关")
- .Parallels(
- ("任务A", "TaskA"),
- ("任务B", "TaskB"),
- ("任务C", "TaskC"))
- .AndJoin("并行汇聚")
- .End("End");
复制代码 复杂分支可以通过 Branch + Lambda 定义分支内部节点:- wf.Start("Start")
- .Split("条件网关")
- .Branch(
- () => wf.Task("条件1处理", "Cond1"),
- () => wf.Task("条件2处理", "Cond2"))
- .End("End");
复制代码 3.3 Build() 与 BuildInMemory()
方法作用数据库wf.Build()序列化流程并插入 wf_process 表写入wf.BuildInMemory()仅生成内存中的 ProcessEntity不涉及使用 UseProcess(workflow) 时,内部调用 BuildInMemory(),并按 ProcessId:Version 将 ProcessEntity 缓存到内存中,不产生数据库写入,适合单元测试、Demo、嵌入式场景;同一进程内多次调用会复用缓存的流程定义。
四、代码方式运行流程
4.1 执行入口
- using Slickflow.Engine.Executor;
- using Slickflow.Engine.Core.Result;
- var wfe = new WorkflowExecutor();
- var result = await wfe
- .UseApp("App-001", "AppName", "AppCode")
- .UseProcess(wf) // 直接传入 Workflow 实例
- .AddVariable("key", value)
- .Run();
- if (result.Status == WfExecutedStatus.Success)
- Console.WriteLine(result.Message);
复制代码 4.2 参数传递
- 输入:AddVariable("key", value) 传入流程变量
- 输出:服务节点 / AI 节点通过 context.Variables["VarName"] = result 写回
- LocalMethod:通过 IEventService.GetVariable 读取、SaveVariable 或返回值写入
五、测试用例一:LocalMethod 订单金额计算流程
5.1 流程说明
流程:开始 → 校验订单 → 计算金额 → 通知结果 → 结束。其中「校验订单」「计算金额」「通知结果」均使用 LocalMethod 绑定本地方法。
5.2 定义本地方法并注册
- using Slickflow.Engine.Executor;
- using Slickflow.Engine.Event;
- using Slickflow.Engine.Common;
- // 校验订单:检查必填变量
- void ValidateOrder(IEventService ctx)
- {
- var orderId = ctx.GetVariable(ProcessVariableScopeEnum.Process, "OrderId")?.ToString();
- var quantity = ctx.GetVariable(ProcessVariableScopeEnum.Process, "Quantity")?.ToString();
- if (string.IsNullOrEmpty(orderId) || string.IsNullOrEmpty(quantity))
- throw new InvalidOperationException("OrderId and Quantity are required.");
- }
- // 计算金额:单价 * 数量,返回 ServiceTaskResult 指定输出变量名
- object CalcAmount(IEventService ctx)
- {
- var price = decimal.Parse(ctx.GetVariable(ProcessVariableScopeEnum.Process, "UnitPrice")?.ToString() ?? "0");
- var qty = int.Parse(ctx.GetVariable(ProcessVariableScopeEnum.Process, "Quantity")?.ToString() ?? "0");
- var total = price * qty;
- return ServiceTaskResult.WithVariable("Var_OrderTotal", total);
- }
- // 通知结果:打印到控制台
- void NotifyResult(IEventService ctx)
- {
- var total = ctx.GetVariable(ProcessVariableScopeEnum.Process, "Var_OrderTotal");
- Console.WriteLine($"[NotifyResult] 订单总金额: {total}");
- }
- // 注册到全局委托表
- ServiceTaskDelegateRegistry.Global.Register("ValidateOrder", ValidateOrder);
- ServiceTaskDelegateRegistry.Global.Register("CalcAmount", CalcAmount);
- ServiceTaskDelegateRegistry.Global.Register("NotifyResult", NotifyResult);
复制代码 5.3 定义流程
- using Slickflow.Graph.Model;
- var wf = new Workflow("OrderCalcProcess", "OrderCalcProcess_Code");
- wf.Start("Start")
- .ServiceTask("校验订单", "Validate001", "ValidateOrder")
- .ServiceTask("计算金额", "Calc001", "CalcAmount")
- .ServiceTask("通知结果", "Notify001", "NotifyResult")
- .End("End");
复制代码 5.4 执行并打印结果
- var result = await new WorkflowExecutor()
- .UseApp("OrderApp-001", "OrderApp")
- .UseProcess(wf)
- .AddVariable("OrderId", "ORD-2025-001")
- .AddVariable("Quantity", "3")
- .AddVariable("UnitPrice", "99.50")
- .Run();
- Console.WriteLine($"Status: {result.Status}");
- Console.WriteLine($"Message: {result.Message}");
- // 从返回结果中获取输出变量(若引擎支持)
- var vars = result.Variables; // 或通过 result 的扩展属性获取
- if (vars != null && vars.TryGetValue("Var_OrderTotal", out var total))
- Console.WriteLine($"OrderTotal: {total}");
复制代码 5.5 输出示例
- [NotifyResult] 订单总金额: 298.50
- Status: Success
- Message: 流程执行成功
- OrderTotal: 298.50
复制代码 六、测试用例二:AI 多轮问答智能客服流程
6.1 流程说明
流程:开始 → RAG 智能回复 → 提取联系方式 → 保存客户 → 保存对话 → 结束。其中 RAG 节点调用大模型实现多轮问答,后续节点处理结构化数据。
6.2 定义流程
- using Slickflow.Graph.Model;
- using Slickflow.Module.External.Customer;
- var wf = new Workflow("ChatAppProcess", "ChatAppProcess_Code");
- wf.Start("Start")
- .RagService("RAG 智能回复", "RAG001")
- .ServiceTask<ContactExtractService>("提取联系方式", "Extract001")
- .ServiceTask<ContactSaveService>("保存客户", "Save001")
- .ServiceTask<ConversationService>("保存对话记录", "Msg001")
- .End("End");
复制代码 6.3 执行流程
- var messageId = Guid.NewGuid().ToString("N");
- var sessionId = "sess-001";
- var userMessage = "你好,我想了解一下你们的产品,我叫张三,手机 13812345678";
- var result = await new WorkflowExecutor()
- .UseApp($"ChatApp-{messageId}-{sessionId}", "ChatAppMessage")
- .UseProcess(wf)
- .AddVariable("user_message", userMessage)
- .AddVariable("customer_id", "cust-xxx")
- .AddVariable("session_id", sessionId)
- .AddVariable("industry_id", "1")
- .SetNotifyClient(sessionId, (sid, data) => Console.WriteLine($"[Notify] {sid}: {data}"))
- .Run();
- // RAG 节点会将 AI 回复写入 ai_response
- var aiResponse = result.AiResponse;
- Console.WriteLine($"AI 回复: {aiResponse}");
复制代码 6.4 数据流说明
变量来源用途user_message调用方 AddVariable用户输入,RAG 与 ConversationService 使用ai_responseRAG 节点写回大模型生成的回复customerContactExtractService 写回提取的联系方式 JSONcustomer_id, session_id, industry_id调用方 AddVariable业务上下文七、LocalMethod 与 AI 节点技术要点
7.1 LocalMethod 执行链路
- WorkflowExecutor.Run 推进到 ServiceTask 节点
- WorkflowActivityExecutor 识别 ServiceDetail.Method == LocalMethod
- 从 ServiceDetail.Expression 取出 delegateKey(如 "CalcAmount")
- ServiceTaskDelegateRegistry 按 key 解析委托
- 调用 ExecuteLocalMethodInExecutor 执行委托,传入 IEventService 上下文
- 若委托返回 ServiceTaskResult,按其中指定的变量名写回;否则写回默认变量
7.2 ServiceTaskResult 自定义输出变量
- // 默认写回 ServiceResult
- return total;
- // 自定义变量名
- return ServiceTaskResult.WithVariable("Var_OrderTotal", total);
复制代码 7.3 AI/RAG 节点执行链路
- 引擎识别活动类型为 AIService / RagService
- 从 ai_activity_config 或扩展属性读取 AI 配置(SystemPrompt、Temperature 等)
- 用 context.Variables 构建 prompt(如 user_message、历史对话)
- 调用大模型 API(OpenAI、DeepSeek、通义千问等)
- 将返回内容写入 ai_response 或配置的输出变量
- 通过 SetNotifyClient 可实时推送流式输出到前端
八、开发建议
8.1 单元测试
- 使用 UseProcess(workflow) 避免数据库依赖
- 使用 ServiceTaskDelegateRegistry 或 WithDelegateRegistry(customRegistry) 注入 Mock 委托
- 断言 result.Status 与 result.Variables 中的关键变量
8.2 多租户与隔离
- 为不同租户创建独立的 ServiceTaskDelegateRegistry,通过 WithDelegateRegistry 注入
- 流程变量中传递 tenantId,在 LocalMethod 内按租户路由
8.3 与人工审批流程的混合
同一流程中可混用自动节点与用户任务:自动节点执行完毕后引擎自动推进;遇到用户任务时流程暂停,等待人工办理后再调用 Run 继续。
九、总结
Slickflow 流程自动化运行通过以下机制实现:
- 代码定义流程:Workflow 类支持 ServiceTask、RagService 等自动节点,BuildInMemory() 生成内存 ProcessEntity
- 代码运行流程:WorkflowExecutor.UseProcess(workflow) 直接使用内存流程,无需数据库
- LocalMethod:通过 ServiceTaskDelegateRegistry 注册本地方法,实现快速原型与嵌入式场景
- AI 节点:RagService 结合向量检索与大模型,实现多轮智能客服
本文所述两个测试用例(订单金额计算、AI 智能客服)可直接在 Slickflow.ConsoleTest 或自建控制台项目中运行,供引擎开发人员参考与扩展。
参考资料
- Slickflow.NET 工作流引擎关于AI大模型的应用实践
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |