Build lightweight AI agent admin
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
"""
|
||||
条件分支节点
|
||||
"""
|
||||
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': '没有条件匹配时执行的分支',
|
||||
},
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user