diff --git a/api/.importlinter b/api/.importlinter index 74dec4a293..f74a1b667d 100644 --- a/api/.importlinter +++ b/api/.importlinter @@ -129,8 +129,6 @@ ignore_imports = core.workflow.nodes.parameter_extractor.parameter_extractor_node -> core.prompt.simple_prompt_transform core.workflow.nodes.parameter_extractor.parameter_extractor_node -> core.model_runtime.model_providers.__base.large_language_model core.workflow.nodes.question_classifier.question_classifier_node -> core.prompt.simple_prompt_transform - core.workflow.nodes.start.entities -> core.app.app_config.entities - core.workflow.nodes.start.start_node -> core.app.app_config.entities core.workflow.workflow_entry -> core.app.apps.exc core.workflow.workflow_entry -> core.app.entities.app_invoke_entities core.workflow.workflow_entry -> core.app.workflow.layers.llm_quota diff --git a/api/controllers/mcp/mcp.py b/api/controllers/mcp/mcp.py index 90137a10ba..991a9166c7 100644 --- a/api/controllers/mcp/mcp.py +++ b/api/controllers/mcp/mcp.py @@ -8,9 +8,9 @@ from sqlalchemy.orm import Session from controllers.common.schema import register_schema_model from controllers.console.app.mcp_server import AppMCPServerStatus from controllers.mcp import mcp_ns -from core.app.app_config.entities import VariableEntity from core.mcp import types as mcp_types from core.mcp.server.streamable_http import handle_mcp_request +from core.workflow.variables.input_entities import VariableEntity from extensions.ext_database import db from libs import helper from models.model import App, AppMCPServer, AppMode, EndUser diff --git a/api/core/app/app_config/easy_ui_based_app/variables/manager.py b/api/core/app/app_config/easy_ui_based_app/variables/manager.py index 6375733448..22d602a190 100644 --- a/api/core/app/app_config/easy_ui_based_app/variables/manager.py +++ b/api/core/app/app_config/easy_ui_based_app/variables/manager.py @@ -1,7 +1,8 @@ import re -from core.app.app_config.entities import ExternalDataVariableEntity, VariableEntity, VariableEntityType +from core.app.app_config.entities import ExternalDataVariableEntity from core.external_data_tool.factory import ExternalDataToolFactory +from core.workflow.variables.input_entities import VariableEntity, VariableEntityType _ALLOWED_VARIABLE_ENTITY_TYPE = frozenset( [ diff --git a/api/core/app/app_config/entities.py b/api/core/app/app_config/entities.py index f8538d474c..062cc6a0b3 100644 --- a/api/core/app/app_config/entities.py +++ b/api/core/app/app_config/entities.py @@ -2,12 +2,12 @@ from collections.abc import Sequence from enum import StrEnum, auto from typing import Any, Literal -from jsonschema import Draft7Validator, SchemaError -from pydantic import BaseModel, Field, field_validator +from pydantic import BaseModel, Field from core.model_runtime.entities.llm_entities import LLMMode from core.model_runtime.entities.message_entities import PromptMessageRole -from core.workflow.file import FileTransferMethod, FileType, FileUploadConfig +from core.workflow.file import FileUploadConfig +from core.workflow.variables.input_entities import VariableEntity as WorkflowVariableEntity from models.model import AppMode @@ -90,61 +90,7 @@ class PromptTemplateEntity(BaseModel): advanced_completion_prompt_template: AdvancedCompletionPromptTemplateEntity | None = None -class VariableEntityType(StrEnum): - TEXT_INPUT = "text-input" - SELECT = "select" - PARAGRAPH = "paragraph" - NUMBER = "number" - EXTERNAL_DATA_TOOL = "external_data_tool" - FILE = "file" - FILE_LIST = "file-list" - CHECKBOX = "checkbox" - JSON_OBJECT = "json_object" - - -class VariableEntity(BaseModel): - """ - Variable Entity. - """ - - # `variable` records the name of the variable in user inputs. - variable: str - label: str - description: str = "" - type: VariableEntityType - required: bool = False - hide: bool = False - default: Any = None - max_length: int | None = None - options: Sequence[str] = Field(default_factory=list) - allowed_file_types: Sequence[FileType] | None = Field(default_factory=list) - allowed_file_extensions: Sequence[str] | None = Field(default_factory=list) - allowed_file_upload_methods: Sequence[FileTransferMethod] | None = Field(default_factory=list) - json_schema: dict | None = Field(default=None) - - @field_validator("description", mode="before") - @classmethod - def convert_none_description(cls, v: Any) -> str: - return v or "" - - @field_validator("options", mode="before") - @classmethod - def convert_none_options(cls, v: Any) -> Sequence[str]: - return v or [] - - @field_validator("json_schema") - @classmethod - def validate_json_schema(cls, schema: dict | None) -> dict | None: - if schema is None: - return None - try: - Draft7Validator.check_schema(schema) - except SchemaError as e: - raise ValueError(f"Invalid JSON schema: {e.message}") - return schema - - -class RagPipelineVariableEntity(VariableEntity): +class RagPipelineVariableEntity(WorkflowVariableEntity): """ Rag Pipeline Variable Entity. """ @@ -314,7 +260,7 @@ class AppConfig(BaseModel): app_id: str app_mode: AppMode additional_features: AppAdditionalFeatures | None = None - variables: list[VariableEntity] = [] + variables: list[WorkflowVariableEntity] = [] sensitive_word_avoidance: SensitiveWordAvoidanceEntity | None = None diff --git a/api/core/app/app_config/workflow_ui_based_app/variables/manager.py b/api/core/app/app_config/workflow_ui_based_app/variables/manager.py index 96b52712ae..ec7d85a09f 100644 --- a/api/core/app/app_config/workflow_ui_based_app/variables/manager.py +++ b/api/core/app/app_config/workflow_ui_based_app/variables/manager.py @@ -1,6 +1,7 @@ import re -from core.app.app_config.entities import RagPipelineVariableEntity, VariableEntity +from core.app.app_config.entities import RagPipelineVariableEntity +from core.workflow.variables.input_entities import VariableEntity from models.workflow import Workflow diff --git a/api/core/app/apps/base_app_generator.py b/api/core/app/apps/base_app_generator.py index 48742205f1..81617c5fb2 100644 --- a/api/core/app/apps/base_app_generator.py +++ b/api/core/app/apps/base_app_generator.py @@ -3,7 +3,6 @@ from typing import TYPE_CHECKING, Any, Union, final from sqlalchemy.orm import Session -from core.app.app_config.entities import VariableEntityType from core.app.entities.app_invoke_entities import InvokeFrom from core.workflow.enums import NodeType from core.workflow.file import File, FileUploadConfig @@ -12,13 +11,14 @@ from core.workflow.repositories.draft_variable_repository import ( DraftVariableSaverFactory, NoopDraftVariableSaver, ) +from core.workflow.variables.input_entities import VariableEntityType from factories import file_factory from libs.orjson import orjson_dumps from models import Account, EndUser from services.workflow_draft_variable_service import DraftVariableSaver as DraftVariableSaverImpl if TYPE_CHECKING: - from core.app.app_config.entities import VariableEntity + from core.workflow.variables.input_entities import VariableEntity class BaseAppGenerator: diff --git a/api/core/mcp/server/streamable_http.py b/api/core/mcp/server/streamable_http.py index 212c2eb073..da747d2c1f 100644 --- a/api/core/mcp/server/streamable_http.py +++ b/api/core/mcp/server/streamable_http.py @@ -4,10 +4,10 @@ from collections.abc import Mapping from typing import Any, cast from configs import dify_config -from core.app.app_config.entities import VariableEntity, VariableEntityType from core.app.entities.app_invoke_entities import InvokeFrom from core.app.features.rate_limiting.rate_limit import RateLimitGenerator from core.mcp import types as mcp_types +from core.workflow.variables.input_entities import VariableEntity, VariableEntityType from models.model import App, AppMCPServer, AppMode, EndUser from services.app_generate_service import AppGenerateService diff --git a/api/core/tools/utils/workflow_configuration_sync.py b/api/core/tools/utils/workflow_configuration_sync.py index 186e1656ba..8e8c5e9c6a 100644 --- a/api/core/tools/utils/workflow_configuration_sync.py +++ b/api/core/tools/utils/workflow_configuration_sync.py @@ -1,11 +1,11 @@ from collections.abc import Mapping, Sequence from typing import Any -from core.app.app_config.entities import VariableEntity from core.tools.entities.tool_entities import WorkflowToolParameterConfiguration from core.tools.errors import WorkflowToolHumanInputNotSupportedError from core.workflow.enums import NodeType from core.workflow.nodes.base.entities import OutputVariableEntity +from core.workflow.variables.input_entities import VariableEntity class WorkflowToolConfigurationUtils: diff --git a/api/core/tools/workflow_as_tool/provider.py b/api/core/tools/workflow_as_tool/provider.py index a706f101ca..56faccb407 100644 --- a/api/core/tools/workflow_as_tool/provider.py +++ b/api/core/tools/workflow_as_tool/provider.py @@ -5,7 +5,6 @@ from collections.abc import Mapping from pydantic import Field from sqlalchemy.orm import Session -from core.app.app_config.entities import VariableEntity, VariableEntityType from core.app.apps.workflow.app_config_manager import WorkflowAppConfigManager from core.db.session_factory import session_factory from core.plugin.entities.parameters import PluginParameterOption @@ -23,6 +22,7 @@ from core.tools.entities.tool_entities import ( ) from core.tools.utils.workflow_configuration_sync import WorkflowToolConfigurationUtils from core.tools.workflow_as_tool.tool import WorkflowTool +from core.workflow.variables.input_entities import VariableEntity, VariableEntityType from extensions.ext_database import db from models.account import Account from models.model import App, AppMode diff --git a/api/core/workflow/nodes/start/entities.py b/api/core/workflow/nodes/start/entities.py index 594d1b7bab..3a99e2cbc2 100644 --- a/api/core/workflow/nodes/start/entities.py +++ b/api/core/workflow/nodes/start/entities.py @@ -2,8 +2,8 @@ from collections.abc import Sequence from pydantic import Field -from core.app.app_config.entities import VariableEntity from core.workflow.nodes.base import BaseNodeData +from core.workflow.variables.input_entities import VariableEntity class StartNodeData(BaseNodeData): diff --git a/api/core/workflow/nodes/start/start_node.py b/api/core/workflow/nodes/start/start_node.py index 53c1b4ee6b..4e5545d330 100644 --- a/api/core/workflow/nodes/start/start_node.py +++ b/api/core/workflow/nodes/start/start_node.py @@ -2,12 +2,12 @@ from typing import Any from jsonschema import Draft7Validator, ValidationError -from core.app.app_config.entities import VariableEntityType from core.workflow.constants import SYSTEM_VARIABLE_NODE_ID from core.workflow.enums import NodeExecutionType, NodeType, WorkflowNodeExecutionStatus from core.workflow.node_events import NodeRunResult from core.workflow.nodes.base.node import Node from core.workflow.nodes.start.entities import StartNodeData +from core.workflow.variables.input_entities import VariableEntityType class StartNode(Node[StartNodeData]): diff --git a/api/core/workflow/variables/__init__.py b/api/core/workflow/variables/__init__.py index 7498224923..be3fc8d97a 100644 --- a/api/core/workflow/variables/__init__.py +++ b/api/core/workflow/variables/__init__.py @@ -1,3 +1,4 @@ +from .input_entities import VariableEntity, VariableEntityType from .segment_group import SegmentGroup from .segments import ( ArrayAnySegment, @@ -64,4 +65,6 @@ __all__ = [ "StringVariable", "Variable", "VariableBase", + "VariableEntity", + "VariableEntityType", ] diff --git a/api/core/workflow/variables/input_entities.py b/api/core/workflow/variables/input_entities.py new file mode 100644 index 0000000000..9a42012f0a --- /dev/null +++ b/api/core/workflow/variables/input_entities.py @@ -0,0 +1,62 @@ +from collections.abc import Sequence +from enum import StrEnum +from typing import Any + +from jsonschema import Draft7Validator, SchemaError +from pydantic import BaseModel, Field, field_validator + +from core.workflow.file import FileTransferMethod, FileType + + +class VariableEntityType(StrEnum): + TEXT_INPUT = "text-input" + SELECT = "select" + PARAGRAPH = "paragraph" + NUMBER = "number" + EXTERNAL_DATA_TOOL = "external_data_tool" + FILE = "file" + FILE_LIST = "file-list" + CHECKBOX = "checkbox" + JSON_OBJECT = "json_object" + + +class VariableEntity(BaseModel): + """ + Shared variable entity used by workflow runtime and app configuration. + """ + + # `variable` records the name of the variable in user inputs. + variable: str + label: str + description: str = "" + type: VariableEntityType + required: bool = False + hide: bool = False + default: Any = None + max_length: int | None = None + options: Sequence[str] = Field(default_factory=list) + allowed_file_types: Sequence[FileType] | None = Field(default_factory=list) + allowed_file_extensions: Sequence[str] | None = Field(default_factory=list) + allowed_file_upload_methods: Sequence[FileTransferMethod] | None = Field(default_factory=list) + json_schema: dict[str, Any] | None = Field(default=None) + + @field_validator("description", mode="before") + @classmethod + def convert_none_description(cls, value: Any) -> str: + return value or "" + + @field_validator("options", mode="before") + @classmethod + def convert_none_options(cls, value: Any) -> Sequence[str]: + return value or [] + + @field_validator("json_schema") + @classmethod + def validate_json_schema(cls, schema: dict[str, Any] | None) -> dict[str, Any] | None: + if schema is None: + return None + try: + Draft7Validator.check_schema(schema) + except SchemaError as error: + raise ValueError(f"Invalid JSON schema: {error.message}") + return schema diff --git a/api/services/workflow/workflow_converter.py b/api/services/workflow/workflow_converter.py index 809151b91a..5527c108a2 100644 --- a/api/services/workflow/workflow_converter.py +++ b/api/services/workflow/workflow_converter.py @@ -8,7 +8,6 @@ from core.app.app_config.entities import ( ExternalDataVariableEntity, ModelConfigEntity, PromptTemplateEntity, - VariableEntity, ) from core.app.apps.agent_chat.app_config_manager import AgentChatAppConfigManager from core.app.apps.chat.app_config_manager import ChatAppConfigManager @@ -20,6 +19,7 @@ from core.prompt.simple_prompt_transform import SimplePromptTransform from core.prompt.utils.prompt_template_parser import PromptTemplateParser from core.workflow.file.models import FileUploadConfig from core.workflow.nodes import NodeType +from core.workflow.variables.input_entities import VariableEntity from events.app_event import app_was_created from extensions.ext_database import db from models import Account diff --git a/api/services/workflow_service.py b/api/services/workflow_service.py index 406fdae525..3b448423e8 100644 --- a/api/services/workflow_service.py +++ b/api/services/workflow_service.py @@ -9,7 +9,6 @@ from sqlalchemy import exists, select from sqlalchemy.orm import Session, sessionmaker from configs import dify_config -from core.app.app_config.entities import VariableEntityType from core.app.apps.advanced_chat.app_config_manager import AdvancedChatAppConfigManager from core.app.apps.workflow.app_config_manager import WorkflowAppConfigManager from core.app.entities.app_invoke_entities import InvokeFrom @@ -40,6 +39,7 @@ from core.workflow.runtime import GraphRuntimeState, VariablePool from core.workflow.system_variable import SystemVariable from core.workflow.variable_loader import load_into_variable_pool from core.workflow.variables import VariableBase +from core.workflow.variables.input_entities import VariableEntityType from core.workflow.variables.variables import Variable from core.workflow.workflow_entry import WorkflowEntry from enums.cloud_plan import CloudPlan diff --git a/api/tests/test_containers_integration_tests/services/workflow/test_workflow_converter.py b/api/tests/test_containers_integration_tests/services/workflow/test_workflow_converter.py index 2c5e719a58..2ffb884b82 100644 --- a/api/tests/test_containers_integration_tests/services/workflow/test_workflow_converter.py +++ b/api/tests/test_containers_integration_tests/services/workflow/test_workflow_converter.py @@ -10,11 +10,10 @@ from core.app.app_config.entities import ( ExternalDataVariableEntity, ModelConfigEntity, PromptTemplateEntity, - VariableEntity, - VariableEntityType, ) from core.model_runtime.entities.llm_entities import LLMMode from core.prompt.utils.prompt_template_parser import PromptTemplateParser +from core.workflow.variables.input_entities import VariableEntity, VariableEntityType from models import Account, Tenant from models.api_based_extension import APIBasedExtension from models.model import App, AppMode, AppModelConfig diff --git a/api/tests/unit_tests/core/app/apps/test_base_app_generator.py b/api/tests/unit_tests/core/app/apps/test_base_app_generator.py index 1000d71399..04c8696525 100644 --- a/api/tests/unit_tests/core/app/apps/test_base_app_generator.py +++ b/api/tests/unit_tests/core/app/apps/test_base_app_generator.py @@ -1,7 +1,7 @@ import pytest -from core.app.app_config.entities import VariableEntity, VariableEntityType from core.app.apps.base_app_generator import BaseAppGenerator +from core.workflow.variables.input_entities import VariableEntity, VariableEntityType def test_validate_inputs_with_zero(): diff --git a/api/tests/unit_tests/core/mcp/server/test_streamable_http.py b/api/tests/unit_tests/core/mcp/server/test_streamable_http.py index fe9f0935d5..40a7700394 100644 --- a/api/tests/unit_tests/core/mcp/server/test_streamable_http.py +++ b/api/tests/unit_tests/core/mcp/server/test_streamable_http.py @@ -4,7 +4,6 @@ from unittest.mock import Mock, patch import jsonschema import pytest -from core.app.app_config.entities import VariableEntity, VariableEntityType from core.app.features.rate_limiting.rate_limit import RateLimitGenerator from core.mcp import types from core.mcp.server.streamable_http import ( @@ -19,6 +18,7 @@ from core.mcp.server.streamable_http import ( prepare_tool_arguments, process_mapping_response, ) +from core.workflow.variables.input_entities import VariableEntity, VariableEntityType from models.model import App, AppMCPServer, AppMode, EndUser diff --git a/api/tests/unit_tests/core/workflow/nodes/test_start_node_json_object.py b/api/tests/unit_tests/core/workflow/nodes/test_start_node_json_object.py index 16b432bae6..8c7dc24868 100644 --- a/api/tests/unit_tests/core/workflow/nodes/test_start_node_json_object.py +++ b/api/tests/unit_tests/core/workflow/nodes/test_start_node_json_object.py @@ -4,12 +4,12 @@ import time import pytest from pydantic import ValidationError as PydanticValidationError -from core.app.app_config.entities import VariableEntity, VariableEntityType from core.workflow.entities import GraphInitParams from core.workflow.nodes.start.entities import StartNodeData from core.workflow.nodes.start.start_node import StartNode from core.workflow.runtime import GraphRuntimeState, VariablePool from core.workflow.system_variable import SystemVariable +from core.workflow.variables.input_entities import VariableEntity, VariableEntityType def make_start_node(user_inputs, variables): diff --git a/api/tests/unit_tests/services/workflow/test_workflow_converter.py b/api/tests/unit_tests/services/workflow/test_workflow_converter.py index 267c0a85a7..8ccbfbb16e 100644 --- a/api/tests/unit_tests/services/workflow/test_workflow_converter.py +++ b/api/tests/unit_tests/services/workflow/test_workflow_converter.py @@ -13,12 +13,11 @@ from core.app.app_config.entities import ( ExternalDataVariableEntity, ModelConfigEntity, PromptTemplateEntity, - VariableEntity, - VariableEntityType, ) from core.helper import encrypter from core.model_runtime.entities.llm_entities import LLMMode from core.model_runtime.entities.message_entities import PromptMessageRole +from core.workflow.variables.input_entities import VariableEntity, VariableEntityType from models.api_based_extension import APIBasedExtension, APIBasedExtensionPoint from models.model import AppMode from services.workflow.workflow_converter import WorkflowConverter