工作流引擎在AI Agent中的应用

引言

随着人工智能技术的快速发展,AI Agent已成为智能化应用的核心组成部分。AI Agent不仅需要具备强大的推理和决策能力,还需要能够处理复杂的工作流程,将多个任务有机地串联起来。本文将以Snowy项目中的工作流引擎为例,探讨工作流引擎在AI Agent中的重要作用和应用场景。

工作流引擎概述

项目中的工作流引擎是一个基于图形化节点的执行引擎,它通过定义不同类型的任务节点并将其连接成工作流,实现了复杂业务逻辑的可视化编排。该引擎具备以下核心特性:

  1. 可扩展的节点执行器:通过NodeExecutor接口,可以灵活扩展不同类型的节点
  2. 图形化流程编排:基于节点(nodes)和边(edges)的数据结构,支持复杂的流程控制
  3. 上下文管理:通过WorkflowContext管理执行过程中的变量和状态
  4. 错误处理机制:完善的异常处理和日志记录功能

以下是WorkflowEngine的主要代码实现:

java 复制代码
package vip.xiaonuo.ai.modular.workflow.executor;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import vip.xiaonuo.ai.modular.workflow.entity.AiWorkflow;
import vip.xiaonuo.ai.modular.workflow.entity.AiWorkflowInstance;
import vip.xiaonuo.ai.modular.workflow.mapper.AiWorkflowInstanceMapper;
import vip.xiaonuo.ai.modular.workflow.service.AiWorkflowService;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 工作流执行引擎
 *
 * @author aiflowy
 * @date 2024/1/11
 **/
@Slf4j
@Component
public class WorkflowEngine {

    @Resource
    private ApplicationContext applicationContext;

    @Resource
    private AiWorkflowInstanceMapper workflowInstanceMapper;

    /** 节点执行器映射 (type -> executor) */
    private final Map<String, NodeExecutor> executorMap = new ConcurrentHashMap<>();

    @PostConstruct
    public void init() {
        // 加载所有节点执行器
        Map<String, NodeExecutor> executors = applicationContext.getBeansOfType(NodeExecutor.class);
        for (NodeExecutor executor : executors.values()) {
            executorMap.put(executor.getType(), executor);
            log.info("Registered node executor: {}", executor.getType());
        }
        log.info("Loaded {} node executors", executorMap.size());
    }

