mirror of
https://github.com/langgenius/dify.git
synced 2026-04-28 20:17:29 +08:00
feat/trigger: support specifying root node (#24388)
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
6aed7e3ff4
commit
a7b558b38b
@ -55,6 +55,7 @@ class WorkflowAppGenerator(BaseAppGenerator):
|
|||||||
call_depth: int,
|
call_depth: int,
|
||||||
workflow_thread_pool_id: Optional[str],
|
workflow_thread_pool_id: Optional[str],
|
||||||
triggered_from: Optional[WorkflowRunTriggeredFrom] = None,
|
triggered_from: Optional[WorkflowRunTriggeredFrom] = None,
|
||||||
|
root_node_id: Optional[str] = None,
|
||||||
) -> Generator[Mapping | str, None, None]: ...
|
) -> Generator[Mapping | str, None, None]: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
@ -70,6 +71,7 @@ class WorkflowAppGenerator(BaseAppGenerator):
|
|||||||
call_depth: int,
|
call_depth: int,
|
||||||
workflow_thread_pool_id: Optional[str],
|
workflow_thread_pool_id: Optional[str],
|
||||||
triggered_from: Optional[WorkflowRunTriggeredFrom] = None,
|
triggered_from: Optional[WorkflowRunTriggeredFrom] = None,
|
||||||
|
root_node_id: Optional[str] = None,
|
||||||
) -> Mapping[str, Any]: ...
|
) -> Mapping[str, Any]: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
@ -85,6 +87,7 @@ class WorkflowAppGenerator(BaseAppGenerator):
|
|||||||
call_depth: int,
|
call_depth: int,
|
||||||
workflow_thread_pool_id: Optional[str],
|
workflow_thread_pool_id: Optional[str],
|
||||||
triggered_from: Optional[WorkflowRunTriggeredFrom] = None,
|
triggered_from: Optional[WorkflowRunTriggeredFrom] = None,
|
||||||
|
root_node_id: Optional[str] = None,
|
||||||
) -> Union[Mapping[str, Any], Generator[Mapping | str, None, None]]: ...
|
) -> Union[Mapping[str, Any], Generator[Mapping | str, None, None]]: ...
|
||||||
|
|
||||||
def generate(
|
def generate(
|
||||||
@ -99,6 +102,7 @@ class WorkflowAppGenerator(BaseAppGenerator):
|
|||||||
call_depth: int = 0,
|
call_depth: int = 0,
|
||||||
workflow_thread_pool_id: Optional[str] = None,
|
workflow_thread_pool_id: Optional[str] = None,
|
||||||
triggered_from: Optional[WorkflowRunTriggeredFrom] = None,
|
triggered_from: Optional[WorkflowRunTriggeredFrom] = None,
|
||||||
|
root_node_id: Optional[str] = None,
|
||||||
) -> Union[Mapping[str, Any], Generator[Mapping | str, None, None]]:
|
) -> Union[Mapping[str, Any], Generator[Mapping | str, None, None]]:
|
||||||
files: Sequence[Mapping[str, Any]] = args.get("files") or []
|
files: Sequence[Mapping[str, Any]] = args.get("files") or []
|
||||||
|
|
||||||
@ -194,6 +198,7 @@ class WorkflowAppGenerator(BaseAppGenerator):
|
|||||||
workflow_node_execution_repository=workflow_node_execution_repository,
|
workflow_node_execution_repository=workflow_node_execution_repository,
|
||||||
streaming=streaming,
|
streaming=streaming,
|
||||||
workflow_thread_pool_id=workflow_thread_pool_id,
|
workflow_thread_pool_id=workflow_thread_pool_id,
|
||||||
|
root_node_id=root_node_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _generate(
|
def _generate(
|
||||||
@ -209,6 +214,7 @@ class WorkflowAppGenerator(BaseAppGenerator):
|
|||||||
streaming: bool = True,
|
streaming: bool = True,
|
||||||
workflow_thread_pool_id: Optional[str] = None,
|
workflow_thread_pool_id: Optional[str] = None,
|
||||||
variable_loader: VariableLoader = DUMMY_VARIABLE_LOADER,
|
variable_loader: VariableLoader = DUMMY_VARIABLE_LOADER,
|
||||||
|
root_node_id: Optional[str] = None,
|
||||||
) -> Union[Mapping[str, Any], Generator[str | Mapping[str, Any], None, None]]:
|
) -> Union[Mapping[str, Any], Generator[str | Mapping[str, Any], None, None]]:
|
||||||
"""
|
"""
|
||||||
Generate App response.
|
Generate App response.
|
||||||
@ -246,6 +252,7 @@ class WorkflowAppGenerator(BaseAppGenerator):
|
|||||||
"context": context,
|
"context": context,
|
||||||
"workflow_thread_pool_id": workflow_thread_pool_id,
|
"workflow_thread_pool_id": workflow_thread_pool_id,
|
||||||
"variable_loader": variable_loader,
|
"variable_loader": variable_loader,
|
||||||
|
"root_node_id": root_node_id,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -442,6 +449,7 @@ class WorkflowAppGenerator(BaseAppGenerator):
|
|||||||
context: contextvars.Context,
|
context: contextvars.Context,
|
||||||
variable_loader: VariableLoader,
|
variable_loader: VariableLoader,
|
||||||
workflow_thread_pool_id: Optional[str] = None,
|
workflow_thread_pool_id: Optional[str] = None,
|
||||||
|
root_node_id: Optional[str] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Generate worker in a new thread.
|
Generate worker in a new thread.
|
||||||
@ -485,6 +493,7 @@ class WorkflowAppGenerator(BaseAppGenerator):
|
|||||||
variable_loader=variable_loader,
|
variable_loader=variable_loader,
|
||||||
workflow=workflow,
|
workflow=workflow,
|
||||||
system_user_id=system_user_id,
|
system_user_id=system_user_id,
|
||||||
|
root_node_id=root_node_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -34,6 +34,7 @@ class WorkflowAppRunner(WorkflowBasedAppRunner):
|
|||||||
workflow_thread_pool_id: Optional[str] = None,
|
workflow_thread_pool_id: Optional[str] = None,
|
||||||
workflow: Workflow,
|
workflow: Workflow,
|
||||||
system_user_id: str,
|
system_user_id: str,
|
||||||
|
root_node_id: Optional[str] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
queue_manager=queue_manager,
|
queue_manager=queue_manager,
|
||||||
@ -44,6 +45,7 @@ class WorkflowAppRunner(WorkflowBasedAppRunner):
|
|||||||
self.workflow_thread_pool_id = workflow_thread_pool_id
|
self.workflow_thread_pool_id = workflow_thread_pool_id
|
||||||
self._workflow = workflow
|
self._workflow = workflow
|
||||||
self._sys_user_id = system_user_id
|
self._sys_user_id = system_user_id
|
||||||
|
self._root_node_id = root_node_id
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
"""
|
"""
|
||||||
@ -93,7 +95,7 @@ class WorkflowAppRunner(WorkflowBasedAppRunner):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# init graph
|
# init graph
|
||||||
graph = self._init_graph(graph_config=self._workflow.graph_dict)
|
graph = self._init_graph(graph_config=self._workflow.graph_dict, root_node_id=self._root_node_id)
|
||||||
|
|
||||||
# RUN WORKFLOW
|
# RUN WORKFLOW
|
||||||
workflow_entry = WorkflowEntry(
|
workflow_entry = WorkflowEntry(
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
from typing import Any, cast
|
from typing import Any, Optional, cast
|
||||||
|
|
||||||
from core.app.apps.base_app_queue_manager import AppQueueManager, PublishFrom
|
from core.app.apps.base_app_queue_manager import AppQueueManager, PublishFrom
|
||||||
from core.app.entities.queue_entities import (
|
from core.app.entities.queue_entities import (
|
||||||
@ -79,7 +79,7 @@ class WorkflowBasedAppRunner:
|
|||||||
self._variable_loader = variable_loader
|
self._variable_loader = variable_loader
|
||||||
self._app_id = app_id
|
self._app_id = app_id
|
||||||
|
|
||||||
def _init_graph(self, graph_config: Mapping[str, Any]) -> Graph:
|
def _init_graph(self, graph_config: Mapping[str, Any], root_node_id: Optional[str] = None) -> Graph:
|
||||||
"""
|
"""
|
||||||
Init graph
|
Init graph
|
||||||
"""
|
"""
|
||||||
@ -92,7 +92,7 @@ class WorkflowBasedAppRunner:
|
|||||||
if not isinstance(graph_config.get("edges"), list):
|
if not isinstance(graph_config.get("edges"), list):
|
||||||
raise ValueError("edges in workflow graph must be a list")
|
raise ValueError("edges in workflow graph must be a list")
|
||||||
# init graph
|
# init graph
|
||||||
graph = Graph.init(graph_config=graph_config)
|
graph = Graph.init(graph_config=graph_config, root_node_id=root_node_id)
|
||||||
|
|
||||||
if not graph:
|
if not graph:
|
||||||
raise ValueError("graph not found in workflow")
|
raise ValueError("graph not found in workflow")
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
"""empty message
|
"""empty message
|
||||||
|
|
||||||
Revision ID: 994bdf7197ab
|
Revision ID: 4558cfabe44e
|
||||||
Revises: fa8b0fa6f407
|
Revises: fa8b0fa6f407
|
||||||
Create Date: 2025-08-23 20:06:35.995973
|
Create Date: 2025-08-23 20:38:20.059323
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from alembic import op
|
from alembic import op
|
||||||
@ -11,7 +11,7 @@ import sqlalchemy as sa
|
|||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision = '994bdf7197ab'
|
revision = '4558cfabe44e'
|
||||||
down_revision = 'fa8b0fa6f407'
|
down_revision = 'fa8b0fa6f407'
|
||||||
branch_labels = None
|
branch_labels = None
|
||||||
depends_on = None
|
depends_on = None
|
||||||
@ -25,6 +25,7 @@ def upgrade():
|
|||||||
sa.Column('app_id', models.types.StringUUID(), nullable=False),
|
sa.Column('app_id', models.types.StringUUID(), nullable=False),
|
||||||
sa.Column('workflow_id', models.types.StringUUID(), nullable=False),
|
sa.Column('workflow_id', models.types.StringUUID(), nullable=False),
|
||||||
sa.Column('workflow_run_id', models.types.StringUUID(), nullable=True),
|
sa.Column('workflow_run_id', models.types.StringUUID(), nullable=True),
|
||||||
|
sa.Column('root_node_id', sa.String(length=255), nullable=True),
|
||||||
sa.Column('trigger_type', sa.String(length=50), nullable=False),
|
sa.Column('trigger_type', sa.String(length=50), nullable=False),
|
||||||
sa.Column('trigger_data', sa.Text(), nullable=False),
|
sa.Column('trigger_data', sa.Text(), nullable=False),
|
||||||
sa.Column('inputs', sa.Text(), nullable=False),
|
sa.Column('inputs', sa.Text(), nullable=False),
|
||||||
@ -1288,6 +1288,7 @@ class WorkflowTriggerLog(Base):
|
|||||||
- app_id (uuid) App ID
|
- app_id (uuid) App ID
|
||||||
- workflow_id (uuid) Workflow ID
|
- workflow_id (uuid) Workflow ID
|
||||||
- workflow_run_id (uuid) Optional - Associated workflow run ID when execution starts
|
- workflow_run_id (uuid) Optional - Associated workflow run ID when execution starts
|
||||||
|
- root_node_id (string) Optional - Custom starting node ID for workflow execution
|
||||||
- trigger_type (string) Type of trigger: webhook, schedule, plugin
|
- trigger_type (string) Type of trigger: webhook, schedule, plugin
|
||||||
- trigger_data (text) Full trigger data including inputs (JSON)
|
- trigger_data (text) Full trigger data including inputs (JSON)
|
||||||
- inputs (text) Input parameters (JSON)
|
- inputs (text) Input parameters (JSON)
|
||||||
@ -1321,6 +1322,7 @@ class WorkflowTriggerLog(Base):
|
|||||||
app_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
|
app_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
|
||||||
workflow_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
|
workflow_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
|
||||||
workflow_run_id: Mapped[Optional[str]] = mapped_column(StringUUID, nullable=True)
|
workflow_run_id: Mapped[Optional[str]] = mapped_column(StringUUID, nullable=True)
|
||||||
|
root_node_id: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
|
||||||
|
|
||||||
trigger_type: Mapped[str] = mapped_column(String(50), nullable=False)
|
trigger_type: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||||
trigger_data: Mapped[str] = mapped_column(sa.Text, nullable=False) # Full TriggerData as JSON
|
trigger_data: Mapped[str] = mapped_column(sa.Text, nullable=False) # Full TriggerData as JSON
|
||||||
|
|||||||
@ -110,6 +110,7 @@ class AsyncWorkflowService:
|
|||||||
tenant_id=trigger_data.tenant_id,
|
tenant_id=trigger_data.tenant_id,
|
||||||
app_id=trigger_data.app_id,
|
app_id=trigger_data.app_id,
|
||||||
workflow_id=workflow.id,
|
workflow_id=workflow.id,
|
||||||
|
root_node_id=trigger_data.root_node_id,
|
||||||
trigger_type=trigger_data.trigger_type,
|
trigger_type=trigger_data.trigger_type,
|
||||||
trigger_data=trigger_data.model_dump_json(),
|
trigger_data=trigger_data.model_dump_json(),
|
||||||
inputs=json.dumps(dict(trigger_data.inputs)),
|
inputs=json.dumps(dict(trigger_data.inputs)),
|
||||||
|
|||||||
@ -25,6 +25,7 @@ class TriggerData(BaseModel):
|
|||||||
app_id: str
|
app_id: str
|
||||||
tenant_id: str
|
tenant_id: str
|
||||||
workflow_id: Optional[str] = None
|
workflow_id: Optional[str] = None
|
||||||
|
root_node_id: str
|
||||||
inputs: Mapping[str, Any]
|
inputs: Mapping[str, Any]
|
||||||
files: Sequence[Mapping[str, Any]] = Field(default_factory=list)
|
files: Sequence[Mapping[str, Any]] = Field(default_factory=list)
|
||||||
trigger_type: WorkflowRunTriggeredFrom
|
trigger_type: WorkflowRunTriggeredFrom
|
||||||
|
|||||||
@ -128,6 +128,7 @@ def _execute_workflow_common(task_data: WorkflowTaskData) -> AsyncTriggerExecuti
|
|||||||
call_depth=0,
|
call_depth=0,
|
||||||
workflow_thread_pool_id=None,
|
workflow_thread_pool_id=None,
|
||||||
triggered_from=trigger_data.trigger_type,
|
triggered_from=trigger_data.trigger_type,
|
||||||
|
root_node_id=trigger_data.root_node_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Calculate elapsed time
|
# Calculate elapsed time
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user