Browse Source

Refactor/remove db from cycle manager (#20455)

Signed-off-by: -LAN- <laipz8200@outlook.com>
tags/1.4.2
-LAN- 5 months ago
parent
commit
482e50aae9
No account linked to committer's email address
81 changed files with 345 additions and 362 deletions
  1. 2
    2
      api/controllers/console/app/workflow_app_log.py
  2. 3
    2
      api/controllers/service_api/app/workflow.py
  3. 2
    2
      api/core/app/apps/advanced_chat/app_generator.py
  4. 16
    15
      api/core/app/apps/advanced_chat/generate_task_pipeline.py
  5. 7
    10
      api/core/app/apps/common/workflow_response_converter.py
  6. 5
    5
      api/core/app/apps/workflow/app_generator.py
  7. 1
    1
      api/core/app/apps/workflow/app_runner.py
  8. 21
    20
      api/core/app/apps/workflow/generate_task_pipeline.py
  9. 1
    1
      api/core/app/apps/workflow_app_runner.py
  10. 3
    4
      api/core/app/entities/app_invoke_entities.py
  11. 2
    1
      api/core/app/entities/queue_entities.py
  12. 2
    4
      api/core/app/entities/task_entities.py
  13. 8
    5
      api/core/ops/entities/trace_entity.py
  14. 1
    1
      api/core/ops/langsmith_trace/langsmith_trace.py
  15. 1
    1
      api/core/ops/opik_trace/opik_trace.py
  16. 2
    2
      api/core/ops/ops_trace_manager.py
  17. 1
    1
      api/core/ops/weave_trace/weave_trace.py
  18. 2
    6
      api/core/rag/extractor/entity/extract_setting.py
  19. 2
    3
      api/core/rag/models/document.py
  20. 22
    8
      api/core/repositories/sqlalchemy_workflow_execution_repository.py
  21. 5
    6
      api/core/repositories/sqlalchemy_workflow_node_execution_repository.py
  22. 1
    27
      api/core/workflow/entities/node_entities.py
  23. 6
    10
      api/core/workflow/entities/workflow_execution.py
  24. 27
    3
      api/core/workflow/entities/workflow_node_execution.py
  25. 1
    1
      api/core/workflow/graph_engine/entities/runtime_route_state.py
  26. 3
    2
      api/core/workflow/graph_engine/graph_engine.py
  27. 1
    1
      api/core/workflow/nodes/agent/agent_node.py
  28. 1
    1
      api/core/workflow/nodes/answer/answer_node.py
  29. 1
    1
      api/core/workflow/nodes/base/node.py
  30. 1
    1
      api/core/workflow/nodes/code/code_node.py
  31. 1
    1
      api/core/workflow/nodes/document_extractor/node.py
  32. 1
    1
      api/core/workflow/nodes/end/end_node.py
  33. 1
    1
      api/core/workflow/nodes/event/event.py
  34. 1
    1
      api/core/workflow/nodes/http_request/node.py
  35. 1
    1
      api/core/workflow/nodes/if_else/if_else_node.py
  36. 1
    2
      api/core/workflow/nodes/iteration/iteration_node.py
  37. 1
    1
      api/core/workflow/nodes/iteration/iteration_start_node.py
  38. 1
    1
      api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py
  39. 1
    1
      api/core/workflow/nodes/list_operator/node.py
  40. 2
    2
      api/core/workflow/nodes/llm/node.py
  41. 1
    1
      api/core/workflow/nodes/loop/loop_end_node.py
  42. 2
    2
      api/core/workflow/nodes/loop/loop_node.py
  43. 1
    1
      api/core/workflow/nodes/loop/loop_start_node.py
  44. 2
    2
      api/core/workflow/nodes/parameter_extractor/parameter_extractor_node.py
  45. 2
    2
      api/core/workflow/nodes/question_classifier/question_classifier_node.py
  46. 1
    1
      api/core/workflow/nodes/start/start_node.py
  47. 1
    1
      api/core/workflow/nodes/template_transform/template_transform_node.py
  48. 2
    2
      api/core/workflow/nodes/tool/tool_node.py
  49. 1
    1
      api/core/workflow/nodes/variable_aggregator/variable_aggregator_node.py
  50. 1
    1
      api/core/workflow/nodes/variable_assigner/v1/node.py
  51. 1
    1
      api/core/workflow/nodes/variable_assigner/v2/node.py
  52. 1
    1
      api/core/workflow/repositories/__init__.py
  53. 1
    1
      api/core/workflow/repositories/workflow_execution_repository.py
  54. 1
    1
      api/core/workflow/repositories/workflow_node_execution_repository.py
  55. 33
    48
      api/core/workflow/workflow_cycle_manager.py
  56. 2
    6
      api/models/__init__.py
  57. 10
    10
      api/models/model.py
  58. 0
    24
      api/models/workflow.py
  59. 0
    1
      api/pytest.ini
  60. 2
    2
      api/services/workflow_app_service.py
  61. 1
    1
      api/services/workflow_run_service.py
  62. 4
    5
      api/services/workflow_service.py
  63. 2
    1
      api/tests/integration_tests/workflow/nodes/test_code.py
  64. 2
    1
      api/tests/integration_tests/workflow/nodes/test_llm.py
  65. 2
    1
      api/tests/integration_tests/workflow/nodes/test_parameter_extractor.py
  66. 2
    1
      api/tests/integration_tests/workflow/nodes/test_template_transform.py
  67. 2
    1
      api/tests/integration_tests/workflow/nodes/test_tool.py
  68. 22
    22
      api/tests/unit_tests/core/prompt/test_extract_thread_messages.py
  69. 1
    1
      api/tests/unit_tests/core/rag/datasource/vdb/milvus/test_milvus.py
  70. 2
    1
      api/tests/unit_tests/core/workflow/graph_engine/test_graph_engine.py
  71. 2
    1
      api/tests/unit_tests/core/workflow/nodes/answer/test_answer.py
  72. 2
    1
      api/tests/unit_tests/core/workflow/nodes/http_request/test_http_request_node.py
  73. 2
    1
      api/tests/unit_tests/core/workflow/nodes/iteration/test_iteration.py
  74. 2
    1
      api/tests/unit_tests/core/workflow/nodes/test_answer.py
  75. 2
    1
      api/tests/unit_tests/core/workflow/nodes/test_continue_on_error.py
  76. 1
    1
      api/tests/unit_tests/core/workflow/nodes/test_document_extractor_node.py
  77. 2
    1
      api/tests/unit_tests/core/workflow/nodes/test_if_else.py
  78. 1
    1
      api/tests/unit_tests/core/workflow/nodes/test_list_operator.py
  79. 2
    1
      api/tests/unit_tests/core/workflow/nodes/tool/test_tool_node.py
  80. 53
    44
      api/tests/unit_tests/core/workflow/test_workflow_cycle_manager.py
  81. 9
    6
      api/tests/unit_tests/repositories/workflow_node_execution/test_sqlalchemy_repository.py

+ 2
- 2
api/controllers/console/app/workflow_app_log.py View File

from controllers.console import api from controllers.console import api
from controllers.console.app.wraps import get_app_model from controllers.console.app.wraps import get_app_model
from controllers.console.wraps import account_initialization_required, setup_required from controllers.console.wraps import account_initialization_required, setup_required
from core.workflow.entities.workflow_execution import WorkflowExecutionStatus
from extensions.ext_database import db from extensions.ext_database import db
from fields.workflow_app_log_fields import workflow_app_log_pagination_fields from fields.workflow_app_log_fields import workflow_app_log_pagination_fields
from libs.login import login_required from libs.login import login_required
from models import App from models import App
from models.model import AppMode from models.model import AppMode
from models.workflow import WorkflowRunStatus
from services.workflow_app_service import WorkflowAppService from services.workflow_app_service import WorkflowAppService




parser.add_argument("limit", type=int_range(1, 100), default=20, location="args") parser.add_argument("limit", type=int_range(1, 100), default=20, location="args")
args = parser.parse_args() args = parser.parse_args()


args.status = WorkflowRunStatus(args.status) if args.status else None
args.status = WorkflowExecutionStatus(args.status) if args.status else None
if args.created_at__before: if args.created_at__before:
args.created_at__before = isoparse(args.created_at__before) args.created_at__before = isoparse(args.created_at__before)



+ 3
- 2
api/controllers/service_api/app/workflow.py View File

QuotaExceededError, QuotaExceededError,
) )
from core.model_runtime.errors.invoke import InvokeError from core.model_runtime.errors.invoke import InvokeError
from core.workflow.entities.workflow_execution import WorkflowExecutionStatus
from extensions.ext_database import db from extensions.ext_database import db
from fields.workflow_app_log_fields import workflow_app_log_pagination_fields from fields.workflow_app_log_fields import workflow_app_log_pagination_fields
from libs import helper from libs import helper
from libs.helper import TimestampField from libs.helper import TimestampField
from models.model import App, AppMode, EndUser from models.model import App, AppMode, EndUser
from models.workflow import WorkflowRun, WorkflowRunStatus
from models.workflow import WorkflowRun
from services.app_generate_service import AppGenerateService from services.app_generate_service import AppGenerateService
from services.errors.llm import InvokeRateLimitError from services.errors.llm import InvokeRateLimitError
from services.workflow_app_service import WorkflowAppService from services.workflow_app_service import WorkflowAppService
parser.add_argument("limit", type=int_range(1, 100), default=20, location="args") parser.add_argument("limit", type=int_range(1, 100), default=20, location="args")
args = parser.parse_args() args = parser.parse_args()


args.status = WorkflowRunStatus(args.status) if args.status else None
args.status = WorkflowExecutionStatus(args.status) if args.status else None
if args.created_at__before: if args.created_at__before:
args.created_at__before = isoparse(args.created_at__before) args.created_at__before = isoparse(args.created_at__before)



+ 2
- 2
api/core/app/apps/advanced_chat/app_generator.py View File

from core.prompt.utils.get_thread_messages_length import get_thread_messages_length from core.prompt.utils.get_thread_messages_length import get_thread_messages_length
from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository
from core.repositories.sqlalchemy_workflow_execution_repository import SQLAlchemyWorkflowExecutionRepository from core.repositories.sqlalchemy_workflow_execution_repository import SQLAlchemyWorkflowExecutionRepository
from core.workflow.repository.workflow_execution_repository import WorkflowExecutionRepository
from core.workflow.repository.workflow_node_execution_repository import WorkflowNodeExecutionRepository
from core.workflow.repositories.workflow_execution_repository import WorkflowExecutionRepository
from core.workflow.repositories.workflow_node_execution_repository import WorkflowNodeExecutionRepository
from extensions.ext_database import db from extensions.ext_database import db
from factories import file_factory from factories import file_factory
from models import Account, App, Conversation, EndUser, Message, Workflow, WorkflowNodeExecutionTriggeredFrom from models import Account, App, Conversation, EndUser, Message, Workflow, WorkflowNodeExecutionTriggeredFrom

+ 16
- 15
api/core/app/apps/advanced_chat/generate_task_pipeline.py View File

from core.model_runtime.entities.llm_entities import LLMUsage from core.model_runtime.entities.llm_entities import LLMUsage
from core.model_runtime.utils.encoders import jsonable_encoder from core.model_runtime.utils.encoders import jsonable_encoder
from core.ops.ops_trace_manager import TraceQueueManager from core.ops.ops_trace_manager import TraceQueueManager
from core.workflow.entities.workflow_execution import WorkflowExecutionStatus, WorkflowType
from core.workflow.enums import SystemVariableKey from core.workflow.enums import SystemVariableKey
from core.workflow.graph_engine.entities.graph_runtime_state import GraphRuntimeState from core.workflow.graph_engine.entities.graph_runtime_state import GraphRuntimeState
from core.workflow.nodes import NodeType from core.workflow.nodes import NodeType
from core.workflow.repository.workflow_execution_repository import WorkflowExecutionRepository
from core.workflow.repository.workflow_node_execution_repository import WorkflowNodeExecutionRepository
from core.workflow.workflow_cycle_manager import WorkflowCycleManager
from core.workflow.repositories.workflow_execution_repository import WorkflowExecutionRepository
from core.workflow.repositories.workflow_node_execution_repository import WorkflowNodeExecutionRepository
from core.workflow.workflow_cycle_manager import CycleManagerWorkflowInfo, WorkflowCycleManager
from events.message_event import message_was_created from events.message_event import message_was_created
from extensions.ext_database import db from extensions.ext_database import db
from models import Conversation, EndUser, Message, MessageFile from models import Conversation, EndUser, Message, MessageFile
from models.account import Account from models.account import Account
from models.enums import CreatorUserRole from models.enums import CreatorUserRole
from models.workflow import (
Workflow,
WorkflowRunStatus,
)
from models.workflow import Workflow


logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)


SystemVariableKey.WORKFLOW_ID: workflow.id, SystemVariableKey.WORKFLOW_ID: workflow.id,
SystemVariableKey.WORKFLOW_RUN_ID: application_generate_entity.workflow_run_id, SystemVariableKey.WORKFLOW_RUN_ID: application_generate_entity.workflow_run_id,
}, },
workflow_info=CycleManagerWorkflowInfo(
workflow_id=workflow.id,
workflow_type=WorkflowType(workflow.type),
version=workflow.version,
graph_data=workflow.graph_dict,
),
workflow_execution_repository=workflow_execution_repository, workflow_execution_repository=workflow_execution_repository,
workflow_node_execution_repository=workflow_node_execution_repository, workflow_node_execution_repository=workflow_node_execution_repository,
) )


with Session(db.engine, expire_on_commit=False) as session: with Session(db.engine, expire_on_commit=False) as session:
# init workflow run # init workflow run
workflow_execution = self._workflow_cycle_manager.handle_workflow_run_start(
session=session,
workflow_id=self._workflow_id,
)
self._workflow_run_id = workflow_execution.id
workflow_execution = self._workflow_cycle_manager.handle_workflow_run_start()
self._workflow_run_id = workflow_execution.id_
message = self._get_message(session=session) message = self._get_message(session=session)
if not message: if not message:
raise ValueError(f"Message not found: {self._message_id}") raise ValueError(f"Message not found: {self._message_id}")
message.workflow_run_id = workflow_execution.id
message.workflow_run_id = workflow_execution.id_
workflow_start_resp = self._workflow_response_converter.workflow_start_to_stream_response( workflow_start_resp = self._workflow_response_converter.workflow_start_to_stream_response(
task_id=self._application_generate_entity.task_id, task_id=self._application_generate_entity.task_id,
workflow_execution=workflow_execution, workflow_execution=workflow_execution,
workflow_run_id=self._workflow_run_id, workflow_run_id=self._workflow_run_id,
total_tokens=graph_runtime_state.total_tokens, total_tokens=graph_runtime_state.total_tokens,
total_steps=graph_runtime_state.node_run_steps, total_steps=graph_runtime_state.node_run_steps,
status=WorkflowRunStatus.FAILED,
status=WorkflowExecutionStatus.FAILED,
error_message=event.error, error_message=event.error,
conversation_id=self._conversation_id, conversation_id=self._conversation_id,
trace_manager=trace_manager, trace_manager=trace_manager,
workflow_run_id=self._workflow_run_id, workflow_run_id=self._workflow_run_id,
total_tokens=graph_runtime_state.total_tokens, total_tokens=graph_runtime_state.total_tokens,
total_steps=graph_runtime_state.node_run_steps, total_steps=graph_runtime_state.node_run_steps,
status=WorkflowRunStatus.STOPPED,
status=WorkflowExecutionStatus.STOPPED,
error_message=event.get_stop_reason(), error_message=event.get_stop_reason(),
conversation_id=self._conversation_id, conversation_id=self._conversation_id,
trace_manager=trace_manager, trace_manager=trace_manager,

+ 7
- 10
api/core/app/apps/common/workflow_response_converter.py View File

) )
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.node_execution_entities import NodeExecution
from core.workflow.entities.workflow_execution_entities import WorkflowExecution
from core.workflow.entities.workflow_execution import WorkflowExecution
from core.workflow.entities.workflow_node_execution import NodeExecution, 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 (
Account, Account,
CreatorUserRole, CreatorUserRole,
EndUser, EndUser,
WorkflowNodeExecutionStatus,
WorkflowRun, WorkflowRun,
) )


) -> WorkflowStartStreamResponse: ) -> WorkflowStartStreamResponse:
return WorkflowStartStreamResponse( return WorkflowStartStreamResponse(
task_id=task_id, task_id=task_id,
workflow_run_id=workflow_execution.id,
workflow_run_id=workflow_execution.id_,
data=WorkflowStartStreamResponse.Data( data=WorkflowStartStreamResponse.Data(
id=workflow_execution.id,
id=workflow_execution.id_,
workflow_id=workflow_execution.workflow_id, workflow_id=workflow_execution.workflow_id,
sequence_number=workflow_execution.sequence_number,
inputs=workflow_execution.inputs, inputs=workflow_execution.inputs,
created_at=int(workflow_execution.started_at.timestamp()), created_at=int(workflow_execution.started_at.timestamp()),
), ),
workflow_execution: WorkflowExecution, workflow_execution: WorkflowExecution,
) -> WorkflowFinishStreamResponse: ) -> WorkflowFinishStreamResponse:
created_by = None created_by = None
workflow_run = session.scalar(select(WorkflowRun).where(WorkflowRun.id == workflow_execution.id))
workflow_run = session.scalar(select(WorkflowRun).where(WorkflowRun.id == workflow_execution.id_))
assert workflow_run is not None assert workflow_run is not None
if workflow_run.created_by_role == CreatorUserRole.ACCOUNT: if workflow_run.created_by_role == CreatorUserRole.ACCOUNT:
stmt = select(Account).where(Account.id == workflow_run.created_by) stmt = select(Account).where(Account.id == workflow_run.created_by)


