From 700a4029c6afb02fdfca5eb8634b3304a4146563 Mon Sep 17 00:00:00 2001 From: -LAN- Date: Fri, 27 Feb 2026 13:29:00 +0800 Subject: [PATCH] refactor(api): inject code executor from node factory (#32618) --- api/.importlinter | 1 - api/core/app/workflow/node_factory.py | 28 ++++++++++++++--- api/core/workflow/nodes/code/code_node.py | 31 ++++++++++++++----- .../workflow/nodes/test_code.py | 1 + .../test_mock_nodes_template_code.py | 13 ++++++++ 5 files changed, 62 insertions(+), 12 deletions(-) diff --git a/api/.importlinter b/api/.importlinter index ee9b8464a4..cd674dbf95 100644 --- a/api/.importlinter +++ b/api/.importlinter @@ -110,7 +110,6 @@ ignore_imports = core.workflow.nodes.agent.agent_node -> core.model_manager core.workflow.nodes.agent.agent_node -> core.provider_manager core.workflow.nodes.agent.agent_node -> core.tools.tool_manager - core.workflow.nodes.code.code_node -> core.helper.code_executor.code_executor core.workflow.nodes.datasource.datasource_node -> models.model core.workflow.nodes.datasource.datasource_node -> models.tools core.workflow.nodes.datasource.datasource_node -> services.datasource_provider_service diff --git a/api/core/app/workflow/node_factory.py b/api/core/app/workflow/node_factory.py index bc4470cd50..965f3ddb1d 100644 --- a/api/core/app/workflow/node_factory.py +++ b/api/core/app/workflow/node_factory.py @@ -1,9 +1,10 @@ -from typing import TYPE_CHECKING, final +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, final from typing_extensions import override from configs import dify_config -from core.helper.code_executor.code_executor import CodeExecutor +from core.helper.code_executor.code_executor import CodeExecutionError, CodeExecutor from core.helper.code_executor.code_node_provider import CodeNodeProvider from core.helper.ssrf_proxy import ssrf_proxy from core.rag.retrieval.dataset_retrieval import DatasetRetrieval @@ -13,7 +14,8 @@ from core.workflow.enums import NodeType from core.workflow.file.file_manager import file_manager from core.workflow.graph.graph import NodeFactory from core.workflow.nodes.base.node import Node -from core.workflow.nodes.code.code_node import CodeNode +from core.workflow.nodes.code.code_node import CodeNode, WorkflowCodeExecutor +from core.workflow.nodes.code.entities import CodeLanguage from core.workflow.nodes.code.limits import CodeNodeLimits from core.workflow.nodes.document_extractor import DocumentExtractorNode, UnstructuredApiConfig from core.workflow.nodes.http_request import HttpRequestNode, build_http_request_config @@ -27,6 +29,24 @@ if TYPE_CHECKING: from core.workflow.runtime import GraphRuntimeState +class DefaultWorkflowCodeExecutor: + def execute( + self, + *, + language: CodeLanguage, + code: str, + inputs: Mapping[str, Any], + ) -> Mapping[str, Any]: + return CodeExecutor.execute_workflow_code_template( + language=language, + code=code, + inputs=inputs, + ) + + def is_execution_error(self, error: Exception) -> bool: + return isinstance(error, CodeExecutionError) + + @final class DifyNodeFactory(NodeFactory): """ @@ -43,7 +63,7 @@ class DifyNodeFactory(NodeFactory): ) -> None: self.graph_init_params = graph_init_params self.graph_runtime_state = graph_runtime_state - self._code_executor: type[CodeExecutor] = CodeExecutor + self._code_executor: WorkflowCodeExecutor = DefaultWorkflowCodeExecutor() self._code_providers: tuple[type[CodeNodeProvider], ...] = CodeNode.default_code_providers() self._code_limits = CodeNodeLimits( max_string_length=dify_config.CODE_MAX_STRING_LENGTH, diff --git a/api/core/workflow/nodes/code/code_node.py b/api/core/workflow/nodes/code/code_node.py index e3035d3bf0..f7a6c41f0a 100644 --- a/api/core/workflow/nodes/code/code_node.py +++ b/api/core/workflow/nodes/code/code_node.py @@ -1,8 +1,7 @@ from collections.abc import Mapping, Sequence from decimal import Decimal -from typing import TYPE_CHECKING, Any, ClassVar, cast +from typing import TYPE_CHECKING, Any, ClassVar, Protocol, cast -from core.helper.code_executor.code_executor import CodeExecutionError, CodeExecutor, CodeLanguage from core.helper.code_executor.code_node_provider import CodeNodeProvider from core.helper.code_executor.javascript.javascript_code_provider import JavascriptCodeProvider from core.helper.code_executor.python3.python3_code_provider import Python3CodeProvider @@ -11,7 +10,7 @@ from core.variables.types import SegmentType from core.workflow.enums import NodeType, WorkflowNodeExecutionStatus from core.workflow.node_events import NodeRunResult from core.workflow.nodes.base.node import Node -from core.workflow.nodes.code.entities import CodeNodeData +from core.workflow.nodes.code.entities import CodeLanguage, CodeNodeData from core.workflow.nodes.code.limits import CodeNodeLimits from .exc import ( @@ -25,6 +24,18 @@ if TYPE_CHECKING: from core.workflow.runtime import GraphRuntimeState +class WorkflowCodeExecutor(Protocol): + def execute( + self, + *, + language: CodeLanguage, + code: str, + inputs: Mapping[str, Any], + ) -> Mapping[str, Any]: ... + + def is_execution_error(self, error: Exception) -> bool: ... + + class CodeNode(Node[CodeNodeData]): node_type = NodeType.CODE _DEFAULT_CODE_PROVIDERS: ClassVar[tuple[type[CodeNodeProvider], ...]] = ( @@ -40,7 +51,7 @@ class CodeNode(Node[CodeNodeData]): graph_init_params: "GraphInitParams", graph_runtime_state: "GraphRuntimeState", *, - code_executor: type[CodeExecutor] | None = None, + code_executor: WorkflowCodeExecutor, code_providers: Sequence[type[CodeNodeProvider]] | None = None, code_limits: CodeNodeLimits, ) -> None: @@ -50,7 +61,7 @@ class CodeNode(Node[CodeNodeData]): graph_init_params=graph_init_params, graph_runtime_state=graph_runtime_state, ) - self._code_executor: type[CodeExecutor] = code_executor or CodeExecutor + self._code_executor: WorkflowCodeExecutor = code_executor self._code_providers: tuple[type[CodeNodeProvider], ...] = ( tuple(code_providers) if code_providers else self._DEFAULT_CODE_PROVIDERS ) @@ -98,7 +109,7 @@ class CodeNode(Node[CodeNodeData]): # Run code try: _ = self._select_code_provider(code_language) - result = self._code_executor.execute_workflow_code_template( + result = self._code_executor.execute( language=code_language, code=code, inputs=variables, @@ -106,7 +117,13 @@ class CodeNode(Node[CodeNodeData]): # Transform result result = self._transform_result(result=result, output_schema=self.node_data.outputs) - except (CodeExecutionError, CodeNodeError) as e: + except CodeNodeError as e: + return NodeRunResult( + status=WorkflowNodeExecutionStatus.FAILED, inputs=variables, error=str(e), error_type=type(e).__name__ + ) + except Exception as e: + if not self._code_executor.is_execution_error(e): + raise return NodeRunResult( status=WorkflowNodeExecutionStatus.FAILED, inputs=variables, error=str(e), error_type=type(e).__name__ ) diff --git a/api/tests/integration_tests/workflow/nodes/test_code.py b/api/tests/integration_tests/workflow/nodes/test_code.py index 1a9d69b2d2..e0ea14b789 100644 --- a/api/tests/integration_tests/workflow/nodes/test_code.py +++ b/api/tests/integration_tests/workflow/nodes/test_code.py @@ -68,6 +68,7 @@ def init_code_node(code_config: dict): config=code_config, graph_init_params=init_params, graph_runtime_state=graph_runtime_state, + code_executor=node_factory._code_executor, code_limits=CodeNodeLimits( max_string_length=dify_config.CODE_MAX_STRING_LENGTH, max_number=dify_config.CODE_MAX_NUMBER, diff --git a/api/tests/unit_tests/core/workflow/graph_engine/test_mock_nodes_template_code.py b/api/tests/unit_tests/core/workflow/graph_engine/test_mock_nodes_template_code.py index de08cc3497..e760d7b3d3 100644 --- a/api/tests/unit_tests/core/workflow/graph_engine/test_mock_nodes_template_code.py +++ b/api/tests/unit_tests/core/workflow/graph_engine/test_mock_nodes_template_code.py @@ -24,6 +24,16 @@ DEFAULT_CODE_LIMITS = CodeNodeLimits( ) +class _NoopCodeExecutor: + def execute(self, *, language: object, code: str, inputs: dict[str, object]) -> dict[str, object]: + _ = (language, code, inputs) + return {} + + def is_execution_error(self, error: Exception) -> bool: + _ = error + return False + + class TestMockTemplateTransformNode: """Test cases for MockTemplateTransformNode.""" @@ -319,6 +329,7 @@ class TestMockCodeNode: graph_init_params=graph_init_params, graph_runtime_state=graph_runtime_state, mock_config=mock_config, + code_executor=_NoopCodeExecutor(), code_limits=DEFAULT_CODE_LIMITS, ) @@ -384,6 +395,7 @@ class TestMockCodeNode: graph_init_params=graph_init_params, graph_runtime_state=graph_runtime_state, mock_config=mock_config, + code_executor=_NoopCodeExecutor(), code_limits=DEFAULT_CODE_LIMITS, ) @@ -453,6 +465,7 @@ class TestMockCodeNode: graph_init_params=graph_init_params, graph_runtime_state=graph_runtime_state, mock_config=mock_config, + code_executor=_NoopCodeExecutor(), code_limits=DEFAULT_CODE_LIMITS, )