Procházet zdrojové kódy

refactor(workflow): Rename workflow node execution models (#20458)

Signed-off-by: -LAN- <laipz8200@outlook.com>
tags/1.4.2
-LAN- před 5 měsíci
rodič
revize
f7fb10635f
Žádný účet není propojen s e-mailovou adresou tvůrce revize

+ 4
- 4
api/core/app/apps/common/workflow_response_converter.py Zobrazit soubor

from core.file import FILE_MODEL_IDENTITY, File from core.file import FILE_MODEL_IDENTITY, File
from core.tools.tool_manager import ToolManager from core.tools.tool_manager import ToolManager
from core.workflow.entities.workflow_execution import WorkflowExecution from core.workflow.entities.workflow_execution import WorkflowExecution
from core.workflow.entities.workflow_node_execution import NodeExecution, WorkflowNodeExecutionStatus
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecution, WorkflowNodeExecutionStatus
from core.workflow.nodes import NodeType from core.workflow.nodes import NodeType
from core.workflow.nodes.tool.entities import ToolNodeData from core.workflow.nodes.tool.entities import ToolNodeData
from models import ( from models import (
*, *,
event: QueueNodeStartedEvent, event: QueueNodeStartedEvent,
task_id: str, task_id: str,
workflow_node_execution: NodeExecution,
workflow_node_execution: WorkflowNodeExecution,
) -> Optional[NodeStartStreamResponse]: ) -> Optional[NodeStartStreamResponse]:
if workflow_node_execution.node_type in {NodeType.ITERATION, NodeType.LOOP}: if workflow_node_execution.node_type in {NodeType.ITERATION, NodeType.LOOP}:
return None return None
| QueueNodeInLoopFailedEvent | QueueNodeInLoopFailedEvent
| QueueNodeExceptionEvent, | QueueNodeExceptionEvent,
task_id: str, task_id: str,
workflow_node_execution: NodeExecution,
workflow_node_execution: WorkflowNodeExecution,
) -> Optional[NodeFinishStreamResponse]: ) -> Optional[NodeFinishStreamResponse]:
if workflow_node_execution.node_type in {NodeType.ITERATION, NodeType.LOOP}: if workflow_node_execution.node_type in {NodeType.ITERATION, NodeType.LOOP}:
return None return None
*, *,
event: QueueNodeRetryEvent, event: QueueNodeRetryEvent,
task_id: str, task_id: str,
workflow_node_execution: NodeExecution,
workflow_node_execution: WorkflowNodeExecution,
) -> Optional[Union[NodeRetryStreamResponse, NodeFinishStreamResponse]]: ) -> Optional[Union[NodeRetryStreamResponse, NodeFinishStreamResponse]]:
if workflow_node_execution.node_type in {NodeType.ITERATION, NodeType.LOOP}: if workflow_node_execution.node_type in {NodeType.ITERATION, NodeType.LOOP}:
return None return None

+ 30
- 30
api/core/repositories/sqlalchemy_workflow_node_execution_repository.py Zobrazit soubor



from core.model_runtime.utils.encoders import jsonable_encoder from core.model_runtime.utils.encoders import jsonable_encoder
from core.workflow.entities.workflow_node_execution import ( from core.workflow.entities.workflow_node_execution import (
NodeExecution,
WorkflowNodeExecution,
WorkflowNodeExecutionMetadataKey, WorkflowNodeExecutionMetadataKey,
WorkflowNodeExecutionStatus, WorkflowNodeExecutionStatus,
) )
Account, Account,
CreatorUserRole, CreatorUserRole,
EndUser, EndUser,
WorkflowNodeExecution,
WorkflowNodeExecutionModel,
WorkflowNodeExecutionTriggeredFrom, WorkflowNodeExecutionTriggeredFrom,
) )




# Initialize in-memory cache for node executions # Initialize in-memory cache for node executions
# Key: node_execution_id, Value: WorkflowNodeExecution (DB model) # Key: node_execution_id, Value: WorkflowNodeExecution (DB model)
self._node_execution_cache: dict[str, WorkflowNodeExecution] = {}
self._node_execution_cache: dict[str, WorkflowNodeExecutionModel] = {}


def _to_domain_model(self, db_model: WorkflowNodeExecution) -> NodeExecution:
def _to_domain_model(self, db_model: WorkflowNodeExecutionModel) -> WorkflowNodeExecution:
""" """
Convert a database model to a domain model. Convert a database model to a domain model.


