|
|
@@ -1,33 +1,41 @@
|
|
|
import json
|
|
|
import time
|
|
|
from datetime import datetime, timezone
|
|
|
-from typing import Optional, Union, cast
|
|
|
+from typing import Any, Optional, Union, cast
|
|
|
|
|
|
-from core.app.entities.app_invoke_entities import InvokeFrom
|
|
|
+from core.app.entities.app_invoke_entities import AdvancedChatAppGenerateEntity, InvokeFrom, WorkflowAppGenerateEntity
|
|
|
from core.app.entities.queue_entities import (
|
|
|
+ QueueIterationCompletedEvent,
|
|
|
+ QueueIterationNextEvent,
|
|
|
+ QueueIterationStartEvent,
|
|
|
QueueNodeFailedEvent,
|
|
|
QueueNodeStartedEvent,
|
|
|
QueueNodeSucceededEvent,
|
|
|
- QueueStopEvent,
|
|
|
- QueueWorkflowFailedEvent,
|
|
|
- QueueWorkflowSucceededEvent,
|
|
|
+ QueueParallelBranchRunFailedEvent,
|
|
|
+ QueueParallelBranchRunStartedEvent,
|
|
|
+ QueueParallelBranchRunSucceededEvent,
|
|
|
)
|
|
|
from core.app.entities.task_entities import (
|
|
|
- NodeExecutionInfo,
|
|
|
+ IterationNodeCompletedStreamResponse,
|
|
|
+ IterationNodeNextStreamResponse,
|
|
|
+ IterationNodeStartStreamResponse,
|
|
|
NodeFinishStreamResponse,
|
|
|
NodeStartStreamResponse,
|
|
|
+ ParallelBranchFinishedStreamResponse,
|
|
|
+ ParallelBranchStartStreamResponse,
|
|
|
WorkflowFinishStreamResponse,
|
|
|
WorkflowStartStreamResponse,
|
|
|
+ WorkflowTaskState,
|
|
|
)
|
|
|
-from core.app.task_pipeline.workflow_iteration_cycle_manage import WorkflowIterationCycleManage
|
|
|
from core.file.file_obj import FileVar
|
|
|
from core.model_runtime.utils.encoders import jsonable_encoder
|
|
|
from core.ops.entities.trace_entity import TraceTaskName
|
|
|
from core.ops.ops_trace_manager import TraceQueueManager, TraceTask
|
|
|
from core.tools.tool_manager import ToolManager
|
|
|
-from core.workflow.entities.node_entities import NodeRunMetadataKey, NodeType
|
|
|
+from core.workflow.entities.node_entities import NodeType
|
|
|
+from core.workflow.enums import SystemVariableKey
|
|
|
from core.workflow.nodes.tool.entities import ToolNodeData
|
|
|
-from core.workflow.workflow_engine_manager import WorkflowEngineManager
|
|
|
+from core.workflow.workflow_entry import WorkflowEntry
|
|
|
from extensions.ext_database import db
|
|
|
from models.account import Account
|
|
|
from models.model import EndUser
|
|
|
@@ -41,54 +49,56 @@ from models.workflow import (
|
|
|
WorkflowRunStatus,
|
|
|
WorkflowRunTriggeredFrom,
|
|
|
)
|
|
|
-from services.workflow_service import WorkflowService
|
|
|
-
|
|
|
-
|
|
|
-class WorkflowCycleManage(WorkflowIterationCycleManage):
|
|
|
- def _init_workflow_run(self, workflow: Workflow,
|
|
|
- triggered_from: WorkflowRunTriggeredFrom,
|
|
|
- user: Union[Account, EndUser],
|
|
|
- user_inputs: dict,
|
|
|
- system_inputs: Optional[dict] = None) -> WorkflowRun:
|
|
|
- """
|
|
|
- Init workflow run
|
|
|
- :param workflow: Workflow instance
|
|
|
- :param triggered_from: triggered from
|
|
|
- :param user: account or end user
|
|
|
- :param user_inputs: user variables inputs
|
|
|
- :param system_inputs: system inputs, like: query, files
|
|
|
- :return:
|
|
|
- """
|
|
|
- max_sequence = db.session.query(db.func.max(WorkflowRun.sequence_number)) \
|
|
|
- .filter(WorkflowRun.tenant_id == workflow.tenant_id) \
|
|
|
- .filter(WorkflowRun.app_id == workflow.app_id) \
|
|
|
- .scalar() or 0
|
|
|
+
|
|
|
+
|
|
|
+class WorkflowCycleManage:
|
|
|
+ _application_generate_entity: Union[AdvancedChatAppGenerateEntity, WorkflowAppGenerateEntity]
|
|
|
+ _workflow: Workflow
|
|
|
+ _user: Union[Account, EndUser]
|
|
|
+ _task_state: WorkflowTaskState
|
|
|
+ _workflow_system_variables: dict[SystemVariableKey, Any]
|
|
|
+
|
|
|
+ def _handle_workflow_run_start(self) -> WorkflowRun:
|
|
|
+ max_sequence = (
|
|
|
+ db.session.query(db.func.max(WorkflowRun.sequence_number))
|
|
|
+ .filter(WorkflowRun.tenant_id == self._workflow.tenant_id)
|
|
|
+ .filter(WorkflowRun.app_id == self._workflow.app_id)
|
|
|
+ .scalar()
|
|
|
+ or 0
|
|
|
+ )
|
|
|
new_sequence_number = max_sequence + 1
|
|
|
|
|
|
- inputs = {**user_inputs}
|
|
|
- for key, value in (system_inputs or {}).items():
|
|
|
+ inputs = {**self._application_generate_entity.inputs}
|
|
|
+ for key, value in (self._workflow_system_variables or {}).items():
|
|
|
if key.value == 'conversation':
|
|
|
continue
|
|
|
|
|
|
inputs[f'sys.{key.value}'] = value
|
|
|
- inputs = WorkflowEngineManager.handle_special_values(inputs)
|
|
|
+
|
|
|
+ inputs = WorkflowEntry.handle_special_values(inputs)
|
|
|
+
|
|
|
+ triggered_from= (
|
|
|
+ WorkflowRunTriggeredFrom.DEBUGGING
|
|
|
+ if self._application_generate_entity.invoke_from == InvokeFrom.DEBUGGER
|
|
|
+ else WorkflowRunTriggeredFrom.APP_RUN
|
|
|
+ )
|
|
|
|
|
|
# init workflow run
|
|
|
- workflow_run = WorkflowRun(
|
|
|
- tenant_id=workflow.tenant_id,
|
|
|
- app_id=workflow.app_id,
|
|
|
- sequence_number=new_sequence_number,
|
|
|
- workflow_id=workflow.id,
|
|
|
- type=workflow.type,
|
|
|
- triggered_from=triggered_from.value,
|
|
|
- version=workflow.version,
|
|
|
- graph=workflow.graph,
|
|
|
- inputs=json.dumps(inputs),
|
|
|
- status=WorkflowRunStatus.RUNNING.value,
|
|
|
- created_by_role=(CreatedByRole.ACCOUNT.value
|
|
|
- if isinstance(user, Account) else CreatedByRole.END_USER.value),
|
|
|
- created_by=user.id
|
|
|
+ workflow_run = WorkflowRun()
|
|
|
+ workflow_run.tenant_id = self._workflow.tenant_id
|
|
|
+ workflow_run.app_id = self._workflow.app_id
|
|
|
+ workflow_run.sequence_number = new_sequence_number
|
|
|
+ workflow_run.workflow_id = self._workflow.id
|
|
|
+ workflow_run.type = self._workflow.type
|
|
|
+ workflow_run.triggered_from = triggered_from.value
|
|
|
+ workflow_run.version = self._workflow.version
|
|
|
+ workflow_run.graph = self._workflow.graph
|
|
|
+ workflow_run.inputs = json.dumps(inputs)
|
|
|
+ workflow_run.status = WorkflowRunStatus.RUNNING.value
|
|
|
+ workflow_run.created_by_role = (
|
|
|
+ CreatedByRole.ACCOUNT.value if isinstance(self._user, Account) else CreatedByRole.END_USER.value
|
|
|
)
|
|
|
+ workflow_run.created_by = self._user.id
|
|
|
|
|
|
db.session.add(workflow_run)
|
|
|
db.session.commit()
|
|
|
@@ -97,33 +107,37 @@ class WorkflowCycleManage(WorkflowIterationCycleManage):
|
|
|
|
|
|
return workflow_run
|
|
|
|
|
|
- def _workflow_run_success(
|
|
|
- self, workflow_run: WorkflowRun,
|
|
|
+ def _handle_workflow_run_success(
|
|
|
+ self,
|
|
|
+ workflow_run: WorkflowRun,
|
|
|
+ start_at: float,
|
|
|
total_tokens: int,
|
|
|
total_steps: int,
|
|
|
outputs: Optional[str] = None,
|
|
|
conversation_id: Optional[str] = None,
|
|
|
- trace_manager: Optional[TraceQueueManager] = None
|
|
|
+ trace_manager: Optional[TraceQueueManager] = None,
|
|
|
) -> WorkflowRun:
|
|
|
"""
|
|
|
Workflow run success
|
|
|
:param workflow_run: workflow run
|
|
|
+ :param start_at: start time
|
|
|
:param total_tokens: total tokens
|
|
|
:param total_steps: total steps
|
|
|
:param outputs: outputs
|
|
|
:param conversation_id: conversation id
|
|
|
:return:
|
|
|
"""
|
|
|
+ workflow_run = self._refetch_workflow_run(workflow_run.id)
|
|
|
+
|
|
|
workflow_run.status = WorkflowRunStatus.SUCCEEDED.value
|
|
|
workflow_run.outputs = outputs
|
|
|
- workflow_run.elapsed_time = WorkflowService.get_elapsed_time(workflow_run_id=workflow_run.id)
|
|
|
+ workflow_run.elapsed_time = time.perf_counter() - start_at
|
|
|
workflow_run.total_tokens = total_tokens
|
|
|
workflow_run.total_steps = total_steps
|
|
|
workflow_run.finished_at = datetime.now(timezone.utc).replace(tzinfo=None)
|
|
|
|
|
|
db.session.commit()
|
|
|
db.session.refresh(workflow_run)
|
|
|
- db.session.close()
|
|
|
|
|
|
if trace_manager:
|
|
|
trace_manager.add_trace_task(
|
|
|
@@ -135,34 +149,58 @@ class WorkflowCycleManage(WorkflowIterationCycleManage):
|
|
|
)
|
|
|
)
|
|
|
|
|
|
+ db.session.close()
|
|
|
+
|
|
|
return workflow_run
|
|
|
|
|
|
- def _workflow_run_failed(
|
|
|
- self, workflow_run: WorkflowRun,
|
|
|
+ def _handle_workflow_run_failed(
|
|
|
+ self,
|
|
|
+ workflow_run: WorkflowRun,
|
|
|
+ start_at: float,
|
|
|
total_tokens: int,
|
|
|
total_steps: int,
|
|
|
status: WorkflowRunStatus,
|
|
|
error: str,
|
|
|
conversation_id: Optional[str] = None,
|
|
|
- trace_manager: Optional[TraceQueueManager] = None
|
|
|
+ trace_manager: Optional[TraceQueueManager] = None,
|
|
|
) -> WorkflowRun:
|
|
|
"""
|
|
|
Workflow run failed
|
|
|
:param workflow_run: workflow run
|
|
|
+ :param start_at: start time
|
|
|
:param total_tokens: total tokens
|
|
|
:param total_steps: total steps
|
|
|
:param status: status
|
|
|
:param error: error message
|
|
|
:return:
|
|
|
"""
|
|
|
+ workflow_run = self._refetch_workflow_run(workflow_run.id)
|
|
|
+
|
|
|
workflow_run.status = status.value
|
|
|
workflow_run.error = error
|
|
|
- workflow_run.elapsed_time = WorkflowService.get_elapsed_time(workflow_run_id=workflow_run.id)
|
|
|
+ workflow_run.elapsed_time = time.perf_counter() - start_at
|
|
|
workflow_run.total_tokens = total_tokens
|
|
|
workflow_run.total_steps = total_steps
|
|
|
workflow_run.finished_at = datetime.now(timezone.utc).replace(tzinfo=None)
|
|
|
|
|
|
db.session.commit()
|
|
|
+
|
|
|
+ running_workflow_node_executions = db.session.query(WorkflowNodeExecution).filter(
|
|
|
+ WorkflowNodeExecution.tenant_id == workflow_run.tenant_id,
|
|
|
+ WorkflowNodeExecution.app_id == workflow_run.app_id,
|
|
|
+ WorkflowNodeExecution.workflow_id == workflow_run.workflow_id,
|
|
|
+ WorkflowNodeExecution.triggered_from == WorkflowNodeExecutionTriggeredFrom.WORKFLOW_RUN.value,
|
|
|
+ WorkflowNodeExecution.workflow_run_id == workflow_run.id,
|
|
|
+ WorkflowNodeExecution.status == WorkflowNodeExecutionStatus.RUNNING.value
|
|
|
+ ).all()
|
|
|
+
|
|
|
+ for workflow_node_execution in running_workflow_node_executions:
|
|
|
+ workflow_node_execution.status = WorkflowNodeExecutionStatus.FAILED.value
|
|
|
+ workflow_node_execution.error = error
|
|
|
+ workflow_node_execution.finished_at = datetime.now(timezone.utc).replace(tzinfo=None)
|
|
|
+ workflow_node_execution.elapsed_time = (workflow_node_execution.finished_at - workflow_node_execution.created_at).total_seconds()
|
|
|
+ db.session.commit()
|
|
|
+
|
|
|
db.session.refresh(workflow_run)
|
|
|
db.session.close()
|
|
|
|
|
|
@@ -178,39 +216,24 @@ class WorkflowCycleManage(WorkflowIterationCycleManage):
|
|
|
|
|
|
return workflow_run
|
|
|
|
|
|
- def _init_node_execution_from_workflow_run(self, workflow_run: WorkflowRun,
|
|
|
- node_id: str,
|
|
|
- node_type: NodeType,
|
|
|
- node_title: str,
|
|
|
- node_run_index: int = 1,
|
|
|
- predecessor_node_id: Optional[str] = None) -> WorkflowNodeExecution:
|
|
|
- """
|
|
|
- Init workflow node execution from workflow run
|
|
|
- :param workflow_run: workflow run
|
|
|
- :param node_id: node id
|
|
|
- :param node_type: node type
|
|
|
- :param node_title: node title
|
|
|
- :param node_run_index: run index
|
|
|
- :param predecessor_node_id: predecessor node id if exists
|
|
|
- :return:
|
|
|
- """
|
|
|
+ def _handle_node_execution_start(self, workflow_run: WorkflowRun, event: QueueNodeStartedEvent) -> WorkflowNodeExecution:
|
|
|
# init workflow node execution
|
|
|
- workflow_node_execution = WorkflowNodeExecution(
|
|
|
- tenant_id=workflow_run.tenant_id,
|
|
|
- app_id=workflow_run.app_id,
|
|
|
- workflow_id=workflow_run.workflow_id,
|
|
|
- triggered_from=WorkflowNodeExecutionTriggeredFrom.WORKFLOW_RUN.value,
|
|
|
- workflow_run_id=workflow_run.id,
|
|
|
- predecessor_node_id=predecessor_node_id,
|
|
|
- index=node_run_index,
|
|
|
- node_id=node_id,
|
|
|
- node_type=node_type.value,
|
|
|
- title=node_title,
|
|
|
- status=WorkflowNodeExecutionStatus.RUNNING.value,
|
|
|
- created_by_role=workflow_run.created_by_role,
|
|
|
- created_by=workflow_run.created_by,
|
|
|
- created_at=datetime.now(timezone.utc).replace(tzinfo=None)
|
|
|
- )
|
|
|
+ workflow_node_execution = WorkflowNodeExecution()
|
|
|
+ workflow_node_execution.tenant_id = workflow_run.tenant_id
|
|
|
+ workflow_node_execution.app_id = workflow_run.app_id
|
|
|
+ workflow_node_execution.workflow_id = workflow_run.workflow_id
|
|
|
+ workflow_node_execution.triggered_from = WorkflowNodeExecutionTriggeredFrom.WORKFLOW_RUN.value
|
|
|
+ workflow_node_execution.workflow_run_id = workflow_run.id
|
|
|
+ workflow_node_execution.predecessor_node_id = event.predecessor_node_id
|
|
|
+ workflow_node_execution.index = event.node_run_index
|
|
|
+ workflow_node_execution.node_execution_id = event.node_execution_id
|
|
|
+ workflow_node_execution.node_id = event.node_id
|
|
|
+ workflow_node_execution.node_type = event.node_type.value
|
|
|
+ workflow_node_execution.title = event.node_data.title
|
|
|
+ workflow_node_execution.status = WorkflowNodeExecutionStatus.RUNNING.value
|
|
|
+ workflow_node_execution.created_by_role = workflow_run.created_by_role
|
|
|
+ workflow_node_execution.created_by = workflow_run.created_by
|
|
|
+ workflow_node_execution.created_at = datetime.now(timezone.utc).replace(tzinfo=None)
|
|
|
|
|
|
db.session.add(workflow_node_execution)
|
|
|
db.session.commit()
|
|
|
@@ -219,33 +242,26 @@ class WorkflowCycleManage(WorkflowIterationCycleManage):
|
|
|
|
|
|
return workflow_node_execution
|
|
|
|
|
|
- def _workflow_node_execution_success(self, workflow_node_execution: WorkflowNodeExecution,
|
|
|
- start_at: float,
|
|
|
- inputs: Optional[dict] = None,
|
|
|
- process_data: Optional[dict] = None,
|
|
|
- outputs: Optional[dict] = None,
|
|
|
- execution_metadata: Optional[dict] = None) -> WorkflowNodeExecution:
|
|
|
+ def _handle_workflow_node_execution_success(self, event: QueueNodeSucceededEvent) -> WorkflowNodeExecution:
|
|
|
"""
|
|
|
Workflow node execution success
|
|
|
- :param workflow_node_execution: workflow node execution
|
|
|
- :param start_at: start time
|
|
|
- :param inputs: inputs
|
|
|
- :param process_data: process data
|
|
|
- :param outputs: outputs
|
|
|
- :param execution_metadata: execution metadata
|
|
|
+ :param event: queue node succeeded event
|
|
|
:return:
|
|
|
"""
|
|
|
- inputs = WorkflowEngineManager.handle_special_values(inputs)
|
|
|
- outputs = WorkflowEngineManager.handle_special_values(outputs)
|
|
|
+ workflow_node_execution = self._refetch_workflow_node_execution(event.node_execution_id)
|
|
|
+
|
|
|
+ inputs = WorkflowEntry.handle_special_values(event.inputs)
|
|
|
+ outputs = WorkflowEntry.handle_special_values(event.outputs)
|
|
|
|
|
|
workflow_node_execution.status = WorkflowNodeExecutionStatus.SUCCEEDED.value
|
|
|
- workflow_node_execution.elapsed_time = time.perf_counter() - start_at
|
|
|
workflow_node_execution.inputs = json.dumps(inputs) if inputs else None
|
|
|
- workflow_node_execution.process_data = json.dumps(process_data) if process_data else None
|
|
|
+ workflow_node_execution.process_data = json.dumps(event.process_data) if event.process_data else None
|
|
|
workflow_node_execution.outputs = json.dumps(outputs) if outputs else None
|
|
|
- workflow_node_execution.execution_metadata = json.dumps(jsonable_encoder(execution_metadata)) \
|
|
|
- if execution_metadata else None
|
|
|
+ workflow_node_execution.execution_metadata = (
|
|
|
+ json.dumps(jsonable_encoder(event.execution_metadata)) if event.execution_metadata else None
|
|
|
+ )
|
|
|
workflow_node_execution.finished_at = datetime.now(timezone.utc).replace(tzinfo=None)
|
|
|
+ workflow_node_execution.elapsed_time = (workflow_node_execution.finished_at - event.start_at).total_seconds()
|
|
|
|
|
|
db.session.commit()
|
|
|
db.session.refresh(workflow_node_execution)
|
|
|
@@ -253,33 +269,24 @@ class WorkflowCycleManage(WorkflowIterationCycleManage):
|
|
|
|
|
|
return workflow_node_execution
|
|
|
|
|
|
- def _workflow_node_execution_failed(self, workflow_node_execution: WorkflowNodeExecution,
|
|
|
- start_at: float,
|
|
|
- error: str,
|
|
|
- inputs: Optional[dict] = None,
|
|
|
- process_data: Optional[dict] = None,
|
|
|
- outputs: Optional[dict] = None,
|
|
|
- execution_metadata: Optional[dict] = None
|
|
|
- ) -> WorkflowNodeExecution:
|
|
|
+ def _handle_workflow_node_execution_failed(self, event: QueueNodeFailedEvent) -> WorkflowNodeExecution:
|
|
|
"""
|
|
|
Workflow node execution failed
|
|
|
- :param workflow_node_execution: workflow node execution
|
|
|
- :param start_at: start time
|
|
|
- :param error: error message
|
|
|
+ :param event: queue node failed event
|
|
|
:return:
|
|
|
"""
|
|
|
- inputs = WorkflowEngineManager.handle_special_values(inputs)
|
|
|
- outputs = WorkflowEngineManager.handle_special_values(outputs)
|
|
|
+ workflow_node_execution = self._refetch_workflow_node_execution(event.node_execution_id)
|
|
|
+
|
|
|
+ inputs = WorkflowEntry.handle_special_values(event.inputs)
|
|
|
+ outputs = WorkflowEntry.handle_special_values(event.outputs)
|
|
|
|
|
|
workflow_node_execution.status = WorkflowNodeExecutionStatus.FAILED.value
|
|
|
- workflow_node_execution.error = error
|
|
|
- workflow_node_execution.elapsed_time = time.perf_counter() - start_at
|
|
|
+ workflow_node_execution.error = event.error
|
|
|
workflow_node_execution.finished_at = datetime.now(timezone.utc).replace(tzinfo=None)
|
|
|
workflow_node_execution.inputs = json.dumps(inputs) if inputs else None
|
|
|
- workflow_node_execution.process_data = json.dumps(process_data) if process_data else None
|
|
|
+ workflow_node_execution.process_data = json.dumps(event.process_data) if event.process_data else None
|
|
|
workflow_node_execution.outputs = json.dumps(outputs) if outputs else None
|
|
|
- workflow_node_execution.execution_metadata = json.dumps(jsonable_encoder(execution_metadata)) \
|
|
|
- if execution_metadata else None
|
|
|
+ workflow_node_execution.elapsed_time = (workflow_node_execution.finished_at - event.start_at).total_seconds()
|
|
|
|
|
|
db.session.commit()
|
|
|
db.session.refresh(workflow_node_execution)
|
|
|
@@ -287,8 +294,13 @@ class WorkflowCycleManage(WorkflowIterationCycleManage):
|
|
|
|
|
|
return workflow_node_execution
|
|
|
|
|
|
- def _workflow_start_to_stream_response(self, task_id: str,
|
|
|
- workflow_run: WorkflowRun) -> WorkflowStartStreamResponse:
|
|
|
+ #################################################
|
|
|
+ # to stream responses #
|
|
|
+ #################################################
|
|
|
+
|
|
|
+ def _workflow_start_to_stream_response(
|
|
|
+ self, task_id: str, workflow_run: WorkflowRun
|
|
|
+ ) -> WorkflowStartStreamResponse:
|
|
|
"""
|
|
|
Workflow start to stream response.
|
|
|
:param task_id: task id
|
|
|
@@ -302,13 +314,14 @@ class WorkflowCycleManage(WorkflowIterationCycleManage):
|
|
|
id=workflow_run.id,
|
|
|
workflow_id=workflow_run.workflow_id,
|
|
|
sequence_number=workflow_run.sequence_number,
|
|
|
- inputs=workflow_run.inputs_dict,
|
|
|
- created_at=int(workflow_run.created_at.timestamp())
|
|
|
- )
|
|
|
+ inputs=workflow_run.inputs_dict or {},
|
|
|
+ created_at=int(workflow_run.created_at.timestamp()),
|
|
|
+ ),
|
|
|
)
|
|
|
|
|
|
- def _workflow_finish_to_stream_response(self, task_id: str,
|
|
|
- workflow_run: WorkflowRun) -> WorkflowFinishStreamResponse:
|
|
|
+ def _workflow_finish_to_stream_response(
|
|
|
+ self, task_id: str, workflow_run: WorkflowRun
|
|
|
+ ) -> WorkflowFinishStreamResponse:
|
|
|
"""
|
|
|
Workflow finish to stream response.
|
|
|
:param task_id: task id
|
|
|
@@ -320,16 +333,16 @@ class WorkflowCycleManage(WorkflowIterationCycleManage):
|
|
|
created_by_account = workflow_run.created_by_account
|
|
|
if created_by_account:
|
|
|
created_by = {
|
|
|
- "id": created_by_account.id,
|
|
|
- "name": created_by_account.name,
|
|
|
- "email": created_by_account.email,
|
|
|
+ 'id': created_by_account.id,
|
|
|
+ 'name': created_by_account.name,
|
|
|
+ 'email': created_by_account.email,
|
|
|
}
|
|
|
else:
|
|
|
created_by_end_user = workflow_run.created_by_end_user
|
|
|
if created_by_end_user:
|
|
|
created_by = {
|
|
|
- "id": created_by_end_user.id,
|
|
|
- "user": created_by_end_user.session_id,
|
|
|
+ 'id': created_by_end_user.id,
|
|
|
+ 'user': created_by_end_user.session_id,
|
|
|
}
|
|
|
|
|
|
return WorkflowFinishStreamResponse(
|
|
|
@@ -348,14 +361,13 @@ class WorkflowCycleManage(WorkflowIterationCycleManage):
|
|
|
created_by=created_by,
|
|
|
created_at=int(workflow_run.created_at.timestamp()),
|
|
|
finished_at=int(workflow_run.finished_at.timestamp()),
|
|
|
- files=self._fetch_files_from_node_outputs(workflow_run.outputs_dict)
|
|
|
- )
|
|
|
+ files=self._fetch_files_from_node_outputs(workflow_run.outputs_dict or {}),
|
|
|
+ ),
|
|
|
)
|
|
|
|
|
|
- def _workflow_node_start_to_stream_response(self, event: QueueNodeStartedEvent,
|
|
|
- task_id: str,
|
|
|
- workflow_node_execution: WorkflowNodeExecution) \
|
|
|
- -> NodeStartStreamResponse:
|
|
|
+ def _workflow_node_start_to_stream_response(
|
|
|
+ self, event: QueueNodeStartedEvent, task_id: str, workflow_node_execution: WorkflowNodeExecution
|
|
|
+ ) -> Optional[NodeStartStreamResponse]:
|
|
|
"""
|
|
|
Workflow node start to stream response.
|
|
|
:param event: queue node started event
|
|
|
@@ -363,6 +375,9 @@ class WorkflowCycleManage(WorkflowIterationCycleManage):
|
|
|
:param workflow_node_execution: workflow node execution
|
|
|
:return:
|
|
|
"""
|
|
|
+ if workflow_node_execution.node_type in [NodeType.ITERATION.value, NodeType.LOOP.value]:
|
|
|
+ return None
|
|
|
+
|
|
|
response = NodeStartStreamResponse(
|
|
|
task_id=task_id,
|
|
|
workflow_run_id=workflow_node_execution.workflow_run_id,
|
|
|
@@ -374,8 +389,13 @@ class WorkflowCycleManage(WorkflowIterationCycleManage):
|
|
|
index=workflow_node_execution.index,
|
|
|
predecessor_node_id=workflow_node_execution.predecessor_node_id,
|
|
|
inputs=workflow_node_execution.inputs_dict,
|
|
|
- created_at=int(workflow_node_execution.created_at.timestamp())
|
|
|
- )
|
|
|
+ created_at=int(workflow_node_execution.created_at.timestamp()),
|
|
|
+ parallel_id=event.parallel_id,
|
|
|
+ parallel_start_node_id=event.parallel_start_node_id,
|
|
|
+ parent_parallel_id=event.parent_parallel_id,
|
|
|
+ parent_parallel_start_node_id=event.parent_parallel_start_node_id,
|
|
|
+ iteration_id=event.in_iteration_id,
|
|
|
+ ),
|
|
|
)
|
|
|
|
|
|
# extras logic
|
|
|
@@ -384,19 +404,27 @@ class WorkflowCycleManage(WorkflowIterationCycleManage):
|
|
|
response.data.extras['icon'] = ToolManager.get_tool_icon(
|
|
|
tenant_id=self._application_generate_entity.app_config.tenant_id,
|
|
|
provider_type=node_data.provider_type,
|
|
|
- provider_id=node_data.provider_id
|
|
|
+ provider_id=node_data.provider_id,
|
|
|
)
|
|
|
|
|
|
return response
|
|
|
|
|
|
- def _workflow_node_finish_to_stream_response(self, task_id: str, workflow_node_execution: WorkflowNodeExecution) \
|
|
|
- -> NodeFinishStreamResponse:
|
|
|
+ def _workflow_node_finish_to_stream_response(
|
|
|
+ self,
|
|
|
+ event: QueueNodeSucceededEvent | QueueNodeFailedEvent,
|
|
|
+ task_id: str,
|
|
|
+ workflow_node_execution: WorkflowNodeExecution
|
|
|
+ ) -> Optional[NodeFinishStreamResponse]:
|
|
|
"""
|
|
|
Workflow node finish to stream response.
|
|
|
+ :param event: queue node succeeded or failed event
|
|
|
:param task_id: task id
|
|
|
:param workflow_node_execution: workflow node execution
|
|
|
:return:
|
|
|
"""
|
|
|
+ if workflow_node_execution.node_type in [NodeType.ITERATION.value, NodeType.LOOP.value]:
|
|
|
+ return None
|
|
|
+
|
|
|
return NodeFinishStreamResponse(
|
|
|
task_id=task_id,
|
|
|
workflow_run_id=workflow_node_execution.workflow_run_id,
|
|
|
@@ -416,181 +444,155 @@ class WorkflowCycleManage(WorkflowIterationCycleManage):
|
|
|
execution_metadata=workflow_node_execution.execution_metadata_dict,
|
|
|
created_at=int(workflow_node_execution.created_at.timestamp()),
|
|
|
finished_at=int(workflow_node_execution.finished_at.timestamp()),
|
|
|
- files=self._fetch_files_from_node_outputs(workflow_node_execution.outputs_dict)
|
|
|
- )
|
|
|
+ files=self._fetch_files_from_node_outputs(workflow_node_execution.outputs_dict or {}),
|
|
|
+ parallel_id=event.parallel_id,
|
|
|
+ parallel_start_node_id=event.parallel_start_node_id,
|
|
|
+ parent_parallel_id=event.parent_parallel_id,
|
|
|
+ parent_parallel_start_node_id=event.parent_parallel_start_node_id,
|
|
|
+ iteration_id=event.in_iteration_id,
|
|
|
+ ),
|
|
|
)
|
|
|
-
|
|
|
- def _handle_workflow_start(self) -> WorkflowRun:
|
|
|
- self._task_state.start_at = time.perf_counter()
|
|
|
-
|
|
|
- workflow_run = self._init_workflow_run(
|
|
|
- workflow=self._workflow,
|
|
|
- triggered_from=WorkflowRunTriggeredFrom.DEBUGGING
|
|
|
- if self._application_generate_entity.invoke_from == InvokeFrom.DEBUGGER
|
|
|
- else WorkflowRunTriggeredFrom.APP_RUN,
|
|
|
- user=self._user,
|
|
|
- user_inputs=self._application_generate_entity.inputs,
|
|
|
- system_inputs=self._workflow_system_variables
|
|
|
+
|
|
|
+ def _workflow_parallel_branch_start_to_stream_response(
|
|
|
+ self,
|
|
|
+ task_id: str,
|
|
|
+ workflow_run: WorkflowRun,
|
|
|
+ event: QueueParallelBranchRunStartedEvent
|
|
|
+ ) -> ParallelBranchStartStreamResponse:
|
|
|
+ """
|
|
|
+ Workflow parallel branch start to stream response
|
|
|
+ :param task_id: task id
|
|
|
+ :param workflow_run: workflow run
|
|
|
+ :param event: parallel branch run started event
|
|
|
+ :return:
|
|
|
+ """
|
|
|
+ return ParallelBranchStartStreamResponse(
|
|
|
+ task_id=task_id,
|
|
|
+ workflow_run_id=workflow_run.id,
|
|
|
+ data=ParallelBranchStartStreamResponse.Data(
|
|
|
+ parallel_id=event.parallel_id,
|
|
|
+ parallel_branch_id=event.parallel_start_node_id,
|
|
|
+ parent_parallel_id=event.parent_parallel_id,
|
|
|
+ parent_parallel_start_node_id=event.parent_parallel_start_node_id,
|
|
|
+ iteration_id=event.in_iteration_id,
|
|
|
+ created_at=int(time.time()),
|
|
|
+ )
|
|
|
)
|
|
|
-
|
|
|
- self._task_state.workflow_run_id = workflow_run.id
|
|
|
-
|
|
|
- db.session.close()
|
|
|
-
|
|
|
- return workflow_run
|
|
|
-
|
|
|
- def _handle_node_start(self, event: QueueNodeStartedEvent) -> WorkflowNodeExecution:
|
|
|
- workflow_run = db.session.query(WorkflowRun).filter(WorkflowRun.id == self._task_state.workflow_run_id).first()
|
|
|
- workflow_node_execution = self._init_node_execution_from_workflow_run(
|
|
|
- workflow_run=workflow_run,
|
|
|
- node_id=event.node_id,
|
|
|
- node_type=event.node_type,
|
|
|
- node_title=event.node_data.title,
|
|
|
- node_run_index=event.node_run_index,
|
|
|
- predecessor_node_id=event.predecessor_node_id
|
|
|
+
|
|
|
+ def _workflow_parallel_branch_finished_to_stream_response(
|
|
|
+ self,
|
|
|
+ task_id: str,
|
|
|
+ workflow_run: WorkflowRun,
|
|
|
+ event: QueueParallelBranchRunSucceededEvent | QueueParallelBranchRunFailedEvent
|
|
|
+ ) -> ParallelBranchFinishedStreamResponse:
|
|
|
+ """
|
|
|
+ Workflow parallel branch finished to stream response
|
|
|
+ :param task_id: task id
|
|
|
+ :param workflow_run: workflow run
|
|
|
+ :param event: parallel branch run succeeded or failed event
|
|
|
+ :return:
|
|
|
+ """
|
|
|
+ return ParallelBranchFinishedStreamResponse(
|
|
|
+ task_id=task_id,
|
|
|
+ workflow_run_id=workflow_run.id,
|
|
|
+ data=ParallelBranchFinishedStreamResponse.Data(
|
|
|
+ parallel_id=event.parallel_id,
|
|
|
+ parallel_branch_id=event.parallel_start_node_id,
|
|
|
+ parent_parallel_id=event.parent_parallel_id,
|
|
|
+ parent_parallel_start_node_id=event.parent_parallel_start_node_id,
|
|
|
+ iteration_id=event.in_iteration_id,
|
|
|
+ status='succeeded' if isinstance(event, QueueParallelBranchRunSucceededEvent) else 'failed',
|
|
|
+ error=event.error if isinstance(event, QueueParallelBranchRunFailedEvent) else None,
|
|
|
+ created_at=int(time.time()),
|
|
|
+ )
|
|
|
)
|
|
|
|
|
|
- latest_node_execution_info = NodeExecutionInfo(
|
|
|
- workflow_node_execution_id=workflow_node_execution.id,
|
|
|
- node_type=event.node_type,
|
|
|
- start_at=time.perf_counter()
|
|
|
+ def _workflow_iteration_start_to_stream_response(
|
|
|
+ self,
|
|
|
+ task_id: str,
|
|
|
+ workflow_run: WorkflowRun,
|
|
|
+ event: QueueIterationStartEvent
|
|
|
+ ) -> IterationNodeStartStreamResponse:
|
|
|
+ """
|
|
|
+ Workflow iteration start to stream response
|
|
|
+ :param task_id: task id
|
|
|
+ :param workflow_run: workflow run
|
|
|
+ :param event: iteration start event
|
|
|
+ :return:
|
|
|
+ """
|
|
|
+ return IterationNodeStartStreamResponse(
|
|
|
+ task_id=task_id,
|
|
|
+ workflow_run_id=workflow_run.id,
|
|
|
+ data=IterationNodeStartStreamResponse.Data(
|
|
|
+ id=event.node_id,
|
|
|
+ node_id=event.node_id,
|
|
|
+ node_type=event.node_type.value,
|
|
|
+ title=event.node_data.title,
|
|
|
+ created_at=int(time.time()),
|
|
|
+ extras={},
|
|
|
+ inputs=event.inputs or {},
|
|
|
+ metadata=event.metadata or {},
|
|
|
+ parallel_id=event.parallel_id,
|
|
|
+ parallel_start_node_id=event.parallel_start_node_id,
|
|
|
+ )
|
|
|
)
|
|
|
|
|
|
- self._task_state.ran_node_execution_infos[event.node_id] = latest_node_execution_info
|
|
|
- self._task_state.latest_node_execution_info = latest_node_execution_info
|
|
|
-
|
|
|
- self._task_state.total_steps += 1
|
|
|
-
|
|
|
- db.session.close()
|
|
|
-
|
|
|
- return workflow_node_execution
|
|
|
-
|
|
|
- def _handle_node_finished(self, event: QueueNodeSucceededEvent | QueueNodeFailedEvent) -> WorkflowNodeExecution:
|
|
|
- current_node_execution = self._task_state.ran_node_execution_infos[event.node_id]
|
|
|
- workflow_node_execution = db.session.query(WorkflowNodeExecution).filter(
|
|
|
- WorkflowNodeExecution.id == current_node_execution.workflow_node_execution_id).first()
|
|
|
-
|
|
|
- execution_metadata = event.execution_metadata if isinstance(event, QueueNodeSucceededEvent) else None
|
|
|
-
|
|
|
- if self._iteration_state and self._iteration_state.current_iterations:
|
|
|
- if not execution_metadata:
|
|
|
- execution_metadata = {}
|
|
|
- current_iteration_data = None
|
|
|
- for iteration_node_id in self._iteration_state.current_iterations:
|
|
|
- data = self._iteration_state.current_iterations[iteration_node_id]
|
|
|
- if data.parent_iteration_id == None:
|
|
|
- current_iteration_data = data
|
|
|
- break
|
|
|
-
|
|
|
- if current_iteration_data:
|
|
|
- execution_metadata[NodeRunMetadataKey.ITERATION_ID] = current_iteration_data.iteration_id
|
|
|
- execution_metadata[NodeRunMetadataKey.ITERATION_INDEX] = current_iteration_data.current_index
|
|
|
-
|
|
|
- if isinstance(event, QueueNodeSucceededEvent):
|
|
|
- workflow_node_execution = self._workflow_node_execution_success(
|
|
|
- workflow_node_execution=workflow_node_execution,
|
|
|
- start_at=current_node_execution.start_at,
|
|
|
- inputs=event.inputs,
|
|
|
- process_data=event.process_data,
|
|
|
- outputs=event.outputs,
|
|
|
- execution_metadata=execution_metadata
|
|
|
+ def _workflow_iteration_next_to_stream_response(self, task_id: str, workflow_run: WorkflowRun, event: QueueIterationNextEvent) -> IterationNodeNextStreamResponse:
|
|
|
+ """
|
|
|
+ Workflow iteration next to stream response
|
|
|
+ :param task_id: task id
|
|
|
+ :param workflow_run: workflow run
|
|
|
+ :param event: iteration next event
|
|
|
+ :return:
|
|
|
+ """
|
|
|
+ return IterationNodeNextStreamResponse(
|
|
|
+ task_id=task_id,
|
|
|
+ workflow_run_id=workflow_run.id,
|
|
|
+ data=IterationNodeNextStreamResponse.Data(
|
|
|
+ id=event.node_id,
|
|
|
+ node_id=event.node_id,
|
|
|
+ node_type=event.node_type.value,
|
|
|
+ title=event.node_data.title,
|
|
|
+ index=event.index,
|
|
|
+ pre_iteration_output=event.output,
|
|
|
+ created_at=int(time.time()),
|
|
|
+ extras={},
|
|
|
+ parallel_id=event.parallel_id,
|
|
|
+ parallel_start_node_id=event.parallel_start_node_id,
|
|
|
)
|
|
|
+ )
|
|
|
|
|
|
- if execution_metadata and execution_metadata.get(NodeRunMetadataKey.TOTAL_TOKENS):
|
|
|
- self._task_state.total_tokens += (
|
|
|
- int(execution_metadata.get(NodeRunMetadataKey.TOTAL_TOKENS)))
|
|
|
-
|
|
|
- if self._iteration_state:
|
|
|
- for iteration_node_id in self._iteration_state.current_iterations:
|
|
|
- data = self._iteration_state.current_iterations[iteration_node_id]
|
|
|
- if execution_metadata.get(NodeRunMetadataKey.TOTAL_TOKENS):
|
|
|
- data.total_tokens += int(execution_metadata.get(NodeRunMetadataKey.TOTAL_TOKENS))
|
|
|
-
|
|
|
- if workflow_node_execution.node_type == NodeType.LLM.value:
|
|
|
- outputs = workflow_node_execution.outputs_dict
|
|
|
- usage_dict = outputs.get('usage', {})
|
|
|
- self._task_state.metadata['usage'] = usage_dict
|
|
|
- else:
|
|
|
- workflow_node_execution = self._workflow_node_execution_failed(
|
|
|
- workflow_node_execution=workflow_node_execution,
|
|
|
- start_at=current_node_execution.start_at,
|
|
|
- error=event.error,
|
|
|
- inputs=event.inputs,
|
|
|
- process_data=event.process_data,
|
|
|
+ def _workflow_iteration_completed_to_stream_response(self, task_id: str, workflow_run: WorkflowRun, event: QueueIterationCompletedEvent) -> IterationNodeCompletedStreamResponse:
|
|
|
+ """
|
|
|
+ Workflow iteration completed to stream response
|
|
|
+ :param task_id: task id
|
|
|
+ :param workflow_run: workflow run
|
|
|
+ :param event: iteration completed event
|
|
|
+ :return:
|
|
|
+ """
|
|
|
+ return IterationNodeCompletedStreamResponse(
|
|
|
+ task_id=task_id,
|
|
|
+ workflow_run_id=workflow_run.id,
|
|
|
+ data=IterationNodeCompletedStreamResponse.Data(
|
|
|
+ id=event.node_id,
|
|
|
+ node_id=event.node_id,
|
|
|
+ node_type=event.node_type.value,
|
|
|
+ title=event.node_data.title,
|
|
|
outputs=event.outputs,
|
|
|
- execution_metadata=execution_metadata
|
|
|
+ created_at=int(time.time()),
|
|
|
+ extras={},
|
|
|
+ inputs=event.inputs or {},
|
|
|
+ status=WorkflowNodeExecutionStatus.SUCCEEDED,
|
|
|
+ error=None,
|
|
|
+ elapsed_time=(datetime.now(timezone.utc).replace(tzinfo=None) - event.start_at).total_seconds(),
|
|
|
+ total_tokens=event.metadata.get('total_tokens', 0) if event.metadata else 0,
|
|
|
+ execution_metadata=event.metadata,
|
|
|
+ finished_at=int(time.time()),
|
|
|
+ steps=event.steps,
|
|
|
+ parallel_id=event.parallel_id,
|
|
|
+ parallel_start_node_id=event.parallel_start_node_id,
|
|
|
)
|
|
|
-
|
|
|
- db.session.close()
|
|
|
-
|
|
|
- return workflow_node_execution
|
|
|
-
|
|
|
- def _handle_workflow_finished(
|
|
|
- self, event: QueueStopEvent | QueueWorkflowSucceededEvent | QueueWorkflowFailedEvent,
|
|
|
- conversation_id: Optional[str] = None,
|
|
|
- trace_manager: Optional[TraceQueueManager] = None
|
|
|
- ) -> Optional[WorkflowRun]:
|
|
|
- workflow_run = db.session.query(WorkflowRun).filter(
|
|
|
- WorkflowRun.id == self._task_state.workflow_run_id).first()
|
|
|
- if not workflow_run:
|
|
|
- return None
|
|
|
-
|
|
|
- if conversation_id is None:
|
|
|
- conversation_id = self._application_generate_entity.inputs.get('sys.conversation_id')
|
|
|
- if isinstance(event, QueueStopEvent):
|
|
|
- workflow_run = self._workflow_run_failed(
|
|
|
- workflow_run=workflow_run,
|
|
|
- total_tokens=self._task_state.total_tokens,
|
|
|
- total_steps=self._task_state.total_steps,
|
|
|
- status=WorkflowRunStatus.STOPPED,
|
|
|
- error='Workflow stopped.',
|
|
|
- conversation_id=conversation_id,
|
|
|
- trace_manager=trace_manager
|
|
|
- )
|
|
|
-
|
|
|
- latest_node_execution_info = self._task_state.latest_node_execution_info
|
|
|
- if latest_node_execution_info:
|
|
|
- workflow_node_execution = db.session.query(WorkflowNodeExecution).filter(
|
|
|
- WorkflowNodeExecution.id == latest_node_execution_info.workflow_node_execution_id).first()
|
|
|
- if (workflow_node_execution
|
|
|
- and workflow_node_execution.status == WorkflowNodeExecutionStatus.RUNNING.value):
|
|
|
- self._workflow_node_execution_failed(
|
|
|
- workflow_node_execution=workflow_node_execution,
|
|
|
- start_at=latest_node_execution_info.start_at,
|
|
|
- error='Workflow stopped.'
|
|
|
- )
|
|
|
- elif isinstance(event, QueueWorkflowFailedEvent):
|
|
|
- workflow_run = self._workflow_run_failed(
|
|
|
- workflow_run=workflow_run,
|
|
|
- total_tokens=self._task_state.total_tokens,
|
|
|
- total_steps=self._task_state.total_steps,
|
|
|
- status=WorkflowRunStatus.FAILED,
|
|
|
- error=event.error,
|
|
|
- conversation_id=conversation_id,
|
|
|
- trace_manager=trace_manager
|
|
|
- )
|
|
|
- else:
|
|
|
- if self._task_state.latest_node_execution_info:
|
|
|
- workflow_node_execution = db.session.query(WorkflowNodeExecution).filter(
|
|
|
- WorkflowNodeExecution.id == self._task_state.latest_node_execution_info.workflow_node_execution_id).first()
|
|
|
- outputs = workflow_node_execution.outputs
|
|
|
- else:
|
|
|
- outputs = None
|
|
|
-
|
|
|
- workflow_run = self._workflow_run_success(
|
|
|
- workflow_run=workflow_run,
|
|
|
- total_tokens=self._task_state.total_tokens,
|
|
|
- total_steps=self._task_state.total_steps,
|
|
|
- outputs=outputs,
|
|
|
- conversation_id=conversation_id,
|
|
|
- trace_manager=trace_manager
|
|
|
- )
|
|
|
-
|
|
|
- self._task_state.workflow_run_id = workflow_run.id
|
|
|
-
|
|
|
- db.session.close()
|
|
|
-
|
|
|
- return workflow_run
|
|
|
+ )
|
|
|
|
|
|
def _fetch_files_from_node_outputs(self, outputs_dict: dict) -> list[dict]:
|
|
|
"""
|
|
|
@@ -647,3 +649,40 @@ class WorkflowCycleManage(WorkflowIterationCycleManage):
|
|
|
return value.to_dict()
|
|
|
|
|
|
return None
|
|
|
+
|
|
|
+ def _refetch_workflow_run(self, workflow_run_id: str) -> WorkflowRun:
|
|
|
+ """
|
|
|
+ Refetch workflow run
|
|
|
+ :param workflow_run_id: workflow run id
|
|
|
+ :return:
|
|
|
+ """
|
|
|
+ workflow_run = db.session.query(WorkflowRun).filter(
|
|
|
+ WorkflowRun.id == workflow_run_id).first()
|
|
|
+
|
|
|
+ if not workflow_run:
|
|
|
+ raise Exception(f'Workflow run not found: {workflow_run_id}')
|
|
|
+
|
|
|
+ return workflow_run
|
|
|
+
|
|
|
+ def _refetch_workflow_node_execution(self, node_execution_id: str) -> WorkflowNodeExecution:
|
|
|
+ """
|
|
|
+ Refetch workflow node execution
|
|
|
+ :param node_execution_id: workflow node execution id
|
|
|
+ :return:
|
|
|
+ """
|
|
|
+ workflow_node_execution = (
|
|
|
+ db.session.query(WorkflowNodeExecution)
|
|
|
+ .filter(
|
|
|
+ WorkflowNodeExecution.tenant_id == self._application_generate_entity.app_config.tenant_id,
|
|
|
+ WorkflowNodeExecution.app_id == self._application_generate_entity.app_config.app_id,
|
|
|
+ WorkflowNodeExecution.workflow_id == self._workflow.id,
|
|
|
+ WorkflowNodeExecution.triggered_from == WorkflowNodeExecutionTriggeredFrom.WORKFLOW_RUN.value,
|
|
|
+ WorkflowNodeExecution.node_execution_id == node_execution_id,
|
|
|
+ )
|
|
|
+ .first()
|
|
|
+ )
|
|
|
+
|
|
|
+ if not workflow_node_execution:
|
|
|
+ raise Exception(f'Workflow node execution not found: {node_execution_id}')
|
|
|
+
|
|
|
+ return workflow_node_execution
|