diff --git a/api/core/app/apps/workflow_app_runner.py b/api/core/app/apps/workflow_app_runner.py index c1372e6f4d..dc6c381e86 100644 --- a/api/core/app/apps/workflow_app_runner.py +++ b/api/core/app/apps/workflow_app_runner.py @@ -733,6 +733,7 @@ class WorkflowBasedAppRunner(AppRunner): node_type=event.node_type, # FIXME(QuantumGhost): rely on private state of queue_manager is not ideal. invoke_from=self.queue_manager._invoke_from, + node_execution_id=event.id, enclosing_node_id=event.in_loop_id or event.in_iteration_id or None, ) draft_var_saver.save(process_data=process_data, outputs=outputs) diff --git a/api/models/workflow.py b/api/models/workflow.py index 9cb6968914..b11f9b89ff 100644 --- a/api/models/workflow.py +++ b/api/models/workflow.py @@ -867,6 +867,16 @@ def _naive_utc_datetime(): class WorkflowDraftVariable(Base): + """`WorkflowDraftVariable` record variables and outputs generated during + debugging worfklow or chatflow. + + IMPORTANT: This model maintains multiple invariant rules that must be preserved. + Do not instantiate this class directly with the constructor. + + Instead, use the factory methods (`new_conversation_variable`, `new_sys_variable`, + `new_node_variable`) defined below to ensure all invariants are properly maintained. + """ + @staticmethod def unique_app_id_node_id_name() -> list[str]: return [ @@ -956,6 +966,7 @@ class WorkflowDraftVariable(Base): # Use double underscore prefix for better encapsulation, # making this attribute harder to access from outside the class. __value: Segment | None + node_execution_id: str | None # pretend this field exists. def __init__(self, *args, **kwargs): """ @@ -1062,6 +1073,7 @@ class WorkflowDraftVariable(Base): node_id: str, name: str, value: Segment, + node_execution_id: str | None = None, description: str = "", ) -> "WorkflowDraftVariable": variable = WorkflowDraftVariable() @@ -1073,6 +1085,7 @@ class WorkflowDraftVariable(Base): variable.name = name variable.set_value(value) variable._set_selector(list(variable_utils.to_selector(node_id, name))) + variable.node_execution_id = node_execution_id return variable @classmethod @@ -1100,9 +1113,16 @@ class WorkflowDraftVariable(Base): app_id: str, name: str, value: Segment, + node_execution_id: str, editable: bool = False, ) -> "WorkflowDraftVariable": - variable = cls._new(app_id=app_id, node_id=SYSTEM_VARIABLE_NODE_ID, name=name, value=value) + variable = cls._new( + app_id=app_id, + node_id=SYSTEM_VARIABLE_NODE_ID, + name=name, + node_execution_id=node_execution_id, + value=value, + ) variable.editable = editable return variable @@ -1114,10 +1134,17 @@ class WorkflowDraftVariable(Base): node_id: str, name: str, value: Segment, + node_execution_id: str, visible: bool = True, editable: bool = True, ) -> "WorkflowDraftVariable": - variable = cls._new(app_id=app_id, node_id=node_id, name=name, value=value) + variable = cls._new( + app_id=app_id, + node_id=node_id, + name=name, + node_execution_id=node_execution_id, + value=value, + ) variable.visible = visible variable.editable = editable return variable diff --git a/api/services/workflow_draft_variable_service.py b/api/services/workflow_draft_variable_service.py index 0e99e20f06..befffc3347 100644 --- a/api/services/workflow_draft_variable_service.py +++ b/api/services/workflow_draft_variable_service.py @@ -460,6 +460,9 @@ class DraftVariableSaver: # Indicates how the workflow execution was triggered (see InvokeFrom). _invoke_from: InvokeFrom + # + _node_execution_id: str + # _enclosing_node_id identifies the container node that the current node belongs to. # For example, if the current node is an LLM node inside an Iteration node # or Loop node, then `_enclosing_node_id` refers to the ID of @@ -476,6 +479,7 @@ class DraftVariableSaver: node_id: str, node_type: NodeType, invoke_from: InvokeFrom, + node_execution_id: str, enclosing_node_id: str | None = None, ): self._session = session @@ -483,6 +487,7 @@ class DraftVariableSaver: self._node_id = node_id self._node_type = node_type self._invoke_from = invoke_from + self._node_execution_id = node_execution_id self._enclosing_node_id = enclosing_node_id def _should_save_output_variables_for_draft(self) -> bool: @@ -512,6 +517,7 @@ class DraftVariableSaver: WorkflowDraftVariable.new_conversation_variable( app_id=self._app_id, name=item.name, + node_execution_id=self._node_execution_id, value=segment, ) ) @@ -531,6 +537,7 @@ class DraftVariableSaver: app_id=self._app_id, node_id=self._node_id, name=name, + node_execution_id=self._node_execution_id, value=value_seg, visible=True, editable=True, @@ -551,6 +558,7 @@ class DraftVariableSaver: WorkflowDraftVariable.new_sys_variable( app_id=self._app_id, name=name, + node_execution_id=self._node_execution_id, value=value_seg, editable=self._should_variable_be_editable(node_id, name), ) @@ -561,6 +569,7 @@ class DraftVariableSaver: app_id=self._app_id, node_id=self._node_id, name=self._DUMMY_OUTPUT_IDENTITY, + node_execution_id=self._node_execution_id, value=_build_segment_for_value(self._DUMMY_OUTPUT_VALUE), visible=False, editable=False, @@ -583,6 +592,7 @@ class DraftVariableSaver: app_id=self._app_id, node_id=self._node_id, name=name, + node_execution_id=self._node_execution_id, value=value_seg, visible=self._should_variable_be_visible(self._node_id, self._node_type, name), )