# Convert status to domain enum # Convert status to domain enum
status = WorkflowNodeExecutionStatus(db_model.status) status = WorkflowNodeExecutionStatus(db_model.status)


return NodeExecution(
return WorkflowNodeExecution(
id=db_model.id, id=db_model.id,
node_execution_id=db_model.node_execution_id, node_execution_id=db_model.node_execution_id,
workflow_id=db_model.workflow_id, workflow_id=db_model.workflow_id,
finished_at=db_model.finished_at, finished_at=db_model.finished_at,
) )


def to_db_model(self, domain_model: NodeExecution) -> WorkflowNodeExecution:
def to_db_model(self, domain_model: WorkflowNodeExecution) -> WorkflowNodeExecutionModel:
""" """
Convert a domain model to a database model. Convert a domain model to a database model.


if not self._creator_user_role: if not self._creator_user_role:
raise ValueError("created_by_role is required in repository constructor") raise ValueError("created_by_role is required in repository constructor")


db_model = WorkflowNodeExecution()
db_model = WorkflowNodeExecutionModel()
db_model.id = domain_model.id db_model.id = domain_model.id
db_model.tenant_id = self._tenant_id db_model.tenant_id = self._tenant_id
if self._app_id is not None: if self._app_id is not None:
db_model.finished_at = domain_model.finished_at db_model.finished_at = domain_model.finished_at
return db_model return db_model


def save(self, execution: NodeExecution) -> None:
def save(self, execution: WorkflowNodeExecution) -> None:
""" """
Save or update a NodeExecution domain entity to the database. Save or update a NodeExecution domain entity to the database.


logger.debug(f"Updating cache for node_execution_id: {db_model.node_execution_id}") logger.debug(f"Updating cache for node_execution_id: {db_model.node_execution_id}")
self._node_execution_cache[db_model.node_execution_id] = db_model self._node_execution_cache[db_model.node_execution_id] = db_model


def get_by_node_execution_id(self, node_execution_id: str) -> Optional[NodeExecution]:
def get_by_node_execution_id(self, node_execution_id: str) -> Optional[WorkflowNodeExecution]:
""" """
Retrieve a NodeExecution by its node_execution_id. Retrieve a NodeExecution by its node_execution_id.


# If not in cache, query the database # If not in cache, query the database
logger.debug(f"Cache miss for node_execution_id: {node_execution_id}, querying database") logger.debug(f"Cache miss for node_execution_id: {node_execution_id}, querying database")
with self._session_factory() as session: with self._session_factory() as session:
stmt = select(WorkflowNodeExecution).where(
WorkflowNodeExecution.node_execution_id == node_execution_id,
WorkflowNodeExecution.tenant_id == self._tenant_id,
stmt = select(WorkflowNodeExecutionModel).where(
WorkflowNodeExecutionModel.node_execution_id == node_execution_id,
WorkflowNodeExecutionModel.tenant_id == self._tenant_id,
) )


if self._app_id: if self._app_id:
stmt = stmt.where(WorkflowNodeExecution.app_id == self._app_id)
stmt = stmt.where(WorkflowNodeExecutionModel.app_id == self._app_id)


db_model = session.scalar(stmt) db_model = session.scalar(stmt)
if db_model: if db_model:
self, self,
workflow_run_id: str, workflow_run_id: str,
order_config: Optional[OrderConfig] = None, order_config: Optional[OrderConfig] = None,
) -> Sequence[WorkflowNodeExecution]:
) -> Sequence[WorkflowNodeExecutionModel]:
""" """
Retrieve all WorkflowNodeExecution database models for a specific workflow run. Retrieve all WorkflowNodeExecution database models for a specific workflow run.


A list of WorkflowNodeExecution database models A list of WorkflowNodeExecution database models
""" """
with self._session_factory() as session: with self._session_factory() as session:
stmt = select(WorkflowNodeExecution).where(
WorkflowNodeExecution.workflow_run_id == workflow_run_id,
WorkflowNodeExecution.tenant_id == self._tenant_id,
WorkflowNodeExecution.triggered_from == WorkflowNodeExecutionTriggeredFrom.WORKFLOW_RUN,
stmt = select(WorkflowNodeExecutionModel).where(
WorkflowNodeExecutionModel.workflow_run_id == workflow_run_id,
WorkflowNodeExecutionModel.tenant_id == self._tenant_id,
WorkflowNodeExecutionModel.triggered_from == WorkflowNodeExecutionTriggeredFrom.WORKFLOW_RUN,
) )


