diff --git a/api/services/workflow_draft_variable_service.py b/api/services/workflow_draft_variable_service.py index 5ec63501b6..566df5ff31 100644 --- a/api/services/workflow_draft_variable_service.py +++ b/api/services/workflow_draft_variable_service.py @@ -21,7 +21,7 @@ from core.workflow.enums import SystemVariableKey from core.workflow.nodes import NodeType from core.workflow.nodes.variable_assigner.common.helpers import get_updated_variables from core.workflow.variable_loader import VariableLoader -from factories.variable_factory import build_segment, segment_to_variable +from factories.variable_factory import build_segment, build_segment_with_type, segment_to_variable from models import App, Conversation from models.enums import DraftVariableType from models.workflow import Workflow, WorkflowDraftVariable, WorkflowNodeExecutionModel, is_system_variable_editable @@ -35,7 +35,15 @@ class WorkflowDraftVariableList: total: int | None = None -class VariableResetError(Exception): +class WorkflowDraftVariableError(Exception): + pass + + +class VariableResetError(WorkflowDraftVariableError): + pass + + +class UpdateNotSupportedError(WorkflowDraftVariableError): pass @@ -199,6 +207,8 @@ class WorkflowDraftVariableService: name: str | None = None, value: Segment | None = None, ) -> WorkflowDraftVariable: + if not variable.editable: + raise UpdateNotSupportedError(f"variable not support updating, id={variable.id}") if name is not None: variable.set_name(name) if value is not None: @@ -208,6 +218,9 @@ class WorkflowDraftVariableService: return variable def _reset_conv_var(self, workflow: Workflow, variable: WorkflowDraftVariable) -> WorkflowDraftVariable | None: + # If a variable does not allow updating, it makes no sence to resetting it. + if not variable.editable: + return variable conv_var_by_name = {i.name: i for i in workflow.conversation_variables} conv_var = conv_var_by_name.get(variable.name) @@ -250,23 +263,29 @@ class WorkflowDraftVariableService: node_config = workflow.get_node_config_by_id(variable.node_id) node_type = workflow.get_node_type_from_node_config(node_config) - # Extract variable value using unified logic - old_value = DraftVariableSaver.extract_variable_value_from_execution_data( - node_exec, - variable.name, - node_type, - ) + outputs_dict = node_exec.outputs_dict or {} - if old_value is not None: - variable.set_value(old_value) - variable.last_edited_at = None # Reset to indicate this is a reset operation - self._session.flush() + # Note: Based on the implementation in `_build_from_variable_assigner_mapping`, + # VariableAssignerNode (both v1 and v2) can only create conversation draft variables. + # For consistency, we should simply return when processing VARIABLE_ASSIGNER nodes. + # + # This implementation must remain synchronized with the `_build_from_variable_assigner_mapping` + # and `save` methods. + if node_type == NodeType.VARIABLE_ASSIGNER: return variable - else: + + if variable.name not in outputs_dict: # If variable not found in execution data, delete the variable self._session.delete(instance=variable) self._session.flush() return None + value = outputs_dict[variable.name] + value_seg = build_segment_with_type(variable.value_type, _rebuild_file_types_from_dict(value)) + # Extract variable value using unified logic + variable.set_value(value_seg) + variable.last_edited_at = None # Reset to indicate this is a reset operation + self._session.flush() + return variable def reset_variable(self, workflow: Workflow, variable: WorkflowDraftVariable) -> WorkflowDraftVariable | None: variable_type = variable.get_variable_type() @@ -690,37 +709,6 @@ class DraftVariableSaver: draft_vars = self._build_variables_from_mapping(outputs) _batch_upsert_draft_varaible(self._session, draft_vars) - @staticmethod - def extract_variable_value_from_execution_data( - node_exec: "WorkflowNodeExecutionModel", variable_name: str, node_type: NodeType - ) -> Segment | None: - """ - Extract variable value from execution data using unified logic. - - Args: - node_exec: Workflow node execution record - variable_name: Name of the variable to extract - node_type: Type of the workflow node - - Returns: - Raw variable value if found, None otherwise - """ - outputs_dict = node_exec.outputs_dict or {} - - # Note: Based on the implementation in `_build_from_variable_assigner_mapping`, - # VariableAssignerNode (both v1 and v2) can only create conversation draft variables. - # For consistency, we should simply return when processing VARIABLE_ASSIGNER nodes. - # - # This implementation must remain synchronized with the `_build_from_variable_assigner_mapping` - # and `save` methods. - if node_type == NodeType.VARIABLE_ASSIGNER: - return None - - if variable_name not in outputs_dict: - return None - value = outputs_dict[variable_name] - return _build_segment_for_value(value) - @staticmethod def _should_variable_be_editable(node_id: str, name: str) -> bool: if node_id in (CONVERSATION_VARIABLE_NODE_ID, ENVIRONMENT_VARIABLE_NODE_ID):