From 7b9fae5d24ef97c3b9b43d53830d51fe253d1685 Mon Sep 17 00:00:00 2001 From: shaohuzhang1 <80892890+shaohuzhang1@users.noreply.github.com> Date: Tue, 9 Dec 2025 20:24:06 +0800 Subject: [PATCH] fix: Set up a complete knowledge base workflow in the loop body, debug prompts that the knowledge base write node cannot be used as the end node (#4476) --- ui/src/workflow/common/validate.ts | 102 ++++++++++++++++-- .../workflow/nodes/loop-body-node/index.vue | 7 +- 2 files changed, 100 insertions(+), 9 deletions(-) diff --git a/ui/src/workflow/common/validate.ts b/ui/src/workflow/common/validate.ts index fd99f9ccb..3fde8df94 100644 --- a/ui/src/workflow/common/validate.ts +++ b/ui/src/workflow/common/validate.ts @@ -45,7 +45,7 @@ const end_nodes_dict = { [WorkflowMode.Application]: end_nodes, [WorkflowMode.Knowledge]: [WorkflowType.KnowledgeWriteNode], [WorkflowMode.ApplicationLoop]: loop_end_nodes, - [WorkflowMode.KnowledgeLoop]: loop_end_nodes, + [WorkflowMode.KnowledgeLoop]: [...loop_end_nodes, WorkflowType.KnowledgeWriteNode], } export class WorkFlowInstance { @@ -218,9 +218,11 @@ export class WorkFlowInstance { export class KnowledgeWorkFlowInstance extends WorkFlowInstance { is_valid_start_node() { - const start_node_list = this.nodes.filter( - (item) => item.properties.kind === WorkflowKind.DataSource, - ) + const start_node_list = + this.workflowModel == WorkflowMode.Knowledge + ? this.nodes.filter((item) => item.properties.kind === WorkflowKind.DataSource) + : this.nodes.filter((item) => item.type === WorkflowType.LoopStartNode) + if (start_node_list.length == 0) { throw t('workflow.validate.startNodeRequired') } @@ -239,9 +241,7 @@ export class KnowledgeWorkFlowInstance extends WorkFlowInstance { is_valid_work_flow() { this.workFlowNodes = [] - const start_node_list = this.nodes.filter( - (item) => item.properties.kind === WorkflowKind.DataSource, - ) + const start_node_list = this.get_start_nodes() start_node_list.forEach((n) => { this._is_valid_work_flow(n) }) @@ -250,10 +250,12 @@ export class KnowledgeWorkFlowInstance extends WorkFlowInstance { .filter( (node: any) => node.id !== WorkflowType.KnowledgeBase && + node.type !== WorkflowType.LoopStartNode && node.properties.kind !== WorkflowKind.DataSource, ) .filter((node) => !this.workFlowNodes.includes(node)) if (notInWorkFlowNodes.length > 0) { + console.log('ss') throw `${t('workflow.validate.notInWorkFlowNode')}:${notInWorkFlowNodes.map((node) => node.properties.stepName).join(',')}` } this.workFlowNodes = [] @@ -263,6 +265,7 @@ export class KnowledgeWorkFlowInstance extends WorkFlowInstance { for (const node of this.nodes) { if ( node.type !== WorkflowType.KnowledgeBase && + node.type !== WorkflowType.LoopStartNode && node.properties.kind !== WorkflowKind.DataSource ) { if (!this.edges.some((edge) => edge.targetNodeId === node.id)) { @@ -271,4 +274,89 @@ export class KnowledgeWorkFlowInstance extends WorkFlowInstance { } } } + get_start_nodes() { + if (this.workflowModel == WorkflowMode.Knowledge) { + return this.nodes.filter((item) => item.properties.kind === WorkflowKind.DataSource) + } else { + return this.nodes.filter((item) => item.type === WorkflowType.LoopStartNode) + } + } + get_end_nodes() { + const start_node_list = this.get_start_nodes() + return start_node_list.flatMap((n) => { + return this._get_end_nodes(n, []) + }) + } + _get_end_nodes(startNode: any, value: Array) { + const next = this.get_next_nodes(startNode) + if (next.length == 0) { + value.push(startNode) + } else { + next.forEach((n) => { + this._get_end_nodes(n, value) + }) + } + return value + } + + /** + * 获取流程下一个节点列表 + * @param node 节点 + * @returns 节点列表 + */ + get_next_nodes(node: any) { + const edge_list = this.edges.filter((edge) => edge.sourceNodeId == node.id) + const node_list = edge_list + .map((edge) => this.nodes.filter((node) => node.id == edge.targetNodeId)) + .reduce((x, y) => [...x, ...y], []) + + return node_list + } + + /** + * 校验节点 + * @param node 节点 + */ + is_valid_node(node: any) { + if (node.properties.status && node.properties.status === 500) { + throw `${node.properties.stepName} ${t('workflow.validate.nodeUnavailable')}` + } + if (node.type === WorkflowType.Condition) { + const branch_list = node.properties.node_data.branch + for (const branch of branch_list) { + const source_anchor_id = `${node.id}_${branch.id}_right` + const edge_list = this.edges.filter((edge) => edge.sourceAnchorId == source_anchor_id) + if (edge_list.length == 0) { + throw `${node.properties.stepName} ${t('workflow.validate.needConnect1')}${branch.type}${t('workflow.validate.needConnect2')}` + } + } + } else { + const edge_list = this.edges.filter((edge) => edge.sourceNodeId == node.id) + const end = end_nodes_dict[this.workflowModel] + if (this.workflowModel == WorkflowMode.KnowledgeLoop) { + if (edge_list.length == 0 && !end.includes(node.type)) { + throw `${node.properties.stepName} ${t('workflow.validate.cannotEndNode')}` + } + return + } + if (edge_list.length == 0 && !end.includes(node.type)) { + if (node.type == WorkflowType.LoopNode) { + if (node.properties.node_data.loop_body) { + const end_nodes = new KnowledgeWorkFlowInstance( + node.properties.node_data.loop_body, + WorkflowMode.KnowledgeLoop, + ).get_end_nodes() + if (!end_nodes.every((n) => end.includes(n.type))) { + throw `${node.properties.stepName} ${t('workflow.validate.cannotEndNode')}` + } + } + } else { + throw `${node.properties.stepName} ${t('workflow.validate.cannotEndNode')}` + } + } + } + if (node.properties.status && node.properties.status !== 200) { + throw `${node.properties.stepName} ${t('workflow.validate.nodeUnavailable')}` + } + } } diff --git a/ui/src/workflow/nodes/loop-body-node/index.vue b/ui/src/workflow/nodes/loop-body-node/index.vue index d010a5746..d62c6a719 100644 --- a/ui/src/workflow/nodes/loop-body-node/index.vue +++ b/ui/src/workflow/nodes/loop-body-node/index.vue @@ -12,7 +12,7 @@ import Dagre from '@/workflow/plugins/dagre' import { initDefaultShortcut } from '@/workflow/common/shortcut' import LoopBodyContainer from '@/workflow/nodes/loop-body-node/LoopBodyContainer.vue' import { WorkflowMode } from '@/enums/application' -import { WorkFlowInstance } from '@/workflow/common/validate' +import { WorkFlowInstance, KnowledgeWorkFlowInstance } from '@/workflow/common/validate' import { t } from '@/locales' import { disconnectByFlow } from '@/workflow/common/teleport' const loop_workflow_mode = inject('loopWorkflowMode') || WorkflowMode.ApplicationLoop @@ -21,7 +21,10 @@ const props = defineProps<{ nodeModel: any }>() const containerRef = ref() const LoopBodyContainerRef = ref>() const validate = () => { - const workflow = new WorkFlowInstance(lf.value.getGraphData(), WorkflowMode.ApplicationLoop) + const workflow = + loop_workflow_mode == WorkflowMode.ApplicationLoop + ? new WorkFlowInstance(lf.value.getGraphData(), WorkflowMode.ApplicationLoop) + : new KnowledgeWorkFlowInstance(lf.value.getGraphData(), WorkflowMode.KnowledgeLoop) return Promise.all(lf.value.graphModel.nodes.map((element: any) => element?.validate?.())) .then(() => { const loop_node_id = props.nodeModel.properties.loop_node_id