今天,我们不仅要学会如何开发自定义节点,更要理解 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
设计亮点分析:
- 泛型约束:
Generic[GenericNodeData]确保类型安全 - 状态隔离:每个节点实例拥有独立的运行时状态
- 配置验证:使用 Pydantic 进行配置校验,避免运行时错误
- 错误恢复:内置重试和错误处理机制
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

最低0.47元/天 解锁文章
3114

被折叠的 条评论
为什么被折叠?