if self._app_id: if self._app_id:
stmt = stmt.where(WorkflowNodeExecution.app_id == self._app_id)
stmt = stmt.where(WorkflowNodeExecutionModel.app_id == self._app_id)


# Apply ordering if provided # Apply ordering if provided
if order_config and order_config.order_by: if order_config and order_config.order_by:
order_columns: list[UnaryExpression] = [] order_columns: list[UnaryExpression] = []
for field in order_config.order_by: for field in order_config.order_by:
column = getattr(WorkflowNodeExecution, field, None)
column = getattr(WorkflowNodeExecutionModel, field, None)
if not column: if not column:
continue continue
if order_config.order_direction == "desc": if order_config.order_direction == "desc":
self, self,
workflow_run_id: str, workflow_run_id: str,
order_config: Optional[OrderConfig] = None, order_config: Optional[OrderConfig] = None,
) -> Sequence[NodeExecution]:
) -> Sequence[WorkflowNodeExecution]:
""" """
Retrieve all NodeExecution instances for a specific workflow run. Retrieve all NodeExecution instances for a specific workflow run.




return domain_models return domain_models


def get_running_executions(self, workflow_run_id: str) -> Sequence[NodeExecution]:
def get_running_executions(self, workflow_run_id: str) -> Sequence[WorkflowNodeExecution]:
""" """
Retrieve all running NodeExecution instances for a specific workflow run. Retrieve all running NodeExecution instances for a specific workflow run.


A list of running NodeExecution instances A list of running NodeExecution instances
""" """
with self._session_factory() as session: with self._session_factory() as session:
stmt = select(WorkflowNodeExecution).where(
WorkflowNodeExecution.workflow_run_id == workflow_run_id,
WorkflowNodeExecution.tenant_id == self._tenant_id,
WorkflowNodeExecution.status == WorkflowNodeExecutionStatus.RUNNING,
WorkflowNodeExecution.triggered_from == WorkflowNodeExecutionTriggeredFrom.WORKFLOW_RUN,
stmt = select(WorkflowNodeExecutionModel).where(
WorkflowNodeExecutionModel.workflow_run_id == workflow_run_id,
WorkflowNodeExecutionModel.tenant_id == self._tenant_id,
WorkflowNodeExecutionModel.status == WorkflowNodeExecutionStatus.RUNNING,
WorkflowNodeExecutionModel.triggered_from == WorkflowNodeExecutionTriggeredFrom.WORKFLOW_RUN,
) )


if self._app_id: if self._app_id:
stmt = stmt.where(WorkflowNodeExecution.app_id == self._app_id)
stmt = stmt.where(WorkflowNodeExecutionModel.app_id == self._app_id)


db_models = session.scalars(stmt).all() db_models = session.scalars(stmt).all()
domain_models = [] domain_models = []
It also clears the in-memory cache. It also clears the in-memory cache.
""" """
with self._session_factory() as session: with self._session_factory() as session:
stmt = delete(WorkflowNodeExecution).where(WorkflowNodeExecution.tenant_id == self._tenant_id)
stmt = delete(WorkflowNodeExecutionModel).where(WorkflowNodeExecutionModel.tenant_id == self._tenant_id)


if self._app_id: if self._app_id:
stmt = stmt.where(WorkflowNodeExecution.app_id == self._app_id)
stmt = stmt.where(WorkflowNodeExecutionModel.app_id == self._app_id)


result = session.execute(stmt) result = session.execute(stmt)
session.commit() session.commit()

+ 1
- 1
api/core/workflow/entities/workflow_node_execution.py Zobrazit soubor

RETRY = "retry" RETRY = "retry"




class NodeExecution(BaseModel):
class WorkflowNodeExecution(BaseModel):
""" """
Domain model for workflow node execution. Domain model for workflow node execution.



+ 5
- 5
api/core/workflow/repositories/workflow_node_execution_repository.py Zobrazit soubor

from dataclasses import dataclass from dataclasses import dataclass
from typing import Literal, Optional, Protocol from typing import Literal, Optional, Protocol


from core.workflow.entities.workflow_node_execution import NodeExecution
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecution




@dataclass @dataclass
application domains or deployment scenarios. application domains or deployment scenarios.
""" """


def save(self, execution: NodeExecution) -> None:
def save(self, execution: WorkflowNodeExecution) -> None:
""" """
Save or update a NodeExecution instance. Save or update a NodeExecution instance.


""" """
... ...


def get_by_node_execution_id(self, node_execution_id: str) -> Optional[NodeExecution]:
def get_by_node_execution_id(self, node_execution_id: str) -> Optional[WorkflowNodeExecution]:
""" """
Retrieve a NodeExecution by its node_execution_id. Retrieve a NodeExecution by its node_execution_id.


