From 3865483d95fb0d6a6bb6b5bcc5d2d14b66869a88 Mon Sep 17 00:00:00 2001 From: FFXN Date: Mon, 30 Mar 2026 16:57:55 +0800 Subject: [PATCH] feat: snippets has no envirment variables. Snippet diable start, human_input, knowledge node. --- api/controllers/console/snippets/payloads.py | 1 - .../console/snippets/snippet_workflow.py | 7 +-- api/services/snippet_dsl_service.py | 18 ++----- api/services/snippet_service.py | 49 ++++++++++++++++--- 4 files changed, 49 insertions(+), 26 deletions(-) diff --git a/api/controllers/console/snippets/payloads.py b/api/controllers/console/snippets/payloads.py index 22a0aec998..b0bf69aa3c 100644 --- a/api/controllers/console/snippets/payloads.py +++ b/api/controllers/console/snippets/payloads.py @@ -71,7 +71,6 @@ class SnippetDraftSyncPayload(BaseModel): graph: dict[str, Any] hash: str | None = None - environment_variables: list[dict[str, Any]] | None = None conversation_variables: list[dict[str, Any]] | None = None input_fields: list[dict[str, Any]] | None = None diff --git a/api/controllers/console/snippets/snippet_workflow.py b/api/controllers/console/snippets/snippet_workflow.py index 01c5a69fad..aa7b1fc491 100644 --- a/api/controllers/console/snippets/snippet_workflow.py +++ b/api/controllers/console/snippets/snippet_workflow.py @@ -135,10 +135,6 @@ class SnippetDraftWorkflowApi(Resource): payload = SnippetDraftSyncPayload.model_validate(console_ns.payload or {}) try: - environment_variables_list = payload.environment_variables or [] - environment_variables = [ - variable_factory.build_environment_variable_from_mapping(obj) for obj in environment_variables_list - ] conversation_variables_list = payload.conversation_variables or [] conversation_variables = [ variable_factory.build_conversation_variable_from_mapping(obj) for obj in conversation_variables_list @@ -149,12 +145,13 @@ class SnippetDraftWorkflowApi(Resource): graph=payload.graph, unique_hash=payload.hash, account=current_user, - environment_variables=environment_variables, conversation_variables=conversation_variables, input_fields=payload.input_fields, ) except WorkflowHashNotEqualError: raise DraftWorkflowNotSync() + except ValueError as e: + return {"message": str(e)}, 400 return { "result": "success", diff --git a/api/services/snippet_dsl_service.py b/api/services/snippet_dsl_service.py index 505474e5a2..6f3cddc794 100644 --- a/api/services/snippet_dsl_service.py +++ b/api/services/snippet_dsl_service.py @@ -22,7 +22,7 @@ from models import Account from models.snippet import CustomizedSnippet, SnippetType from models.workflow import Workflow from services.plugin.dependencies_analysis import DependenciesAnalysisService -from services.snippet_service import SnippetService +from services.snippet_service import SNIPPET_FORBIDDEN_NODE_TYPES, SnippetService logger = logging.getLogger(__name__) @@ -32,13 +32,6 @@ IMPORT_INFO_REDIS_EXPIRY = 10 * 60 # 10 minutes DSL_MAX_SIZE = 10 * 1024 * 1024 # 10MB CURRENT_DSL_VERSION = "0.1.0" -# List of node types that are not allowed in snippets -FORBIDDEN_NODE_TYPES = [ - BuiltinNodeTypes.START, - BuiltinNodeTypes.HUMAN_INPUT, -] - - class ImportMode(StrEnum): YAML_CONTENT = "yaml-content" YAML_URL = "yaml-url" @@ -230,7 +223,7 @@ class SnippetDslService: if not node_data: continue node_type = node_data.get("type", "") - if node_type in FORBIDDEN_NODE_TYPES: + if node_type in SNIPPET_FORBIDDEN_NODE_TYPES: forbidden_nodes_found.append(node_type) if forbidden_nodes_found: @@ -427,12 +420,8 @@ class SnippetDslService: # Create or update draft workflow if workflow_data: graph = workflow_data.get("graph", {}) - environment_variables_list = workflow_data.get("environment_variables", []) conversation_variables_list = workflow_data.get("conversation_variables", []) - environment_variables = [ - variable_factory.build_environment_variable_from_mapping(obj) for obj in environment_variables_list - ] conversation_variables = [ variable_factory.build_conversation_variable_from_mapping(obj) for obj in conversation_variables_list ] @@ -447,7 +436,6 @@ class SnippetDslService: graph=graph, unique_hash=unique_hash, account=account, - environment_variables=environment_variables, conversation_variables=conversation_variables, input_fields=input_fields, ) @@ -494,6 +482,8 @@ class SnippetDslService: """ workflow_dict = workflow.to_dict(include_secret=include_secret) # Filter workspace related data from nodes + workflow_dict["environment_variables"] = [] + for node in workflow_dict.get("graph", {}).get("nodes", []): node_data = node.get("data", {}) if not node_data: diff --git a/api/services/snippet_service.py b/api/services/snippet_service.py index 47bce144a9..2b50663c52 100644 --- a/api/services/snippet_service.py +++ b/api/services/snippet_service.py @@ -9,7 +9,7 @@ from sqlalchemy.orm import Session, sessionmaker from core.workflow.node_factory import LATEST_VERSION, NODE_TYPE_CLASSES_MAPPING from extensions.ext_database import db -from graphon.enums import NodeType +from graphon.enums import BuiltinNodeTypes, NodeType from graphon.variables.variables import VariableBase from libs.infinite_scroll_pagination import InfiniteScrollPagination from models import Account @@ -26,6 +26,15 @@ from services.errors.app import WorkflowHashNotEqualError logger = logging.getLogger(__name__) +# Node types not allowed in snippet workflows (sync, publish, DSL import). +SNIPPET_FORBIDDEN_NODE_TYPES: frozenset[str] = frozenset( + { + BuiltinNodeTypes.START, + BuiltinNodeTypes.HUMAN_INPUT, + BuiltinNodeTypes.KNOWLEDGE_RETRIEVAL, + } +) + class SnippetService: """Service for managing customized snippets.""" @@ -39,6 +48,29 @@ class SnippetService: ) self._workflow_run_repo = DifyAPIRepositoryFactory.create_api_workflow_run_repository(session_maker) + @staticmethod + def validate_snippet_graph_forbidden_nodes(graph: Mapping[str, Any]) -> None: + """Reject graphs that contain node types not allowed in snippets.""" + nodes = graph.get("nodes") or [] + disallowed: list[tuple[str, str]] = [] + for node in nodes: + if not isinstance(node, dict): + continue + node_data = node.get("data") or {} + node_type = node_data.get("type") + if not isinstance(node_type, str): + continue + if node_type in SNIPPET_FORBIDDEN_NODE_TYPES: + node_id = node.get("id") + disallowed.append((str(node_id) if node_id is not None else "?", node_type)) + if not disallowed: + return + detail = ", ".join(f"{nid}:{t}" for nid, t in disallowed) + raise ValueError( + "Snippet workflow cannot contain start, human-input, or knowledge-retrieval nodes. " + f"Found: {detail}" + ) + # --- CRUD Operations --- @staticmethod @@ -276,23 +308,25 @@ class SnippetService: graph: dict, unique_hash: str | None, account: Account, - environment_variables: Sequence[VariableBase], conversation_variables: Sequence[VariableBase], input_fields: list[dict] | None = None, ) -> Workflow: """ Sync draft workflow for snippet. + Snippet workflows do not persist environment variables (always empty). + :param snippet: CustomizedSnippet instance :param graph: Workflow graph configuration :param unique_hash: Hash for conflict detection :param account: Account making the change - :param environment_variables: Environment variables :param conversation_variables: Conversation variables :param input_fields: Input fields for snippet :return: Synced Workflow :raises WorkflowHashNotEqualError: If hash mismatch """ + SnippetService.validate_snippet_graph_forbidden_nodes(graph) + workflow = self.get_draft_workflow(snippet=snippet) if workflow and workflow.unique_hash != unique_hash: @@ -308,7 +342,7 @@ class SnippetService: version="draft", graph=json.dumps(graph), created_by=account.id, - environment_variables=environment_variables, + environment_variables=[], conversation_variables=conversation_variables, ) db.session.add(workflow) @@ -318,7 +352,7 @@ class SnippetService: workflow.graph = json.dumps(graph) workflow.updated_by = account.id workflow.updated_at = datetime.now(UTC).replace(tzinfo=None) - workflow.environment_variables = environment_variables + workflow.environment_variables = [] workflow.conversation_variables = conversation_variables # Update snippet's input_fields if provided @@ -356,6 +390,8 @@ class SnippetService: if not draft_workflow: raise ValueError("No valid workflow found.") + SnippetService.validate_snippet_graph_forbidden_nodes(draft_workflow.graph_dict) + # Create new published workflow workflow = Workflow.new( tenant_id=snippet.tenant_id, @@ -365,8 +401,9 @@ class SnippetService: graph=draft_workflow.graph, features=draft_workflow.features, created_by=account.id, - environment_variables=draft_workflow.environment_variables, + environment_variables=[], conversation_variables=draft_workflow.conversation_variables, + rag_pipeline_variables=draft_workflow.rag_pipeline_variables, marked_name="", marked_comment="", )