refactor: rename new runtime as sandbox feature

This commit is contained in:
Harry 2026-01-12 01:53:26 +08:00
parent 3d2840edb6
commit 9dd0361d0e
13 changed files with 47 additions and 18 deletions

View File

@ -41,6 +41,7 @@ from factories import file_factory
from libs.flask_utils import preserve_flask_contexts
from models import Account, App, Conversation, EndUser, Message, Workflow, WorkflowNodeExecutionTriggeredFrom
from models.enums import WorkflowRunTriggeredFrom
from models.workflow_features import WorkflowFeatures
from services.conversation_service import ConversationService
from services.workflow_draft_variable_service import (
DraftVarLoader,
@ -513,10 +514,8 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator):
if workflow is None:
raise ValueError("Workflow not found")
# FIXME:(sandbox) Consolidate runtime config checking into a unified location.
runtime = workflow.features_dict.get("runtime")
graph_engine_layers: tuple = ()
if isinstance(runtime, dict) and runtime.get("enabled"):
if workflow.get_feature(WorkflowFeatures.SANDBOX).enabled:
graph_engine_layers = (SandboxLayer(tenant_id=application_generate_entity.app_config.tenant_id),)
# Determine system_user_id based on invocation source

View File

@ -38,6 +38,7 @@ from factories import file_factory
from libs.flask_utils import preserve_flask_contexts
from models import Account, App, EndUser, Workflow, WorkflowNodeExecutionTriggeredFrom
from models.enums import WorkflowRunTriggeredFrom
from models.workflow_features import WorkflowFeatures
from services.workflow_draft_variable_service import DraftVarLoader, WorkflowDraftVariableService
SKIP_PREPARE_USER_INPUTS_KEY = "_skip_prepare_user_inputs"
@ -488,9 +489,7 @@ class WorkflowAppGenerator(BaseAppGenerator):
if workflow is None:
raise ValueError("Workflow not found")
# FIXME:(sandbox) Consolidate runtime config checking into a unified location.
runtime = workflow.features_dict.get("runtime")
if isinstance(runtime, dict) and runtime.get("enabled"):
if workflow.get_feature(WorkflowFeatures.SANDBOX).enabled:
graph_engine_layers = (
*graph_engine_layers,
SandboxLayer(tenant_id=application_generate_entity.app_config.tenant_id),

View File

@ -109,6 +109,7 @@ from .workflow import (
WorkflowRun,
WorkflowType,
)
from .workflow_features import WorkflowFeature, WorkflowFeatures
__all__ = [
"APIBasedExtension",
@ -202,6 +203,8 @@ __all__ = [
"Workflow",
"WorkflowAppLog",
"WorkflowAppLogCreatedFrom",
"WorkflowFeature",
"WorkflowFeatures",
"WorkflowNodeExecutionModel",
"WorkflowNodeExecutionOffload",
"WorkflowNodeExecutionTriggeredFrom",

View File

@ -37,13 +37,13 @@ from extensions.ext_storage import Storage
from factories.variable_factory import TypeMismatchError, build_segment_with_type
from libs.datetime_utils import naive_utc_now
from libs.uuid_utils import uuidv7
from models.workflow_features import WorkflowFeature, WorkflowFeatures
from ._workflow_exc import NodeNotFoundError, WorkflowDataError
if TYPE_CHECKING:
from .model import AppMode, UploadFile
from constants import DEFAULT_FILE_NUMBER_LIMITS, HIDDEN_VALUE
from core.helper import encrypter
from core.variables import SecretVariable, Segment, SegmentType, Variable
@ -345,6 +345,9 @@ class Workflow(Base): # bug
def features_dict(self) -> dict[str, Any]:
return json.loads(self.features) if self.features else {}
def get_feature(self, key: WorkflowFeatures) -> WorkflowFeature:
return WorkflowFeature.from_dict(self.features_dict.get(key.value))
def walk_nodes(
self, specific_node_type: NodeType | None = None
) -> Generator[tuple[str, Mapping[str, Any]], None, None]:

View File

@ -0,0 +1,26 @@
from collections.abc import Mapping
from dataclasses import dataclass
from enum import StrEnum
from typing import Any
class WorkflowFeatures(StrEnum):
SANDBOX = "sandbox"
SPEECH_TO_TEXT = "speech_to_text"
TEXT_TO_SPEECH = "text_to_speech"
RETRIEVER_RESOURCE = "retriever_resource"
SENSITIVE_WORD_AVOIDANCE = "sensitive_word_avoidance"
FILE_UPLOAD = "file_upload"
SUGGESTED_QUESTIONS_AFTER_ANSWER = "suggested_questions_after_answer"
@dataclass(frozen=True)
class WorkflowFeature:
enabled: bool
config: Mapping[str, Any]
@classmethod
def from_dict(cls, data: Mapping[str, Any] | None) -> "WorkflowFeature":
if data is None or not isinstance(data, dict):
return cls(enabled=False, config={})
return cls(enabled=bool(data.get("enabled", False)), config=data)

View File

@ -38,6 +38,7 @@ from models import Account
from models.model import App, AppMode
from models.tools import WorkflowToolProvider
from models.workflow import Workflow, WorkflowNodeExecutionModel, WorkflowNodeExecutionTriggeredFrom, WorkflowType
from models.workflow_features import WorkflowFeatures
from repositories.factory import DifyAPIRepositoryFactory
from services.billing_service import BillingService
from services.enterprise.plugin_manager_service import PluginCredentialType
@ -697,11 +698,9 @@ class WorkflowService:
else:
enclosing_node_id = None
# TODO: Consolidate runtime config checking into a unified location.
runtime = draft_workflow.features_dict.get("runtime")
sandbox = None
single_step_execution_id: str | None = None
if isinstance(runtime, dict) and runtime.get("enabled"):
if draft_workflow.get_feature(WorkflowFeatures.SANDBOX).enabled:
sandbox = SandboxProviderService.create_sandbox(tenant_id=draft_workflow.tenant_id)
single_step_execution_id = f"single-step-{uuid.uuid4()}"
from core.virtual_environment.sandbox_manager import SandboxManager

View File

@ -54,7 +54,7 @@ export const createFeaturesStore = (initProps?: Partial<FeaturesState>) => {
annotationReply: {
enabled: false,
},
runtime: {
sandbox: {
enabled: false,
},
},

View File

@ -91,7 +91,7 @@ export enum FeatureEnum {
moderation = 'moderation',
file = 'file',
annotationReply = 'annotationReply',
runtime = 'runtime',
sandbox = 'sandbox',
}
export type Features = {
@ -104,7 +104,7 @@ export type Features = {
[FeatureEnum.moderation]?: SensitiveWordAvoidance
[FeatureEnum.file]?: FileUpload
[FeatureEnum.annotationReply]?: AnnotationReplyConfig
[FeatureEnum.runtime]?: Runtime
[FeatureEnum.sandbox]?: Runtime
}
export type OnFeaturesChange = (features?: Features) => void

View File

@ -71,7 +71,7 @@ export const useNodesSyncDraft = () => {
retriever_resource: features.citation,
sensitive_word_avoidance: features.moderation,
file_upload: features.file,
runtime: features.runtime,
sandbox: features.sandbox,
},
environment_variables: environmentVariables,
conversation_variables: conversationVariables,

View File

@ -99,7 +99,7 @@ export const useWorkflowInit = () => {
},
features: {
retriever_resource: { enabled: true },
runtime: { enabled: enableSandboxRuntime },
sandbox: { enabled: enableSandboxRuntime },
},
environment_variables: [],
conversation_variables: [],

View File

@ -748,7 +748,7 @@ export const useWorkflowRun = () => {
citation: publishedWorkflow.features.retriever_resource,
moderation: publishedWorkflow.features.sensitive_word_avoidance,
file: publishedWorkflow.features.file_upload,
runtime: publishedWorkflow.features.runtime || { enabled: false },
sandbox: publishedWorkflow.features.sandbox || { enabled: false },
}
featuresStore?.setState({ features: mappedFeatures })

View File

@ -192,7 +192,7 @@ const WorkflowAppWithAdditionalContext = () => {
text2speech: features.text_to_speech || { enabled: false },
citation: features.retriever_resource || { enabled: false },
moderation: features.sensitive_word_avoidance || { enabled: false },
runtime: features.runtime || { enabled: false },
sandbox: features.sandbox || { enabled: false },
}
return (

View File

@ -121,7 +121,7 @@ const UpdateDSLModal = ({
text2speech: features.text_to_speech || { enabled: false },
citation: features.retriever_resource || { enabled: false },
moderation: features.sensitive_word_avoidance || { enabled: false },
runtime: features.runtime || { enabled: false },
sandbox: features.sandbox || { enabled: false },
}
eventEmitter?.emit({