self, self,
workflow_run_id: str, workflow_run_id: str,
order_config: Optional[OrderConfig] = None, order_config: Optional[OrderConfig] = None,
) -> Sequence[NodeExecution]:
) -> Sequence[WorkflowNodeExecution]:
""" """
Retrieve all NodeExecution instances for a specific workflow run. Retrieve all NodeExecution instances for a specific workflow run.


""" """
... ...


def get_running_executions(self, workflow_run_id: str) -> Sequence[NodeExecution]:
def get_running_executions(self, workflow_run_id: str) -> Sequence[WorkflowNodeExecution]:
""" """
Retrieve all running NodeExecution instances for a specific workflow run. Retrieve all running NodeExecution instances for a specific workflow run.



+ 7
- 7
api/core/workflow/workflow_cycle_manager.py Zobrazit soubor

from core.ops.ops_trace_manager import TraceQueueManager, TraceTask from core.ops.ops_trace_manager import TraceQueueManager, TraceTask
from core.workflow.entities.workflow_execution import WorkflowExecution, WorkflowExecutionStatus, WorkflowType from core.workflow.entities.workflow_execution import WorkflowExecution, WorkflowExecutionStatus, WorkflowType
from core.workflow.entities.workflow_node_execution import ( from core.workflow.entities.workflow_node_execution import (
NodeExecution,
WorkflowNodeExecution,
WorkflowNodeExecutionMetadataKey, WorkflowNodeExecutionMetadataKey,
WorkflowNodeExecutionStatus, WorkflowNodeExecutionStatus,
) )
*, *,
workflow_execution_id: str, workflow_execution_id: str,
event: QueueNodeStartedEvent, event: QueueNodeStartedEvent,
) -> NodeExecution:
) -> WorkflowNodeExecution:
workflow_execution = self._get_workflow_execution_or_raise_error(workflow_execution_id) workflow_execution = self._get_workflow_execution_or_raise_error(workflow_execution_id)


# Create a domain model # Create a domain model
WorkflowNodeExecutionMetadataKey.LOOP_ID: event.in_loop_id, WorkflowNodeExecutionMetadataKey.LOOP_ID: event.in_loop_id,
} }


domain_execution = NodeExecution(
domain_execution = WorkflowNodeExecution(
id=str(uuid4()), id=str(uuid4()),
workflow_id=workflow_execution.workflow_id, workflow_id=workflow_execution.workflow_id,
workflow_execution_id=workflow_execution.id_, workflow_execution_id=workflow_execution.id_,


return domain_execution return domain_execution


def handle_workflow_node_execution_success(self, *, event: QueueNodeSucceededEvent) -> NodeExecution:
def handle_workflow_node_execution_success(self, *, event: QueueNodeSucceededEvent) -> WorkflowNodeExecution:
# Get the domain model from repository # Get the domain model from repository
domain_execution = self._workflow_node_execution_repository.get_by_node_execution_id(event.node_execution_id) domain_execution = self._workflow_node_execution_repository.get_by_node_execution_id(event.node_execution_id)
if not domain_execution: if not domain_execution:
| QueueNodeInIterationFailedEvent | QueueNodeInIterationFailedEvent
| QueueNodeInLoopFailedEvent | QueueNodeInLoopFailedEvent
| QueueNodeExceptionEvent, | QueueNodeExceptionEvent,
) -> NodeExecution:
) -> WorkflowNodeExecution:
""" """
Workflow node execution failed Workflow node execution failed
:param event: queue node failed event :param event: queue node failed event


def handle_workflow_node_execution_retried( def handle_workflow_node_execution_retried(
self, *, workflow_execution_id: str, event: QueueNodeRetryEvent self, *, workflow_execution_id: str, event: QueueNodeRetryEvent
) -> NodeExecution:
) -> WorkflowNodeExecution:
workflow_execution = self._get_workflow_execution_or_raise_error(workflow_execution_id) workflow_execution = self._get_workflow_execution_or_raise_error(workflow_execution_id)
created_at = event.start_at created_at = event.start_at
finished_at = datetime.now(UTC).replace(tzinfo=None) finished_at = datetime.now(UTC).replace(tzinfo=None)
merged_metadata = {**execution_metadata_dict, **origin_metadata} if execution_metadata_dict else origin_metadata merged_metadata = {**execution_metadata_dict, **origin_metadata} if execution_metadata_dict else origin_metadata


