""" 条件分支节点 """ import logging import operator from typing import Any, Dict from ..base import BaseNode, NodeContext, NodeResult from ..registry import NodeRegistry logger = logging.getLogger(__name__) @NodeRegistry.register class ConditionNode(BaseNode): """ 条件分支节点 根据条件判断选择不同的分支 """ node_type = 'condition' node_name = '条件分支' node_category = 'logic' node_icon = 'git-branch' node_description = '根据条件判断选择不同的执行分支' supports_branches = True inputs = [ { 'name': 'value', 'type': 'any', 'description': '要判断的值', }, ] outputs = [ { 'name': 'branch', 'type': 'string', 'description': '选中的分支', }, ] # 支持的操作符 OPERATORS = { 'eq': operator.eq, # 等于 'ne': operator.ne, # 不等于 'gt': operator.gt, # 大于 'gte': operator.ge, # 大于等于 'lt': operator.lt, # 小于 'lte': operator.le, # 小于等于 'contains': lambda a, b: b in str(a), # 包含 'not_contains': lambda a, b: b not in str(a), # 不包含 'starts_with': lambda a, b: str(a).startswith(str(b)), # 开头是 'ends_with': lambda a, b: str(a).endswith(str(b)), # 结尾是 'is_empty': lambda a, _: not a, # 为空 'is_not_empty': lambda a, _: bool(a), # 不为空 } def execute(self, context: NodeContext) -> NodeResult: """执行条件判断""" try: branches = self.config.get('branches', []) # 遍历所有 IF 分支 for branch in branches: branch_id = branch.get('id') conditions = branch.get('conditions', []) # 评估该分支的所有条件 if self._evaluate_branch(conditions, context): return NodeResult( success=True, output=branch_id, next_node_id=branch_id, metadata={'matched_branch': branch_id}, ) # 没有匹配的条件,走 ELSE 分支 return NodeResult( success=True, output='else', next_node_id='else', metadata={'matched_branch': 'else'}, ) except Exception as e: logger.exception(f'条件节点执行失败: {e}') return NodeResult( success=False, error=str(e), ) def _evaluate_branch(self, conditions: list, context: NodeContext) -> bool: """评估分支的一组条件 (AND 关系)""" if not conditions: return True # 无条件默认为真 for condition in conditions: if not self._evaluate_condition(condition, context): return False return True def _resolve_variable(self, variable: str, context: NodeContext) -> Any: """ 解析变量引用 支持格式: - {{variable_name}} - 直接变量引用 - {{NodeID.key}} - 节点输出引用 - 普通字符串 - 直接返回 """ if not isinstance(variable, str): return variable # 检查是否是 {{...}} 格式 if not (variable.startswith('{{') and variable.endswith('}}')): return variable content = variable[2:-2].strip() logger.info(f'解析变量: {variable}, content={content}, 上下文变量keys={list(context.variables.keys())}') # 尝试解析 NodeID.key 格式 if '.' in content: parts = content.split('.', 1) node_id = parts[0] var_key = parts[1] logger.info(f'解析节点变量: node_id={node_id}, var_key={var_key}') # 先尝试从节点输出命名空间获取 node_outputs = context.get_variable(f'_node_{node_id}') logger.info(f'节点输出 _node_{node_id}: {node_outputs}') if isinstance(node_outputs, dict) and var_key in node_outputs: return node_outputs[var_key] # 回退:尝试直接从变量获取 node_data = context.get_variable(node_id) logger.info(f'直接变量 {node_id}: {node_data}') if isinstance(node_data, dict) and var_key in node_data: return node_data[var_key] # 直接变量引用 value = context.get_variable(content) if value is not None: return value return None def _evaluate_condition(self, condition: Dict, context: NodeContext) -> bool: """评估单个条件""" variable = condition.get('variable', '') op_name = condition.get('operator', 'equals') value = condition.get('value', '') # 解析变量和值 left_val = self._resolve_variable(variable, context) right_val = self._resolve_variable(value, context) logger.info(f'条件评估: variable={variable}, left_val={left_val}, op={op_name}, right_val={right_val}') # 映射操作符 op_mapping = { 'equals': 'eq', 'not_equals': 'ne', } op_key = op_mapping.get(op_name, op_name) op_func = self.OPERATORS.get(op_key, operator.eq) try: # 特殊处理空值判断,不需要右值 if op_key in ['is_empty', 'is_not_empty']: return op_func(left_val, None) # 布尔值比较:将字符串 "true"/"false" 转换为布尔值 if isinstance(left_val, bool) and isinstance(right_val, str): if right_val.lower() in ['true', '1', 'yes']: right_val = True elif right_val.lower() in ['false', '0', 'no']: right_val = False elif isinstance(right_val, bool) and isinstance(left_val, str): if left_val.lower() in ['true', '1', 'yes']: left_val = True elif left_val.lower() in ['false', '0', 'no']: left_val = False # 尝试转换类型以进行比较 (如数字) if isinstance(left_val, (int, float)) and isinstance(right_val, str): try: if '.' in right_val: right_val = float(right_val) else: right_val = int(right_val) except: pass # 字符串比较忽略类型差异 if op_key in ['contains', 'not_contains', 'starts_with', 'ends_with']: return op_func(str(left_val), str(right_val)) return op_func(left_val, right_val) except Exception as e: logger.warning(f'条件评估失败: {e}') return False @classmethod def get_config_schema(cls) -> Dict[str, Any]: """获取配置 Schema""" return { 'type': 'object', 'properties': { 'conditions': { 'type': 'array', 'title': '条件列表', 'items': { 'type': 'object', 'properties': { 'variable': { 'type': 'string', 'title': '变量名', }, 'operator': { 'type': 'string', 'title': '操作符', 'enum': list(cls.OPERATORS.keys()), 'default': 'eq', }, 'value': { 'type': 'string', 'title': '比较值', }, 'branch_id': { 'type': 'string', 'title': '分支 ID', }, }, }, }, 'default_branch': { 'type': 'string', 'title': '默认分支', 'description': '没有条件匹配时执行的分支', }, }, }