【Dify精讲】第15章:自定义节点开发实战

该文章已生成可运行项目,

今天,我们不仅要学会如何开发自定义节点,更要理解 Dify 节点系统的设计哲学,掌握从设计到测试的全流程开发技巧。

一、节点开发规范深度解析

1.1 节点架构的精髓

在深入代码之前,我们先来理解 Dify 节点系统的核心设计思想。

每个节点都继承自 BaseNode,这不是简单的继承关系,而是精心设计的架构模式:

# 来源:api/core/workflow/nodes/base/node.py
class BaseNode(Generic[GenericNodeData]):
    _node_data_cls: type[GenericNodeData]  # 节点数据类型
    _node_type: NodeType                   # 节点类型枚举
    
    def __init__(self, 
                 id: str,
                 config: Mapping[str, Any],
                 graph_init_params: "GraphInitParams",
                 graph: "Graph",
                 graph_runtime_state: "GraphRuntimeState",
                 previous_node_id: Optional[str] = None,
                 thread_pool_id: Optional[str] = None) -> None:
        # 节点基础信息初始化
        self.id = id
        self.tenant_id = graph_init_params.tenant_id
        self.app_id = graph_init_params.app_id
        self.workflow_type = graph_init_params.workflow_type
        # ... 更多属性初始化
        
        # 关键:节点数据验证与转换
        node_data = self._node_data_cls.model_validate(config.get("data", {
   
   }))
        self.node_data = node_data
    
    @abstractmethod
    def _run(self) -> NodeRunResult | Generator[Union[NodeEvent, "InNodeEvent"], None, None]:
        """核心执行方法 - 子类必须实现"""
        raise NotImplementedError

设计亮点分析

  1. 泛型约束Generic[GenericNodeData] 确保类型安全
  2. 状态隔离:每个节点实例拥有独立的运行时状态
  3. 配置验证:使用 Pydantic 进行配置校验,避免运行时错误
  4. 错误恢复:内置重试和错误处理机制

1.2 节点生命周期管理

理解节点的生命周期对开发至关重要:

# 节点执行的完整流程
def run(self) -> Generator[Union[NodeEvent, "InNodeEvent"], None, None]:
    try:
        # 1. 执行前置检查
        result = self._run()
    except Exception as e:
        # 2. 异常处理
        logger.exception(f"Node {
     
     self.node_id} failed to run")
        result = NodeRunResult(
            status=WorkflowNodeExecutionStatus.FAILED,
            error=str(e),
            error_type="WorkflowNodeError",
        )
    
    # 3. 结果处理和事件发送
    if isinstance(result, NodeRunResult):
        yield RunCompletedEvent(run_result=result)
    else:
        # 流式处理
        yield from result

二、自定义逻辑节点实战开发

让我们通过一个实际的例子来学习如何开发自定义节点。我们要开发一个 数据验证节点,它能够验证输入数据的格式,并根据验证结果进行不同的处理。

2.1 定义节点数据结构

首先,我们需要定义节点的配置数据结构:

# entities.py
from typing import Any, Dict, List, Literal, Optional
from pydantic import BaseModel, Field
from core.workflow.nodes.base import BaseNodeData

class ValidationRule(BaseModel):
    """单个验证规则"""
    field_path: str = Field(..., description="字段路径,支持点号分隔")
    rule_type: Literal["required", "type", "range", "regex", "custom"] = Field(..., description="规则类型")
    expected_value: Any = Field(None, description="期望值或范围")
    error_message: str = Field("", description="验证失败时的错误信息")

class DataValidationNodeData(BaseNodeData):
    """数据验证节点配置"""
    # 输入变量选择器 - 要验证的数据
    input_variable: str = Field(..., description="输入数据变量")
    
    # 验证规则列表
    validation_rules: List[ValidationRule] = Field(default_factory=list, description="验证规则列表")
    
    # 验证模式
    validation_mode: Literal["strict", "loose"] = Field(default="strict", description="验证模式")
    
    # 输出配置
    output_valid_data: bool = Field(default=True, description="是否输出有效数据")
    output_errors: bool = Field(default=True, description="是否输出错误信息")
    
    # 失败处理方式
    on_failure: Literal["stop", "continue", "branch"] = Field(default="stop", description="验证失败时的处理方式")

设计思考

  • 使用 Pydantic 进行配置验证,确保运行时数据安全
  • 支持多种验证规则类型,扩展性良好
  • 灵活的失败处理策略,适应不同业务场景

2.2 实现核心验证逻辑

接下来实现节点的核心逻辑:

# data_validation_node.py
import re
import logging
from collections.abc import Mapping, Sequence
from typing import Any, Dict, List, Optional, Union
from jsonpath import JSONPathMatch