# Create a domain model # Create a domain model
domain_execution = NodeExecution(
domain_execution = WorkflowNodeExecution(
id=str(uuid4()), id=str(uuid4()),
workflow_id=workflow_execution.workflow_id, workflow_id=workflow_execution.workflow_id,
workflow_execution_id=workflow_execution.id_, workflow_execution_id=workflow_execution.id_,

+ 2
- 2
api/models/__init__.py Zobrazit soubor

Workflow, Workflow,
WorkflowAppLog, WorkflowAppLog,
WorkflowAppLogCreatedFrom, WorkflowAppLogCreatedFrom,
WorkflowNodeExecution,
WorkflowNodeExecutionModel,
WorkflowNodeExecutionTriggeredFrom, WorkflowNodeExecutionTriggeredFrom,
WorkflowRun, WorkflowRun,
WorkflowType, WorkflowType,
"Workflow", "Workflow",
"WorkflowAppLog", "WorkflowAppLog",
"WorkflowAppLogCreatedFrom", "WorkflowAppLogCreatedFrom",
"WorkflowNodeExecution",
"WorkflowNodeExecutionModel",
"WorkflowNodeExecutionTriggeredFrom", "WorkflowNodeExecutionTriggeredFrom",
"WorkflowRun", "WorkflowRun",
"WorkflowRunTriggeredFrom", "WorkflowRunTriggeredFrom",

+ 1
- 1
api/models/workflow.py Zobrazit soubor

WORKFLOW_RUN = "workflow-run" WORKFLOW_RUN = "workflow-run"




class WorkflowNodeExecution(Base):
class WorkflowNodeExecutionModel(Base):
""" """
Workflow Node Execution Workflow Node Execution



+ 7
- 6
api/services/clear_free_plan_tenant_expired_logs.py Zobrazit soubor

from extensions.ext_storage import storage from extensions.ext_storage import storage
from models.account import Tenant from models.account import Tenant
from models.model import App, Conversation, Message from models.model import App, Conversation, Message
from models.workflow import WorkflowNodeExecution, WorkflowRun
from models.workflow import WorkflowNodeExecutionModel, WorkflowRun
from services.billing_service import BillingService from services.billing_service import BillingService


logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
while True: while True:
with Session(db.engine).no_autoflush as session: with Session(db.engine).no_autoflush as session:
workflow_node_executions = ( workflow_node_executions = (
session.query(WorkflowNodeExecution)
session.query(WorkflowNodeExecutionModel)
.filter( .filter(
WorkflowNodeExecution.tenant_id == tenant_id,
WorkflowNodeExecution.created_at < datetime.datetime.now() - datetime.timedelta(days=days),
WorkflowNodeExecutionModel.tenant_id == tenant_id,
WorkflowNodeExecutionModel.created_at
< datetime.datetime.now() - datetime.timedelta(days=days),
) )
.limit(batch) .limit(batch)
.all() .all()
] ]


# delete workflow node executions # delete workflow node executions
session.query(WorkflowNodeExecution).filter(
WorkflowNodeExecution.id.in_(workflow_node_execution_ids),
session.query(WorkflowNodeExecutionModel).filter(
WorkflowNodeExecutionModel.id.in_(workflow_node_execution_ids),
).delete(synchronize_session=False) ).delete(synchronize_session=False)
session.commit() session.commit()



+ 2
- 2
api/services/workflow_run_service.py Zobrazit soubor

Account, Account,
App, App,
EndUser, EndUser,
WorkflowNodeExecution,
WorkflowNodeExecutionModel,
WorkflowRun, WorkflowRun,
WorkflowRunTriggeredFrom, WorkflowRunTriggeredFrom,
) )
app_model: App, app_model: App,
run_id: str, run_id: str,
user: Account | EndUser, user: Account | EndUser,
) -> Sequence[WorkflowNodeExecution]:
) -> Sequence[WorkflowNodeExecutionModel]:
""" """
Get workflow run node execution list Get workflow run node execution list
""" """

+ 6
- 6
api/services/workflow_service.py Zobrazit soubor

