mirror of
https://github.com/langgenius/dify.git
synced 2026-07-20 09:38:32 +08:00
fix(agent): allow dangling refs in workflow drafts (#39192)
This commit is contained in:
parent
6e5fc1081b
commit
af4b65b295
@ -61,16 +61,33 @@ class WorkflowAgentNodeValidator:
|
||||
|
||||
@classmethod
|
||||
def validate_draft_workflow(cls, *, session: Session, workflow: Workflow) -> None:
|
||||
cls._validate_workflow(session=session, workflow=workflow, require_binding=False)
|
||||
cls._validate_workflow(
|
||||
session=session,
|
||||
workflow=workflow,
|
||||
require_binding=False,
|
||||
validate_previous_node_topology=False,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def validate_published_workflow(cls, *, session: Session, workflow: Workflow) -> None:
|
||||
cls._validate_workflow(session=session, workflow=workflow, require_binding=True)
|
||||
cls._validate_workflow(
|
||||
session=session,
|
||||
workflow=workflow,
|
||||
require_binding=True,
|
||||
validate_previous_node_topology=True,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _validate_workflow(cls, *, session: Session, workflow: Workflow, require_binding: bool) -> None:
|
||||
def _validate_workflow(
|
||||
cls,
|
||||
*,
|
||||
session: Session,
|
||||
workflow: Workflow,
|
||||
require_binding: bool,
|
||||
validate_previous_node_topology: bool,
|
||||
) -> None:
|
||||
graph = workflow.graph_dict
|
||||
topology = _WorkflowGraphTopology.from_graph(graph)
|
||||
topology = _WorkflowGraphTopology.from_graph(graph) if validate_previous_node_topology else None
|
||||
for node_id, node_data in cls.iter_agent_v2_nodes(graph):
|
||||
cls._validate_node_schema(node_id=node_id, node_data=node_data)
|
||||
binding = cls._find_binding(
|
||||
@ -185,12 +202,12 @@ class WorkflowAgentNodeValidator:
|
||||
raise WorkflowAgentNodeValidationError(
|
||||
f"Workflow Agent node {binding.node_id} has invalid previous node output ref."
|
||||
)
|
||||
if topology is None:
|
||||
continue
|
||||
if len(selector) < 2:
|
||||
raise WorkflowAgentNodeValidationError(
|
||||
f"Workflow Agent node {binding.node_id} has incomplete previous node output ref."
|
||||
)
|
||||
if topology is None:
|
||||
continue
|
||||
source_node_id = selector[0]
|
||||
if not topology.has_node(source_node_id):
|
||||
raise WorkflowAgentNodeValidationError(
|
||||
|
||||
@ -187,6 +187,51 @@ def test_draft_validation_allows_unbound_agent_node():
|
||||
)
|
||||
|
||||
|
||||
def test_draft_validation_allows_missing_previous_node():
|
||||
node_job = WorkflowNodeJobConfig.model_validate(
|
||||
{"previous_node_output_refs": [{"node_id": "missing-node", "output": "text"}]}
|
||||
)
|
||||
session = Mock()
|
||||
session.scalar.side_effect = [_binding(node_job), _agent(), _snapshot()]
|
||||
|
||||
WorkflowAgentNodeValidator.validate_draft_workflow(
|
||||
session=session,
|
||||
workflow=_workflow(_graph([{"source": "start", "target": "agent-node"}])),
|
||||
)
|
||||
|
||||
|
||||
def test_draft_validation_allows_non_upstream_previous_output_ref():
|
||||
node_job = WorkflowNodeJobConfig.model_validate(
|
||||
{"previous_node_output_refs": [{"node_id": "later-node", "output": "text"}]}
|
||||
)
|
||||
session = Mock()
|
||||
session.scalar.side_effect = [_binding(node_job), _agent(), _snapshot()]
|
||||
|
||||
WorkflowAgentNodeValidator.validate_draft_workflow(
|
||||
session=session,
|
||||
workflow=_workflow(
|
||||
_graph(
|
||||
[
|
||||
{"source": "start", "target": "agent-node"},
|
||||
{"source": "agent-node", "target": "later-node"},
|
||||
]
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def test_draft_validation_rejects_incomplete_previous_output_ref():
|
||||
node_job = WorkflowNodeJobConfig.model_validate({"previous_node_output_refs": [{"selector": ["previous-node"]}]})
|
||||
session = Mock()
|
||||
session.scalar.side_effect = [_binding(node_job), _agent(), _snapshot()]
|
||||
|
||||
with pytest.raises(WorkflowAgentNodeValidationError, match="incomplete previous node output ref"):
|
||||
WorkflowAgentNodeValidator.validate_draft_workflow(
|
||||
session=session,
|
||||
workflow=_workflow(_graph([{"source": "start", "target": "agent-node"}])),
|
||||
)
|
||||
|
||||
|
||||
def test_publish_validation_requires_binding():
|
||||
session = Mock()
|
||||
session.scalar.return_value = None
|
||||
|
||||
Loading…
Reference in New Issue
Block a user