    /**
     * 执行工作流
     */
    public AiWorkflowInstance execute(AiWorkflow workflow, Map<String, Object> inputs, String userId) {
        // 创建实例
        AiWorkflowInstance instance = new AiWorkflowInstance();
        instance.setId(IdUtil.getSnowflakeNextIdStr());
        instance.setWorkflowId(workflow.getId());
        instance.setWorkflowVersion(workflow.getVersion());
        instance.setTriggerUser(userId);
        instance.setTriggerType("MANUAL");
        instance.setInputData(JSONUtil.toJsonStr(inputs));
        instance.setStatus("RUNNING");
        instance.setStartTime(new Date());
        workflowInstanceMapper.insert(instance);

        // 解析工作流定义
        JSONObject definition = JSONUtil.parseObj(workflow.getFlowJson());
        JSONArray nodes = definition.getJSONArray("nodes");
        JSONArray edges = definition.getJSONArray("edges");

        if (nodes == null || nodes.isEmpty()) {
            return completeInstance(instance, null, "FAILED", "No nodes defined");
        }

        // 构建节点图
        Map<String, JSONObject> nodeMap = new HashMap<>();
        Map<String, List<String>> edgeMap = new HashMap<>(); // sourceId -> [targetIds]
        String startNodeId = null;

        for (int i = 0; i < nodes.size(); i++) {
            JSONObject node = nodes.getJSONObject(i);
            String nodeId = node.getStr("id");
            nodeMap.put(nodeId, node);
            if ("start".equals(node.getStr("type"))) {
                startNodeId = nodeId;
            }
        }

        if (edges != null) {
            for (int i = 0; i < edges.size(); i++) {
                JSONObject edge = edges.getJSONObject(i);
                String source = edge.getStr("source");
                String target = edge.getStr("target");
                edgeMap.computeIfAbsent(source, k -> new ArrayList<>()).add(target);
            }
        }

        // 创建执行上下文
        WorkflowContext context = WorkflowContext.builder()
                .workflowId(workflow.getId())
                .instanceId(instance.getId())
                .userId(userId)
                .variables(new HashMap<>(inputs != null ? inputs : Map.of()))
                .nodeOutputs(new HashMap<>())
                .interrupted(false)
                .build();

        // 执行日志
        List<Map<String, Object>> executionLog = new ArrayList<>();

        try {
            // 从起始节点开始执行
            String currentNodeId = startNodeId;
            if (currentNodeId == null) {
                // 如果没有start节点,取第一个节点
                currentNodeId = nodes.getJSONObject(0).getStr("id");
            }

            int maxIterations = 100; // 防止无限循环
            int iteration = 0;

            while (currentNodeId != null && iteration < maxIterations && !context.isInterrupted()) {
                iteration++;

                JSONObject nodeConfig = nodeMap.get(currentNodeId);
                if (nodeConfig == null) {
                    log.error("节点不存在: {}", currentNodeId);
                    log.error("可用节点列表: {}", nodeMap.keySet());
                    return completeInstance(instance, null, "FAILED", "Node not found: " + currentNodeId);
                }

                String nodeType = nodeConfig.getStr("type");
                log.info("========== 第{}次迭代 - 执行节点: {} ({}) ==========", iteration, currentNodeId, nodeType);
                log.info("节点配置: {}", nodeConfig);

                // 获取执行器
                NodeExecutor executor = executorMap.get(nodeType);
                if (executor == null) {
                    // 跳过未知类型的节点
                    log.warn("⚠️ 找不到节点类型 '{}' 的执行器,跳过该节点", nodeType);
                    List<String> nextNodes = edgeMap.get(currentNodeId);
                    currentNodeId = (nextNodes != null && !nextNodes.isEmpty()) ? nextNodes.get(0) : null;
                    continue;
                }
                log.info("✅ 找到执行器: {}", executor.getClass().getSimpleName());

                // 执行节点
                long startTime = System.currentTimeMillis();
                log.info(">>> 开始执行节点 {}", nodeType);
                NodeResult result = executor.execute(context, nodeConfig);
                result.setDuration(System.currentTimeMillis() - startTime);
                log.info("<<< 节点执行完成 - 成功: {}, 耗时: {}ms", result.isSuccess(), result.getDuration());
                if (result.isSuccess()) {
                    log.info("节点输出: {}", result.getOutput());
                } else {
                    log.error("节点执行失败: {}", result.getErrorMsg());
                }

                // 记录执行日志
                Map<String, Object> logEntry = new HashMap<>();
                logEntry.put("nodeId", currentNodeId);
                logEntry.put("nodeType", nodeType);
                logEntry.put("success", result.isSuccess());
                logEntry.put("duration", result.getDuration());
                logEntry.put("timestamp", new Date());
                if (!result.isSuccess()) {
                    logEntry.put("error", result.getErrorMsg());
                }
                executionLog.add(logEntry);

                if (!result.isSuccess()) {
                    return completeInstance(instance, null, "FAILED", result.getErrorMsg());
                }

                // 保存节点输出
                context.setNodeOutput(currentNodeId, result.getOutput());
                // 保存最后一个节点的输出,供结束节点使用
                context.setVariable("_lastNodeResult", result.getOutput());

                // 确定下一个节点
                if (StrUtil.isNotEmpty(result.getNextNodeId())) {
                    currentNodeId = result.getNextNodeId();
                    log.info("节点指定了下一个节点: {}", currentNodeId);
                } else if ("end".equals(nodeType)) {
                    currentNodeId = null;
                    log.info("到达结束节点,工作流完成");
                } else {
                    List<String> nextNodes = edgeMap.get(currentNodeId);
                    log.info("从edgeMap查找下一个节点 - 当前节点: {}, 找到的下一个节点列表: {}", currentNodeId, nextNodes);
                    currentNodeId = (nextNodes != null && !nextNodes.isEmpty()) ? nextNodes.get(0) : null;
                    if (currentNodeId == null) {
                        log.warn("⚠️ 没有找到下一个节点,工作流将结束");
                    } else {
                        log.info("下一个节点ID: {}", currentNodeId);
                    }
                }
            }

            // 完成执行
            log.info("工作流执行完成,最终变量: {}", context.getVariables());
            return completeInstance(instance, context.getVariables(), "SUCCESS", null);

        } catch (Exception e) {
            log.error("Workflow execution failed", e);
            return completeInstance(instance, null, "FAILED", e.getMessage());
        }
    }

    private AiWorkflowInstance completeInstance(AiWorkflowInstance instance, Map<String, Object> output,
                                                 String status, String errorMsg) {
        instance.setStatus(status);
        instance.setErrorMessage(errorMsg);
        instance.setOutputData(output != null ? JSONUtil.toJsonStr(output) : null);
        instance.setEndTime(new Date());
        instance.setCostTime((int)(instance.getEndTime().getTime() - instance.getStartTime().getTime()));
        workflowInstanceMapper.updateById(instance);
        return instance;
    }

    /**
     * 获取所有节点类型
     */
    public List<String> getNodeTypes() {
        return new ArrayList<>(executorMap.keySet());
    }

    /**
     * 获取节点执行器
     */
    public NodeExecutor getExecutor(String type) {
        return executorMap.get(type);
    }
}

AI Agent中的工作流需求

AI Agent在实际应用中面临诸多挑战,包括:

  1. 多步骤推理:复杂的任务往往需要分解为多个子任务
  2. 工具调用:需要调用外部API或工具来获取信息
  3. 条件分支:根据中间结果决定后续执行路径
  4. 状态管理:保持和传递中间状态信息
  5. 容错处理:处理执行过程中的错误和异常

