From ff992b8903e0af0637284edef5f52f7c303beb0d Mon Sep 17 00:00:00 2001 From: clay Date: Tue, 22 Apr 2025 22:28:28 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=95=B4=E4=BD=93=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E6=9E=B6=E6=9E=84=E5=AE=8C=E6=88=90,=20=E8=BF=90=E8=A1=8C?= =?UTF-8?q?=E7=9A=84=E6=A0=B8=E5=BF=83=E7=AE=97=E6=B3=95=E5=AE=8C=E6=88=90?= =?UTF-8?q?,=20=E8=87=AA=E5=AE=9A=E4=B9=89=E8=8A=82=E7=82=B9starter?= =?UTF-8?q?=E4=BB=A5=E5=A4=96=E5=AE=9A=E4=B9=89=E8=8A=82=E7=82=B9=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E9=80=9A=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/metisapp/custom/CustomTestConfig.java | 10 + .../com/metisapp/custom/CustomTestRunner.java | 27 + .../metisapp/custom/CustomTestValidator.java | 32 + .../src/main/resources/json/test.json | 623 +++++++++++++++--- .../metis/domain/context/RunningContext.java | 7 + .../metis/domain/context/RunningResult.java | 12 + .../com/metis/domain/entity/GraphDemo.java | 52 -- .../com/metis/domain/entity/GraphDto.java | 131 ++++ .../entity/config/node/LLMNodeConfig.java | 10 + .../config/node/QuestionClassifierConfig.java | 10 + .../engine/impl/AppEngineServiceImpl.java | 1 + .../impl/AppFlowEngineRunnerServiceImpl.java | 57 +- .../main/java/com/metis/enums/NodeType.java | 12 +- .../metis/facade/ProcessDefinitionFacade.java | 2 +- .../com/metis/runner/CustomNodeRunner.java | 2 +- .../java/com/metis/runner/NodeRunner.java | 17 + .../java/com/metis/runner/RunnerResult.java | 5 +- .../runner/factory/RunnerInitialize.java | 2 +- .../com/metis/runner/impl/EndNodeRunner.java | 5 +- .../com/metis/runner/impl/LLMNodeRunner.java | 27 + .../runner/impl/QuestionClassifierRunner.java | 36 + .../metis/runner/impl/StartNodeRunner.java | 2 - .../metis/validator/CustomNodeValidator.java | 2 +- .../factory/ValidatorInitialize.java | 2 +- .../validator/impl/ValidatorServiceImpl.java | 2 +- .../validator/impl/node/EndNodeValidator.java | 4 +- .../validator/impl/node/LLMNodeValidator.java | 33 + .../node/QuestionClassifierValidator.java | 31 + .../impl/node/StartNodeValidator.java | 4 +- 29 files changed, 969 insertions(+), 191 deletions(-) create mode 100644 metis-applicant/src/main/java/com/metisapp/custom/CustomTestConfig.java create mode 100644 metis-applicant/src/main/java/com/metisapp/custom/CustomTestRunner.java create mode 100644 metis-applicant/src/main/java/com/metisapp/custom/CustomTestValidator.java delete mode 100644 metis-starter/src/main/java/com/metis/domain/entity/GraphDemo.java create mode 100644 metis-starter/src/main/java/com/metis/domain/entity/GraphDto.java create mode 100644 metis-starter/src/main/java/com/metis/domain/entity/config/node/LLMNodeConfig.java create mode 100644 metis-starter/src/main/java/com/metis/domain/entity/config/node/QuestionClassifierConfig.java create mode 100644 metis-starter/src/main/java/com/metis/runner/impl/LLMNodeRunner.java create mode 100644 metis-starter/src/main/java/com/metis/runner/impl/QuestionClassifierRunner.java create mode 100644 metis-starter/src/main/java/com/metis/validator/impl/node/LLMNodeValidator.java create mode 100644 metis-starter/src/main/java/com/metis/validator/impl/node/QuestionClassifierValidator.java diff --git a/metis-applicant/src/main/java/com/metisapp/custom/CustomTestConfig.java b/metis-applicant/src/main/java/com/metisapp/custom/CustomTestConfig.java new file mode 100644 index 0000000..b7028c1 --- /dev/null +++ b/metis-applicant/src/main/java/com/metisapp/custom/CustomTestConfig.java @@ -0,0 +1,10 @@ +package com.metisapp.custom; + +import com.metis.domain.entity.base.NodeConfig; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class CustomTestConfig extends NodeConfig { +} diff --git a/metis-applicant/src/main/java/com/metisapp/custom/CustomTestRunner.java b/metis-applicant/src/main/java/com/metisapp/custom/CustomTestRunner.java new file mode 100644 index 0000000..d3ec3be --- /dev/null +++ b/metis-applicant/src/main/java/com/metisapp/custom/CustomTestRunner.java @@ -0,0 +1,27 @@ +package com.metisapp.custom; + +import com.metis.domain.context.RunningContext; +import com.metis.domain.context.RunningResult; +import com.metis.domain.entity.base.Edge; +import com.metis.domain.entity.base.Node; +import com.metis.runner.CustomNodeRunner; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Slf4j +@Service +public class CustomTestRunner implements CustomNodeRunner { + + @Override + public String getCustomNodeType() { + return "test"; + } + + @Override + public RunningResult run(RunningContext context, Node node, List edges) { + log.info("自定义节点测试"); + return RunningResult.buildResult(); + } +} diff --git a/metis-applicant/src/main/java/com/metisapp/custom/CustomTestValidator.java b/metis-applicant/src/main/java/com/metisapp/custom/CustomTestValidator.java new file mode 100644 index 0000000..60ce8b7 --- /dev/null +++ b/metis-applicant/src/main/java/com/metisapp/custom/CustomTestValidator.java @@ -0,0 +1,32 @@ +package com.metisapp.custom; + +import com.metis.domain.entity.base.Edge; +import com.metis.domain.entity.base.Node; +import com.metis.validator.CustomNodeValidator; +import com.metis.validator.ValidatorResult; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Slf4j +@Service +public class CustomTestValidator implements CustomNodeValidator { + + @Override + public String getCustomNodeType() { + return "test"; + } + + @Override + public ValidatorResult validateValue(Node node) { + CustomTestConfig config = node.getConfig(); + return ValidatorResult.valid(); + } + + @Override + public ValidatorResult validateRelation(Node node, List sources, List targets) { + CustomTestConfig config = node.getConfig(); + return ValidatorResult.valid(); + } +} diff --git a/metis-applicant/src/main/resources/json/test.json b/metis-applicant/src/main/resources/json/test.json index 00df0ea..05521ec 100644 --- a/metis-applicant/src/main/resources/json/test.json +++ b/metis-applicant/src/main/resources/json/test.json @@ -1,4 +1,3 @@ - { "id": 0, "name": "测试流程", @@ -8,145 +7,561 @@ { "id": "5", "type": "start", - "dimensions": { - "width": 300, - "height": 300 + "initialized": false, + "position": { + "x": -81.81250000000003, + "y": 275.49609375 }, - "draggable": true, - "resizing": false, - "selected": true, "data": { "label": "开始", + "icon": "SuitcaseLine", "toolbarPosition": "right", + "description": "开始述描述", "config": { - "variables": [ - { - "variable": "context", - "label": "段落", - "type": "paragraph", - "maxLength": 48, - "required": true - }, - { - "variable": "text", - "label": "文本", - "type": "text-input", - "maxLength": 48, - "required": true, - "options": [] - }, - { - "variable": "select", - "label": "下拉", - "type": "select", - "maxLength": 48, - "required": true, - "options": [ - { - "label": "选型1", - "value": "1" - }, - { - "label": "选型2", - "value": "2" - } - ] - }, - { - "variable": "number", - "label": "数字", - "type": "number", - "maxLength": 48, - "required": true, - "options": [] - }, - { - "variable": "singlefile", - "label": "singlefile单文件", - "type": "file", - "maxLength": 48, - "required": true, - "options": [], - "allowedFileUploadMethods": [ - "local_file", - "remote_url" - ], - "allowedFileTypes": [ - "image", - "document", - "audio", - "video" - ], - "allowedFileExtensions": [] - }, - { - "variable": "mufile", - "label": "多文件", - "type": "file-list", - "maxLength": 5, - "required": true, - "options": [], - "allowedFileUploadMethods": [ - "local_file" - ], - "allowedFileTypes": [ - "custom" - ], - "allowedFileExtensions": [ - "docx", - "aaa" - ] - } - ], "parent": "234" }, "handles": [ { - "id": "7", + "id": "51", "position": "right", "type": "source", "connectable": true } - ] + ], + "item": { + "id": "2", + "type": "run", + "labelType": "knowledge", + "label": "知识检索" + } }, - "position": { "x": 0, "y": 300 } + "width": 200, + "height": 40 }, { "id": "6", "type": "end", - "selected": false, + "initialized": false, + "position": { + "x": 1281.582055572882, + "y": 236.2912067630247 + }, "data": { "label": "结束", "toolbarPosition": "right", "handles": [ { - "id": "8", + "id": "61", "position": "left", "type": "target", "connectable": true } ] }, - "position": { "x": 500, "y": 300 } + "width": 200, + "height": 40 + }, + { + "id": "188", + "type": "custom", + "customType": "test", + "initialized": false, + "position": { + "x": 265.87532955148635, + "y": 71.40983063296031 + }, + "data": { + "label": "llm1", + "icon": "", + "description": "llm描述描述", + "toolbarPosition": "right", + "config": { + "labelType": "llm" + }, + "handles": [ + { + "id": "11", + "position": "left", + "type": "source", + "connectable": true + }, + { + "id": "45", + "position": "right", + "type": "source", + "connectable": true + } + ] + }, + "width": 200, + "height": null + }, + { + "id": "850", + "type": "llm", + "initialized": false, + "position": { + "x": 269.7896091295129, + "y": 253.80570624004747 + }, + "data": { + "label": "llm2", + "icon": "", + "description": "llm描述描述", + "toolbarPosition": "right", + "config": { + "labelType": "llm" + }, + "handles": [ + { + "id": "43", + "position": "left", + "type": "source", + "connectable": true + }, + { + "id": "57", + "position": "right", + "type": "source", + "connectable": true + } + ] + }, + "width": 200, + "height": null + }, + { + "id": "979", + "type": "llm", + "initialized": false, + "position": { + "x": 257.0893454883237, + "y": 360 + }, + "data": { + "label": "llm3", + "icon": "", + "description": "llm描述描述", + "toolbarPosition": "right", + "config": { + "labelType": "llm" + }, + "handles": [ + { + "id": "40", + "position": "left", + "type": "source", + "connectable": true + }, + { + "id": "46", + "position": "right", + "type": "source", + "connectable": true + } + ], + "item": { + "id": "1", + "type": "run", + "labelType": "llm", + "label": "llm" + } + }, + "width": 200, + "height": null + }, + { + "id": "818", + "type": "questionClassifier", + "initialized": false, + "position": { + "x": 247.56414775743187, + "y": 467.44099824508874 + }, + "data": { + "label": "知识检索条件", + "icon": "", + "description": "知识检索描述描述", + "toolbarPosition": "right", + "config": { + "labelType": "knowledge" + }, + "handles": [ + { + "id": "86", + "position": "left", + "type": "source", + "connectable": true + }, + { + "id": "11", + "position": "right", + "type": "source", + "connectable": true + } + ], + "item": { + "id": "1", + "type": "run", + "labelType": "llm", + "label": "llm" + } + }, + "width": 200, + "height": null + }, + { + "id": "288", + "type": "llm", + "initialized": false, + "position": { + "x": 545.6891477574318, + "y": 431.4042601585389 + }, + "data": { + "label": "llm5", + "icon": "", + "description": "llm描述描述", + "toolbarPosition": "right", + "config": { + "labelType": "llm" + }, + "handles": [ + { + "id": "27", + "position": "left", + "type": "source", + "connectable": true + }, + { + "id": "90", + "position": "right", + "type": "source", + "connectable": true + } + ] + }, + "width": 200, + "height": null + }, + { + "id": "873", + "type": "llm", + "initialized": false, + "position": { + "x": 561.0654659633774, + "y": 547.6673703535477 + }, + "data": { + "label": "llm6", + "icon": "", + "description": "llm描述描述", + "toolbarPosition": "right", + "config": { + "labelType": "llm" + }, + "handles": [ + { + "id": "31", + "position": "left", + "type": "source", + "connectable": true + }, + { + "id": "51", + "position": "right", + "type": "source", + "connectable": true + } + ] + }, + "width": 200, + "height": null + }, + { + "id": "244", + "type": "llm", + "initialized": false, + "position": { + "x": 543.1540601095024, + "y": 653.309197614774 + }, + "data": { + "label": "llm7", + "icon": "", + "description": "llm描述描述", + "toolbarPosition": "right", + "config": { + "labelType": "llm" + }, + "handles": [ + { + "id": "33", + "position": "left", + "type": "source", + "connectable": true + }, + { + "id": "13", + "position": "right", + "type": "source", + "connectable": true + } + ] + }, + "width": 200, + "height": null + }, + { + "id": "308", + "type": "llm", + "initialized": false, + "position": { + "x": 588.7086470279171, + "y": 318.8609378786052 + }, + "data": { + "label": "llm4", + "icon": "", + "description": "llm描述描述", + "toolbarPosition": "right", + "config": { + "labelType": "llm" + }, + "handles": [ + { + "id": "17", + "position": "left", + "type": "source", + "connectable": true + }, + { + "id": "73", + "position": "right", + "type": "source", + "connectable": true + } + ] + }, + "width": 200, + "height": null } ], "edges": [ { - "id": "6", + "id": "188", "type": "default", "source": "5", - "target": "6", - "sourceHandle": "7", + "target": "188", + "sourceHandle": "51", + "targetHandle": "11", + "data": {}, + "label": "", "animated": true, - "targetHandle": "8", - "label": "线标签", - "markerEnd": "none", - "markerStart": "none", - "updatable": true, - "selectable": true, - "deletable": true + "sourceX": 124.52083333333331, + "sourceY": 295.3294270833333, + "targetX": 262.70866773530213, + "targetY": 98.2431395846395 + }, + { + "id": "vueflow__edge-18845-661", + "type": "default", + "source": "188", + "target": "6", + "sourceHandle": "45", + "targetHandle": "61", + "data": {}, + "label": "", + "sourceX": 472.20841119628, + "sourceY": 98.2431395846395, + "targetX": 1278.4153889062152, + "targetY": 256.12454009635803 + }, + { + "id": "850", + "type": "default", + "source": "5", + "target": "850", + "sourceHandle": "51", + "targetHandle": "43", + "data": {}, + "label": "", + "animated": true, + "sourceX": 124.52083333333331, + "sourceY": 295.3294270833333, + "targetX": 266.62293379655364, + "targetY": 280.6389855757115 + }, + { + "id": "vueflow__edge-85057-661", + "type": "default", + "source": "850", + "target": "6", + "sourceHandle": "57", + "targetHandle": "61", + "data": {}, + "label": "", + "sourceX": 476.1223496202179, + "sourceY": 280.6389855757115, + "targetX": 1278.4153889062152, + "targetY": 256.12454009635803 + }, + { + "id": "979", + "type": "default", + "source": "5", + "target": "979", + "sourceHandle": "51", + "targetHandle": "40", + "data": {}, + "label": "", + "animated": true, + "sourceX": 124.52083333333331, + "sourceY": 295.3294270833333, + "targetX": 253.92267015536444, + "targetY": 386.83332778318527 + }, + { + "id": "818", + "type": "default", + "source": "5", + "target": "818", + "sourceHandle": "51", + "targetHandle": "86", + "data": {}, + "label": "", + "animated": true, + "sourceX": 124.52083333333331, + "sourceY": 295.3294270833333, + "targetX": 244.3974724244726, + "targetY": 494.2742291332315 + }, + { + "id": "288", + "type": "default", + "source": "818", + "target": "288", + "sourceHandle": "11", + "targetHandle": "27", + "data": {}, + "label": "", + "animated": true, + "sourceX": 453.89688824813686, + "sourceY": 494.2742291332315, + "targetX": 542.5224239769512, + "targetY": 458.2374910466816 + }, + { + "id": "873", + "type": "default", + "source": "818", + "target": "873", + "sourceHandle": "11", + "targetHandle": "31", + "data": {}, + "label": "", + "animated": true, + "sourceX": 453.89688824813686, + "sourceY": 494.2742291332315, + "targetX": 557.8988921284383, + "targetY": 574.5006486940537 + }, + { + "id": "244", + "type": "default", + "source": "818", + "target": "244", + "sourceHandle": "11", + "targetHandle": "33", + "data": {}, + "label": "", + "animated": true, + "sourceX": 453.89688824813686, + "sourceY": 494.2742291332315, + "targetX": 539.9875200651626, + "targetY": 680.1424930622838 + }, + { + "id": "vueflow__edge-28890-661", + "type": "default", + "source": "288", + "target": "6", + "sourceHandle": "90", + "targetHandle": "61", + "data": {}, + "label": "", + "sourceX": 752.0218882481367, + "sourceY": 458.2374910466816, + "targetX": 1278.4153889062152, + "targetY": 256.12454009635803 + }, + { + "id": "vueflow__edge-87351-661", + "type": "default", + "source": "873", + "target": "6", + "sourceHandle": "51", + "targetHandle": "61", + "data": {}, + "label": "", + "sourceX": 767.3988903613488, + "sourceY": 574.5006486940537, + "targetX": 1278.4153889062152, + "targetY": 256.12454009635803 + }, + { + "id": "vueflow__edge-24413-661", + "type": "default", + "source": "244", + "target": "6", + "sourceHandle": "13", + "targetHandle": "61", + "data": {}, + "label": "", + "sourceX": 749.4874144445799, + "sourceY": 680.1424930622838, + "targetX": 1278.4153889062152, + "targetY": 256.12454009635803 + }, + { + "id": "308", + "type": "default", + "source": "979", + "target": "308", + "sourceHandle": "46", + "targetHandle": "17", + "data": {}, + "label": "", + "animated": true, + "sourceX": 463.42208597902874, + "sourceY": 386.83332778318527, + "targetX": 585.5419963013785, + "targetY": 345.6941732530918 + }, + { + "id": "vueflow__edge-30873-661", + "type": "default", + "source": "308", + "target": "6", + "sourceHandle": "73", + "targetHandle": "61", + "data": {}, + "label": "", + "sourceX": 795.0417833691082, + "sourceY": 345.6941732530918, + "targetX": 1278.4153889062152, + "targetY": 256.12454009635803 } - ] + ], + "position": [ + 447.2060780237498, + 377.80084997067763 + ], + "zoom": 0.8311688311688327, + "viewport": { + "x": 447.2060780237498, + "y": 377.80084997067763, + "zoom": 0.8311688311688327 + } } - } \ No newline at end of file diff --git a/metis-starter/src/main/java/com/metis/domain/context/RunningContext.java b/metis-starter/src/main/java/com/metis/domain/context/RunningContext.java index 4d43f8d..c2d1d68 100644 --- a/metis-starter/src/main/java/com/metis/domain/context/RunningContext.java +++ b/metis-starter/src/main/java/com/metis/domain/context/RunningContext.java @@ -44,6 +44,11 @@ public class RunningContext { this.nodeRunningContext.put(nodeId, nodeRunningContext); } + public JSONObject getRunningContext(Long nodeId) { + return this.nodeRunningContext.get(nodeId); + } + + /** * 构建上下文 * @@ -58,4 +63,6 @@ public class RunningContext { .build(); } + + } diff --git a/metis-starter/src/main/java/com/metis/domain/context/RunningResult.java b/metis-starter/src/main/java/com/metis/domain/context/RunningResult.java index 4b43f76..556e6a6 100644 --- a/metis-starter/src/main/java/com/metis/domain/context/RunningResult.java +++ b/metis-starter/src/main/java/com/metis/domain/context/RunningResult.java @@ -35,6 +35,18 @@ public class RunningResult { .build(); } + /** + * 构建结果 + * + * @param nextRunNodeId 下一个运行节点id + * @return {@link RunningResult } + */ + public static RunningResult buildResult(Set nextRunNodeId) { + return RunningResult.builder() + .nextRunNodeId(nextRunNodeId) + .build(); + } + /** * 构建结果 * diff --git a/metis-starter/src/main/java/com/metis/domain/entity/GraphDemo.java b/metis-starter/src/main/java/com/metis/domain/entity/GraphDemo.java deleted file mode 100644 index 79dc4aa..0000000 --- a/metis-starter/src/main/java/com/metis/domain/entity/GraphDemo.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.metis.domain.entity; - -import com.metis.domain.entity.base.Edge; -import com.metis.domain.entity.base.Node; - -import java.util.*; - -public class GraphDemo { - private Map nodes = new HashMap<>(); - private Map> adjacencyList = new HashMap<>(); - - public void addNode(Node node) { - nodes.put(node.getId(), node); - adjacencyList.put(node.getId(), new ArrayList<>()); - } - - public void addEdge(Edge edge) { - adjacencyList.get(edge.getSource()) - .add(edge.getTarget()); - } - - public List topologicalSort() { - List sortedNodes = new ArrayList<>(); - Set visited = new HashSet<>(); - Set visiting = new HashSet<>(); - - for (Long nodeId : nodes.keySet()) { - if (!visited.contains(nodeId)) { - dfs(nodeId, visited, visiting, sortedNodes); - } - } - - Collections.reverse(sortedNodes); - return sortedNodes; - } - - private void dfs(Long nodeId, Set visited, Set visiting, List sortedNodes) { - if (visiting.contains(nodeId)) { - throw new IllegalStateException("Cycle detected in the graph"); - } - - if (!visited.contains(nodeId)) { - visiting.add(nodeId); - for (Long neighbor : adjacencyList.get(nodeId)) { - dfs(neighbor, visited, visiting, sortedNodes); - } - visiting.remove(nodeId); - visited.add(nodeId); - sortedNodes.add(nodes.get(nodeId)); - } - } -} diff --git a/metis-starter/src/main/java/com/metis/domain/entity/GraphDto.java b/metis-starter/src/main/java/com/metis/domain/entity/GraphDto.java new file mode 100644 index 0000000..5de1cc2 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/domain/entity/GraphDto.java @@ -0,0 +1,131 @@ +package com.metis.domain.entity; + + +import com.metis.domain.entity.base.Edge; +import com.metis.domain.entity.base.Graph; +import com.metis.domain.entity.base.Node; +import com.metis.enums.NodeType; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class GraphDto { + + private final Map nodeMap; + + private final Map nodeReadyMap; + + private final Map> edgeMap; + + private final Map> adjacencyList = new HashMap<>(); + + private final List sortedNodes = new ArrayList<>(); + + + public List getSortedNodes() { + return new ArrayList<>(sortedNodes); + } + + public List getEdgeNodeId(Long nodeId) { + return edgeMap.getOrDefault(nodeId, new ArrayList<>()); + } + + public Node getEndNode(){ + return sortedNodes.stream() + .filter(node -> NodeType.END.equals(node.getType())) + .findFirst() + .orElse(null); + } + + public Node getNode(Long nodeId) { + return nodeMap.get(nodeId); + } + + public void updateNodeReadyMap(Long nodeId, Boolean ready) { + nodeReadyMap.put(nodeId, ready); + } + + public Boolean isNodeReady(Long nodeId) { + return nodeReadyMap.get(nodeId); + } + + + private GraphDto(List nodes, List edges) { + this.edgeMap = edges.stream() + .collect(Collectors.groupingBy(Edge::getSource)); + this.nodeMap = nodes.stream() + .collect(Collectors.toMap(Node::getId, Function.identity())); + this.nodeReadyMap = nodes.stream() + .collect(Collectors.toMap(Node::getId, node -> false)); + initAdjacencyList(edges); + List nodeList = topologicalSort(); + this.sortedNodes.addAll(nodeList); + Node node = sortedNodes.get(0); + if (NodeType.START.equals(node.getType())) { + nodeReadyMap.put(node.getId(), true); + } + } + + private void initAdjacencyList(List edges) { + for (Edge edge : edges) { + List targetList = adjacencyList.getOrDefault(edge.getSource(), new ArrayList<>()); + targetList.add(edge.getTarget()); + adjacencyList.put(edge.getSource(), targetList); + } + } + + + /** + * 拓扑排序 + * + * @return {@link List }<{@link Node }> + */ + private List topologicalSort() { + List sortedNodes = new ArrayList<>(); + Set visited = new HashSet<>(); + Set visiting = new HashSet<>(); + for (Long nodeId : nodeMap.keySet()) { + if (!visited.contains(nodeId)) { + dfs(nodeId, visited, visiting, sortedNodes); + } + } + Collections.reverse(sortedNodes); + return sortedNodes; + } + + /** + * 深度遍历找到运行顺序 + * + * @param nodeId 节点id + * @param visited 参观了 + * @param visiting 参观 + * @param sortedNodes 排序节点 + */ + private void dfs(Long nodeId, Set visited, Set visiting, List sortedNodes) { + if (visiting.contains(nodeId)) { + throw new IllegalStateException("Cycle detected in the graph"); + } + if (!visited.contains(nodeId)) { + visiting.add(nodeId); + for (Long neighbor : adjacencyList.getOrDefault(nodeId, new ArrayList<>())) { + dfs(neighbor, visited, visiting, sortedNodes); + } + visiting.remove(nodeId); + visited.add(nodeId); + sortedNodes.add(nodeMap.get(nodeId)); + } + } + + + /** + * 构建对象 + * + * @param graph 图 + * @return {@link GraphDto } + */ + public static GraphDto of(Graph graph) { + return new GraphDto(graph.getNodes(), graph.getEdges()); + } + +} diff --git a/metis-starter/src/main/java/com/metis/domain/entity/config/node/LLMNodeConfig.java b/metis-starter/src/main/java/com/metis/domain/entity/config/node/LLMNodeConfig.java new file mode 100644 index 0000000..6a2df06 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/domain/entity/config/node/LLMNodeConfig.java @@ -0,0 +1,10 @@ +package com.metis.domain.entity.config.node; + +import com.metis.domain.entity.base.NodeConfig; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class LLMNodeConfig extends NodeConfig { +} diff --git a/metis-starter/src/main/java/com/metis/domain/entity/config/node/QuestionClassifierConfig.java b/metis-starter/src/main/java/com/metis/domain/entity/config/node/QuestionClassifierConfig.java new file mode 100644 index 0000000..caef097 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/domain/entity/config/node/QuestionClassifierConfig.java @@ -0,0 +1,10 @@ +package com.metis.domain.entity.config.node; + +import com.metis.domain.entity.base.NodeConfig; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class QuestionClassifierConfig extends NodeConfig { +} diff --git a/metis-starter/src/main/java/com/metis/engine/impl/AppEngineServiceImpl.java b/metis-starter/src/main/java/com/metis/engine/impl/AppEngineServiceImpl.java index 2547843..0a3a993 100644 --- a/metis-starter/src/main/java/com/metis/engine/impl/AppEngineServiceImpl.java +++ b/metis-starter/src/main/java/com/metis/engine/impl/AppEngineServiceImpl.java @@ -68,6 +68,7 @@ public class AppEngineServiceImpl implements AppEngineService { } @Override + @Transactional(rollbackFor = Exception.class) public App create(CreateApp createApp) { BuildApp buildApp = BaseAppConvert.INSTANCE.toBuildApp(createApp); // 校验 diff --git a/metis-starter/src/main/java/com/metis/engine/impl/AppFlowEngineRunnerServiceImpl.java b/metis-starter/src/main/java/com/metis/engine/impl/AppFlowEngineRunnerServiceImpl.java index a98ba67..9f64f8f 100644 --- a/metis-starter/src/main/java/com/metis/engine/impl/AppFlowEngineRunnerServiceImpl.java +++ b/metis-starter/src/main/java/com/metis/engine/impl/AppFlowEngineRunnerServiceImpl.java @@ -5,12 +5,14 @@ import cn.hutool.core.lang.Assert; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; import com.metis.domain.context.RunningContext; import com.metis.domain.context.RunningResult; import com.metis.domain.context.SysContext; import com.metis.domain.entity.App; +import com.metis.domain.entity.GraphDto; import com.metis.domain.entity.base.Edge; -import com.metis.domain.entity.base.Graph; import com.metis.domain.entity.base.Node; import com.metis.engine.AppEngineService; import com.metis.engine.AppFlowEngineRunnerService; @@ -25,8 +27,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.util.*; -import java.util.function.Function; -import java.util.stream.Collectors; @Slf4j @Service @@ -40,7 +40,7 @@ public class AppFlowEngineRunnerServiceImpl implements AppFlowEngineRunnerServic public RunnerResult running(FlowRunningContext context) { App app = getApp(context); Assert.isTrue(ObjectUtil.isNotNull(app), "app为空"); - // todo 构建运行实例, 并将运行实例放入上下文 + // 构建运行实例, 并将运行实例放入上下文 Long instanceId = IdUtil.getSnowflakeNextId(); // 构建系统上下文信息 SysContext sysContext = SysContext.builder() @@ -52,26 +52,49 @@ public class AppFlowEngineRunnerServiceImpl implements AppFlowEngineRunnerServic // 构建运行中上下文 RunningContext runningContext = RunningContext.buildContext(sysContext, context); // 构建节点映射对象 - Graph graph = app.getGraph(); - Map nodeMap = graph.getNodes().stream() - .collect(Collectors.toMap(Node::getId, Function.identity())); - Map> edgeMap = graph.getEdges().stream() - .collect(Collectors.groupingBy(Edge::getSource)); + GraphDto graph = GraphDto.of(app.getGraph()); Set readyRunningNode = new HashSet<>(); // 获取到开始节点 // 开始节点为空,则表示数据存在异常 Assert.isTrue(ObjectUtil.isNotNull(readyRunningNode), "流程图不存在开始节点"); - while (CollUtil.isNotEmpty(readyRunningNode)) { - // todo 出现多个节点同时运行, 需要找到他们最终运行的聚合节点, 前期默认只有一条线路运行, 不支持并行流程 - doRunning(readyRunningNode, edgeMap, runningContext); - - - readyRunningNode = null; + for (Node node : graph.getSortedNodes()) { + Long nodeId = node.getId(); + if (!graph.isNodeReady(nodeId)) { + continue; + } + log.info("当前运行节点 id:{}, name:{}, type:{}", node.getId(), node.getData().getLabel(), node.getType()); + // 当前节点接下来的连接线信息 + List edges = graph.getEdgeNodeId(nodeId); + // 执行 + NodeRunner nodeRunner = getNodeRunner(node); + node.setConfigClass(GenericInterfacesUtils.getClass(nodeRunner)); + // 下一个需要运行的节点id加入到可以运行的节点中 + // 获取到返回结果 + RunningResult result = nodeRunner.run(runningContext, node, edges); + log.info("节点执行结果:{}", JSON.toJSONString(result)); + // 节点执行结果参数放入上下文中 + if (ObjectUtil.isNotNull(result.getNodeContext())) { + runningContext.addNodeRunningContext(node.getId(), result.getNodeContext()); + } + // 下一个需要运行的节点id加入到可以运行的节点中 + if (CollUtil.isNotEmpty(result.getNextRunNodeId())) { + for (Long nextNodeId : result.getNextRunNodeId()) { + graph.updateNodeReadyMap(nextNodeId, true); + } + } else { + // 如果没有返回, 则认为所有的下级节点都需要运行 + edges.forEach(edge -> { + graph.updateNodeReadyMap(edge.getTarget(), true); + }); + } } + Node endNode = graph.getEndNode(); + + JSONObject endRunningContext = runningContext.getRunningContext(endNode.getId()); return RunnerResult.builder() - .content("你他妈的!") + .result(endRunningContext) .context(sysContext) .build(); @@ -113,7 +136,7 @@ public class AppFlowEngineRunnerServiceImpl implements AppFlowEngineRunnerServic * @return {@link NodeRunner } */ private NodeRunner getNodeRunner(Node node) { - if (NodeType.CUSTOM_NODE.equals(node.getType())) { + if (NodeType.CUSTOM.equals(node.getType())) { Assert.isTrue(StrUtil.isNotBlank(node.getCustomType()), "自定义节点类型不能为空"); return NodeRunnerFactory.getCustom(node.getCustomType()); } diff --git a/metis-starter/src/main/java/com/metis/enums/NodeType.java b/metis-starter/src/main/java/com/metis/enums/NodeType.java index aed1d1a..89717e7 100644 --- a/metis-starter/src/main/java/com/metis/enums/NodeType.java +++ b/metis-starter/src/main/java/com/metis/enums/NodeType.java @@ -13,8 +13,14 @@ public enum NodeType { START(1, "start", "开始"), END(2, "end", "结束"), - DOCUMENT_EXTRACTOR(3, "document-extractor", "文档提取器"), - CUSTOM_NODE(4, "Custom-Node", "自定义节点"); + DOCUMENT_EXTRACTOR(3, "documentExtractor", "文档提取器"), + CUSTOM(4, "custom", "自定义节点"), + LLM(5, "llm", "LLM"), + QUESTION_CLASSIFIER(6, "questionClassifier", "问题分类器"), + IF_ELSE(7, "ifElse", "条件判断"), + + + ; private final Integer code; @@ -24,8 +30,6 @@ public enum NodeType { private final String name; -// private final Class configClass; - /** * 枚举序列化器(前端传code时自动转换为对应枚举) diff --git a/metis-starter/src/main/java/com/metis/facade/ProcessDefinitionFacade.java b/metis-starter/src/main/java/com/metis/facade/ProcessDefinitionFacade.java index 9e6b1e3..7926301 100644 --- a/metis-starter/src/main/java/com/metis/facade/ProcessDefinitionFacade.java +++ b/metis-starter/src/main/java/com/metis/facade/ProcessDefinitionFacade.java @@ -1,8 +1,8 @@ package com.metis.facade; -import com.metis.domain.bo.ProcessBo; import com.metis.convert.GraphConvert; import com.metis.domain.bo.CreateApp; +import com.metis.domain.bo.ProcessBo; import com.metis.domain.bo.UpdateApp; import com.metis.domain.entity.App; import com.metis.domain.entity.base.Graph; diff --git a/metis-starter/src/main/java/com/metis/runner/CustomNodeRunner.java b/metis-starter/src/main/java/com/metis/runner/CustomNodeRunner.java index 8247f5d..c0b7143 100644 --- a/metis-starter/src/main/java/com/metis/runner/CustomNodeRunner.java +++ b/metis-starter/src/main/java/com/metis/runner/CustomNodeRunner.java @@ -26,7 +26,7 @@ public interface CustomNodeRunner extends NodeRunner { * @return {@link NodeType } */ default NodeType getType() { - return NodeType.CUSTOM_NODE; + return NodeType.CUSTOM; } diff --git a/metis-starter/src/main/java/com/metis/runner/NodeRunner.java b/metis-starter/src/main/java/com/metis/runner/NodeRunner.java index d219fcb..cd59b92 100644 --- a/metis-starter/src/main/java/com/metis/runner/NodeRunner.java +++ b/metis-starter/src/main/java/com/metis/runner/NodeRunner.java @@ -1,5 +1,6 @@ package com.metis.runner; +import cn.hutool.core.collection.CollUtil; import com.metis.domain.context.RunningContext; import com.metis.domain.context.RunningResult; import com.metis.domain.entity.base.Edge; @@ -8,6 +9,8 @@ import com.metis.domain.entity.base.NodeConfig; import com.metis.enums.NodeType; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; /** * 内置节点运行器 @@ -36,4 +39,18 @@ public interface NodeRunner { */ NodeType getType(); + + /** + * 获取下一个节点id + * + * @param edges 边缘 + * @return {@link Set }<{@link Long }> + */ + default Set getNextNodeIds(List edges) { + if (CollUtil.isEmpty(edges)) { + return Set.of(); + } + return edges.stream().map(Edge::getTarget).collect(Collectors.toSet()); + } + } diff --git a/metis-starter/src/main/java/com/metis/runner/RunnerResult.java b/metis-starter/src/main/java/com/metis/runner/RunnerResult.java index b24e84b..3306ea5 100644 --- a/metis-starter/src/main/java/com/metis/runner/RunnerResult.java +++ b/metis-starter/src/main/java/com/metis/runner/RunnerResult.java @@ -1,10 +1,13 @@ package com.metis.runner; +import com.alibaba.fastjson2.JSONObject; import com.metis.domain.context.SysContext; import lombok.Builder; import lombok.Data; +import java.util.Map; + /** * 运行结果 * @@ -18,7 +21,7 @@ public class RunnerResult { /** * 运行内容 */ - private String content; + private JSONObject result; /** * 上下文 diff --git a/metis-starter/src/main/java/com/metis/runner/factory/RunnerInitialize.java b/metis-starter/src/main/java/com/metis/runner/factory/RunnerInitialize.java index 0a730b9..b02873a 100644 --- a/metis-starter/src/main/java/com/metis/runner/factory/RunnerInitialize.java +++ b/metis-starter/src/main/java/com/metis/runner/factory/RunnerInitialize.java @@ -26,7 +26,7 @@ public class RunnerInitialize implements ApplicationContextAware { Map runnerMap = applicationContext.getBeansOfType(NodeRunner.class); runnerMap.forEach((runnerBeanName, runner) -> { - if (NodeType.CUSTOM_NODE.equals(runner.getType())) { + if (NodeType.CUSTOM.equals(runner.getType())) { Assert.isTrue(runner instanceof CustomNodeRunner, "自定义节点必须实现CustomNodeRunner接口"); NodeRunnerFactory.registerCustom((CustomNodeRunner) runner); } else { diff --git a/metis-starter/src/main/java/com/metis/runner/impl/EndNodeRunner.java b/metis-starter/src/main/java/com/metis/runner/impl/EndNodeRunner.java index c25b7b4..4aacda8 100644 --- a/metis-starter/src/main/java/com/metis/runner/impl/EndNodeRunner.java +++ b/metis-starter/src/main/java/com/metis/runner/impl/EndNodeRunner.java @@ -1,6 +1,7 @@ package com.metis.runner.impl; +import com.alibaba.fastjson2.JSONObject; import com.metis.domain.context.RunningContext; import com.metis.domain.context.RunningResult; import com.metis.domain.entity.base.Edge; @@ -19,7 +20,9 @@ public class EndNodeRunner implements NodeRunner { @Override public RunningResult run(RunningContext context, Node node, List edges) { - return RunningResult.buildResult(); + JSONObject contextNodeValue = new JSONObject(); + contextNodeValue.put("userId", context.getSys().getAppId()); + return RunningResult.buildResult(contextNodeValue); } @Override diff --git a/metis-starter/src/main/java/com/metis/runner/impl/LLMNodeRunner.java b/metis-starter/src/main/java/com/metis/runner/impl/LLMNodeRunner.java new file mode 100644 index 0000000..1779a34 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/runner/impl/LLMNodeRunner.java @@ -0,0 +1,27 @@ +package com.metis.runner.impl; + +import com.metis.domain.context.RunningContext; +import com.metis.domain.context.RunningResult; +import com.metis.domain.entity.base.Edge; +import com.metis.domain.entity.base.Node; +import com.metis.domain.entity.config.node.LLMNodeConfig; +import com.metis.enums.NodeType; +import com.metis.runner.NodeRunner; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Slf4j +@Service +public class LLMNodeRunner implements NodeRunner { + @Override + public RunningResult run(RunningContext context, Node node, List edges) { + return RunningResult.buildResult(); + } + + @Override + public NodeType getType() { + return NodeType.LLM; + } +} diff --git a/metis-starter/src/main/java/com/metis/runner/impl/QuestionClassifierRunner.java b/metis-starter/src/main/java/com/metis/runner/impl/QuestionClassifierRunner.java new file mode 100644 index 0000000..d23f7a5 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/runner/impl/QuestionClassifierRunner.java @@ -0,0 +1,36 @@ +package com.metis.runner.impl; + +import com.metis.domain.context.RunningContext; +import com.metis.domain.context.RunningResult; +import com.metis.domain.entity.base.Edge; +import com.metis.domain.entity.base.Node; +import com.metis.domain.entity.config.node.QuestionClassifierConfig; +import com.metis.enums.NodeType; +import com.metis.runner.NodeRunner; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.Set; + +@Slf4j +@Service +public class QuestionClassifierRunner implements NodeRunner { + + @Override + public RunningResult run(RunningContext context, Node node, List edges) { + Set nextNodeIds = getNextNodeIds(edges); + // 生成随机索引 + Random random = new Random(); + int randomIndex = random.nextInt(nextNodeIds.size()); + List nodeIds = new ArrayList<>(nextNodeIds); + return RunningResult.buildResult(Set.of(nodeIds.get(randomIndex))); + } + + @Override + public NodeType getType() { + return NodeType.QUESTION_CLASSIFIER; + } +} diff --git a/metis-starter/src/main/java/com/metis/runner/impl/StartNodeRunner.java b/metis-starter/src/main/java/com/metis/runner/impl/StartNodeRunner.java index 0aeca90..7019f92 100644 --- a/metis-starter/src/main/java/com/metis/runner/impl/StartNodeRunner.java +++ b/metis-starter/src/main/java/com/metis/runner/impl/StartNodeRunner.java @@ -27,7 +27,6 @@ public class StartNodeRunner implements NodeRunner { @Override public RunningResult run(RunningContext context, Node node, List edges) { - log.info("开始节点{}, 节点id: {} 运行", node.getData().getLabel(), node.getId()); StartNodeConfig config = node.getConfig(); // 获取到节点的自定义参数 List variables = config.getVariables(); @@ -46,7 +45,6 @@ public class StartNodeRunner implements NodeRunner { } - @Override public NodeType getType() { return NodeType.START; diff --git a/metis-starter/src/main/java/com/metis/validator/CustomNodeValidator.java b/metis-starter/src/main/java/com/metis/validator/CustomNodeValidator.java index dab89c6..ee37924 100644 --- a/metis-starter/src/main/java/com/metis/validator/CustomNodeValidator.java +++ b/metis-starter/src/main/java/com/metis/validator/CustomNodeValidator.java @@ -26,7 +26,7 @@ public interface CustomNodeValidator extends NodeValidator * @return {@link NodeType } */ default NodeType getType() { - return NodeType.CUSTOM_NODE; + return NodeType.CUSTOM; } diff --git a/metis-starter/src/main/java/com/metis/validator/factory/ValidatorInitialize.java b/metis-starter/src/main/java/com/metis/validator/factory/ValidatorInitialize.java index 743d8ac..40b92fe 100644 --- a/metis-starter/src/main/java/com/metis/validator/factory/ValidatorInitialize.java +++ b/metis-starter/src/main/java/com/metis/validator/factory/ValidatorInitialize.java @@ -30,7 +30,7 @@ public class ValidatorInitialize implements ApplicationContextAware { Map nodeMap = applicationContext.getBeansOfType(NodeValidator.class); nodeMap.forEach((nodeValidatorBeanName, nodeValidator) -> { - if (NodeType.CUSTOM_NODE.equals(nodeValidator.getType())) { + if (NodeType.CUSTOM.equals(nodeValidator.getType())) { Assert.isTrue(nodeValidator instanceof CustomNodeValidator, "自定义节点必须实现CustomNodeValidator接口"); NodeValidatorFactory.registerCustom((CustomNodeValidator) nodeValidator); } else { diff --git a/metis-starter/src/main/java/com/metis/validator/impl/ValidatorServiceImpl.java b/metis-starter/src/main/java/com/metis/validator/impl/ValidatorServiceImpl.java index 48bffd0..bd42907 100644 --- a/metis-starter/src/main/java/com/metis/validator/impl/ValidatorServiceImpl.java +++ b/metis-starter/src/main/java/com/metis/validator/impl/ValidatorServiceImpl.java @@ -258,7 +258,7 @@ public class ValidatorServiceImpl implements ValidatorService { * @return {@link NodeValidator } */ private NodeValidator getNodeValidator(Node node) { - if (NodeType.CUSTOM_NODE.equals(node.getType())) { + if (NodeType.CUSTOM.equals(node.getType())) { Assert.isTrue(StrUtil.isNotBlank(node.getCustomType()), "自定义节点类型不能为空"); return NodeValidatorFactory.getCustom(node.getCustomType()); } diff --git a/metis-starter/src/main/java/com/metis/validator/impl/node/EndNodeValidator.java b/metis-starter/src/main/java/com/metis/validator/impl/node/EndNodeValidator.java index f1eb8c1..9e62e56 100644 --- a/metis-starter/src/main/java/com/metis/validator/impl/node/EndNodeValidator.java +++ b/metis-starter/src/main/java/com/metis/validator/impl/node/EndNodeValidator.java @@ -31,8 +31,8 @@ public class EndNodeValidator implements NodeValidator { Assert.isTrue(targets.isEmpty(), "结束节点 {} 不允许有目标连接", node.getId()); // 2. 检查 sources 数量是否小于 handles 数量 - int handleCount = node.getData().getHandles().size(); - Assert.isTrue(sources.size() <= handleCount, "结束节点 {} 的源连接数超过 handles 数量", node.getId()); +// int handleCount = node.getData().getHandles().size(); +// Assert.isTrue(sources.size() <= handleCount, "结束节点 {} 的源连接数超过 handles 数量", node.getId()); return ValidatorResult.valid(); } diff --git a/metis-starter/src/main/java/com/metis/validator/impl/node/LLMNodeValidator.java b/metis-starter/src/main/java/com/metis/validator/impl/node/LLMNodeValidator.java new file mode 100644 index 0000000..2dd04a6 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/validator/impl/node/LLMNodeValidator.java @@ -0,0 +1,33 @@ +package com.metis.validator.impl.node; + +import com.metis.domain.entity.base.Edge; +import com.metis.domain.entity.base.Node; +import com.metis.domain.entity.config.node.LLMNodeConfig; +import com.metis.enums.NodeType; +import com.metis.validator.NodeValidator; +import com.metis.validator.ValidatorResult; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor +public class LLMNodeValidator implements NodeValidator { + @Override + public ValidatorResult validateValue(Node node) { + return ValidatorResult.valid(); + } + + @Override + public ValidatorResult validateRelation(Node node, List sources, List targets) { + return ValidatorResult.valid(); + } + + @Override + public NodeType getType() { + return NodeType.LLM; + } +} diff --git a/metis-starter/src/main/java/com/metis/validator/impl/node/QuestionClassifierValidator.java b/metis-starter/src/main/java/com/metis/validator/impl/node/QuestionClassifierValidator.java new file mode 100644 index 0000000..23c3e19 --- /dev/null +++ b/metis-starter/src/main/java/com/metis/validator/impl/node/QuestionClassifierValidator.java @@ -0,0 +1,31 @@ +package com.metis.validator.impl.node; + +import com.metis.domain.entity.base.Edge; +import com.metis.domain.entity.base.Node; +import com.metis.domain.entity.config.node.QuestionClassifierConfig; +import com.metis.enums.NodeType; +import com.metis.validator.NodeValidator; +import com.metis.validator.ValidatorResult; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Slf4j +@Service +public class QuestionClassifierValidator implements NodeValidator { + @Override + public ValidatorResult validateValue(Node node) { + return ValidatorResult.valid(); + } + + @Override + public ValidatorResult validateRelation(Node node, List sources, List targets) { + return ValidatorResult.valid(); + } + + @Override + public NodeType getType() { + return NodeType.QUESTION_CLASSIFIER; + } +} diff --git a/metis-starter/src/main/java/com/metis/validator/impl/node/StartNodeValidator.java b/metis-starter/src/main/java/com/metis/validator/impl/node/StartNodeValidator.java index 62d39e5..797873d 100644 --- a/metis-starter/src/main/java/com/metis/validator/impl/node/StartNodeValidator.java +++ b/metis-starter/src/main/java/com/metis/validator/impl/node/StartNodeValidator.java @@ -84,8 +84,8 @@ public class StartNodeValidator implements NodeValidator { Assert.isTrue(sources.isEmpty(), "开始节点 {} 不允许有源连接", node.getId()); // 2. 检查 targets 数量是否小于 handles 数量 - int handleCount = node.getData().getHandles().size(); - Assert.isTrue(targets.size() <= handleCount, "开始节点 {} 的目标连接数超过 handles 数量", node.getId()); +// int handleCount = node.getData().getHandles().size(); +// Assert.isTrue(targets.size() <= handleCount, "开始节点 {} 的目标连接数超过 handles 数量", node.getId()); return ValidatorResult.valid(); }