return WorkflowFinishStreamResponse( return WorkflowFinishStreamResponse(
task_id=task_id, task_id=task_id,
workflow_run_id=workflow_execution.id,
workflow_run_id=workflow_execution.id_,
data=WorkflowFinishStreamResponse.Data( data=WorkflowFinishStreamResponse.Data(
id=workflow_execution.id,
id=workflow_execution.id_,
workflow_id=workflow_execution.workflow_id, workflow_id=workflow_execution.workflow_id,
sequence_number=workflow_execution.sequence_number,
status=workflow_execution.status, status=workflow_execution.status,
outputs=workflow_execution.outputs, outputs=workflow_execution.outputs,
error=workflow_execution.error_message, error=workflow_execution.error_message,

+ 5
- 5
api/core/app/apps/workflow/app_generator.py View File

from core.ops.ops_trace_manager import TraceQueueManager from core.ops.ops_trace_manager import TraceQueueManager
from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository
from core.repositories.sqlalchemy_workflow_execution_repository import SQLAlchemyWorkflowExecutionRepository from core.repositories.sqlalchemy_workflow_execution_repository import SQLAlchemyWorkflowExecutionRepository
from core.workflow.repository.workflow_execution_repository import WorkflowExecutionRepository
from core.workflow.repository.workflow_node_execution_repository import WorkflowNodeExecutionRepository
from core.workflow.repositories.workflow_execution_repository import WorkflowExecutionRepository
from core.workflow.repositories.workflow_node_execution_repository import WorkflowNodeExecutionRepository
from extensions.ext_database import db from extensions.ext_database import db
from factories import file_factory from factories import file_factory
from models import Account, App, EndUser, Workflow, WorkflowNodeExecutionTriggeredFrom from models import Account, App, EndUser, Workflow, WorkflowNodeExecutionTriggeredFrom
invoke_from=invoke_from, invoke_from=invoke_from,
call_depth=call_depth, call_depth=call_depth,
trace_manager=trace_manager, trace_manager=trace_manager,
workflow_run_id=workflow_run_id,
workflow_execution_id=workflow_run_id,
) )


contexts.plugin_tool_providers.set({}) contexts.plugin_tool_providers.set({})
single_iteration_run=WorkflowAppGenerateEntity.SingleIterationRunEntity( single_iteration_run=WorkflowAppGenerateEntity.SingleIterationRunEntity(
node_id=node_id, inputs=args["inputs"] node_id=node_id, inputs=args["inputs"]
), ),
workflow_run_id=str(uuid.uuid4()),
workflow_execution_id=str(uuid.uuid4()),
) )
contexts.plugin_tool_providers.set({}) contexts.plugin_tool_providers.set({})
contexts.plugin_tool_providers_lock.set(threading.Lock()) contexts.plugin_tool_providers_lock.set(threading.Lock())
invoke_from=InvokeFrom.DEBUGGER, invoke_from=InvokeFrom.DEBUGGER,
extras={"auto_generate_conversation_name": False}, extras={"auto_generate_conversation_name": False},
single_loop_run=WorkflowAppGenerateEntity.SingleLoopRunEntity(node_id=node_id, inputs=args["inputs"]), single_loop_run=WorkflowAppGenerateEntity.SingleLoopRunEntity(node_id=node_id, inputs=args["inputs"]),
workflow_run_id=str(uuid.uuid4()),
workflow_execution_id=str(uuid.uuid4()),
) )
contexts.plugin_tool_providers.set({}) contexts.plugin_tool_providers.set({})
contexts.plugin_tool_providers_lock.set(threading.Lock()) contexts.plugin_tool_providers_lock.set(threading.Lock())

+ 1
- 1
api/core/app/apps/workflow/app_runner.py View File

SystemVariableKey.USER_ID: user_id, SystemVariableKey.USER_ID: user_id,
SystemVariableKey.APP_ID: app_config.app_id, SystemVariableKey.APP_ID: app_config.app_id,
SystemVariableKey.WORKFLOW_ID: app_config.workflow_id, SystemVariableKey.WORKFLOW_ID: app_config.workflow_id,
SystemVariableKey.WORKFLOW_RUN_ID: self.application_generate_entity.workflow_run_id,
SystemVariableKey.WORKFLOW_RUN_ID: self.application_generate_entity.workflow_execution_id,
} }


variable_pool = VariablePool( variable_pool = VariablePool(

+ 21
- 20
api/core/app/apps/workflow/generate_task_pipeline.py View File

from core.app.task_pipeline.based_generate_task_pipeline import BasedGenerateTaskPipeline from core.app.task_pipeline.based_generate_task_pipeline import BasedGenerateTaskPipeline
from core.base.tts import AppGeneratorTTSPublisher, AudioTrunk from core.base.tts import AppGeneratorTTSPublisher, AudioTrunk
from core.ops.ops_trace_manager import TraceQueueManager from core.ops.ops_trace_manager import TraceQueueManager
from core.workflow.entities.workflow_execution_entities import WorkflowExecution
from core.workflow.entities.workflow_execution import WorkflowExecution, WorkflowExecutionStatus, WorkflowType
from core.workflow.enums import SystemVariableKey from core.workflow.enums import SystemVariableKey
from core.workflow.repository.workflow_execution_repository import WorkflowExecutionRepository
from core.workflow.repository.workflow_node_execution_repository import WorkflowNodeExecutionRepository
from core.workflow.workflow_cycle_manager import WorkflowCycleManager
from core.workflow.repositories.workflow_execution_repository import WorkflowExecutionRepository
from core.workflow.repositories.workflow_node_execution_repository import WorkflowNodeExecutionRepository
from core.workflow.workflow_cycle_manager import CycleManagerWorkflowInfo, WorkflowCycleManager
from extensions.ext_database import db from extensions.ext_database import db
from models.account import Account from models.account import Account
from models.enums import CreatorUserRole from models.enums import CreatorUserRole
WorkflowAppLog, WorkflowAppLog,
WorkflowAppLogCreatedFrom, WorkflowAppLogCreatedFrom,
WorkflowRun, WorkflowRun,
WorkflowRunStatus,
) )


logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
SystemVariableKey.USER_ID: user_session_id, SystemVariableKey.USER_ID: user_session_id,
SystemVariableKey.APP_ID: application_generate_entity.app_config.app_id, SystemVariableKey.APP_ID: application_generate_entity.app_config.app_id,
SystemVariableKey.WORKFLOW_ID: workflow.id, SystemVariableKey.WORKFLOW_ID: workflow.id,
SystemVariableKey.WORKFLOW_RUN_ID: application_generate_entity.workflow_run_id,
SystemVariableKey.WORKFLOW_RUN_ID: application_generate_entity.workflow_execution_id,
}, },
workflow_info=CycleManagerWorkflowInfo(
workflow_id=workflow.id,
workflow_type=WorkflowType(workflow.type),
version=workflow.version,
graph_data=workflow.graph_dict,
),
workflow_execution_repository=workflow_execution_repository, workflow_execution_repository=workflow_execution_repository,
workflow_node_execution_repository=workflow_node_execution_repository, workflow_node_execution_repository=workflow_node_execution_repository,
) )
# override graph runtime state # override graph runtime state
graph_runtime_state = event.graph_runtime_state graph_runtime_state = event.graph_runtime_state


with Session(db.engine, expire_on_commit=False) as session:
# init workflow run
workflow_execution = self._workflow_cycle_manager.handle_workflow_run_start(
session=session,
workflow_id=self._workflow_id,
)
self._workflow_run_id = workflow_execution.id
start_resp = self._workflow_response_converter.workflow_start_to_stream_response(
task_id=self._application_generate_entity.task_id,
workflow_execution=workflow_execution,
)
# init workflow run
workflow_execution = self._workflow_cycle_manager.handle_workflow_run_start()
self._workflow_run_id = workflow_execution.id_
start_resp = self._workflow_response_converter.workflow_start_to_stream_response(
task_id=self._application_generate_entity.task_id,
workflow_execution=workflow_execution,
)


yield start_resp yield start_resp
elif isinstance( elif isinstance(
workflow_run_id=self._workflow_run_id, workflow_run_id=self._workflow_run_id,
total_tokens=graph_runtime_state.total_tokens, total_tokens=graph_runtime_state.total_tokens,
total_steps=graph_runtime_state.node_run_steps, total_steps=graph_runtime_state.node_run_steps,
status=WorkflowRunStatus.FAILED
status=WorkflowExecutionStatus.FAILED
if isinstance(event, QueueWorkflowFailedEvent) if isinstance(event, QueueWorkflowFailedEvent)
else WorkflowRunStatus.STOPPED,
else WorkflowExecutionStatus.STOPPED,
error_message=event.error error_message=event.error
if isinstance(event, QueueWorkflowFailedEvent) if isinstance(event, QueueWorkflowFailedEvent)
else event.get_stop_reason(), else event.get_stop_reason(),
tts_publisher.publish(None) tts_publisher.publish(None)


def _save_workflow_app_log(self, *, session: Session, workflow_execution: WorkflowExecution) -> None: def _save_workflow_app_log(self, *, session: Session, workflow_execution: WorkflowExecution) -> None:
workflow_run = session.scalar(select(WorkflowRun).where(WorkflowRun.id == workflow_execution.id))
workflow_run = session.scalar(select(WorkflowRun).where(WorkflowRun.id == workflow_execution.id_))
assert workflow_run is not None assert workflow_run is not None
invoke_from = self._application_generate_entity.invoke_from invoke_from = self._application_generate_entity.invoke_from
if invoke_from == InvokeFrom.SERVICE_API: if invoke_from == InvokeFrom.SERVICE_API:

+ 1
- 1
api/core/app/apps/workflow_app_runner.py View File

QueueWorkflowStartedEvent, QueueWorkflowStartedEvent,
QueueWorkflowSucceededEvent, QueueWorkflowSucceededEvent,
) )
from core.workflow.entities.node_entities import NodeRunMetadataKey
from core.workflow.entities.variable_pool import VariablePool from core.workflow.entities.variable_pool import VariablePool
from core.workflow.entities.workflow_node_execution import NodeRunMetadataKey
from core.workflow.graph_engine.entities.event import ( from core.workflow.graph_engine.entities.event import (
AgentLogEvent, AgentLogEvent,
GraphEngineEvent, GraphEngineEvent,

+ 3
- 4
api/core/app/entities/app_invoke_entities.py View File

App Generate Entity. App Generate Entity.
""" """


model_config = ConfigDict(arbitrary_types_allowed=True)

task_id: str task_id: str


# app config # app config
# tracing instance # tracing instance
trace_manager: Optional[TraceQueueManager] = None trace_manager: Optional[TraceQueueManager] = None


class Config:
arbitrary_types_allowed = True



class EasyUIBasedAppGenerateEntity(AppGenerateEntity): class EasyUIBasedAppGenerateEntity(AppGenerateEntity):
""" """


# app config # app config
app_config: WorkflowUIBasedAppConfig app_config: WorkflowUIBasedAppConfig
workflow_run_id: str
workflow_execution_id: str


class SingleIterationRunEntity(BaseModel): class SingleIterationRunEntity(BaseModel):
""" """

+ 2
- 1
api/core/app/entities/queue_entities.py View File

from pydantic import BaseModel from pydantic import BaseModel


from core.model_runtime.entities.llm_entities import LLMResult, LLMResultChunk from core.model_runtime.entities.llm_entities import LLMResult, LLMResultChunk
from core.workflow.entities.node_entities import AgentNodeStrategyInit, NodeRunMetadataKey
from core.workflow.entities.node_entities import AgentNodeStrategyInit
from core.workflow.entities.workflow_node_execution import NodeRunMetadataKey
from core.workflow.graph_engine.entities.graph_runtime_state import GraphRuntimeState from core.workflow.graph_engine.entities.graph_runtime_state import GraphRuntimeState
from core.workflow.nodes import NodeType from core.workflow.nodes import NodeType
from core.workflow.nodes.base import BaseNodeData from core.workflow.nodes.base import BaseNodeData

+ 2
- 4
api/core/app/entities/task_entities.py View File



from core.model_runtime.entities.llm_entities import LLMResult from core.model_runtime.entities.llm_entities import LLMResult
from core.model_runtime.utils.encoders import jsonable_encoder from core.model_runtime.utils.encoders import jsonable_encoder
from core.workflow.entities.node_entities import AgentNodeStrategyInit, NodeRunMetadataKey
from models.workflow import WorkflowNodeExecutionStatus
from core.workflow.entities.node_entities import AgentNodeStrategyInit
from core.workflow.entities.workflow_node_execution import NodeRunMetadataKey, WorkflowNodeExecutionStatus




class TaskState(BaseModel): class TaskState(BaseModel):


id: str id: str
workflow_id: str workflow_id: str
sequence_number: int
inputs: Mapping[str, Any] inputs: Mapping[str, Any]
created_at: int created_at: int




id: str id: str
workflow_id: str workflow_id: str
sequence_number: int
status: str status: str
outputs: Optional[Mapping[str, Any]] = None outputs: Optional[Mapping[str, Any]] = None
error: Optional[str] = None error: Optional[str] = None

+ 8
- 5
api/core/ops/entities/trace_entity.py View File

from enum import StrEnum from enum import StrEnum
from typing import Any, Optional, Union from typing import Any, Optional, Union


from pydantic import BaseModel, ConfigDict, field_validator
from pydantic import BaseModel, ConfigDict, field_serializer, field_validator




class BaseTraceInfo(BaseModel): class BaseTraceInfo(BaseModel):
return v return v
return "" return ""


class Config:
json_encoders = {
datetime: lambda v: v.isoformat(),
}
model_config = ConfigDict(protected_namespaces=())

@field_serializer("start_time", "end_time")
def serialize_datetime(self, dt: datetime | None) -> str | None:
if dt is None:
return None
return dt.isoformat()




class WorkflowTraceInfo(BaseTraceInfo): class WorkflowTraceInfo(BaseTraceInfo):

+ 1
- 1
api/core/ops/langsmith_trace/langsmith_trace.py View File

) )
from core.ops.utils import filter_none_values, generate_dotted_order from core.ops.utils import filter_none_values, generate_dotted_order
from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository
from core.workflow.entities.node_entities import NodeRunMetadataKey
from core.workflow.entities.workflow_node_execution import NodeRunMetadataKey
from core.workflow.nodes.enums import NodeType from core.workflow.nodes.enums import NodeType
from extensions.ext_database import db from extensions.ext_database import db
from models import Account, App, EndUser, MessageFile, WorkflowNodeExecutionTriggeredFrom from models import Account, App, EndUser, MessageFile, WorkflowNodeExecutionTriggeredFrom

+ 1
- 1
api/core/ops/opik_trace/opik_trace.py View File