这些需求正是工作流引擎所擅长解决的问题。

工作流引擎在AI Agent中的应用

1. 任务分解与编排

在AI Agent中,复杂任务通常需要被分解为一系列有序的子任务。工作流引擎允许我们将这些子任务定义为不同的节点类型:

  • Start节点:接收初始输入和任务描述
  • LLM节点:执行大语言模型推理
  • Tool节点:调用外部工具或API
  • Condition节点:根据条件决定分支路径
  • End节点:汇总结果并返回

通过可视化界面,用户可以直观地设计AI Agent的工作流程,而无需编写复杂的硬编码逻辑。

2. 条件分支与决策

AI Agent经常需要根据中间结果做出决策。例如,在一个智能客服Agent中,可能需要先理解用户意图,然后根据意图类型选择不同的处理路径:
咨询产品
投诉建议
技术支持
Start
LLM理解意图
判断意图类型
查询产品信息
记录投诉
调用技术知识库
End

工作流引擎通过边的连接关系自然地支持这种条件分支逻辑。

3. 状态管理与数据传递

AI Agent在执行过程中需要保持和传递状态信息。WorkflowContext提供了统一的状态管理机制,使得每个节点都可以访问和修改共享的变量空间。这对于实现记忆功能和上下文感知非常重要。

4. 工具集成与调用

现代AI Agent通常需要调用外部工具来完成特定任务。工作流引擎可以将工具调用封装为专用的节点类型,使AI Agent能够:

  • 调用数据库查询工具获取实时数据
  • 调用搜索引擎获取最新信息
  • 调用计算工具进行数学运算
  • 调用API获取第三方服务

5. 错误处理与恢复

AI Agent在执行过程中可能会遇到各种错误,如网络异常、API调用失败等。Snowy的WorkflowEngine内置了完善的错误处理机制,可以在节点执行失败时进行适当的处理,甚至支持重试机制。

实际应用场景

智能客服Agent

在智能客服场景中,工作流引擎可以编排以下流程:

  1. 接收用户咨询
  2. 使用LLM理解用户意图
  3. 根据意图类型调用相应工具(如查询订单、获取产品信息)
  4. 生成回复
  5. 记录会话历史

文档分析Agent

文档分析Agent可以利用工作流引擎:

  1. 接收上传的文档
  2. 使用OCR或解析工具提取内容
  3. 使用LLM进行内容理解和分类
  4. 根据分类调用不同的处理逻辑
  5. 生成分析报告

自动化数据处理Agent

对于数据处理任务,工作流引擎可以:

  1. 从指定源获取数据
  2. 进行数据清洗和预处理
  3. 应用AI模型进行分析
  4. 将结果存储到目标位置
  5. 生成执行报告

技术优势

1. 可视化设计

工作流引擎提供了图形化的设计界面,非技术人员也可以参与AI Agent的工作流程设计,降低了开发门槛。

2. 灵活性

通过节点的组合和连接,可以轻松构建各种复杂的AI Agent行为模式,而不需要重新编程。

3. 可维护性

可视化的工作流比硬编码的逻辑更容易理解和维护,便于调试和优化。

4. 可复用性

设计好的工作流可以在不同的AI Agent之间复用,提高开发效率。

总结

工作流引擎为AI Agent提供了强大而灵活的编排能力,使其能够处理复杂的多步骤任务。Snowy项目中的WorkflowEngine通过其可扩展的节点执行器、完善的上下文管理和错误处理机制,为AI Agent的开发提供了坚实的基础。随着AI技术的不断发展,工作流引擎将在构建更加智能和复杂的AI Agent系统中发挥越来越重要的作用。

相关推荐
NAGNIP8 小时前
一文搞懂深度学习中的通用逼近定理!
人工智能·算法·面试
冬奇Lab9 小时前
一天一个开源项目(第36篇):EverMemOS - 跨 LLM 与平台的长时记忆 OS,让 Agent 会记忆更会推理
人工智能·开源·资讯
冬奇Lab9 小时前
OpenClaw 源码深度解析(一):Gateway——为什么需要一个"中枢"
人工智能·开源·源码阅读
AngelPP13 小时前
OpenClaw 架构深度解析:如何把 AI 助手搬到你的个人设备上
人工智能
宅小年13 小时前
Claude Code 换成了Kimi K2.5后,我再也回不去了
人工智能·ai编程·claude
九狼13 小时前
Flutter URL Scheme 跨平台跳转
人工智能·flutter·github
ZFSS13 小时前
Kimi Chat Completion API 申请及使用
前端·人工智能
天翼云开发者社区14 小时前
春节复工福利就位!天翼云息壤2500万Tokens免费送,全品类大模型一键畅玩!
人工智能·算力服务·息壤
知识浅谈14 小时前
教你如何用 Gemini 将课本图片一键转为精美 PPT
人工智能
Ray Liang15 小时前
被低估的量化版模型,小身材也能干大事
人工智能·ai·ai助手·mindx