from core.workflow.entities.node_entities import NodeRunResult
from core.workflow.entities.variable_entities import VariableSelector
from core.workflow.nodes.base import BaseNode
from core.workflow.nodes.enums import NodeType
from models.workflow import WorkflowNodeExecutionStatus

from .entities import DataValidationNodeData, ValidationRule

logger = logging.getLogger(__name__)

class DataValidationNode(BaseNode[DataValidationNodeData]):
    """数据验证节点 - 验证输入数据的格式和内容"""
    
    _node_data_cls = DataValidationNodeData
    _node_type = NodeType.DATA_VALIDATION  # 需要在枚举中添加

    def _run(self) -> NodeRunResult:
        """执行数据验证"""
        try:
            # 1. 获取输入数据
            input_data = self._get_input_data()
            if input_data is None:
                return self._create_error_result("输入数据为空")
            
            # 2. 执行验证
            validation_result = self._validate_data(input_data)
            
            # 3. 处理验证结果
            return self._process_validation_result(input_data, validation_result)
            
        except Exception as e:
            logger.exception(f"数据验证节点执行失败: {
     
     str(e)}")
            return self._create_error_result(f"执行异常: {
     
     str(e)}")
    
    def _get_input_data(self) -> Any:
        """获取输入数据"""
        variable_selector = VariableSelector.model_validate(self.node_data.input_variable)
        variable = self.graph_runtime_state.variable_pool.get(variable_selector.value_selector)
        return variable.to_object() if variable else None
    
    def _validate_data(self, data: Any) -> Dict[str, Any]:
        """执行数据验证"""
        result = {
   
   
            "is_valid": True,
            "errors": [],
            "warnings": [],
            "validated_fields": []
        }
        
        for rule in self.node_data.validation_rules:
            try:
                field_result = self._validate_field(data, rule)
                result["validated_fields"].append(field_result)
                
                if not field_result["is_valid"]:
                    result["is_valid"] = False
                    result["errors"].append(field_result["error"])
                    
            except Exception as e:
                logger.warning(f"验证规则执行失败: {
     
     rule.field_path} - {
     
     str(e)}")
                if self.node_data.validation_mode == "strict":
                    result["is_valid"] = False
                    result["errors"].append(f"规则执行失败: {
     
     str(e)}")
                else:
                    result["warnings"].append(f"规则执行失败: {
     
     str(e)}")
        
        return result
    
    def _validate_field(self, data: Any, rule: ValidationRule) -> Dict[str, Any]:
        """验证单个字段"""
        field_result = {
   
   
            "field_path": rule.field_path,
            "rule_type": rule.rule_type,
            "is_valid": True,
            "error": None,
            "actual_value": None
        }
        
        try:
            # 获取字段值
            field_value = self._get_field_value(data, rule.field_path)
            field_result["actual_value"] = field_value
            
            # 根据规则类型进行验证
            is_valid = self._apply_validation_rule(field_value, rule)
            
            if not is_valid:
                field_result["is_valid"] = False
                field_result["error"] = rule.error_message or f"{
     
     rule.field_path} 验证失败"
                
        except Exception as e:
            field_result["is_valid"] = False
            field_result["error"] = f"字段访问失败: {
     
     str(e)}"
        
        return field_result
    
    def _get_field_value(self, data: Any, field_path: str) -> Any:
        """根据路径获取字段值 - 支持点号分隔和数组索引"""
        if not field_path:
            return data
        
        current = data
        parts = field_path.split('.')
        
        for part in parts:
            # 处理数组索引 如 items[0]
            if '[' in part and ']' in part:
                field_name = part.split('[')[0]
                index_str = part.split('[')[1].split(']')[0]
                
                if field_name:
                    current = current[field_name]
                
                if index_str.isdigit():
                    current = current[int(index_str)]
                else:
                    # 支持字典键访问
                    current = current[index_str]
            else:
                # 普通字段访问
                if isinstance(current, dict):
                    current = current.get(part)
                else:
                    current = getattr(current, part, None)
        
        return current
    
    def _apply_validation_rule(self, value: Any, rule: ValidationRule) -> bool:
        """应用验证规则"""
        try:
            if rule.rule_type == "required":
                return value is not None and value != ""
            
            elif rule.rule_type == "type":
                expected_type = rule.expected_value
                if expected_type == "string":
                    return isinstance(value, str)
                elif expected_type == "number":
                    return isinstance(value, (int, float))
                elif expected_type == "boolean":
                    return isinstance(value, bool)
                elif expected_type == "array":
                    return isinstance(value, list)
                elif expected_type == "object":
                    return isinstance(value, dict)
                
            elif rule.rule_type == "range":
                if not 
本文章已经生成可运行项目
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值