from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository
from core.variables import Variable from core.variables import Variable
from core.workflow.entities.node_entities import NodeRunResult from core.workflow.entities.node_entities import NodeRunResult
from core.workflow.entities.workflow_node_execution import NodeExecution, WorkflowNodeExecutionStatus
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecution, WorkflowNodeExecutionStatus
from core.workflow.errors import WorkflowNodeRunFailedError from core.workflow.errors import WorkflowNodeRunFailedError
from core.workflow.graph_engine.entities.event import InNodeEvent from core.workflow.graph_engine.entities.event import InNodeEvent
from core.workflow.nodes import NodeType from core.workflow.nodes import NodeType
from models.tools import WorkflowToolProvider from models.tools import WorkflowToolProvider
from models.workflow import ( from models.workflow import (
Workflow, Workflow,
WorkflowNodeExecution,
WorkflowNodeExecutionModel,
WorkflowNodeExecutionTriggeredFrom, WorkflowNodeExecutionTriggeredFrom,
WorkflowType, WorkflowType,
) )


def run_draft_workflow_node( def run_draft_workflow_node(
self, app_model: App, node_id: str, user_inputs: dict, account: Account self, app_model: App, node_id: str, user_inputs: dict, account: Account
) -> WorkflowNodeExecution:
) -> WorkflowNodeExecutionModel:
""" """
Run draft workflow node Run draft workflow node
""" """


def run_free_workflow_node( def run_free_workflow_node(
self, node_data: dict, tenant_id: str, user_id: str, node_id: str, user_inputs: dict[str, Any] self, node_data: dict, tenant_id: str, user_id: str, node_id: str, user_inputs: dict[str, Any]
) -> NodeExecution:
) -> WorkflowNodeExecution:
""" """
Run draft workflow node Run draft workflow node
""" """
invoke_node_fn: Callable[[], tuple[BaseNode, Generator[NodeEvent | InNodeEvent, None, None]]], invoke_node_fn: Callable[[], tuple[BaseNode, Generator[NodeEvent | InNodeEvent, None, None]]],
start_at: float, start_at: float,
node_id: str, node_id: str,
) -> NodeExecution:
) -> WorkflowNodeExecution:
try: try:
node_instance, generator = invoke_node_fn() node_instance, generator = invoke_node_fn()


error = e.error error = e.error


# Create a NodeExecution domain model # Create a NodeExecution domain model
node_execution = NodeExecution(
node_execution = WorkflowNodeExecution(
id=str(uuid4()), id=str(uuid4()),
workflow_id="", # This is a single-step execution, so no workflow ID workflow_id="", # This is a single-step execution, so no workflow ID
index=1, index=1,

+ 4
- 4
api/tasks/remove_app_and_related_data_task.py Zobrazit soubor

) )
from models.tools import WorkflowToolProvider from models.tools import WorkflowToolProvider
from models.web import PinnedConversation, SavedMessage from models.web import PinnedConversation, SavedMessage
from models.workflow import ConversationVariable, Workflow, WorkflowAppLog, WorkflowNodeExecution, WorkflowRun
from models.workflow import ConversationVariable, Workflow, WorkflowAppLog, WorkflowNodeExecutionModel, WorkflowRun




@shared_task(queue="app_deletion", bind=True, max_retries=3) @shared_task(queue="app_deletion", bind=True, max_retries=3)


def _delete_app_workflow_node_executions(tenant_id: str, app_id: str): def _delete_app_workflow_node_executions(tenant_id: str, app_id: str):
def del_workflow_node_execution(workflow_node_execution_id: str): def del_workflow_node_execution(workflow_node_execution_id: str):
db.session.query(WorkflowNodeExecution).filter(WorkflowNodeExecution.id == workflow_node_execution_id).delete(
synchronize_session=False
)
db.session.query(WorkflowNodeExecutionModel).filter(
WorkflowNodeExecutionModel.id == workflow_node_execution_id
).delete(synchronize_session=False)


_delete_records( _delete_records(
"""select id from workflow_node_executions where tenant_id=:tenant_id and app_id=:app_id limit 1000""", """select id from workflow_node_executions where tenant_id=:tenant_id and app_id=:app_id limit 1000""",

+ 3
- 3
api/tests/unit_tests/core/workflow/test_workflow_cycle_manager.py Zobrazit soubor

) )
from core.workflow.entities.workflow_execution import WorkflowExecution, WorkflowExecutionStatus, WorkflowType from core.workflow.entities.workflow_execution import WorkflowExecution, WorkflowExecutionStatus, WorkflowType
from core.workflow.entities.workflow_node_execution import ( from core.workflow.entities.workflow_node_execution import (
NodeExecution,
WorkflowNodeExecution,
WorkflowNodeExecutionMetadataKey, WorkflowNodeExecutionMetadataKey,
WorkflowNodeExecutionStatus, WorkflowNodeExecutionStatus,
) )


# Create a real node execution # Create a real node execution


