From 837237aa6d00582aee2621f96974df306239d779 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=9E=E6=B3=95=E6=93=8D=E4=BD=9C?= Date: Tue, 13 Jan 2026 10:11:18 +0800 Subject: [PATCH] fix: use node factory for single-step workflow nodes (#30859) --- api/core/workflow/workflow_entry.py | 13 ++--- .../core/workflow/test_workflow_entry.py | 56 +++++++++++++++++++ 2 files changed, 62 insertions(+), 7 deletions(-) diff --git a/api/core/workflow/workflow_entry.py b/api/core/workflow/workflow_entry.py index ddf545bb34..fd3fc02f62 100644 --- a/api/core/workflow/workflow_entry.py +++ b/api/core/workflow/workflow_entry.py @@ -19,6 +19,7 @@ from core.workflow.graph_engine.protocols.command_channel import CommandChannel from core.workflow.graph_events import GraphEngineEvent, GraphNodeEventBase, GraphRunFailedEvent from core.workflow.nodes import NodeType from core.workflow.nodes.base.node import Node +from core.workflow.nodes.node_factory import DifyNodeFactory from core.workflow.nodes.node_mapping import NODE_TYPE_CLASSES_MAPPING from core.workflow.runtime import GraphRuntimeState, VariablePool from core.workflow.system_variable import SystemVariable @@ -136,13 +137,11 @@ class WorkflowEntry: :param user_inputs: user inputs :return: """ - node_config = workflow.get_node_config_by_id(node_id) + node_config = dict(workflow.get_node_config_by_id(node_id)) node_config_data = node_config.get("data", {}) - # Get node class + # Get node type node_type = NodeType(node_config_data.get("type")) - node_version = node_config_data.get("version", "1") - node_cls = NODE_TYPE_CLASSES_MAPPING[node_type][node_version] # init graph init params and runtime state graph_init_params = GraphInitParams( @@ -158,12 +157,12 @@ class WorkflowEntry: graph_runtime_state = GraphRuntimeState(variable_pool=variable_pool, start_at=time.perf_counter()) # init workflow run state - node = node_cls( - id=str(uuid.uuid4()), - config=node_config, + node_factory = DifyNodeFactory( graph_init_params=graph_init_params, graph_runtime_state=graph_runtime_state, ) + node = node_factory.create_node(node_config) + node_cls = type(node) try: # variable selector to variable mapping diff --git a/api/tests/unit_tests/core/workflow/test_workflow_entry.py b/api/tests/unit_tests/core/workflow/test_workflow_entry.py index 68d6c109e8..b38e070ffc 100644 --- a/api/tests/unit_tests/core/workflow/test_workflow_entry.py +++ b/api/tests/unit_tests/core/workflow/test_workflow_entry.py @@ -2,13 +2,17 @@ from types import SimpleNamespace import pytest +from configs import dify_config from core.file.enums import FileType from core.file.models import File, FileTransferMethod +from core.helper.code_executor.code_executor import CodeLanguage from core.variables.variables import StringVariable from core.workflow.constants import ( CONVERSATION_VARIABLE_NODE_ID, ENVIRONMENT_VARIABLE_NODE_ID, ) +from core.workflow.nodes.code.code_node import CodeNode +from core.workflow.nodes.code.limits import CodeNodeLimits from core.workflow.runtime import VariablePool from core.workflow.system_variable import SystemVariable from core.workflow.workflow_entry import WorkflowEntry @@ -96,6 +100,58 @@ class TestWorkflowEntry: assert output_var is not None assert output_var.value == "system_user" + def test_single_step_run_injects_code_limits(self): + """Ensure single-step CodeNode execution configures limits.""" + # Arrange + node_id = "code_node" + node_data = { + "type": "code", + "title": "Code", + "desc": None, + "variables": [], + "code_language": CodeLanguage.PYTHON3, + "code": "def main():\n return {}", + "outputs": {}, + } + node_config = {"id": node_id, "data": node_data} + + class StubWorkflow: + def __init__(self): + self.tenant_id = "tenant" + self.app_id = "app" + self.id = "workflow" + self.graph_dict = {"nodes": [node_config], "edges": []} + + def get_node_config_by_id(self, target_id: str): + assert target_id == node_id + return node_config + + workflow = StubWorkflow() + variable_pool = VariablePool(system_variables=SystemVariable.empty(), user_inputs={}) + expected_limits = CodeNodeLimits( + max_string_length=dify_config.CODE_MAX_STRING_LENGTH, + max_number=dify_config.CODE_MAX_NUMBER, + min_number=dify_config.CODE_MIN_NUMBER, + max_precision=dify_config.CODE_MAX_PRECISION, + max_depth=dify_config.CODE_MAX_DEPTH, + max_number_array_length=dify_config.CODE_MAX_NUMBER_ARRAY_LENGTH, + max_string_array_length=dify_config.CODE_MAX_STRING_ARRAY_LENGTH, + max_object_array_length=dify_config.CODE_MAX_OBJECT_ARRAY_LENGTH, + ) + + # Act + node, _ = WorkflowEntry.single_step_run( + workflow=workflow, + node_id=node_id, + user_id="user", + user_inputs={}, + variable_pool=variable_pool, + ) + + # Assert + assert isinstance(node, CodeNode) + assert node._limits == expected_limits + def test_mapping_user_inputs_to_variable_pool_with_env_variables(self): """Test mapping environment variables from user inputs to variable pool.""" # Initialize variable pool with environment variables