WorkflowTraceInfo, WorkflowTraceInfo,
) )
from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository
from core.workflow.entities.node_entities import NodeRunMetadataKey
from core.workflow.entities.workflow_node_execution import NodeRunMetadataKey
from core.workflow.nodes.enums import NodeType from core.workflow.nodes.enums import NodeType
from extensions.ext_database import db from extensions.ext_database import db
from models import Account, App, EndUser, MessageFile, WorkflowNodeExecutionTriggeredFrom from models import Account, App, EndUser, MessageFile, WorkflowNodeExecutionTriggeredFrom

+ 2
- 2
api/core/ops/ops_trace_manager.py View File

WorkflowTraceInfo, WorkflowTraceInfo,
) )
from core.ops.utils import get_message_data from core.ops.utils import get_message_data
from core.workflow.entities.workflow_execution_entities import WorkflowExecution
from core.workflow.entities.workflow_execution import WorkflowExecution
from extensions.ext_database import db from extensions.ext_database import db
from extensions.ext_storage import storage from extensions.ext_storage import storage
from models.model import App, AppModelConfig, Conversation, Message, MessageFile, TraceAppConfig from models.model import App, AppModelConfig, Conversation, Message, MessageFile, TraceAppConfig
): ):
self.trace_type = trace_type self.trace_type = trace_type
self.message_id = message_id self.message_id = message_id
self.workflow_run_id = workflow_execution.id if workflow_execution else None
self.workflow_run_id = workflow_execution.id_ if workflow_execution else None
self.conversation_id = conversation_id self.conversation_id = conversation_id
self.user_id = user_id self.user_id = user_id
self.timer = timer self.timer = timer

+ 1
- 1
api/core/ops/weave_trace/weave_trace.py View File

) )
from core.ops.weave_trace.entities.weave_trace_entity import WeaveTraceModel from core.ops.weave_trace.entities.weave_trace_entity import WeaveTraceModel
from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository
from core.workflow.entities.node_entities import NodeRunMetadataKey
from core.workflow.entities.workflow_node_execution import NodeRunMetadataKey
from core.workflow.nodes.enums import NodeType from core.workflow.nodes.enums import NodeType
from extensions.ext_database import db from extensions.ext_database import db
from models import Account, App, EndUser, MessageFile, WorkflowNodeExecutionTriggeredFrom from models import Account, App, EndUser, MessageFile, WorkflowNodeExecutionTriggeredFrom

+ 2
- 6
api/core/rag/extractor/entity/extract_setting.py View File

website import info. website import info.
""" """


model_config = ConfigDict(arbitrary_types_allowed=True)

provider: str provider: str
job_id: str job_id: str
url: str url: str
tenant_id: str tenant_id: str
only_main_content: bool = False only_main_content: bool = False


class Config:
arbitrary_types_allowed = True

def __init__(self, **data) -> None:
super().__init__(**data)



class ExtractSetting(BaseModel): class ExtractSetting(BaseModel):
""" """

+ 2
- 3
api/core/rag/models/document.py View File

.. code-block:: python .. code-block:: python


class EmbeddingsRedundantFilter(BaseDocumentTransformer, BaseModel): class EmbeddingsRedundantFilter(BaseDocumentTransformer, BaseModel):
model_config = ConfigDict(arbitrary_types_allowed=True)

embeddings: Embeddings embeddings: Embeddings
similarity_fn: Callable = cosine_similarity similarity_fn: Callable = cosine_similarity
similarity_threshold: float = 0.95 similarity_threshold: float = 0.95


class Config:
arbitrary_types_allowed = True

def transform_documents( def transform_documents(
self, documents: Sequence[Document], **kwargs: Any self, documents: Sequence[Document], **kwargs: Any
) -> Sequence[Document]: ) -> Sequence[Document]:

+ 22
- 8
api/core/repositories/sqlalchemy_workflow_execution_repository.py View File

from sqlalchemy.engine import Engine from sqlalchemy.engine import Engine
from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import sessionmaker


