001/** 002 * Copyright (c) 2025-2026, Michael Yang 杨福海 (fuhai999@gmail.com). 003 * <p> 004 * Licensed under the GNU Lesser General Public License (LGPL) ,Version 3.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * http://www.gnu.org/licenses/lgpl-3.0.txt 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package dev.tinyflow.core.parser; 017 018import com.agentsflex.core.chain.Chain; 019import com.agentsflex.core.chain.ChainEdge; 020import com.agentsflex.core.chain.ChainNode; 021import com.agentsflex.core.chain.JsCodeCondition; 022import com.agentsflex.core.util.CollectionUtil; 023import com.agentsflex.core.util.StringUtil; 024import com.alibaba.fastjson.JSON; 025import com.alibaba.fastjson.JSONArray; 026import com.alibaba.fastjson.JSONObject; 027import dev.tinyflow.core.Tinyflow; 028import dev.tinyflow.core.parser.impl.*; 029 030import java.util.HashMap; 031import java.util.Map; 032 033public class ChainParser { 034 035 private Map<String, NodeParser> nodeParserMap = new HashMap<>(); 036 037 public ChainParser() { 038 039 initDefaultParsers(); 040 } 041 042 private void initDefaultParsers() { 043 nodeParserMap.put("startNode", new StartNodeParser()); 044 nodeParserMap.put("codeNode", new CodeNodeParser()); 045 046 nodeParserMap.put("httpNode", new HttpNodeParser()); 047 nodeParserMap.put("knowledgeNode", new KnowledgeNodeParser()); 048 nodeParserMap.put("loopNode", new LoopNodeParser()); 049 nodeParserMap.put("searchEngineNode", new SearchEngineNodeParser()); 050 nodeParserMap.put("templateNode", new TemplateNodeParser()); 051 052 nodeParserMap.put("endNode", new EndNodeParser()); 053 nodeParserMap.put("llmNode", new LlmNodeParser()); 054 } 055 056 public Map<String, NodeParser> getNodeParserMap() { 057 return nodeParserMap; 058 } 059 060 public void setNodeParserMap(Map<String, NodeParser> nodeParserMap) { 061 this.nodeParserMap = nodeParserMap; 062 } 063 064 public void addNodeParser(String type, NodeParser nodeParser) { 065 this.nodeParserMap.put(type, nodeParser); 066 } 067 068 public Chain parse(Tinyflow tinyflow) { 069 String jsonString = tinyflow.getData(); 070 if (StringUtil.noText(jsonString)) { 071 return null; 072 } 073 074 JSONObject root = JSON.parseObject(jsonString); 075 JSONArray nodes = root.getJSONArray("nodes"); 076 JSONArray edges = root.getJSONArray("edges"); 077 078 return parse(tinyflow, nodes, edges, null); 079 } 080 081 public Chain parse(Tinyflow tinyflow, JSONArray nodes, JSONArray edges, JSONObject parentNode) { 082 if (CollectionUtil.noItems(nodes) || CollectionUtil.noItems(edges)) { 083 return null; 084 } 085 086 Chain chain = new Chain(); 087 for (int i = 0; i < nodes.size(); i++) { 088 JSONObject nodeObject = nodes.getJSONObject(i); 089 if ((parentNode == null && StringUtil.noText(nodeObject.getString("parentId"))) 090 || (parentNode != null && parentNode.getString("id").equals(nodeObject.getString("parentId")))) { 091 ChainNode node = parseNode(tinyflow, nodeObject); 092 if (node != null) { 093 094 node.setId(nodeObject.getString("id")); 095 node.setName(nodeObject.getString("label")); 096 node.setDescription(nodeObject.getString("description")); 097 098 JSONObject dataJsonObject = nodeObject.getJSONObject("data"); 099 if (dataJsonObject != null && !dataJsonObject.isEmpty()) { 100 String conditionString = dataJsonObject.getString("condition"); 101 102 if (StringUtil.hasText(conditionString)) { 103 node.setCondition(new JsCodeCondition(conditionString.trim())); 104 } 105 106 Boolean async = dataJsonObject.getBoolean("async"); 107 if (async != null) { 108 node.setAsync(async); 109 } 110 111 String name = dataJsonObject.getString("title"); 112 if (StringUtil.hasText(name)) { 113 node.setName(name); 114 } 115 116 String description = dataJsonObject.getString("description"); 117 if (StringUtil.hasText(description)) { 118 node.setDescription(description); 119 } 120 121 Boolean loopEnable = dataJsonObject.getBoolean("loopEnable"); 122 if (loopEnable != null) { 123 node.setLoopEnable(loopEnable); 124 } 125 126 Long loopIntervalMs = dataJsonObject.getLong("loopIntervalMs"); 127 if (loopIntervalMs != null) { 128 node.setLoopIntervalMs(loopIntervalMs); 129 } 130 131 Integer maxLoopCount = dataJsonObject.getInteger("maxLoopCount"); 132 if (maxLoopCount != null) { 133 node.setMaxLoopCount(maxLoopCount); 134 } 135 136 String loopBreakCondition = dataJsonObject.getString("loopBreakCondition"); 137 if (StringUtil.hasText(loopBreakCondition)) { 138 node.setLoopBreakCondition(new JsCodeCondition(loopBreakCondition.trim())); 139 } 140 } 141 142 chain.addNode(node); 143 } 144 } 145 } 146 147 for (int i = 0; i < edges.size(); i++) { 148 JSONObject edgeObject = edges.getJSONObject(i); 149 JSONObject edgeData = edgeObject.getJSONObject("data"); 150 151 if ((parentNode == null && (edgeData == null || StringUtil.noText(edgeData.getString("parentNodeId")))) 152 || (parentNode != null && edgeData != null && edgeData.getString("parentNodeId").equals(parentNode.getString("id")) 153 //不添加子流程里的第一条 edge(也就是父节点连接子节点的第一条线) 154 && !parentNode.getString("id").equals(edgeObject.getString("source")))) { 155 ChainEdge edge = parseEdge(edgeObject); 156 if (edge != null) { 157 chain.addEdge(edge); 158 } 159 } 160 } 161 162 return chain; 163 } 164 165 private ChainNode parseNode(Tinyflow tinyflow, JSONObject nodeObject) { 166 String type = nodeObject.getString("type"); 167 if (StringUtil.noText(type)) { 168 return null; 169 } 170 171 NodeParser nodeParser = nodeParserMap.get(type); 172 return nodeParser == null ? null : nodeParser.parse(nodeObject, tinyflow); 173 } 174 175 176 private ChainEdge parseEdge(JSONObject edgeObject) { 177 if (edgeObject == null) return null; 178 ChainEdge edge = new ChainEdge(); 179 edge.setId(edgeObject.getString("id")); 180 edge.setSource(edgeObject.getString("source")); 181 edge.setTarget(edgeObject.getString("target")); 182 183 JSONObject data = edgeObject.getJSONObject("data"); 184 if (data == null || data.isEmpty()) { 185 return edge; 186 } 187 188 String conditionString = data.getString("condition"); 189 if (StringUtil.hasText(conditionString)) { 190 edge.setCondition(new JsCodeCondition(conditionString.trim())); 191 } 192 return edge; 193 } 194}