node_execution = NodeExecution(
node_execution = WorkflowNodeExecution(
id="test-node-execution-record-id", id="test-node-execution-record-id",
node_execution_id="test-node-execution-id", node_execution_id="test-node-execution-id",
workflow_id="test-workflow-id", workflow_id="test-workflow-id",


# Create a real node execution # Create a real node execution


node_execution = NodeExecution(
node_execution = WorkflowNodeExecution(
id="test-node-execution-record-id", id="test-node-execution-record-id",
node_execution_id="test-node-execution-id", node_execution_id="test-node-execution-id",
workflow_id="test-workflow-id", workflow_id="test-workflow-id",

+ 2
- 2
api/tests/unit_tests/models/test_workflow.py Zobrazit soubor



from constants import HIDDEN_VALUE from constants import HIDDEN_VALUE
from core.variables import FloatVariable, IntegerVariable, SecretVariable, StringVariable from core.variables import FloatVariable, IntegerVariable, SecretVariable, StringVariable
from models.workflow import Workflow, WorkflowNodeExecution
from models.workflow import Workflow, WorkflowNodeExecutionModel




def test_environment_variables(): def test_environment_variables():


class TestWorkflowNodeExecution: class TestWorkflowNodeExecution:
def test_execution_metadata_dict(self): def test_execution_metadata_dict(self):
node_exec = WorkflowNodeExecution()
node_exec = WorkflowNodeExecutionModel()
node_exec.execution_metadata = None node_exec.execution_metadata = None
assert node_exec.execution_metadata_dict == {} assert node_exec.execution_metadata_dict == {}



+ 14
- 14
api/tests/unit_tests/repositories/workflow_node_execution/test_sqlalchemy_repository.py Zobrazit soubor

from core.model_runtime.utils.encoders import jsonable_encoder from core.model_runtime.utils.encoders import jsonable_encoder
from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository
from core.workflow.entities.workflow_node_execution import ( from core.workflow.entities.workflow_node_execution import (
NodeExecution,
WorkflowNodeExecution,
WorkflowNodeExecutionMetadataKey, WorkflowNodeExecutionMetadataKey,
WorkflowNodeExecutionStatus, WorkflowNodeExecutionStatus,
) )
from core.workflow.nodes.enums import NodeType from core.workflow.nodes.enums import NodeType
from core.workflow.repositories.workflow_node_execution_repository import OrderConfig from core.workflow.repositories.workflow_node_execution_repository import OrderConfig
from models.account import Account, Tenant from models.account import Account, Tenant
from models.workflow import WorkflowNodeExecution, WorkflowNodeExecutionTriggeredFrom
from models.workflow import WorkflowNodeExecutionModel, WorkflowNodeExecutionTriggeredFrom




def configure_mock_execution(mock_execution): def configure_mock_execution(mock_execution):
"""Test save method.""" """Test save method."""
session_obj, _ = session session_obj, _ = session
# Create a mock execution # Create a mock execution
execution = MagicMock(spec=WorkflowNodeExecution)
execution = MagicMock(spec=WorkflowNodeExecutionModel)
execution.tenant_id = None execution.tenant_id = None
execution.app_id = None execution.app_id = None
execution.inputs = None execution.inputs = None
"""Test save method with existing tenant_id.""" """Test save method with existing tenant_id."""
session_obj, _ = session session_obj, _ = session
# Create a mock execution with existing tenant_id # Create a mock execution with existing tenant_id
execution = MagicMock(spec=WorkflowNodeExecution)
execution = MagicMock(spec=WorkflowNodeExecutionModel)
execution.tenant_id = "existing-tenant" execution.tenant_id = "existing-tenant"
execution.app_id = None execution.app_id = None
execution.inputs = None execution.inputs = None
execution.metadata = None execution.metadata = None


# Create a modified execution that will be returned by _to_db_model # Create a modified execution that will be returned by _to_db_model
modified_execution = MagicMock(spec=WorkflowNodeExecution)
modified_execution = MagicMock(spec=WorkflowNodeExecutionModel)
modified_execution.tenant_id = "existing-tenant" # Tenant ID should not change modified_execution.tenant_id = "existing-tenant" # Tenant ID should not change
modified_execution.app_id = repository._app_id # App ID should be set modified_execution.app_id = repository._app_id # App ID should be set


mock_stmt.where.return_value = mock_stmt mock_stmt.where.return_value = mock_stmt


# Create a properly configured mock execution # Create a properly configured mock execution
mock_execution = mocker.MagicMock(spec=WorkflowNodeExecution)
mock_execution = mocker.MagicMock(spec=WorkflowNodeExecutionModel)
configure_mock_execution(mock_execution) configure_mock_execution(mock_execution)
session_obj.scalar.return_value = mock_execution session_obj.scalar.return_value = mock_execution


mock_stmt.order_by.return_value = mock_stmt mock_stmt.order_by.return_value = mock_stmt


# Create a properly configured mock execution # Create a properly configured mock execution
mock_execution = mocker.MagicMock(spec=WorkflowNodeExecution)
mock_execution = mocker.MagicMock(spec=WorkflowNodeExecutionModel)
configure_mock_execution(mock_execution) configure_mock_execution(mock_execution)
session_obj.scalars.return_value.all.return_value = [mock_execution] session_obj.scalars.return_value.all.return_value = [mock_execution]


mock_stmt.where.return_value = mock_stmt mock_stmt.where.return_value = mock_stmt


# Create a properly configured mock execution # Create a properly configured mock execution
mock_execution = mocker.MagicMock(spec=WorkflowNodeExecution)
mock_execution = mocker.MagicMock(spec=WorkflowNodeExecutionModel)
configure_mock_execution(mock_execution) configure_mock_execution(mock_execution)
session_obj.scalars.return_value.all.return_value = [mock_execution] session_obj.scalars.return_value.all.return_value = [mock_execution]


"""Test updating an existing record via save method.""" """Test updating an existing record via save method."""
session_obj, _ = session session_obj, _ = session
# Create a mock execution # Create a mock execution
execution = MagicMock(spec=WorkflowNodeExecution)
execution = MagicMock(spec=WorkflowNodeExecutionModel)
execution.tenant_id = None execution.tenant_id = None
execution.app_id = None execution.app_id = None
execution.inputs = None execution.inputs = None
repository.clear() repository.clear()


# Assert delete was called with correct parameters # Assert delete was called with correct parameters
mock_delete.assert_called_once_with(WorkflowNodeExecution)
mock_delete.assert_called_once_with(WorkflowNodeExecutionModel)
mock_stmt.where.assert_called() mock_stmt.where.assert_called()
session_obj.execute.assert_called_once_with(mock_stmt) session_obj.execute.assert_called_once_with(mock_stmt)
session_obj.commit.assert_called_once() session_obj.commit.assert_called_once()
def test_to_db_model(repository): def test_to_db_model(repository):
"""Test to_db_model method.""" """Test to_db_model method."""
# Create a domain model # Create a domain model
domain_model = NodeExecution(
domain_model = WorkflowNodeExecution(
id="test-id", id="test-id",
workflow_id="test-workflow-id", workflow_id="test-workflow-id",
node_execution_id="test-node-execution-id", node_execution_id="test-node-execution-id",
db_model = repository.to_db_model(domain_model) db_model = repository.to_db_model(domain_model)


# Assert DB model has correct values # Assert DB model has correct values
assert isinstance(db_model, WorkflowNodeExecution)
assert isinstance(db_model, WorkflowNodeExecutionModel)
assert db_model.id == domain_model.id assert db_model.id == domain_model.id
assert db_model.tenant_id == repository._tenant_id assert db_model.tenant_id == repository._tenant_id
assert db_model.app_id == repository._app_id assert db_model.app_id == repository._app_id
metadata_dict = {str(WorkflowNodeExecutionMetadataKey.TOTAL_TOKENS): 100} metadata_dict = {str(WorkflowNodeExecutionMetadataKey.TOTAL_TOKENS): 100}


# Create a DB model using our custom subclass # Create a DB model using our custom subclass
db_model = WorkflowNodeExecution()
db_model = WorkflowNodeExecutionModel()
db_model.id = "test-id" db_model.id = "test-id"
db_model.tenant_id = "test-tenant-id" db_model.tenant_id = "test-tenant-id"
db_model.app_id = "test-app-id" db_model.app_id = "test-app-id"
domain_model = repository._to_domain_model(db_model) domain_model = repository._to_domain_model(db_model)


# Assert domain model has correct values # Assert domain model has correct values
assert isinstance(domain_model, NodeExecution)
assert isinstance(domain_model, WorkflowNodeExecution)
assert domain_model.id == db_model.id assert domain_model.id == db_model.id
assert domain_model.workflow_id == db_model.workflow_id assert domain_model.workflow_id == db_model.workflow_id
assert domain_model.workflow_execution_id == db_model.workflow_run_id assert domain_model.workflow_execution_id == db_model.workflow_run_id

Načítá se…
Zrušit
Uložit