from core.workflow.entities.workflow_execution_entities import (
from core.workflow.entities.workflow_execution import (
WorkflowExecution, WorkflowExecution,
WorkflowExecutionStatus, WorkflowExecutionStatus,
WorkflowType, WorkflowType,
) )
from core.workflow.repository.workflow_execution_repository import WorkflowExecutionRepository
from core.workflow.repositories.workflow_execution_repository import WorkflowExecutionRepository
from models import ( from models import (
Account, Account,
CreatorUserRole, CreatorUserRole,
status = WorkflowExecutionStatus(db_model.status) status = WorkflowExecutionStatus(db_model.status)


return WorkflowExecution( return WorkflowExecution(
id=db_model.id,
id_=db_model.id,
workflow_id=db_model.workflow_id, workflow_id=db_model.workflow_id,
sequence_number=db_model.sequence_number,
type=WorkflowType(db_model.type),
workflow_type=WorkflowType(db_model.type),
workflow_version=db_model.version, workflow_version=db_model.version,
graph=graph, graph=graph,
inputs=inputs, inputs=inputs,
raise ValueError("created_by_role is required in repository constructor") raise ValueError("created_by_role is required in repository constructor")


db_model = WorkflowRun() db_model = WorkflowRun()
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.app_id = self._app_id db_model.app_id = self._app_id
db_model.workflow_id = domain_model.workflow_id db_model.workflow_id = domain_model.workflow_id
db_model.triggered_from = self._triggered_from db_model.triggered_from = self._triggered_from
db_model.sequence_number = domain_model.sequence_number
db_model.type = domain_model.type

# Check if this is a new record
with self._session_factory() as session:
existing = session.scalar(select(WorkflowRun).where(WorkflowRun.id == domain_model.id_))
if not existing:
# For new records, get the next sequence number
stmt = select(WorkflowRun.sequence_number).where(
WorkflowRun.app_id == self._app_id,
WorkflowRun.tenant_id == self._tenant_id,
)
max_sequence = session.scalar(stmt.order_by(WorkflowRun.sequence_number.desc()))
db_model.sequence_number = (max_sequence or 0) + 1
else:
# For updates, keep the existing sequence number
db_model.sequence_number = existing.sequence_number

db_model.type = domain_model.workflow_type
db_model.version = domain_model.workflow_version db_model.version = domain_model.workflow_version
db_model.graph = json.dumps(domain_model.graph) if domain_model.graph else None db_model.graph = json.dumps(domain_model.graph) if domain_model.graph else None
db_model.inputs = json.dumps(domain_model.inputs) if domain_model.inputs else None db_model.inputs = json.dumps(domain_model.inputs) if domain_model.inputs else None

+ 5
- 6
api/core/repositories/sqlalchemy_workflow_node_execution_repository.py View File

from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import sessionmaker


from core.model_runtime.utils.encoders import jsonable_encoder from core.model_runtime.utils.encoders import jsonable_encoder
from core.workflow.entities.node_entities import NodeRunMetadataKey
from core.workflow.entities.node_execution_entities import (
from core.workflow.entities.workflow_node_execution import (
NodeExecution, NodeExecution,
NodeExecutionStatus,
NodeRunMetadataKey,
WorkflowNodeExecutionStatus,
) )
from core.workflow.nodes.enums import NodeType from core.workflow.nodes.enums import NodeType
from core.workflow.repository.workflow_node_execution_repository import OrderConfig, WorkflowNodeExecutionRepository
from core.workflow.repositories.workflow_node_execution_repository import OrderConfig, WorkflowNodeExecutionRepository
from models import ( from models import (
Account, Account,
CreatorUserRole, CreatorUserRole,
EndUser, EndUser,
WorkflowNodeExecution, WorkflowNodeExecution,
WorkflowNodeExecutionStatus,
WorkflowNodeExecutionTriggeredFrom, WorkflowNodeExecutionTriggeredFrom,
) )


metadata = {NodeRunMetadataKey(k): v for k, v in db_model.execution_metadata_dict.items()} metadata = {NodeRunMetadataKey(k): v for k, v in db_model.execution_metadata_dict.items()}


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


return NodeExecution( return NodeExecution(
id=db_model.id, id=db_model.id,

+ 1
- 27
api/core/workflow/entities/node_entities.py View File

from collections.abc import Mapping from collections.abc import Mapping
from enum import StrEnum
from typing import Any, Optional from typing import Any, Optional


from pydantic import BaseModel from pydantic import BaseModel


from core.model_runtime.entities.llm_entities import LLMUsage from core.model_runtime.entities.llm_entities import LLMUsage
from models.workflow import WorkflowNodeExecutionStatus


class NodeRunMetadataKey(StrEnum):
"""
Node Run Metadata Key.
"""

TOTAL_TOKENS = "total_tokens"
TOTAL_PRICE = "total_price"
CURRENCY = "currency"
TOOL_INFO = "tool_info"
AGENT_LOG = "agent_log"
ITERATION_ID = "iteration_id"
ITERATION_INDEX = "iteration_index"
LOOP_ID = "loop_id"
LOOP_INDEX = "loop_index"
PARALLEL_ID = "parallel_id"
PARALLEL_START_NODE_ID = "parallel_start_node_id"
PARENT_PARALLEL_ID = "parent_parallel_id"
PARENT_PARALLEL_START_NODE_ID = "parent_parallel_start_node_id"
PARALLEL_MODE_RUN_ID = "parallel_mode_run_id"
ITERATION_DURATION_MAP = "iteration_duration_map" # single iteration duration if iteration node runs
LOOP_DURATION_MAP = "loop_duration_map" # single loop duration if loop node runs
ERROR_STRATEGY = "error_strategy" # node in continue on error mode return the field
LOOP_VARIABLE_MAP = "loop_variable_map" # single loop variable output
from core.workflow.entities.workflow_node_execution import NodeRunMetadataKey, WorkflowNodeExecutionStatus




class NodeRunResult(BaseModel): class NodeRunResult(BaseModel):

api/core/workflow/entities/workflow_execution_entities.py → api/core/workflow/entities/workflow_execution.py View File

user, tenant, and app attributes. user, tenant, and app attributes.
""" """


id: str = Field(...)
id_: str = Field(...)
workflow_id: str = Field(...) workflow_id: str = Field(...)
workflow_version: str = Field(...) workflow_version: str = Field(...)
sequence_number: int = Field(...)

type: WorkflowType = Field(...)
workflow_type: WorkflowType = Field(...)
graph: Mapping[str, Any] = Field(...) graph: Mapping[str, Any] = Field(...)


inputs: Mapping[str, Any] = Field(...) inputs: Mapping[str, Any] = Field(...)
def new( def new(
cls, cls,
*, *,
id: str,
id_: str,
workflow_id: str, workflow_id: str,
sequence_number: int,
type: WorkflowType,
workflow_type: WorkflowType,
workflow_version: str, workflow_version: str,
graph: Mapping[str, Any], graph: Mapping[str, Any],
inputs: Mapping[str, Any], inputs: Mapping[str, Any],
started_at: datetime, started_at: datetime,
) -> "WorkflowExecution": ) -> "WorkflowExecution":
return WorkflowExecution( return WorkflowExecution(
id=id,
id_=id_,
workflow_id=workflow_id, workflow_id=workflow_id,
sequence_number=sequence_number,
type=type,
workflow_type=workflow_type,
workflow_version=workflow_version, workflow_version=workflow_version,
graph=graph, graph=graph,
inputs=inputs, inputs=inputs,

api/core/workflow/entities/node_execution_entities.py → api/core/workflow/entities/workflow_node_execution.py View File



from pydantic import BaseModel, Field from pydantic import BaseModel, Field


from core.workflow.entities.node_entities import NodeRunMetadataKey
from core.workflow.nodes.enums import NodeType from core.workflow.nodes.enums import NodeType




class NodeExecutionStatus(StrEnum):
class NodeRunMetadataKey(StrEnum):
"""
Node Run Metadata Key.
"""

TOTAL_TOKENS = "total_tokens"
TOTAL_PRICE = "total_price"
CURRENCY = "currency"
TOOL_INFO = "tool_info"
AGENT_LOG = "agent_log"
ITERATION_ID = "iteration_id"
ITERATION_INDEX = "iteration_index"
LOOP_ID = "loop_id"
LOOP_INDEX = "loop_index"
PARALLEL_ID = "parallel_id"
PARALLEL_START_NODE_ID = "parallel_start_node_id"
PARENT_PARALLEL_ID = "parent_parallel_id"
PARENT_PARALLEL_START_NODE_ID = "parent_parallel_start_node_id"
PARALLEL_MODE_RUN_ID = "parallel_mode_run_id"
ITERATION_DURATION_MAP = "iteration_duration_map" # single iteration duration if iteration node runs
LOOP_DURATION_MAP = "loop_duration_map" # single loop duration if loop node runs
ERROR_STRATEGY = "error_strategy" # node in continue on error mode return the field
LOOP_VARIABLE_MAP = "loop_variable_map" # single loop variable output


class WorkflowNodeExecutionStatus(StrEnum):
""" """
Node Execution Status Enum. Node Execution Status Enum.
""" """
outputs: Optional[Mapping[str, Any]] = None # Output variables produced by this node outputs: Optional[Mapping[str, Any]] = None # Output variables produced by this node


# Execution state # Execution state
status: NodeExecutionStatus = NodeExecutionStatus.RUNNING # Current execution status
status: WorkflowNodeExecutionStatus = WorkflowNodeExecutionStatus.RUNNING # Current execution status
error: Optional[str] = None # Error message if execution failed error: Optional[str] = None # Error message if execution failed
elapsed_time: float = Field(default=0.0) # Time taken for execution in seconds elapsed_time: float = Field(default=0.0) # Time taken for execution in seconds



+ 1
- 1
api/core/workflow/graph_engine/entities/runtime_route_state.py View File

from pydantic import BaseModel, Field from pydantic import BaseModel, Field


from core.workflow.entities.node_entities import NodeRunResult from core.workflow.entities.node_entities import NodeRunResult
from models.workflow import WorkflowNodeExecutionStatus
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus




class RouteNodeState(BaseModel): class RouteNodeState(BaseModel):

+ 3
- 2
api/core/workflow/graph_engine/graph_engine.py View File

from configs import dify_config from configs import dify_config
from core.app.apps.base_app_queue_manager import GenerateTaskStoppedError from core.app.apps.base_app_queue_manager import GenerateTaskStoppedError
from core.app.entities.app_invoke_entities import InvokeFrom from core.app.entities.app_invoke_entities import InvokeFrom
from core.workflow.entities.node_entities import AgentNodeStrategyInit, NodeRunMetadataKey, NodeRunResult
from core.workflow.entities.node_entities import AgentNodeStrategyInit, NodeRunResult
from core.workflow.entities.variable_pool import VariablePool, VariableValue from core.workflow.entities.variable_pool import VariablePool, VariableValue
from core.workflow.entities.workflow_node_execution import NodeRunMetadataKey, WorkflowNodeExecutionStatus
from core.workflow.graph_engine.condition_handlers.condition_manager import ConditionManager from core.workflow.graph_engine.condition_handlers.condition_manager import ConditionManager
from core.workflow.graph_engine.entities.event import ( from core.workflow.graph_engine.entities.event import (
BaseAgentEvent, BaseAgentEvent,
from core.workflow.nodes.node_mapping import NODE_TYPE_CLASSES_MAPPING from core.workflow.nodes.node_mapping import NODE_TYPE_CLASSES_MAPPING
from extensions.ext_database import db from extensions.ext_database import db
from models.enums import UserFrom from models.enums import UserFrom
from models.workflow import WorkflowNodeExecutionStatus, WorkflowType
from models.workflow import WorkflowType


logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)



+ 1
- 1
api/core/workflow/nodes/agent/agent_node.py View File

from core.variables.segments import StringSegment from core.variables.segments import StringSegment
from core.workflow.entities.node_entities import NodeRunResult from core.workflow.entities.node_entities import NodeRunResult
from core.workflow.entities.variable_pool import VariablePool from core.workflow.entities.variable_pool import VariablePool
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from core.workflow.enums import SystemVariableKey from core.workflow.enums import SystemVariableKey
from core.workflow.nodes.agent.entities import AgentNodeData, AgentOldVersionModelFeatures, ParamsAutoGenerated from core.workflow.nodes.agent.entities import AgentNodeData, AgentOldVersionModelFeatures, ParamsAutoGenerated
from core.workflow.nodes.base.entities import BaseNodeData from core.workflow.nodes.base.entities import BaseNodeData
from extensions.ext_database import db from extensions.ext_database import db
from factories.agent_factory import get_plugin_agent_strategy from factories.agent_factory import get_plugin_agent_strategy
from models.model import Conversation from models.model import Conversation
from models.workflow import WorkflowNodeExecutionStatus




class AgentNode(ToolNode): class AgentNode(ToolNode):

+ 1
- 1
api/core/workflow/nodes/answer/answer_node.py View File



from core.variables import ArrayFileSegment, FileSegment from core.variables import ArrayFileSegment, FileSegment
from core.workflow.entities.node_entities import NodeRunResult from core.workflow.entities.node_entities import NodeRunResult
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from core.workflow.nodes.answer.answer_stream_generate_router import AnswerStreamGeneratorRouter from core.workflow.nodes.answer.answer_stream_generate_router import AnswerStreamGeneratorRouter
from core.workflow.nodes.answer.entities import ( from core.workflow.nodes.answer.entities import (
AnswerNodeData, AnswerNodeData,
from core.workflow.nodes.base import BaseNode from core.workflow.nodes.base import BaseNode
from core.workflow.nodes.enums import NodeType from core.workflow.nodes.enums import NodeType
from core.workflow.utils.variable_template_parser import VariableTemplateParser from core.workflow.utils.variable_template_parser import VariableTemplateParser
from models.workflow import WorkflowNodeExecutionStatus




class AnswerNode(BaseNode[AnswerNodeData]): class AnswerNode(BaseNode[AnswerNodeData]):

+ 1
- 1
api/core/workflow/nodes/base/node.py View File

from typing import TYPE_CHECKING, Any, Generic, Optional, TypeVar, Union, cast from typing import TYPE_CHECKING, Any, Generic, Optional, TypeVar, Union, cast


from core.workflow.entities.node_entities import NodeRunResult from core.workflow.entities.node_entities import NodeRunResult
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from core.workflow.nodes.enums import CONTINUE_ON_ERROR_NODE_TYPE, RETRY_ON_ERROR_NODE_TYPE, NodeType from core.workflow.nodes.enums import CONTINUE_ON_ERROR_NODE_TYPE, RETRY_ON_ERROR_NODE_TYPE, NodeType
from core.workflow.nodes.event import NodeEvent, RunCompletedEvent from core.workflow.nodes.event import NodeEvent, RunCompletedEvent
from models.workflow import WorkflowNodeExecutionStatus


from .entities import BaseNodeData from .entities import BaseNodeData



+ 1
- 1
api/core/workflow/nodes/code/code_node.py View File

from core.helper.code_executor.python3.python3_code_provider import Python3CodeProvider from core.helper.code_executor.python3.python3_code_provider import Python3CodeProvider
from core.variables.segments import ArrayFileSegment from core.variables.segments import ArrayFileSegment
from core.workflow.entities.node_entities import NodeRunResult from core.workflow.entities.node_entities import NodeRunResult
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from core.workflow.nodes.base import BaseNode from core.workflow.nodes.base import BaseNode
from core.workflow.nodes.code.entities import CodeNodeData from core.workflow.nodes.code.entities import CodeNodeData
from core.workflow.nodes.enums import NodeType from core.workflow.nodes.enums import NodeType
from models.workflow import WorkflowNodeExecutionStatus


from .exc import ( from .exc import (
CodeNodeError, CodeNodeError,

+ 1
- 1
api/core/workflow/nodes/document_extractor/node.py View File

from core.variables import ArrayFileSegment from core.variables import ArrayFileSegment
from core.variables.segments import FileSegment from core.variables.segments import FileSegment
from core.workflow.entities.node_entities import NodeRunResult from core.workflow.entities.node_entities import NodeRunResult
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from core.workflow.nodes.base import BaseNode from core.workflow.nodes.base import BaseNode
from core.workflow.nodes.enums import NodeType from core.workflow.nodes.enums import NodeType
from models.workflow import WorkflowNodeExecutionStatus


from .entities import DocumentExtractorNodeData from .entities import DocumentExtractorNodeData
from .exc import DocumentExtractorError, FileDownloadError, TextExtractionError, UnsupportedFileTypeError from .exc import DocumentExtractorError, FileDownloadError, TextExtractionError, UnsupportedFileTypeError

+ 1
- 1
api/core/workflow/nodes/end/end_node.py View File

from core.workflow.entities.node_entities import NodeRunResult from core.workflow.entities.node_entities import NodeRunResult
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from core.workflow.nodes.base import BaseNode from core.workflow.nodes.base import BaseNode
from core.workflow.nodes.end.entities import EndNodeData from core.workflow.nodes.end.entities import EndNodeData
from core.workflow.nodes.enums import NodeType from core.workflow.nodes.enums import NodeType
from models.workflow import WorkflowNodeExecutionStatus




class EndNode(BaseNode[EndNodeData]): class EndNode(BaseNode[EndNodeData]):

+ 1
- 1
api/core/workflow/nodes/event/event.py View File



from core.model_runtime.entities.llm_entities import LLMUsage from core.model_runtime.entities.llm_entities import LLMUsage
from core.workflow.entities.node_entities import NodeRunResult from core.workflow.entities.node_entities import NodeRunResult
from models.workflow import WorkflowNodeExecutionStatus
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus




class RunCompletedEvent(BaseModel): class RunCompletedEvent(BaseModel):

+ 1
- 1
api/core/workflow/nodes/http_request/node.py View File

from core.tools.tool_file_manager import ToolFileManager from core.tools.tool_file_manager import ToolFileManager
from core.workflow.entities.node_entities import NodeRunResult from core.workflow.entities.node_entities import NodeRunResult
from core.workflow.entities.variable_entities import VariableSelector from core.workflow.entities.variable_entities import VariableSelector
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from core.workflow.nodes.base import BaseNode from core.workflow.nodes.base import BaseNode
from core.workflow.nodes.enums import NodeType from core.workflow.nodes.enums import NodeType
from core.workflow.nodes.http_request.executor import Executor from core.workflow.nodes.http_request.executor import Executor
from core.workflow.utils import variable_template_parser from core.workflow.utils import variable_template_parser
from factories import file_factory from factories import file_factory
from models.workflow import WorkflowNodeExecutionStatus


from .entities import ( from .entities import (
HttpRequestNodeData, HttpRequestNodeData,

+ 1
- 1
api/core/workflow/nodes/if_else/if_else_node.py View File



from core.workflow.entities.node_entities import NodeRunResult from core.workflow.entities.node_entities import NodeRunResult
from core.workflow.entities.variable_pool import VariablePool from core.workflow.entities.variable_pool import VariablePool
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from core.workflow.nodes.base import BaseNode from core.workflow.nodes.base import BaseNode
from core.workflow.nodes.enums import NodeType from core.workflow.nodes.enums import NodeType
from core.workflow.nodes.if_else.entities import IfElseNodeData from core.workflow.nodes.if_else.entities import IfElseNodeData
from core.workflow.utils.condition.entities import Condition from core.workflow.utils.condition.entities import Condition
from core.workflow.utils.condition.processor import ConditionProcessor from core.workflow.utils.condition.processor import ConditionProcessor
from models.workflow import WorkflowNodeExecutionStatus




class IfElseNode(BaseNode[IfElseNodeData]): class IfElseNode(BaseNode[IfElseNodeData]):

+ 1
- 2
api/core/workflow/nodes/iteration/iteration_node.py View File

from configs import dify_config from configs import dify_config
from core.variables import ArrayVariable, IntegerVariable, NoneVariable from core.variables import ArrayVariable, IntegerVariable, NoneVariable
from core.workflow.entities.node_entities import ( from core.workflow.entities.node_entities import (
NodeRunMetadataKey,
NodeRunResult, NodeRunResult,
) )
from core.workflow.entities.variable_pool import VariablePool from core.workflow.entities.variable_pool import VariablePool
from core.workflow.entities.workflow_node_execution import NodeRunMetadataKey, WorkflowNodeExecutionStatus
from core.workflow.graph_engine.entities.event import ( from core.workflow.graph_engine.entities.event import (
BaseGraphEvent, BaseGraphEvent,
BaseNodeEvent, BaseNodeEvent,
from core.workflow.nodes.enums import NodeType from core.workflow.nodes.enums import NodeType
from core.workflow.nodes.event import NodeEvent, RunCompletedEvent from core.workflow.nodes.event import NodeEvent, RunCompletedEvent
from core.workflow.nodes.iteration.entities import ErrorHandleMode, IterationNodeData from core.workflow.nodes.iteration.entities import ErrorHandleMode, IterationNodeData
from models.workflow import WorkflowNodeExecutionStatus


from .exc import ( from .exc import (
InvalidIteratorValueError, InvalidIteratorValueError,

+ 1
- 1
api/core/workflow/nodes/iteration/iteration_start_node.py View File

from core.workflow.entities.node_entities import NodeRunResult from core.workflow.entities.node_entities import NodeRunResult
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from core.workflow.nodes.base import BaseNode from core.workflow.nodes.base import BaseNode
from core.workflow.nodes.enums import NodeType from core.workflow.nodes.enums import NodeType
from core.workflow.nodes.iteration.entities import IterationStartNodeData from core.workflow.nodes.iteration.entities import IterationStartNodeData
from models.workflow import WorkflowNodeExecutionStatus




class IterationStartNode(BaseNode[IterationStartNodeData]): class IterationStartNode(BaseNode[IterationStartNodeData]):

+ 1
- 1
api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py View File

from core.rag.retrieval.retrieval_methods import RetrievalMethod from core.rag.retrieval.retrieval_methods import RetrievalMethod
from core.variables import StringSegment from core.variables import StringSegment
from core.workflow.entities.node_entities import NodeRunResult from core.workflow.entities.node_entities import NodeRunResult
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from core.workflow.nodes.enums import NodeType from core.workflow.nodes.enums import NodeType
from core.workflow.nodes.event.event import ModelInvokeCompletedEvent from core.workflow.nodes.event.event import ModelInvokeCompletedEvent
from core.workflow.nodes.knowledge_retrieval.template_prompts import ( from core.workflow.nodes.knowledge_retrieval.template_prompts import (
from extensions.ext_redis import redis_client from extensions.ext_redis import redis_client
from libs.json_in_md_parser import parse_and_check_json_markdown from libs.json_in_md_parser import parse_and_check_json_markdown
from models.dataset import Dataset, DatasetMetadata, Document, RateLimitLog from models.dataset import Dataset, DatasetMetadata, Document, RateLimitLog
from models.workflow import WorkflowNodeExecutionStatus
from services.feature_service import FeatureService from services.feature_service import FeatureService


from .entities import KnowledgeRetrievalNodeData, ModelConfig from .entities import KnowledgeRetrievalNodeData, ModelConfig

+ 1
- 1
api/core/workflow/nodes/list_operator/node.py View File

from core.file import File from core.file import File
from core.variables import ArrayFileSegment, ArrayNumberSegment, ArrayStringSegment from core.variables import ArrayFileSegment, ArrayNumberSegment, ArrayStringSegment
from core.workflow.entities.node_entities import NodeRunResult from core.workflow.entities.node_entities import NodeRunResult
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from core.workflow.nodes.base import BaseNode from core.workflow.nodes.base import BaseNode
from core.workflow.nodes.enums import NodeType from core.workflow.nodes.enums import NodeType
from models.workflow import WorkflowNodeExecutionStatus


from .entities import ListOperatorNodeData from .entities import ListOperatorNodeData
from .exc import InvalidConditionError, InvalidFilterValueError, InvalidKeyError, ListOperatorError from .exc import InvalidConditionError, InvalidFilterValueError, InvalidKeyError, ListOperatorError

+ 2
- 2
api/core/workflow/nodes/llm/node.py View File

StringSegment, StringSegment,
) )
from core.workflow.constants import SYSTEM_VARIABLE_NODE_ID from core.workflow.constants import SYSTEM_VARIABLE_NODE_ID
from core.workflow.entities.node_entities import NodeRunMetadataKey, NodeRunResult
from core.workflow.entities.node_entities import NodeRunResult
from core.workflow.entities.variable_entities import VariableSelector from core.workflow.entities.variable_entities import VariableSelector
from core.workflow.entities.variable_pool import VariablePool from core.workflow.entities.variable_pool import VariablePool
from core.workflow.entities.workflow_node_execution import NodeRunMetadataKey, WorkflowNodeExecutionStatus
from core.workflow.enums import SystemVariableKey from core.workflow.enums import SystemVariableKey
from core.workflow.graph_engine.entities.event import InNodeEvent from core.workflow.graph_engine.entities.event import InNodeEvent
from core.workflow.nodes.base import BaseNode from core.workflow.nodes.base import BaseNode
from extensions.ext_database import db from extensions.ext_database import db
from models.model import Conversation from models.model import Conversation
from models.provider import Provider, ProviderType from models.provider import Provider, ProviderType
from models.workflow import WorkflowNodeExecutionStatus


from .entities import ( from .entities import (
LLMNodeChatModelMessage, LLMNodeChatModelMessage,

+ 1
- 1
api/core/workflow/nodes/loop/loop_end_node.py View File

from core.workflow.entities.node_entities import NodeRunResult from core.workflow.entities.node_entities import NodeRunResult
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from core.workflow.nodes.base import BaseNode from core.workflow.nodes.base import BaseNode
from core.workflow.nodes.enums import NodeType from core.workflow.nodes.enums import NodeType
from core.workflow.nodes.loop.entities import LoopEndNodeData from core.workflow.nodes.loop.entities import LoopEndNodeData
from models.workflow import WorkflowNodeExecutionStatus




class LoopEndNode(BaseNode[LoopEndNodeData]): class LoopEndNode(BaseNode[LoopEndNodeData]):

+ 2
- 2
api/core/workflow/nodes/loop/loop_node.py View File

SegmentType, SegmentType,
StringSegment, StringSegment,
) )
from core.workflow.entities.node_entities import NodeRunMetadataKey, NodeRunResult
from core.workflow.entities.node_entities import NodeRunResult
from core.workflow.entities.workflow_node_execution import NodeRunMetadataKey, WorkflowNodeExecutionStatus
from core.workflow.graph_engine.entities.event import ( from core.workflow.graph_engine.entities.event import (
BaseGraphEvent, BaseGraphEvent,
BaseNodeEvent, BaseNodeEvent,
from core.workflow.nodes.event import NodeEvent, RunCompletedEvent from core.workflow.nodes.event import NodeEvent, RunCompletedEvent
from core.workflow.nodes.loop.entities import LoopNodeData from core.workflow.nodes.loop.entities import LoopNodeData
from core.workflow.utils.condition.processor import ConditionProcessor from core.workflow.utils.condition.processor import ConditionProcessor
from models.workflow import WorkflowNodeExecutionStatus


if TYPE_CHECKING: if TYPE_CHECKING:
from core.workflow.entities.variable_pool import VariablePool from core.workflow.entities.variable_pool import VariablePool

+ 1
- 1
api/core/workflow/nodes/loop/loop_start_node.py View File

from core.workflow.entities.node_entities import NodeRunResult from core.workflow.entities.node_entities import NodeRunResult
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from core.workflow.nodes.base import BaseNode from core.workflow.nodes.base import BaseNode
from core.workflow.nodes.enums import NodeType from core.workflow.nodes.enums import NodeType
from core.workflow.nodes.loop.entities import LoopStartNodeData from core.workflow.nodes.loop.entities import LoopStartNodeData
from models.workflow import WorkflowNodeExecutionStatus




class LoopStartNode(BaseNode[LoopStartNodeData]): class LoopStartNode(BaseNode[LoopStartNodeData]):

+ 2
- 2
api/core/workflow/nodes/parameter_extractor/parameter_extractor_node.py View File

from core.prompt.entities.advanced_prompt_entities import ChatModelMessage, CompletionModelPromptTemplate from core.prompt.entities.advanced_prompt_entities import ChatModelMessage, CompletionModelPromptTemplate
from core.prompt.simple_prompt_transform import ModelMode from core.prompt.simple_prompt_transform import ModelMode
from core.prompt.utils.prompt_message_util import PromptMessageUtil from core.prompt.utils.prompt_message_util import PromptMessageUtil
from core.workflow.entities.node_entities import NodeRunMetadataKey, NodeRunResult
from core.workflow.entities.node_entities import NodeRunResult
from core.workflow.entities.variable_pool import VariablePool from core.workflow.entities.variable_pool import VariablePool
from core.workflow.entities.workflow_node_execution import NodeRunMetadataKey, WorkflowNodeExecutionStatus
from core.workflow.nodes.enums import NodeType from core.workflow.nodes.enums import NodeType
from core.workflow.nodes.llm import LLMNode, ModelConfig from core.workflow.nodes.llm import LLMNode, ModelConfig
from core.workflow.utils import variable_template_parser from core.workflow.utils import variable_template_parser
from extensions.ext_database import db from extensions.ext_database import db
from models.workflow import WorkflowNodeExecutionStatus


from .entities import ParameterExtractorNodeData from .entities import ParameterExtractorNodeData
from .exc import ( from .exc import (

+ 2
- 2
api/core/workflow/nodes/question_classifier/question_classifier_node.py View File

from core.prompt.advanced_prompt_transform import AdvancedPromptTransform from core.prompt.advanced_prompt_transform import AdvancedPromptTransform
from core.prompt.simple_prompt_transform import ModelMode from core.prompt.simple_prompt_transform import ModelMode
from core.prompt.utils.prompt_message_util import PromptMessageUtil from core.prompt.utils.prompt_message_util import PromptMessageUtil
from core.workflow.entities.node_entities import NodeRunMetadataKey, NodeRunResult
from core.workflow.entities.node_entities import NodeRunResult
from core.workflow.entities.workflow_node_execution import NodeRunMetadataKey, WorkflowNodeExecutionStatus
from core.workflow.nodes.enums import NodeType from core.workflow.nodes.enums import NodeType
from core.workflow.nodes.event import ModelInvokeCompletedEvent from core.workflow.nodes.event import ModelInvokeCompletedEvent
from core.workflow.nodes.llm import ( from core.workflow.nodes.llm import (
) )
from core.workflow.utils.variable_template_parser import VariableTemplateParser from core.workflow.utils.variable_template_parser import VariableTemplateParser
from libs.json_in_md_parser import parse_and_check_json_markdown from libs.json_in_md_parser import parse_and_check_json_markdown
from models.workflow import WorkflowNodeExecutionStatus


from .entities import QuestionClassifierNodeData from .entities import QuestionClassifierNodeData
from .exc import InvalidModelTypeError from .exc import InvalidModelTypeError

+ 1
- 1
api/core/workflow/nodes/start/start_node.py View File

from core.workflow.constants import SYSTEM_VARIABLE_NODE_ID from core.workflow.constants import SYSTEM_VARIABLE_NODE_ID
from core.workflow.entities.node_entities import NodeRunResult from core.workflow.entities.node_entities import NodeRunResult
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from core.workflow.nodes.base import BaseNode from core.workflow.nodes.base import BaseNode
from core.workflow.nodes.enums import NodeType from core.workflow.nodes.enums import NodeType
from core.workflow.nodes.start.entities import StartNodeData from core.workflow.nodes.start.entities import StartNodeData
from models.workflow import WorkflowNodeExecutionStatus




class StartNode(BaseNode[StartNodeData]): class StartNode(BaseNode[StartNodeData]):

+ 1
- 1
api/core/workflow/nodes/template_transform/template_transform_node.py View File



from core.helper.code_executor.code_executor import CodeExecutionError, CodeExecutor, CodeLanguage from core.helper.code_executor.code_executor import CodeExecutionError, CodeExecutor, CodeLanguage
from core.workflow.entities.node_entities import NodeRunResult from core.workflow.entities.node_entities import NodeRunResult
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from core.workflow.nodes.base import BaseNode from core.workflow.nodes.base import BaseNode
from core.workflow.nodes.enums import NodeType from core.workflow.nodes.enums import NodeType
from core.workflow.nodes.template_transform.entities import TemplateTransformNodeData from core.workflow.nodes.template_transform.entities import TemplateTransformNodeData
from models.workflow import WorkflowNodeExecutionStatus


MAX_TEMPLATE_TRANSFORM_OUTPUT_LENGTH = int(os.environ.get("TEMPLATE_TRANSFORM_MAX_LENGTH", "80000")) MAX_TEMPLATE_TRANSFORM_OUTPUT_LENGTH = int(os.environ.get("TEMPLATE_TRANSFORM_MAX_LENGTH", "80000"))



+ 2
- 2
api/core/workflow/nodes/tool/tool_node.py View File

from core.tools.utils.message_transformer import ToolFileMessageTransformer from core.tools.utils.message_transformer import ToolFileMessageTransformer
from core.variables.segments import ArrayAnySegment from core.variables.segments import ArrayAnySegment
from core.variables.variables import ArrayAnyVariable from core.variables.variables import ArrayAnyVariable
from core.workflow.entities.node_entities import NodeRunMetadataKey, NodeRunResult
from core.workflow.entities.node_entities import NodeRunResult
from core.workflow.entities.variable_pool import VariablePool from core.workflow.entities.variable_pool import VariablePool
from core.workflow.entities.workflow_node_execution import NodeRunMetadataKey, WorkflowNodeExecutionStatus
from core.workflow.enums import SystemVariableKey from core.workflow.enums import SystemVariableKey
from core.workflow.graph_engine.entities.event import AgentLogEvent from core.workflow.graph_engine.entities.event import AgentLogEvent
from core.workflow.nodes.base import BaseNode from core.workflow.nodes.base import BaseNode
from extensions.ext_database import db from extensions.ext_database import db
from factories import file_factory from factories import file_factory
from models import ToolFile from models import ToolFile
from models.workflow import WorkflowNodeExecutionStatus
from services.tools.builtin_tools_manage_service import BuiltinToolManageService from services.tools.builtin_tools_manage_service import BuiltinToolManageService


from .entities import ToolNodeData from .entities import ToolNodeData

+ 1
- 1
api/core/workflow/nodes/variable_aggregator/variable_aggregator_node.py View File

from core.workflow.entities.node_entities import NodeRunResult from core.workflow.entities.node_entities import NodeRunResult
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from core.workflow.nodes.base import BaseNode from core.workflow.nodes.base import BaseNode
from core.workflow.nodes.enums import NodeType from core.workflow.nodes.enums import NodeType
from core.workflow.nodes.variable_aggregator.entities import VariableAssignerNodeData from core.workflow.nodes.variable_aggregator.entities import VariableAssignerNodeData
from models.workflow import WorkflowNodeExecutionStatus




class VariableAggregatorNode(BaseNode[VariableAssignerNodeData]): class VariableAggregatorNode(BaseNode[VariableAssignerNodeData]):

+ 1
- 1
api/core/workflow/nodes/variable_assigner/v1/node.py View File

from core.variables import SegmentType, Variable from core.variables import SegmentType, 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 WorkflowNodeExecutionStatus
from core.workflow.nodes.base import BaseNode from core.workflow.nodes.base import BaseNode
from core.workflow.nodes.enums import NodeType from core.workflow.nodes.enums import NodeType
from core.workflow.nodes.variable_assigner.common import helpers as common_helpers from core.workflow.nodes.variable_assigner.common import helpers as common_helpers
from core.workflow.nodes.variable_assigner.common.exc import VariableOperatorNodeError from core.workflow.nodes.variable_assigner.common.exc import VariableOperatorNodeError
from factories import variable_factory from factories import variable_factory
from models.workflow import WorkflowNodeExecutionStatus


from .node_data import VariableAssignerData, WriteMode from .node_data import VariableAssignerData, WriteMode



+ 1
- 1
api/core/workflow/nodes/variable_assigner/v2/node.py View File

from core.variables import SegmentType, Variable from core.variables import SegmentType, Variable
from core.workflow.constants import CONVERSATION_VARIABLE_NODE_ID from core.workflow.constants import CONVERSATION_VARIABLE_NODE_ID
from core.workflow.entities.node_entities import NodeRunResult from core.workflow.entities.node_entities import NodeRunResult
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from core.workflow.nodes.base import BaseNode from core.workflow.nodes.base import BaseNode
from core.workflow.nodes.enums import NodeType from core.workflow.nodes.enums import NodeType
from core.workflow.nodes.variable_assigner.common import helpers as common_helpers from core.workflow.nodes.variable_assigner.common import helpers as common_helpers
from core.workflow.nodes.variable_assigner.common.exc import VariableOperatorNodeError from core.workflow.nodes.variable_assigner.common.exc import VariableOperatorNodeError
from models.workflow import WorkflowNodeExecutionStatus


from . import helpers from . import helpers
from .constants import EMPTY_VALUE_MAPPING from .constants import EMPTY_VALUE_MAPPING

api/core/workflow/repository/__init__.py → api/core/workflow/repositories/__init__.py View File

storage mechanism. storage mechanism.
""" """


from core.workflow.repository.workflow_node_execution_repository import OrderConfig, WorkflowNodeExecutionRepository
from core.workflow.repositories.workflow_node_execution_repository import OrderConfig, WorkflowNodeExecutionRepository


__all__ = [ __all__ = [
"OrderConfig", "OrderConfig",

api/core/workflow/repository/workflow_execution_repository.py → api/core/workflow/repositories/workflow_execution_repository.py View File

from typing import Optional, Protocol from typing import Optional, Protocol


from core.workflow.entities.workflow_execution_entities import WorkflowExecution
from core.workflow.entities.workflow_execution import WorkflowExecution




class WorkflowExecutionRepository(Protocol): class WorkflowExecutionRepository(Protocol):

api/core/workflow/repository/workflow_node_execution_repository.py → api/core/workflow/repositories/workflow_node_execution_repository.py View File

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


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




@dataclass @dataclass

+ 33
- 48
api/core/workflow/workflow_cycle_manager.py View File

from collections.abc import Mapping from collections.abc import Mapping
from dataclasses import dataclass
from datetime import UTC, datetime from datetime import UTC, datetime
from typing import Any, Optional, Union from typing import Any, Optional, Union
from uuid import uuid4 from uuid import uuid4


from sqlalchemy import func, select
from sqlalchemy.orm import Session

from core.app.entities.app_invoke_entities import AdvancedChatAppGenerateEntity, WorkflowAppGenerateEntity from core.app.entities.app_invoke_entities import AdvancedChatAppGenerateEntity, WorkflowAppGenerateEntity
from core.app.entities.queue_entities import ( from core.app.entities.queue_entities import (
QueueNodeExceptionEvent, QueueNodeExceptionEvent,
from core.app.task_pipeline.exc import WorkflowRunNotFoundError from core.app.task_pipeline.exc import WorkflowRunNotFoundError
from core.ops.entities.trace_entity import TraceTaskName from core.ops.entities.trace_entity import TraceTaskName
from core.ops.ops_trace_manager import TraceQueueManager, TraceTask from core.ops.ops_trace_manager import TraceQueueManager, TraceTask
from core.workflow.entities.node_entities import NodeRunMetadataKey
from core.workflow.entities.node_execution_entities import (
from core.workflow.entities.workflow_execution import WorkflowExecution, WorkflowExecutionStatus, WorkflowType
from core.workflow.entities.workflow_node_execution import (
NodeExecution, NodeExecution,
NodeExecutionStatus,
NodeRunMetadataKey,
WorkflowNodeExecutionStatus,
) )
from core.workflow.entities.workflow_execution_entities import WorkflowExecution, WorkflowExecutionStatus, WorkflowType
from core.workflow.enums import SystemVariableKey from core.workflow.enums import SystemVariableKey
from core.workflow.repository.workflow_execution_repository import WorkflowExecutionRepository
from core.workflow.repository.workflow_node_execution_repository import WorkflowNodeExecutionRepository
from core.workflow.repositories.workflow_execution_repository import WorkflowExecutionRepository
from core.workflow.repositories.workflow_node_execution_repository import WorkflowNodeExecutionRepository
from core.workflow.workflow_entry import WorkflowEntry from core.workflow.workflow_entry import WorkflowEntry
from models import (
Workflow,
WorkflowRun,
WorkflowRunStatus,
)


@dataclass
class CycleManagerWorkflowInfo:
workflow_id: str
workflow_type: WorkflowType
version: str
graph_data: Mapping[str, Any]




class WorkflowCycleManager: class WorkflowCycleManager:
*, *,
application_generate_entity: Union[AdvancedChatAppGenerateEntity, WorkflowAppGenerateEntity], application_generate_entity: Union[AdvancedChatAppGenerateEntity, WorkflowAppGenerateEntity],
workflow_system_variables: dict[SystemVariableKey, Any], workflow_system_variables: dict[SystemVariableKey, Any],
workflow_info: CycleManagerWorkflowInfo,
workflow_execution_repository: WorkflowExecutionRepository, workflow_execution_repository: WorkflowExecutionRepository,
workflow_node_execution_repository: WorkflowNodeExecutionRepository, workflow_node_execution_repository: WorkflowNodeExecutionRepository,
) -> None: ) -> None:
self._application_generate_entity = application_generate_entity self._application_generate_entity = application_generate_entity
self._workflow_system_variables = workflow_system_variables self._workflow_system_variables = workflow_system_variables
self._workflow_info = workflow_info
self._workflow_execution_repository = workflow_execution_repository self._workflow_execution_repository = workflow_execution_repository
self._workflow_node_execution_repository = workflow_node_execution_repository self._workflow_node_execution_repository = workflow_node_execution_repository


def handle_workflow_run_start(
self,
*,
session: Session,
workflow_id: str,
) -> WorkflowExecution:
workflow_stmt = select(Workflow).where(Workflow.id == workflow_id)
workflow = session.scalar(workflow_stmt)
if not workflow:
raise ValueError(f"Workflow not found: {workflow_id}")

max_sequence_stmt = select(func.max(WorkflowRun.sequence_number)).where(
WorkflowRun.tenant_id == workflow.tenant_id,
WorkflowRun.app_id == workflow.app_id,
)
max_sequence = session.scalar(max_sequence_stmt) or 0
new_sequence_number = max_sequence + 1

def handle_workflow_run_start(self) -> WorkflowExecution:
inputs = {**self._application_generate_entity.inputs} inputs = {**self._application_generate_entity.inputs}
for key, value in (self._workflow_system_variables or {}).items(): for key, value in (self._workflow_system_variables or {}).items():
if key.value == "conversation": if key.value == "conversation":
# TODO: This workflow_run_id should always not be None, maybe we can use a more elegant way to handle this # TODO: This workflow_run_id should always not be None, maybe we can use a more elegant way to handle this
execution_id = str(self._workflow_system_variables.get(SystemVariableKey.WORKFLOW_RUN_ID) or uuid4()) execution_id = str(self._workflow_system_variables.get(SystemVariableKey.WORKFLOW_RUN_ID) or uuid4())
execution = WorkflowExecution.new( execution = WorkflowExecution.new(
id=execution_id,
workflow_id=workflow.id,
sequence_number=new_sequence_number,
type=WorkflowType(workflow.type),
workflow_version=workflow.version,
graph=workflow.graph_dict,
id_=execution_id,
workflow_id=self._workflow_info.workflow_id,
workflow_type=self._workflow_info.workflow_type,
workflow_version=self._workflow_info.version,
graph=self._workflow_info.graph_data,
inputs=inputs, inputs=inputs,
started_at=datetime.now(UTC).replace(tzinfo=None), started_at=datetime.now(UTC).replace(tzinfo=None),
) )
workflow_run_id: str, workflow_run_id: str,
total_tokens: int, total_tokens: int,
total_steps: int, total_steps: int,
status: WorkflowRunStatus,
status: WorkflowExecutionStatus,
error_message: str, error_message: str,
conversation_id: Optional[str] = None, conversation_id: Optional[str] = None,
trace_manager: Optional[TraceQueueManager] = None, trace_manager: Optional[TraceQueueManager] = None,


# Use the instance repository to find running executions for a workflow run # Use the instance repository to find running executions for a workflow run
running_node_executions = self._workflow_node_execution_repository.get_running_executions( running_node_executions = self._workflow_node_execution_repository.get_running_executions(
workflow_run_id=workflow_execution.id
workflow_run_id=workflow_execution.id_
) )


# Update the domain models # Update the domain models
for node_execution in running_node_executions: for node_execution in running_node_executions:
if node_execution.node_execution_id: if node_execution.node_execution_id:
# Update the domain model # Update the domain model
node_execution.status = NodeExecutionStatus.FAILED
node_execution.status = WorkflowNodeExecutionStatus.FAILED
node_execution.error = error_message node_execution.error = error_message
node_execution.finished_at = now node_execution.finished_at = now
node_execution.elapsed_time = (now - node_execution.created_at).total_seconds() node_execution.elapsed_time = (now - node_execution.created_at).total_seconds()
domain_execution = NodeExecution( domain_execution = NodeExecution(
id=str(uuid4()), id=str(uuid4()),
workflow_id=workflow_execution.workflow_id, workflow_id=workflow_execution.workflow_id,
workflow_run_id=workflow_execution.id,
workflow_run_id=workflow_execution.id_,
predecessor_node_id=event.predecessor_node_id, predecessor_node_id=event.predecessor_node_id,
index=event.node_run_index, index=event.node_run_index,
node_execution_id=event.node_execution_id, node_execution_id=event.node_execution_id,
node_id=event.node_id, node_id=event.node_id,
node_type=event.node_type, node_type=event.node_type,
title=event.node_data.title, title=event.node_data.title,
status=NodeExecutionStatus.RUNNING,
status=WorkflowNodeExecutionStatus.RUNNING,
metadata=metadata, metadata=metadata,
created_at=created_at, created_at=created_at,
) )
elapsed_time = (finished_at - event.start_at).total_seconds() elapsed_time = (finished_at - event.start_at).total_seconds()


# Update domain model # Update domain model
domain_execution.status = NodeExecutionStatus.SUCCEEDED
domain_execution.status = WorkflowNodeExecutionStatus.SUCCEEDED
domain_execution.update_from_mapping( domain_execution.update_from_mapping(
inputs=inputs, process_data=process_data, outputs=outputs, metadata=execution_metadata_dict inputs=inputs, process_data=process_data, outputs=outputs, metadata=execution_metadata_dict
) )


# Update domain model # Update domain model
domain_execution.status = ( domain_execution.status = (
NodeExecutionStatus.FAILED
WorkflowNodeExecutionStatus.FAILED
if not isinstance(event, QueueNodeExceptionEvent) if not isinstance(event, QueueNodeExceptionEvent)
else NodeExecutionStatus.EXCEPTION
else WorkflowNodeExecutionStatus.EXCEPTION
) )
domain_execution.error = event.error domain_execution.error = event.error
domain_execution.update_from_mapping( domain_execution.update_from_mapping(
domain_execution = NodeExecution( domain_execution = NodeExecution(
id=str(uuid4()), id=str(uuid4()),
workflow_id=workflow_execution.workflow_id, workflow_id=workflow_execution.workflow_id,
workflow_run_id=workflow_execution.id,
workflow_run_id=workflow_execution.id_,
predecessor_node_id=event.predecessor_node_id, predecessor_node_id=event.predecessor_node_id,
node_execution_id=event.node_execution_id, node_execution_id=event.node_execution_id,
node_id=event.node_id, node_id=event.node_id,
node_type=event.node_type, node_type=event.node_type,
title=event.node_data.title, title=event.node_data.title,
status=NodeExecutionStatus.RETRY,
status=WorkflowNodeExecutionStatus.RETRY,
created_at=created_at, created_at=created_at,
finished_at=finished_at, finished_at=finished_at,
elapsed_time=elapsed_time, elapsed_time=elapsed_time,

+ 2
- 6
api/models/__init__.py View File

WorkflowAppLog, WorkflowAppLog,
WorkflowAppLogCreatedFrom, WorkflowAppLogCreatedFrom,
WorkflowNodeExecution, WorkflowNodeExecution,
WorkflowNodeExecutionStatus,
WorkflowNodeExecutionTriggeredFrom, WorkflowNodeExecutionTriggeredFrom,
WorkflowRun, WorkflowRun,
WorkflowRunStatus,
WorkflowType, WorkflowType,
) )


"AccountStatus", "AccountStatus",
"ApiRequest", "ApiRequest",
"ApiToken", "ApiToken",
"ApiToolProvider", # Added
"ApiToolProvider",
"App", "App",
"AppAnnotationHitHistory", "AppAnnotationHitHistory",
"AppAnnotationSetting", "AppAnnotationSetting",
"AppDatasetJoin", "AppDatasetJoin",
"AppMode", "AppMode",
"AppModelConfig", "AppModelConfig",
"BuiltinToolProvider", # Added
"BuiltinToolProvider",
"CeleryTask", "CeleryTask",
"CeleryTaskSet", "CeleryTaskSet",
"Conversation", "Conversation",
"WorkflowAppLog", "WorkflowAppLog",
"WorkflowAppLogCreatedFrom", "WorkflowAppLogCreatedFrom",
"WorkflowNodeExecution", "WorkflowNodeExecution",
"WorkflowNodeExecutionStatus",
"WorkflowNodeExecutionTriggeredFrom", "WorkflowNodeExecutionTriggeredFrom",
"WorkflowRun", "WorkflowRun",
"WorkflowRunStatus",
"WorkflowRunTriggeredFrom", "WorkflowRunTriggeredFrom",
"WorkflowToolProvider", "WorkflowToolProvider",
"WorkflowType", "WorkflowType",

+ 10
- 10
api/models/model.py View File

from core.plugin.entities.plugin import GenericProviderID from core.plugin.entities.plugin import GenericProviderID
from core.tools.entities.tool_entities import ToolProviderType from core.tools.entities.tool_entities import ToolProviderType
from core.tools.signature import sign_tool_file from core.tools.signature import sign_tool_file
from core.workflow.entities.workflow_execution import WorkflowExecutionStatus
from services.plugin.plugin_service import PluginService from services.plugin.plugin_service import PluginService


if TYPE_CHECKING: if TYPE_CHECKING:
from .engine import db from .engine import db
from .enums import CreatorUserRole from .enums import CreatorUserRole
from .types import StringUUID from .types import StringUUID
from .workflow import WorkflowRunStatus


if TYPE_CHECKING: if TYPE_CHECKING:
from .workflow import Workflow from .workflow import Workflow
def status_count(self): def status_count(self):
messages = db.session.query(Message).filter(Message.conversation_id == self.id).all() messages = db.session.query(Message).filter(Message.conversation_id == self.id).all()
status_counts = { status_counts = {
WorkflowRunStatus.RUNNING: 0,
WorkflowRunStatus.SUCCEEDED: 0,
WorkflowRunStatus.FAILED: 0,
WorkflowRunStatus.STOPPED: 0,
WorkflowRunStatus.PARTIAL_SUCCEEDED: 0,
WorkflowExecutionStatus.RUNNING: 0,
WorkflowExecutionStatus.SUCCEEDED: 0,
WorkflowExecutionStatus.FAILED: 0,
WorkflowExecutionStatus.STOPPED: 0,
WorkflowExecutionStatus.PARTIAL_SUCCEEDED: 0,
} }


for message in messages: for message in messages:
if message.workflow_run: if message.workflow_run:
status_counts[WorkflowRunStatus(message.workflow_run.status)] += 1
status_counts[WorkflowExecutionStatus(message.workflow_run.status)] += 1


return ( return (
{ {
"success": status_counts[WorkflowRunStatus.SUCCEEDED],
"failed": status_counts[WorkflowRunStatus.FAILED],
"partial_success": status_counts[WorkflowRunStatus.PARTIAL_SUCCEEDED],
"success": status_counts[WorkflowExecutionStatus.SUCCEEDED],
"failed": status_counts[WorkflowExecutionStatus.FAILED],
"partial_success": status_counts[WorkflowExecutionStatus.PARTIAL_SUCCEEDED],
} }
if messages if messages
else None else None

+ 0
- 24
api/models/workflow.py View File

) )




class WorkflowRunStatus(StrEnum):
"""
Workflow Run Status Enum
"""

RUNNING = "running"
SUCCEEDED = "succeeded"
FAILED = "failed"
STOPPED = "stopped"
PARTIAL_SUCCEEDED = "partial-succeeded"


class WorkflowRun(Base): class WorkflowRun(Base):
""" """
Workflow Run Workflow Run
WORKFLOW_RUN = "workflow-run" WORKFLOW_RUN = "workflow-run"




class WorkflowNodeExecutionStatus(StrEnum):
"""
Workflow Node Execution Status Enum
"""

RUNNING = "running"
SUCCEEDED = "succeeded"
FAILED = "failed"
EXCEPTION = "exception"
RETRY = "retry"


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

+ 0
- 1
api/pytest.ini View File

[pytest] [pytest]
continue-on-collection-errors = true
addopts = --cov=./api --cov-report=json --cov-report=xml addopts = --cov=./api --cov-report=json --cov-report=xml
env = env =
ANTHROPIC_API_KEY = sk-ant-api11-IamNotARealKeyJustForMockTestKawaiiiiiiiiii-NotBaka-ASkksz ANTHROPIC_API_KEY = sk-ant-api11-IamNotARealKeyJustForMockTestKawaiiiiiiiiii-NotBaka-ASkksz

+ 2
- 2
api/services/workflow_app_service.py View File

from sqlalchemy import and_, func, or_, select from sqlalchemy import and_, func, or_, select
from sqlalchemy.orm import Session from sqlalchemy.orm import Session


from core.workflow.entities.workflow_execution import WorkflowExecutionStatus
from models import App, EndUser, WorkflowAppLog, WorkflowRun from models import App, EndUser, WorkflowAppLog, WorkflowRun
from models.enums import CreatorUserRole from models.enums import CreatorUserRole
from models.workflow import WorkflowRunStatus




class WorkflowAppService: class WorkflowAppService:
session: Session, session: Session,
app_model: App, app_model: App,
keyword: str | None = None, keyword: str | None = None,
status: WorkflowRunStatus | None = None,
status: WorkflowExecutionStatus | None = None,
created_at_before: datetime | None = None, created_at_before: datetime | None = None,
created_at_after: datetime | None = None, created_at_after: datetime | None = None,
page: int = 1, page: int = 1,

+ 1
- 1
api/services/workflow_run_service.py View File



import contexts import contexts
from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository from core.repositories import SQLAlchemyWorkflowNodeExecutionRepository
from core.workflow.repository.workflow_node_execution_repository import OrderConfig
from core.workflow.repositories.workflow_node_execution_repository import OrderConfig
from extensions.ext_database import db from extensions.ext_database import db
from libs.infinite_scroll_pagination import InfiniteScrollPagination from libs.infinite_scroll_pagination import InfiniteScrollPagination
from models import ( from models import (

+ 4
- 5
api/services/workflow_service.py View File

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.node_execution_entities import NodeExecution, NodeExecutionStatus
from core.workflow.entities.workflow_node_execution import NodeExecution, 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.workflow import ( from models.workflow import (
Workflow, Workflow,
WorkflowNodeExecution, WorkflowNodeExecution,
WorkflowNodeExecutionStatus,
WorkflowNodeExecutionTriggeredFrom, WorkflowNodeExecutionTriggeredFrom,
WorkflowType, WorkflowType,
) )


# Map status from WorkflowNodeExecutionStatus to NodeExecutionStatus # Map status from WorkflowNodeExecutionStatus to NodeExecutionStatus
if node_run_result.status == WorkflowNodeExecutionStatus.SUCCEEDED: if node_run_result.status == WorkflowNodeExecutionStatus.SUCCEEDED:
node_execution.status = NodeExecutionStatus.SUCCEEDED
node_execution.status = WorkflowNodeExecutionStatus.SUCCEEDED
elif node_run_result.status == WorkflowNodeExecutionStatus.EXCEPTION: elif node_run_result.status == WorkflowNodeExecutionStatus.EXCEPTION:
node_execution.status = NodeExecutionStatus.EXCEPTION
node_execution.status = WorkflowNodeExecutionStatus.EXCEPTION
node_execution.error = node_run_result.error node_execution.error = node_run_result.error
else: else:
# Set failed status and error # Set failed status and error
node_execution.status = NodeExecutionStatus.FAILED
node_execution.status = WorkflowNodeExecutionStatus.FAILED
node_execution.error = error node_execution.error = error


return node_execution return node_execution

+ 2
- 1
api/tests/integration_tests/workflow/nodes/test_code.py View File

from core.app.entities.app_invoke_entities import InvokeFrom from core.app.entities.app_invoke_entities import InvokeFrom
from core.workflow.entities.node_entities import NodeRunResult from core.workflow.entities.node_entities import NodeRunResult
from core.workflow.entities.variable_pool import VariablePool from core.workflow.entities.variable_pool import VariablePool
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from core.workflow.enums import SystemVariableKey from core.workflow.enums import SystemVariableKey
from core.workflow.graph_engine.entities.graph import Graph from core.workflow.graph_engine.entities.graph import Graph
from core.workflow.graph_engine.entities.graph_init_params import GraphInitParams from core.workflow.graph_engine.entities.graph_init_params import GraphInitParams
from core.workflow.nodes.code.code_node import CodeNode from core.workflow.nodes.code.code_node import CodeNode
from core.workflow.nodes.code.entities import CodeNodeData from core.workflow.nodes.code.entities import CodeNodeData
from models.enums import UserFrom from models.enums import UserFrom
from models.workflow import WorkflowNodeExecutionStatus, WorkflowType
from models.workflow import WorkflowType
from tests.integration_tests.workflow.nodes.__mock.code_executor import setup_code_executor_mock from tests.integration_tests.workflow.nodes.__mock.code_executor import setup_code_executor_mock


CODE_MAX_STRING_LENGTH = int(getenv("CODE_MAX_STRING_LENGTH", "10000")) CODE_MAX_STRING_LENGTH = int(getenv("CODE_MAX_STRING_LENGTH", "10000"))

+ 2
- 1
api/tests/integration_tests/workflow/nodes/test_llm.py View File



from core.app.entities.app_invoke_entities import InvokeFrom from core.app.entities.app_invoke_entities import InvokeFrom
from core.workflow.entities.variable_pool import VariablePool from core.workflow.entities.variable_pool import VariablePool
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from core.workflow.enums import SystemVariableKey from core.workflow.enums import SystemVariableKey
from core.workflow.graph_engine.entities.graph import Graph from core.workflow.graph_engine.entities.graph import Graph
from core.workflow.graph_engine.entities.graph_init_params import GraphInitParams from core.workflow.graph_engine.entities.graph_init_params import GraphInitParams
from core.workflow.nodes.llm.node import LLMNode from core.workflow.nodes.llm.node import LLMNode
from extensions.ext_database import db from extensions.ext_database import db
from models.enums import UserFrom from models.enums import UserFrom
from models.workflow import WorkflowNodeExecutionStatus, WorkflowType
from models.workflow import WorkflowType
from tests.integration_tests.workflow.nodes.__mock.model import get_mocked_fetch_model_config from tests.integration_tests.workflow.nodes.__mock.model import get_mocked_fetch_model_config


"""FOR MOCK FIXTURES, DO NOT REMOVE""" """FOR MOCK FIXTURES, DO NOT REMOVE"""

+ 2
- 1
api/tests/integration_tests/workflow/nodes/test_parameter_extractor.py View File

from core.app.entities.app_invoke_entities import InvokeFrom from core.app.entities.app_invoke_entities import InvokeFrom
from core.model_runtime.entities import AssistantPromptMessage from core.model_runtime.entities import AssistantPromptMessage
from core.workflow.entities.variable_pool import VariablePool from core.workflow.entities.variable_pool import VariablePool
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from core.workflow.enums import SystemVariableKey from core.workflow.enums import SystemVariableKey
from core.workflow.graph_engine.entities.graph import Graph from core.workflow.graph_engine.entities.graph import Graph
from core.workflow.graph_engine.entities.graph_init_params import GraphInitParams from core.workflow.graph_engine.entities.graph_init_params import GraphInitParams
from tests.integration_tests.workflow.nodes.__mock.model import get_mocked_fetch_model_config from tests.integration_tests.workflow.nodes.__mock.model import get_mocked_fetch_model_config


"""FOR MOCK FIXTURES, DO NOT REMOVE""" """FOR MOCK FIXTURES, DO NOT REMOVE"""
from models.workflow import WorkflowNodeExecutionStatus, WorkflowType
from models.workflow import WorkflowType
from tests.integration_tests.model_runtime.__mock.plugin_daemon import setup_model_mock from tests.integration_tests.model_runtime.__mock.plugin_daemon import setup_model_mock





+ 2
- 1
api/tests/integration_tests/workflow/nodes/test_template_transform.py View File



from core.app.entities.app_invoke_entities import InvokeFrom from core.app.entities.app_invoke_entities import InvokeFrom
from core.workflow.entities.variable_pool import VariablePool from core.workflow.entities.variable_pool import VariablePool
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from core.workflow.enums import SystemVariableKey from core.workflow.enums import SystemVariableKey
from core.workflow.graph_engine.entities.graph import Graph from core.workflow.graph_engine.entities.graph import Graph
from core.workflow.graph_engine.entities.graph_init_params import GraphInitParams from core.workflow.graph_engine.entities.graph_init_params import GraphInitParams
from core.workflow.graph_engine.entities.graph_runtime_state import GraphRuntimeState from core.workflow.graph_engine.entities.graph_runtime_state import GraphRuntimeState
from core.workflow.nodes.template_transform.template_transform_node import TemplateTransformNode from core.workflow.nodes.template_transform.template_transform_node import TemplateTransformNode
from models.enums import UserFrom from models.enums import UserFrom
from models.workflow import WorkflowNodeExecutionStatus, WorkflowType
from models.workflow import WorkflowType
from tests.integration_tests.workflow.nodes.__mock.code_executor import setup_code_executor_mock from tests.integration_tests.workflow.nodes.__mock.code_executor import setup_code_executor_mock





+ 2
- 1
api/tests/integration_tests/workflow/nodes/test_tool.py View File

from core.app.entities.app_invoke_entities import InvokeFrom from core.app.entities.app_invoke_entities import InvokeFrom
from core.tools.utils.configuration import ToolParameterConfigurationManager from core.tools.utils.configuration import ToolParameterConfigurationManager
from core.workflow.entities.variable_pool import VariablePool from core.workflow.entities.variable_pool import VariablePool
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from core.workflow.enums import SystemVariableKey from core.workflow.enums import SystemVariableKey
from core.workflow.graph_engine.entities.graph import Graph from core.workflow.graph_engine.entities.graph import Graph
from core.workflow.graph_engine.entities.graph_init_params import GraphInitParams from core.workflow.graph_engine.entities.graph_init_params import GraphInitParams
from core.workflow.nodes.event.event import RunCompletedEvent from core.workflow.nodes.event.event import RunCompletedEvent
from core.workflow.nodes.tool.tool_node import ToolNode from core.workflow.nodes.tool.tool_node import ToolNode
from models.enums import UserFrom from models.enums import UserFrom
from models.workflow import WorkflowNodeExecutionStatus, WorkflowType
from models.workflow import WorkflowType




def init_tool_node(config: dict): def init_tool_node(config: dict):

+ 22
- 22
api/tests/unit_tests/core/prompt/test_extract_thread_messages.py View File

from core.prompt.utils.extract_thread_messages import extract_thread_messages from core.prompt.utils.extract_thread_messages import extract_thread_messages




class TestMessage:
class MockMessage:
def __init__(self, id, parent_message_id): def __init__(self, id, parent_message_id):
self.id = id self.id = id
self.parent_message_id = parent_message_id self.parent_message_id = parent_message_id




def test_extract_thread_messages_single_message(): def test_extract_thread_messages_single_message():
messages = [TestMessage(str(uuid4()), UUID_NIL)]
messages = [MockMessage(str(uuid4()), UUID_NIL)]
result = extract_thread_messages(messages) result = extract_thread_messages(messages)
assert len(result) == 1 assert len(result) == 1
assert result[0] == messages[0] assert result[0] == messages[0]
def test_extract_thread_messages_linear_thread(): def test_extract_thread_messages_linear_thread():
id1, id2, id3, id4, id5 = str(uuid4()), str(uuid4()), str(uuid4()), str(uuid4()), str(uuid4()) id1, id2, id3, id4, id5 = str(uuid4()), str(uuid4()), str(uuid4()), str(uuid4()), str(uuid4())
messages = [ messages = [
TestMessage(id5, id4),
TestMessage(id4, id3),
TestMessage(id3, id2),
TestMessage(id2, id1),
TestMessage(id1, UUID_NIL),
MockMessage(id5, id4),
MockMessage(id4, id3),
MockMessage(id3, id2),
MockMessage(id2, id1),
MockMessage(id1, UUID_NIL),
] ]
result = extract_thread_messages(messages) result = extract_thread_messages(messages)
assert len(result) == 5 assert len(result) == 5
def test_extract_thread_messages_branched_thread(): def test_extract_thread_messages_branched_thread():
id1, id2, id3, id4 = str(uuid4()), str(uuid4()), str(uuid4()), str(uuid4()) id1, id2, id3, id4 = str(uuid4()), str(uuid4()), str(uuid4()), str(uuid4())
messages = [ messages = [
TestMessage(id4, id2),
TestMessage(id3, id2),
TestMessage(id2, id1),
TestMessage(id1, UUID_NIL),
MockMessage(id4, id2),
MockMessage(id3, id2),
MockMessage(id2, id1),
MockMessage(id1, UUID_NIL),
] ]
result = extract_thread_messages(messages) result = extract_thread_messages(messages)
assert len(result) == 3 assert len(result) == 3
def test_extract_thread_messages_partially_loaded(): def test_extract_thread_messages_partially_loaded():
id0, id1, id2, id3 = str(uuid4()), str(uuid4()), str(uuid4()), str(uuid4()) id0, id1, id2, id3 = str(uuid4()), str(uuid4()), str(uuid4()), str(uuid4())
messages = [ messages = [
TestMessage(id3, id2),
TestMessage(id2, id1),
TestMessage(id1, id0),
MockMessage(id3, id2),
MockMessage(id2, id1),
MockMessage(id1, id0),
] ]
result = extract_thread_messages(messages) result = extract_thread_messages(messages)
assert len(result) == 3 assert len(result) == 3
def test_extract_thread_messages_legacy_messages(): def test_extract_thread_messages_legacy_messages():
id1, id2, id3 = str(uuid4()), str(uuid4()), str(uuid4()) id1, id2, id3 = str(uuid4()), str(uuid4()), str(uuid4())
messages = [ messages = [
TestMessage(id3, UUID_NIL),
TestMessage(id2, UUID_NIL),
TestMessage(id1, UUID_NIL),
MockMessage(id3, UUID_NIL),
MockMessage(id2, UUID_NIL),
MockMessage(id1, UUID_NIL),
] ]
result = extract_thread_messages(messages) result = extract_thread_messages(messages)
assert len(result) == 3 assert len(result) == 3
def test_extract_thread_messages_mixed_with_legacy_messages(): def test_extract_thread_messages_mixed_with_legacy_messages():
id1, id2, id3, id4, id5 = str(uuid4()), str(uuid4()), str(uuid4()), str(uuid4()), str(uuid4()) id1, id2, id3, id4, id5 = str(uuid4()), str(uuid4()), str(uuid4()), str(uuid4()), str(uuid4())
messages = [ messages = [
TestMessage(id5, id4),
TestMessage(id4, id2),
TestMessage(id3, id2),
TestMessage(id2, UUID_NIL),
TestMessage(id1, UUID_NIL),
MockMessage(id5, id4),
MockMessage(id4, id2),
MockMessage(id3, id2),
MockMessage(id2, UUID_NIL),
MockMessage(id1, UUID_NIL),
] ]
result = extract_thread_messages(messages) result = extract_thread_messages(messages)
assert len(result) == 4 assert len(result) == 4

+ 1
- 1
api/tests/unit_tests/core/rag/datasource/vdb/milvus/test_milvus.py View File

import pytest import pytest
from pydantic.error_wrappers import ValidationError
from pydantic import ValidationError


from core.rag.datasource.vdb.milvus.milvus_vector import MilvusConfig from core.rag.datasource.vdb.milvus.milvus_vector import MilvusConfig



+ 2
- 1
api/tests/unit_tests/core/workflow/graph_engine/test_graph_engine.py View File

from core.app.entities.app_invoke_entities import InvokeFrom from core.app.entities.app_invoke_entities import InvokeFrom
from core.workflow.entities.node_entities import NodeRunMetadataKey, NodeRunResult from core.workflow.entities.node_entities import NodeRunMetadataKey, NodeRunResult
from core.workflow.entities.variable_pool import VariablePool from core.workflow.entities.variable_pool import VariablePool
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from core.workflow.enums import SystemVariableKey from core.workflow.enums import SystemVariableKey
from core.workflow.graph_engine.entities.event import ( from core.workflow.graph_engine.entities.event import (
BaseNodeEvent, BaseNodeEvent,
from core.workflow.nodes.llm.node import LLMNode from core.workflow.nodes.llm.node import LLMNode
from core.workflow.nodes.question_classifier.question_classifier_node import QuestionClassifierNode from core.workflow.nodes.question_classifier.question_classifier_node import QuestionClassifierNode
from models.enums import UserFrom from models.enums import UserFrom
from models.workflow import WorkflowNodeExecutionStatus, WorkflowType
from models.workflow import WorkflowType




@pytest.fixture @pytest.fixture

+ 2
- 1
api/tests/unit_tests/core/workflow/nodes/answer/test_answer.py View File



from core.app.entities.app_invoke_entities import InvokeFrom from core.app.entities.app_invoke_entities import InvokeFrom
from core.workflow.entities.variable_pool import VariablePool from core.workflow.entities.variable_pool import VariablePool
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from core.workflow.enums import SystemVariableKey from core.workflow.enums import SystemVariableKey
from core.workflow.graph_engine.entities.graph import Graph from core.workflow.graph_engine.entities.graph import Graph
from core.workflow.graph_engine.entities.graph_init_params import GraphInitParams from core.workflow.graph_engine.entities.graph_init_params import GraphInitParams
from core.workflow.nodes.answer.answer_node import AnswerNode from core.workflow.nodes.answer.answer_node import AnswerNode
from extensions.ext_database import db from extensions.ext_database import db
from models.enums import UserFrom from models.enums import UserFrom
from models.workflow import WorkflowNodeExecutionStatus, WorkflowType
from models.workflow import WorkflowType




def test_execute_answer(): def test_execute_answer():

+ 2
- 1
api/tests/unit_tests/core/workflow/nodes/http_request/test_http_request_node.py View File

from core.file import File, FileTransferMethod, FileType from core.file import File, FileTransferMethod, FileType
from core.variables import ArrayFileVariable, FileVariable from core.variables import ArrayFileVariable, FileVariable
from core.workflow.entities.variable_pool import VariablePool from core.workflow.entities.variable_pool import VariablePool
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from core.workflow.graph_engine import Graph, GraphInitParams, GraphRuntimeState from core.workflow.graph_engine import Graph, GraphInitParams, GraphRuntimeState
from core.workflow.nodes.answer import AnswerStreamGenerateRoute from core.workflow.nodes.answer import AnswerStreamGenerateRoute
from core.workflow.nodes.end import EndStreamParam from core.workflow.nodes.end import EndStreamParam
HttpRequestNodeData, HttpRequestNodeData,
) )
from models.enums import UserFrom from models.enums import UserFrom
from models.workflow import WorkflowNodeExecutionStatus, WorkflowType
from models.workflow import WorkflowType




def test_http_request_node_binary_file(monkeypatch): def test_http_request_node_binary_file(monkeypatch):

+ 2
- 1
api/tests/unit_tests/core/workflow/nodes/iteration/test_iteration.py View File

from core.app.entities.app_invoke_entities import InvokeFrom from core.app.entities.app_invoke_entities import InvokeFrom
from core.workflow.entities.node_entities import NodeRunResult from core.workflow.entities.node_entities import NodeRunResult
from core.workflow.entities.variable_pool import VariablePool from core.workflow.entities.variable_pool import VariablePool
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from core.workflow.enums import SystemVariableKey from core.workflow.enums import SystemVariableKey
from core.workflow.graph_engine.entities.graph import Graph from core.workflow.graph_engine.entities.graph import Graph
from core.workflow.graph_engine.entities.graph_init_params import GraphInitParams from core.workflow.graph_engine.entities.graph_init_params import GraphInitParams
from core.workflow.nodes.iteration.iteration_node import IterationNode from core.workflow.nodes.iteration.iteration_node import IterationNode
from core.workflow.nodes.template_transform.template_transform_node import TemplateTransformNode from core.workflow.nodes.template_transform.template_transform_node import TemplateTransformNode
from models.enums import UserFrom from models.enums import UserFrom
from models.workflow import WorkflowNodeExecutionStatus, WorkflowType
from models.workflow import WorkflowType




def test_run(): def test_run():

+ 2
- 1
api/tests/unit_tests/core/workflow/nodes/test_answer.py View File



from core.app.entities.app_invoke_entities import InvokeFrom from core.app.entities.app_invoke_entities import InvokeFrom
from core.workflow.entities.variable_pool import VariablePool from core.workflow.entities.variable_pool import VariablePool
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from core.workflow.enums import SystemVariableKey from core.workflow.enums import SystemVariableKey
from core.workflow.graph_engine.entities.graph import Graph from core.workflow.graph_engine.entities.graph import Graph
from core.workflow.graph_engine.entities.graph_init_params import GraphInitParams from core.workflow.graph_engine.entities.graph_init_params import GraphInitParams
from core.workflow.nodes.answer.answer_node import AnswerNode from core.workflow.nodes.answer.answer_node import AnswerNode
from extensions.ext_database import db from extensions.ext_database import db
from models.enums import UserFrom from models.enums import UserFrom
from models.workflow import WorkflowNodeExecutionStatus, WorkflowType
from models.workflow import WorkflowType




def test_execute_answer(): def test_execute_answer():

+ 2
- 1
api/tests/unit_tests/core/workflow/nodes/test_continue_on_error.py View File



from core.app.entities.app_invoke_entities import InvokeFrom from core.app.entities.app_invoke_entities import InvokeFrom
from core.workflow.entities.node_entities import NodeRunMetadataKey, NodeRunResult from core.workflow.entities.node_entities import NodeRunMetadataKey, NodeRunResult
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from core.workflow.enums import SystemVariableKey from core.workflow.enums import SystemVariableKey
from core.workflow.graph_engine.entities.event import ( from core.workflow.graph_engine.entities.event import (
GraphRunPartialSucceededEvent, GraphRunPartialSucceededEvent,
from core.workflow.nodes.event.event import RunCompletedEvent, RunStreamChunkEvent from core.workflow.nodes.event.event import RunCompletedEvent, RunStreamChunkEvent
from core.workflow.nodes.llm.node import LLMNode from core.workflow.nodes.llm.node import LLMNode
from models.enums import UserFrom from models.enums import UserFrom
from models.workflow import WorkflowNodeExecutionStatus, WorkflowType
from models.workflow import WorkflowType




class ContinueOnErrorTestHelper: class ContinueOnErrorTestHelper:

+ 1
- 1
api/tests/unit_tests/core/workflow/nodes/test_document_extractor_node.py View File

from core.variables import ArrayFileSegment from core.variables import ArrayFileSegment
from core.variables.variables import StringVariable from core.variables.variables import StringVariable
from core.workflow.entities.node_entities import NodeRunResult from core.workflow.entities.node_entities import NodeRunResult
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from core.workflow.nodes.document_extractor import DocumentExtractorNode, DocumentExtractorNodeData from core.workflow.nodes.document_extractor import DocumentExtractorNode, DocumentExtractorNodeData
from core.workflow.nodes.document_extractor.node import ( from core.workflow.nodes.document_extractor.node import (
_extract_text_from_docx, _extract_text_from_docx,
_extract_text_from_plain_text, _extract_text_from_plain_text,
) )
from core.workflow.nodes.enums import NodeType from core.workflow.nodes.enums import NodeType
from models.workflow import WorkflowNodeExecutionStatus




@pytest.fixture @pytest.fixture

+ 2
- 1
api/tests/unit_tests/core/workflow/nodes/test_if_else.py View File

from core.file import File, FileTransferMethod, FileType from core.file import File, FileTransferMethod, FileType
from core.variables import ArrayFileSegment from core.variables import ArrayFileSegment
from core.workflow.entities.variable_pool import VariablePool from core.workflow.entities.variable_pool import VariablePool
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from core.workflow.enums import SystemVariableKey from core.workflow.enums import SystemVariableKey
from core.workflow.graph_engine.entities.graph import Graph from core.workflow.graph_engine.entities.graph import Graph
from core.workflow.graph_engine.entities.graph_init_params import GraphInitParams from core.workflow.graph_engine.entities.graph_init_params import GraphInitParams
from core.workflow.utils.condition.entities import Condition, SubCondition, SubVariableCondition from core.workflow.utils.condition.entities import Condition, SubCondition, SubVariableCondition
from extensions.ext_database import db from extensions.ext_database import db
from models.enums import UserFrom from models.enums import UserFrom
from models.workflow import WorkflowNodeExecutionStatus, WorkflowType
from models.workflow import WorkflowType




def test_execute_if_else_result_true(): def test_execute_if_else_result_true():

+ 1
- 1
api/tests/unit_tests/core/workflow/nodes/test_list_operator.py View File



from core.file import File, FileTransferMethod, FileType from core.file import File, FileTransferMethod, FileType
from core.variables import ArrayFileSegment from core.variables import ArrayFileSegment
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from core.workflow.nodes.list_operator.entities import ( from core.workflow.nodes.list_operator.entities import (
ExtractConfig, ExtractConfig,
FilterBy, FilterBy,
) )
from core.workflow.nodes.list_operator.exc import InvalidKeyError from core.workflow.nodes.list_operator.exc import InvalidKeyError
from core.workflow.nodes.list_operator.node import ListOperatorNode, _get_file_extract_string_func from core.workflow.nodes.list_operator.node import ListOperatorNode, _get_file_extract_string_func
from models.workflow import WorkflowNodeExecutionStatus




@pytest.fixture @pytest.fixture

+ 2
- 1
api/tests/unit_tests/core/workflow/nodes/tool/test_tool_node.py View File

from core.tools.errors import ToolInvokeError from core.tools.errors import ToolInvokeError
from core.workflow.entities.node_entities import NodeRunResult from core.workflow.entities.node_entities import NodeRunResult
from core.workflow.entities.variable_pool import VariablePool from core.workflow.entities.variable_pool import VariablePool
from core.workflow.entities.workflow_node_execution import WorkflowNodeExecutionStatus
from core.workflow.graph_engine import Graph, GraphInitParams, GraphRuntimeState from core.workflow.graph_engine import Graph, GraphInitParams, GraphRuntimeState
from core.workflow.nodes.answer import AnswerStreamGenerateRoute from core.workflow.nodes.answer import AnswerStreamGenerateRoute
from core.workflow.nodes.end import EndStreamParam from core.workflow.nodes.end import EndStreamParam
from core.workflow.nodes.event import RunCompletedEvent from core.workflow.nodes.event import RunCompletedEvent
from core.workflow.nodes.tool import ToolNode from core.workflow.nodes.tool import ToolNode
from core.workflow.nodes.tool.entities import ToolNodeData from core.workflow.nodes.tool.entities import ToolNodeData
from models import UserFrom, WorkflowNodeExecutionStatus, WorkflowType
from models import UserFrom, WorkflowType




def _create_tool_node(): def _create_tool_node():

+ 53
- 44
api/tests/unit_tests/core/workflow/test_workflow_cycle_manager.py View File

QueueNodeStartedEvent, QueueNodeStartedEvent,
QueueNodeSucceededEvent, QueueNodeSucceededEvent,
) )
from core.workflow.entities.node_entities import NodeRunMetadataKey
from core.workflow.entities.node_execution_entities import NodeExecution, NodeExecutionStatus
from core.workflow.entities.workflow_execution_entities import WorkflowExecution, WorkflowExecutionStatus, WorkflowType
from core.workflow.entities.workflow_execution import WorkflowExecution, WorkflowExecutionStatus, WorkflowType
from core.workflow.entities.workflow_node_execution import (
NodeExecution,
NodeRunMetadataKey,
WorkflowNodeExecutionStatus,
)
from core.workflow.enums import SystemVariableKey from core.workflow.enums import SystemVariableKey
from core.workflow.nodes import NodeType from core.workflow.nodes import NodeType
from core.workflow.repository.workflow_execution_repository import WorkflowExecutionRepository
from core.workflow.repository.workflow_node_execution_repository import WorkflowNodeExecutionRepository
from core.workflow.workflow_cycle_manager import WorkflowCycleManager
from core.workflow.repositories.workflow_execution_repository import WorkflowExecutionRepository
from core.workflow.repositories.workflow_node_execution_repository import WorkflowNodeExecutionRepository
from core.workflow.workflow_cycle_manager import CycleManagerWorkflowInfo, WorkflowCycleManager
from models.enums import CreatorUserRole from models.enums import CreatorUserRole
from models.model import AppMode from models.model import AppMode
from models.workflow import (
Workflow,
WorkflowRun,
WorkflowRunStatus,
)
from models.workflow import Workflow, WorkflowRun




@pytest.fixture @pytest.fixture
return repo return repo




@pytest.fixture
def real_workflow_entity():
return CycleManagerWorkflowInfo(
workflow_id="test-workflow-id", # Matches ID used in other fixtures
workflow_type=WorkflowType.CHAT,
version="1.0.0",
graph_data={
"nodes": [
{
"id": "node1",
"type": "chat", # NodeType is a string enum
"name": "Chat Node",
"data": {"model": "gpt-3.5-turbo", "prompt": "test prompt"},
}
],
"edges": [],
},
)


@pytest.fixture @pytest.fixture
def workflow_cycle_manager( def workflow_cycle_manager(
real_app_generate_entity, real_app_generate_entity,
real_workflow_system_variables, real_workflow_system_variables,
mock_workflow_execution_repository, mock_workflow_execution_repository,
mock_node_execution_repository, mock_node_execution_repository,
real_workflow_entity,
): ):
return WorkflowCycleManager( return WorkflowCycleManager(
application_generate_entity=real_app_generate_entity, application_generate_entity=real_app_generate_entity,
workflow_system_variables=real_workflow_system_variables, workflow_system_variables=real_workflow_system_variables,
workflow_info=real_workflow_entity,
workflow_execution_repository=mock_workflow_execution_repository, workflow_execution_repository=mock_workflow_execution_repository,
workflow_node_execution_repository=mock_node_execution_repository, workflow_node_execution_repository=mock_node_execution_repository,
) )
workflow_run.version = "1.0" workflow_run.version = "1.0"
workflow_run.graph = json.dumps({"nodes": [], "edges": []}) workflow_run.graph = json.dumps({"nodes": [], "edges": []})
workflow_run.inputs = json.dumps({"query": "test query"}) workflow_run.inputs = json.dumps({"query": "test query"})
workflow_run.status = WorkflowRunStatus.RUNNING
workflow_run.status = WorkflowExecutionStatus.RUNNING
workflow_run.outputs = json.dumps({"answer": "test answer"}) workflow_run.outputs = json.dumps({"answer": "test answer"})
workflow_run.created_by_role = CreatorUserRole.ACCOUNT workflow_run.created_by_role = CreatorUserRole.ACCOUNT
workflow_run.created_by = "test-user-id" workflow_run.created_by = "test-user-id"
assert workflow_cycle_manager._workflow_node_execution_repository == mock_node_execution_repository assert workflow_cycle_manager._workflow_node_execution_repository == mock_node_execution_repository




def test_handle_workflow_run_start(workflow_cycle_manager, mock_session, real_workflow):
def test_handle_workflow_run_start(workflow_cycle_manager):
"""Test handle_workflow_run_start method""" """Test handle_workflow_run_start method"""
# Mock session.scalar to return the workflow and max sequence
mock_session.scalar.side_effect = [real_workflow, 5]

# Call the method # Call the method
workflow_execution = workflow_cycle_manager.handle_workflow_run_start(
session=mock_session,
workflow_id="test-workflow-id",
)
workflow_execution = workflow_cycle_manager.handle_workflow_run_start()


# Verify the result # Verify the result
assert workflow_execution.workflow_id == real_workflow.id
assert workflow_execution.sequence_number == 6 # max_sequence + 1
assert workflow_execution.workflow_id == "test-workflow-id"


# Verify the workflow_execution_repository.save was called # Verify the workflow_execution_repository.save was called
workflow_cycle_manager._workflow_execution_repository.save.assert_called_once_with(workflow_execution) workflow_cycle_manager._workflow_execution_repository.save.assert_called_once_with(workflow_execution)
# Create a real WorkflowExecution # Create a real WorkflowExecution


workflow_execution = WorkflowExecution( workflow_execution = WorkflowExecution(
id="test-workflow-run-id",
id_="test-workflow-run-id",
workflow_id="test-workflow-id", workflow_id="test-workflow-id",
workflow_version="1.0", workflow_version="1.0",
sequence_number=1,
type=WorkflowType.CHAT,
workflow_type=WorkflowType.CHAT,
graph={"nodes": [], "edges": []}, graph={"nodes": [], "edges": []},
inputs={"query": "test query"}, inputs={"query": "test query"},
started_at=datetime.now(UTC).replace(tzinfo=None), started_at=datetime.now(UTC).replace(tzinfo=None),
# Create a real WorkflowExecution # Create a real WorkflowExecution


workflow_execution = WorkflowExecution( workflow_execution = WorkflowExecution(
id="test-workflow-run-id",
id_="test-workflow-run-id",
workflow_id="test-workflow-id", workflow_id="test-workflow-id",
workflow_version="1.0", workflow_version="1.0",
sequence_number=1,
type=WorkflowType.CHAT,
workflow_type=WorkflowType.CHAT,
graph={"nodes": [], "edges": []}, graph={"nodes": [], "edges": []},
inputs={"query": "test query"}, inputs={"query": "test query"},
started_at=datetime.now(UTC).replace(tzinfo=None), started_at=datetime.now(UTC).replace(tzinfo=None),
workflow_run_id="test-workflow-run-id", workflow_run_id="test-workflow-run-id",
total_tokens=50, total_tokens=50,
total_steps=3, total_steps=3,
status=WorkflowRunStatus.FAILED,
status=WorkflowExecutionStatus.FAILED,
error_message="Test error message", error_message="Test error message",
) )


# Verify the result # Verify the result
assert result == workflow_execution assert result == workflow_execution
assert result.status == WorkflowExecutionStatus(WorkflowRunStatus.FAILED.value)
assert result.status == WorkflowExecutionStatus.FAILED
assert result.error_message == "Test error message" assert result.error_message == "Test error message"
assert result.total_tokens == 50 assert result.total_tokens == 50
assert result.total_steps == 3 assert result.total_steps == 3
# Create a real WorkflowExecution # Create a real WorkflowExecution


workflow_execution = WorkflowExecution( workflow_execution = WorkflowExecution(
id="test-workflow-execution-id",
id_="test-workflow-execution-id",
workflow_id="test-workflow-id", workflow_id="test-workflow-id",
workflow_version="1.0", workflow_version="1.0",
sequence_number=1,
type=WorkflowType.CHAT,
workflow_type=WorkflowType.CHAT,
graph={"nodes": [], "edges": []}, graph={"nodes": [], "edges": []},
inputs={"query": "test query"}, inputs={"query": "test query"},
started_at=datetime.now(UTC).replace(tzinfo=None), started_at=datetime.now(UTC).replace(tzinfo=None),


# Call the method # Call the method
result = workflow_cycle_manager.handle_node_execution_start( result = workflow_cycle_manager.handle_node_execution_start(
workflow_execution_id=workflow_execution.id,
workflow_execution_id=workflow_execution.id_,
event=event, event=event,
) )


# Verify the result # Verify the result
assert result.workflow_id == workflow_execution.workflow_id assert result.workflow_id == workflow_execution.workflow_id
assert result.workflow_run_id == workflow_execution.id
assert result.workflow_run_id == workflow_execution.id_
assert result.node_execution_id == event.node_execution_id assert result.node_execution_id == event.node_execution_id
assert result.node_id == event.node_id assert result.node_id == event.node_id
assert result.node_type == event.node_type assert result.node_type == event.node_type
assert result.title == event.node_data.title assert result.title == event.node_data.title
assert result.status == NodeExecutionStatus.RUNNING
assert result.status == WorkflowNodeExecutionStatus.RUNNING


# Verify save was called # Verify save was called
workflow_cycle_manager._workflow_node_execution_repository.save.assert_called_once_with(result) workflow_cycle_manager._workflow_node_execution_repository.save.assert_called_once_with(result)
# Create a real WorkflowExecution # Create a real WorkflowExecution


workflow_execution = WorkflowExecution( workflow_execution = WorkflowExecution(
id="test-workflow-run-id",
id_="test-workflow-run-id",
workflow_id="test-workflow-id", workflow_id="test-workflow-id",
workflow_version="1.0", workflow_version="1.0",
sequence_number=1,
type=WorkflowType.CHAT,
workflow_type=WorkflowType.CHAT,
graph={"nodes": [], "edges": []}, graph={"nodes": [], "edges": []},
inputs={"query": "test query"}, inputs={"query": "test query"},
started_at=datetime.now(UTC).replace(tzinfo=None), started_at=datetime.now(UTC).replace(tzinfo=None),


# Verify the result # Verify the result
assert result == node_execution assert result == node_execution
assert result.status == NodeExecutionStatus.SUCCEEDED
assert result.status == WorkflowNodeExecutionStatus.SUCCEEDED


# Verify save was called # Verify save was called
workflow_cycle_manager._workflow_node_execution_repository.save.assert_called_once_with(node_execution) workflow_cycle_manager._workflow_node_execution_repository.save.assert_called_once_with(node_execution)
# Create a real WorkflowExecution # Create a real WorkflowExecution


workflow_execution = WorkflowExecution( workflow_execution = WorkflowExecution(
id="test-workflow-run-id",
id_="test-workflow-run-id",
workflow_id="test-workflow-id", workflow_id="test-workflow-id",
workflow_version="1.0", workflow_version="1.0",
sequence_number=1,
type=WorkflowType.CHAT,
workflow_type=WorkflowType.CHAT,
graph={"nodes": [], "edges": []}, graph={"nodes": [], "edges": []},
inputs={"query": "test query"}, inputs={"query": "test query"},
started_at=datetime.now(UTC).replace(tzinfo=None), started_at=datetime.now(UTC).replace(tzinfo=None),


# Verify the result # Verify the result
assert result == node_execution assert result == node_execution
assert result.status == NodeExecutionStatus.FAILED
assert result.status == WorkflowNodeExecutionStatus.FAILED
assert result.error == "Test error message" assert result.error == "Test error message"


# Verify save was called # Verify save was called

+ 9
- 6
api/tests/unit_tests/repositories/workflow_node_execution/test_sqlalchemy_repository.py View File



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.node_entities import NodeRunMetadataKey
from core.workflow.entities.node_execution_entities import NodeExecution, NodeExecutionStatus
from core.workflow.entities.workflow_node_execution import (
NodeExecution,
NodeRunMetadataKey,
WorkflowNodeExecutionStatus,
)
from core.workflow.nodes.enums import NodeType from core.workflow.nodes.enums import NodeType
from core.workflow.repository.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, WorkflowNodeExecutionStatus, WorkflowNodeExecutionTriggeredFrom
from models.workflow import WorkflowNodeExecution, WorkflowNodeExecutionTriggeredFrom




def configure_mock_execution(mock_execution): def configure_mock_execution(mock_execution):
inputs={"input_key": "input_value"}, inputs={"input_key": "input_value"},
process_data={"process_key": "process_value"}, process_data={"process_key": "process_value"},
outputs={"output_key": "output_value"}, outputs={"output_key": "output_value"},
status=NodeExecutionStatus.RUNNING,
status=WorkflowNodeExecutionStatus.RUNNING,
error=None, error=None,
elapsed_time=1.5, elapsed_time=1.5,
metadata={NodeRunMetadataKey.TOTAL_TOKENS: 100, NodeRunMetadataKey.TOTAL_PRICE: Decimal("0.0")}, metadata={NodeRunMetadataKey.TOTAL_TOKENS: 100, NodeRunMetadataKey.TOTAL_PRICE: Decimal("0.0")},
assert domain_model.inputs == inputs_dict assert domain_model.inputs == inputs_dict
assert domain_model.process_data == process_data_dict assert domain_model.process_data == process_data_dict
assert domain_model.outputs == outputs_dict assert domain_model.outputs == outputs_dict
assert domain_model.status == NodeExecutionStatus(db_model.status)
assert domain_model.status == WorkflowNodeExecutionStatus(db_model.status)
assert domain_model.error == db_model.error assert domain_model.error == db_model.error
assert domain_model.elapsed_time == db_model.elapsed_time assert domain_model.elapsed_time == db_model.elapsed_time
assert domain_model.metadata == metadata_dict assert domain_model.metadata == metadata_dict

Loading…
Cancel
Save