fix(agent): allow dangling refs in workflow drafts (#39192)

This commit is contained in:
zyssyz123 2026-07-17 16:42:58 +08:00 committed by GitHub
parent 6e5fc1081b
commit af4b65b295
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 68 additions and 6 deletions

View File

@ -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(

View File

@ -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