引言
随着人工智能技术的快速发展,AI Agent已成为智能化应用的核心组成部分。AI Agent不仅需要具备强大的推理和决策能力,还需要能够处理复杂的工作流程,将多个任务有机地串联起来。本文将以Snowy项目中的工作流引擎为例,探讨工作流引擎在AI Agent中的重要作用和应用场景。
工作流引擎概述
项目中的工作流引擎是一个基于图形化节点的执行引擎,它通过定义不同类型的任务节点并将其连接成工作流,实现了复杂业务逻辑的可视化编排。该引擎具备以下核心特性:
- 可扩展的节点执行器:通过NodeExecutor接口,可以灵活扩展不同类型的节点
- 图形化流程编排:基于节点(nodes)和边(edges)的数据结构,支持复杂的流程控制
- 上下文管理:通过WorkflowContext管理执行过程中的变量和状态
- 错误处理机制:完善的异常处理和日志记录功能
以下是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在实际应用中面临诸多挑战,包括:
- 多步骤推理:复杂的任务往往需要分解为多个子任务
- 工具调用:需要调用外部API或工具来获取信息
- 条件分支:根据中间结果决定后续执行路径
- 状态管理:保持和传递中间状态信息
- 容错处理:处理执行过程中的错误和异常
这些需求正是工作流引擎所擅长解决的问题。
工作流引擎在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
在智能客服场景中,工作流引擎可以编排以下流程:
- 接收用户咨询
- 使用LLM理解用户意图
- 根据意图类型调用相应工具(如查询订单、获取产品信息)
- 生成回复
- 记录会话历史
文档分析Agent
文档分析Agent可以利用工作流引擎:
- 接收上传的文档
- 使用OCR或解析工具提取内容
- 使用LLM进行内容理解和分类
- 根据分类调用不同的处理逻辑
- 生成分析报告
自动化数据处理Agent
对于数据处理任务,工作流引擎可以:
- 从指定源获取数据
- 进行数据清洗和预处理
- 应用AI模型进行分析
- 将结果存储到目标位置
- 生成执行报告
技术优势
1. 可视化设计
工作流引擎提供了图形化的设计界面,非技术人员也可以参与AI Agent的工作流程设计,降低了开发门槛。
2. 灵活性
通过节点的组合和连接,可以轻松构建各种复杂的AI Agent行为模式,而不需要重新编程。
3. 可维护性
可视化的工作流比硬编码的逻辑更容易理解和维护,便于调试和优化。
4. 可复用性
设计好的工作流可以在不同的AI Agent之间复用,提高开发效率。
总结
工作流引擎为AI Agent提供了强大而灵活的编排能力,使其能够处理复杂的多步骤任务。Snowy项目中的WorkflowEngine通过其可扩展的节点执行器、完善的上下文管理和错误处理机制,为AI Agent的开发提供了坚实的基础。随着AI技术的不断发展,工作流引擎将在构建更加智能和复杂的AI Agent系统中发挥越来越重要的作用。