Build lightweight AI agent admin

This commit is contained in:
Codex
2026-06-08 18:14:59 +08:00
commit e164840f43
2530 changed files with 435693 additions and 0 deletions
@@ -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': '没有条件匹配时执行的分支',
},
},
}