diff --git a/api/core/app/apps/workflow_app_runner.py b/api/core/app/apps/workflow_app_runner.py index 2ca153f835..74e8712645 100644 --- a/api/core/app/apps/workflow_app_runner.py +++ b/api/core/app/apps/workflow_app_runner.py @@ -157,7 +157,7 @@ class WorkflowBasedAppRunner: # Create initial runtime state with variable pool containing environment variables graph_runtime_state = GraphRuntimeState( variable_pool=VariablePool( - system_variables=SystemVariable.empty(), + system_variables=SystemVariable.default(), user_inputs={}, environment_variables=workflow.environment_variables, ), @@ -268,7 +268,9 @@ class WorkflowBasedAppRunner: ) # init graph - graph = Graph.init(graph_config=graph_config, node_factory=node_factory, root_node_id=node_id) + graph = Graph.init( + graph_config=graph_config, node_factory=node_factory, root_node_id=node_id, skip_validation=True + ) if not graph: raise ValueError("graph not found in workflow") diff --git a/api/core/workflow/graph/graph.py b/api/core/workflow/graph/graph.py index 7be94c2426..31bf6f3b27 100644 --- a/api/core/workflow/graph/graph.py +++ b/api/core/workflow/graph/graph.py @@ -288,6 +288,7 @@ class Graph: graph_config: Mapping[str, object], node_factory: NodeFactory, root_node_id: str | None = None, + skip_validation: bool = False, ) -> Graph: """ Initialize graph @@ -339,8 +340,9 @@ class Graph: root_node=root_node, ) - # Validate the graph structure using built-in validators - get_graph_validator().validate(graph) + if not skip_validation: + # Validate the graph structure using built-in validators + get_graph_validator().validate(graph) return graph diff --git a/api/core/workflow/runtime/variable_pool.py b/api/core/workflow/runtime/variable_pool.py index d205c6ac8f..c4b077fa69 100644 --- a/api/core/workflow/runtime/variable_pool.py +++ b/api/core/workflow/runtime/variable_pool.py @@ -44,7 +44,7 @@ class VariablePool(BaseModel): ) system_variables: SystemVariable = Field( description="System variables", - default_factory=SystemVariable.empty, + default_factory=SystemVariable.default, ) environment_variables: Sequence[Variable] = Field( description="Environment variables.", @@ -271,4 +271,4 @@ class VariablePool(BaseModel): @classmethod def empty(cls) -> VariablePool: """Create an empty variable pool.""" - return cls(system_variables=SystemVariable.empty()) + return cls(system_variables=SystemVariable.default()) diff --git a/api/core/workflow/system_variable.py b/api/core/workflow/system_variable.py index cda8091771..6946e3e6ab 100644 --- a/api/core/workflow/system_variable.py +++ b/api/core/workflow/system_variable.py @@ -3,6 +3,7 @@ from __future__ import annotations from collections.abc import Mapping, Sequence from types import MappingProxyType from typing import Any +from uuid import uuid4 from pydantic import AliasChoices, BaseModel, ConfigDict, Field, model_validator @@ -72,8 +73,8 @@ class SystemVariable(BaseModel): return data @classmethod - def empty(cls) -> SystemVariable: - return cls() + def default(cls) -> SystemVariable: + return cls(workflow_execution_id=str(uuid4())) def to_dict(self) -> dict[SystemVariableKey, Any]: # NOTE: This method is provided for compatibility with legacy code. diff --git a/api/core/workflow/workflow_entry.py b/api/core/workflow/workflow_entry.py index c7bcc66c8b..9f22f7676a 100644 --- a/api/core/workflow/workflow_entry.py +++ b/api/core/workflow/workflow_entry.py @@ -276,7 +276,7 @@ class WorkflowEntry: # init variable pool variable_pool = VariablePool( - system_variables=SystemVariable.empty(), + system_variables=SystemVariable.default(), user_inputs={}, environment_variables=[], ) diff --git a/api/services/rag_pipeline/rag_pipeline.py b/api/services/rag_pipeline/rag_pipeline.py index 2d8418900c..ccc6abcc06 100644 --- a/api/services/rag_pipeline/rag_pipeline.py +++ b/api/services/rag_pipeline/rag_pipeline.py @@ -436,7 +436,7 @@ class RagPipelineService: user_inputs=user_inputs, user_id=account.id, variable_pool=VariablePool( - system_variables=SystemVariable.empty(), + system_variables=SystemVariable.default(), user_inputs=user_inputs, environment_variables=[], conversation_variables=[], diff --git a/api/services/workflow_service.py b/api/services/workflow_service.py index d8c3159178..6404136994 100644 --- a/api/services/workflow_service.py +++ b/api/services/workflow_service.py @@ -675,7 +675,7 @@ class WorkflowService: else: variable_pool = VariablePool( - system_variables=SystemVariable.empty(), + system_variables=SystemVariable.default(), user_inputs=user_inputs, environment_variables=draft_workflow.environment_variables, conversation_variables=[], @@ -1063,7 +1063,7 @@ def _setup_variable_pool( system_variable.conversation_id = conversation_id system_variable.dialogue_count = 1 else: - system_variable = SystemVariable.empty() + system_variable = SystemVariable.default() # init variable pool variable_pool = VariablePool( diff --git a/api/tests/unit_tests/core/workflow/nodes/http_request/test_http_request_executor.py b/api/tests/unit_tests/core/workflow/nodes/http_request/test_http_request_executor.py index 27df938102..43116bc35e 100644 --- a/api/tests/unit_tests/core/workflow/nodes/http_request/test_http_request_executor.py +++ b/api/tests/unit_tests/core/workflow/nodes/http_request/test_http_request_executor.py @@ -16,7 +16,7 @@ from core.workflow.system_variable import SystemVariable def test_executor_with_json_body_and_number_variable(): # Prepare the variable pool variable_pool = VariablePool( - system_variables=SystemVariable.empty(), + system_variables=SystemVariable.default(), user_inputs={}, ) variable_pool.add(["pre_node_id", "number"], 42) @@ -69,7 +69,7 @@ def test_executor_with_json_body_and_number_variable(): def test_executor_with_json_body_and_object_variable(): # Prepare the variable pool variable_pool = VariablePool( - system_variables=SystemVariable.empty(), + system_variables=SystemVariable.default(), user_inputs={}, ) variable_pool.add(["pre_node_id", "object"], {"name": "John Doe", "age": 30, "email": "john@example.com"}) @@ -124,7 +124,7 @@ def test_executor_with_json_body_and_object_variable(): def test_executor_with_json_body_and_nested_object_variable(): # Prepare the variable pool variable_pool = VariablePool( - system_variables=SystemVariable.empty(), + system_variables=SystemVariable.default(), user_inputs={}, ) variable_pool.add(["pre_node_id", "object"], {"name": "John Doe", "age": 30, "email": "john@example.com"}) @@ -178,7 +178,7 @@ def test_executor_with_json_body_and_nested_object_variable(): def test_extract_selectors_from_template_with_newline(): - variable_pool = VariablePool(system_variables=SystemVariable.empty()) + variable_pool = VariablePool(system_variables=SystemVariable.default()) variable_pool.add(("node_id", "custom_query"), "line1\nline2") node_data = HttpRequestNodeData( title="Test JSON Body with Nested Object Variable", @@ -205,7 +205,7 @@ def test_extract_selectors_from_template_with_newline(): def test_executor_with_form_data(): # Prepare the variable pool variable_pool = VariablePool( - system_variables=SystemVariable.empty(), + system_variables=SystemVariable.default(), user_inputs={}, ) variable_pool.add(["pre_node_id", "text_field"], "Hello, World!") @@ -290,7 +290,7 @@ def test_init_headers(): return Executor( node_data=node_data, timeout=timeout, - variable_pool=VariablePool(system_variables=SystemVariable.empty()), + variable_pool=VariablePool(system_variables=SystemVariable.default()), ) executor = create_executor("aa\n cc:") @@ -324,7 +324,7 @@ def test_init_params(): return Executor( node_data=node_data, timeout=timeout, - variable_pool=VariablePool(system_variables=SystemVariable.empty()), + variable_pool=VariablePool(system_variables=SystemVariable.default()), ) # Test basic key-value pairs @@ -355,7 +355,7 @@ def test_init_params(): def test_empty_api_key_raises_error_bearer(): """Test that empty API key raises AuthorizationConfigError for bearer auth.""" - variable_pool = VariablePool(system_variables=SystemVariable.empty()) + variable_pool = VariablePool(system_variables=SystemVariable.default()) node_data = HttpRequestNodeData( title="test", method="get", @@ -379,7 +379,7 @@ def test_empty_api_key_raises_error_bearer(): def test_empty_api_key_raises_error_basic(): """Test that empty API key raises AuthorizationConfigError for basic auth.""" - variable_pool = VariablePool(system_variables=SystemVariable.empty()) + variable_pool = VariablePool(system_variables=SystemVariable.default()) node_data = HttpRequestNodeData( title="test", method="get", @@ -403,7 +403,7 @@ def test_empty_api_key_raises_error_basic(): def test_empty_api_key_raises_error_custom(): """Test that empty API key raises AuthorizationConfigError for custom auth.""" - variable_pool = VariablePool(system_variables=SystemVariable.empty()) + variable_pool = VariablePool(system_variables=SystemVariable.default()) node_data = HttpRequestNodeData( title="test", method="get", @@ -427,7 +427,7 @@ def test_empty_api_key_raises_error_custom(): def test_whitespace_only_api_key_raises_error(): """Test that whitespace-only API key raises AuthorizationConfigError.""" - variable_pool = VariablePool(system_variables=SystemVariable.empty()) + variable_pool = VariablePool(system_variables=SystemVariable.default()) node_data = HttpRequestNodeData( title="test", method="get", @@ -451,7 +451,7 @@ def test_whitespace_only_api_key_raises_error(): def test_valid_api_key_works(): """Test that valid API key works correctly for bearer auth.""" - variable_pool = VariablePool(system_variables=SystemVariable.empty()) + variable_pool = VariablePool(system_variables=SystemVariable.default()) node_data = HttpRequestNodeData( title="test", method="get", diff --git a/api/tests/unit_tests/core/workflow/nodes/llm/test_node.py b/api/tests/unit_tests/core/workflow/nodes/llm/test_node.py index 77264022bc..3d1b8b2f27 100644 --- a/api/tests/unit_tests/core/workflow/nodes/llm/test_node.py +++ b/api/tests/unit_tests/core/workflow/nodes/llm/test_node.py @@ -86,7 +86,7 @@ def graph_init_params() -> GraphInitParams: @pytest.fixture def graph_runtime_state() -> GraphRuntimeState: variable_pool = VariablePool( - system_variables=SystemVariable.empty(), + system_variables=SystemVariable.default(), user_inputs={}, ) return GraphRuntimeState( diff --git a/api/tests/unit_tests/core/workflow/nodes/webhook/test_webhook_file_conversion.py b/api/tests/unit_tests/core/workflow/nodes/webhook/test_webhook_file_conversion.py index ead2334473..d8f6b41f89 100644 --- a/api/tests/unit_tests/core/workflow/nodes/webhook/test_webhook_file_conversion.py +++ b/api/tests/unit_tests/core/workflow/nodes/webhook/test_webhook_file_conversion.py @@ -111,7 +111,7 @@ def test_webhook_node_file_conversion_to_file_variable(): ) variable_pool = VariablePool( - system_variables=SystemVariable.empty(), + system_variables=SystemVariable.default(), user_inputs={ "webhook_data": { "headers": {}, @@ -184,7 +184,7 @@ def test_webhook_node_file_conversion_with_missing_files(): ) variable_pool = VariablePool( - system_variables=SystemVariable.empty(), + system_variables=SystemVariable.default(), user_inputs={ "webhook_data": { "headers": {}, @@ -219,7 +219,7 @@ def test_webhook_node_file_conversion_with_none_file(): ) variable_pool = VariablePool( - system_variables=SystemVariable.empty(), + system_variables=SystemVariable.default(), user_inputs={ "webhook_data": { "headers": {}, @@ -256,7 +256,7 @@ def test_webhook_node_file_conversion_with_non_dict_file(): ) variable_pool = VariablePool( - system_variables=SystemVariable.empty(), + system_variables=SystemVariable.default(), user_inputs={ "webhook_data": { "headers": {}, @@ -300,7 +300,7 @@ def test_webhook_node_file_conversion_mixed_parameters(): ) variable_pool = VariablePool( - system_variables=SystemVariable.empty(), + system_variables=SystemVariable.default(), user_inputs={ "webhook_data": { "headers": {}, @@ -370,7 +370,7 @@ def test_webhook_node_different_file_types(): ) variable_pool = VariablePool( - system_variables=SystemVariable.empty(), + system_variables=SystemVariable.default(), user_inputs={ "webhook_data": { "headers": {}, @@ -430,7 +430,7 @@ def test_webhook_node_file_conversion_with_non_dict_wrapper(): ) variable_pool = VariablePool( - system_variables=SystemVariable.empty(), + system_variables=SystemVariable.default(), user_inputs={ "webhook_data": { "headers": {}, diff --git a/api/tests/unit_tests/core/workflow/nodes/webhook/test_webhook_node.py b/api/tests/unit_tests/core/workflow/nodes/webhook/test_webhook_node.py index bbb5511923..3b5aedebca 100644 --- a/api/tests/unit_tests/core/workflow/nodes/webhook/test_webhook_node.py +++ b/api/tests/unit_tests/core/workflow/nodes/webhook/test_webhook_node.py @@ -75,7 +75,7 @@ def test_webhook_node_basic_initialization(): ) variable_pool = VariablePool( - system_variables=SystemVariable.empty(), + system_variables=SystemVariable.default(), user_inputs={}, ) @@ -118,7 +118,7 @@ def test_webhook_node_run_with_headers(): ) variable_pool = VariablePool( - system_variables=SystemVariable.empty(), + system_variables=SystemVariable.default(), user_inputs={ "webhook_data": { "headers": { @@ -154,7 +154,7 @@ def test_webhook_node_run_with_query_params(): ) variable_pool = VariablePool( - system_variables=SystemVariable.empty(), + system_variables=SystemVariable.default(), user_inputs={ "webhook_data": { "headers": {}, @@ -190,7 +190,7 @@ def test_webhook_node_run_with_body_params(): ) variable_pool = VariablePool( - system_variables=SystemVariable.empty(), + system_variables=SystemVariable.default(), user_inputs={ "webhook_data": { "headers": {}, @@ -249,7 +249,7 @@ def test_webhook_node_run_with_file_params(): ) variable_pool = VariablePool( - system_variables=SystemVariable.empty(), + system_variables=SystemVariable.default(), user_inputs={ "webhook_data": { "headers": {}, @@ -302,7 +302,7 @@ def test_webhook_node_run_mixed_parameters(): ) variable_pool = VariablePool( - system_variables=SystemVariable.empty(), + system_variables=SystemVariable.default(), user_inputs={ "webhook_data": { "headers": {"Authorization": "Bearer token"}, @@ -342,7 +342,7 @@ def test_webhook_node_run_empty_webhook_data(): ) variable_pool = VariablePool( - system_variables=SystemVariable.empty(), + system_variables=SystemVariable.default(), user_inputs={}, # No webhook_data ) @@ -368,7 +368,7 @@ def test_webhook_node_run_case_insensitive_headers(): ) variable_pool = VariablePool( - system_variables=SystemVariable.empty(), + system_variables=SystemVariable.default(), user_inputs={ "webhook_data": { "headers": { @@ -398,7 +398,7 @@ def test_webhook_node_variable_pool_user_inputs(): # Add some additional variables to the pool variable_pool = VariablePool( - system_variables=SystemVariable.empty(), + system_variables=SystemVariable.default(), user_inputs={ "webhook_data": {"headers": {}, "query_params": {}, "body": {}, "files": {}}, "other_var": "should_be_included", @@ -429,7 +429,7 @@ def test_webhook_node_different_methods(method): ) variable_pool = VariablePool( - system_variables=SystemVariable.empty(), + system_variables=SystemVariable.default(), user_inputs={ "webhook_data": { "headers": {}, 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 b38e070ffc..27ffa455d6 100644 --- a/api/tests/unit_tests/core/workflow/test_workflow_entry.py +++ b/api/tests/unit_tests/core/workflow/test_workflow_entry.py @@ -127,7 +127,7 @@ class TestWorkflowEntry: return node_config workflow = StubWorkflow() - variable_pool = VariablePool(system_variables=SystemVariable.empty(), user_inputs={}) + variable_pool = VariablePool(system_variables=SystemVariable.default(), user_inputs={}) expected_limits = CodeNodeLimits( max_string_length=dify_config.CODE_MAX_STRING_LENGTH, max_number=dify_config.CODE_MAX_NUMBER, @@ -157,7 +157,7 @@ class TestWorkflowEntry: # Initialize variable pool with environment variables env_var = StringVariable(name="API_KEY", value="existing_key") variable_pool = VariablePool( - system_variables=SystemVariable.empty(), + system_variables=SystemVariable.default(), environment_variables=[env_var], user_inputs={}, ) @@ -198,7 +198,7 @@ class TestWorkflowEntry: # Initialize variable pool with conversation variables conv_var = StringVariable(name="last_message", value="Hello") variable_pool = VariablePool( - system_variables=SystemVariable.empty(), + system_variables=SystemVariable.default(), conversation_variables=[conv_var], user_inputs={}, ) @@ -239,7 +239,7 @@ class TestWorkflowEntry: """Test mapping regular node variables from user inputs to variable pool.""" # Initialize empty variable pool variable_pool = VariablePool( - system_variables=SystemVariable.empty(), + system_variables=SystemVariable.default(), user_inputs={}, ) @@ -281,7 +281,7 @@ class TestWorkflowEntry: def test_mapping_user_inputs_with_file_handling(self): """Test mapping file inputs from user inputs to variable pool.""" variable_pool = VariablePool( - system_variables=SystemVariable.empty(), + system_variables=SystemVariable.default(), user_inputs={}, ) @@ -340,7 +340,7 @@ class TestWorkflowEntry: def test_mapping_user_inputs_missing_variable_error(self): """Test that mapping raises error when required variable is missing.""" variable_pool = VariablePool( - system_variables=SystemVariable.empty(), + system_variables=SystemVariable.default(), user_inputs={}, ) @@ -366,7 +366,7 @@ class TestWorkflowEntry: def test_mapping_user_inputs_with_alternative_key_format(self): """Test mapping with alternative key format (without node prefix).""" variable_pool = VariablePool( - system_variables=SystemVariable.empty(), + system_variables=SystemVariable.default(), user_inputs={}, ) @@ -396,7 +396,7 @@ class TestWorkflowEntry: def test_mapping_user_inputs_with_complex_selectors(self): """Test mapping with complex node variable keys.""" variable_pool = VariablePool( - system_variables=SystemVariable.empty(), + system_variables=SystemVariable.default(), user_inputs={}, ) @@ -432,7 +432,7 @@ class TestWorkflowEntry: def test_mapping_user_inputs_invalid_node_variable(self): """Test that mapping handles invalid node variable format.""" variable_pool = VariablePool( - system_variables=SystemVariable.empty(), + system_variables=SystemVariable.default(), user_inputs={}, )