feat(ai-web): 完成流程引擎雏形

This commit is contained in:
v-zhangjc9
2025-07-01 15:33:07 +08:00
parent e59e89a5ad
commit c5c62ab713
16 changed files with 415 additions and 381 deletions

View File

@@ -1,11 +1,8 @@
package com.lanyuanxiaoyao.service.ai.web.engine;
import com.lanyuanxiaoyao.service.ai.web.engine.entity.FlowContext;
import com.lanyuanxiaoyao.service.ai.web.engine.entity.FlowEdge;
import com.lanyuanxiaoyao.service.ai.web.engine.entity.FlowGraph;
import com.lanyuanxiaoyao.service.ai.web.engine.entity.FlowNode;
import com.lanyuanxiaoyao.service.ai.web.engine.entity.FlowNodeRunner;
import java.lang.reflect.InvocationTargetException;
import com.lanyuanxiaoyao.service.ai.web.engine.store.FlowStore;
import java.util.LinkedList;
import java.util.Queue;
import org.eclipse.collections.api.map.ImmutableMap;
@@ -17,25 +14,17 @@ import org.eclipse.collections.api.map.ImmutableMap;
* @version 20250630
*/
public class FlowExecutor {
private final FlowStore flowStore;
private final ImmutableMap<String, Class<? extends FlowNodeRunner>> runnerMap;
private final Queue<FlowNode> executionQueue = new LinkedList<>();
public FlowExecutor(ImmutableMap<String, Class<? extends FlowNodeRunner>> runnerMap) {
public FlowExecutor(FlowStore flowStore, ImmutableMap<String, Class<? extends FlowNodeRunner>> runnerMap) {
this.flowStore = flowStore;
this.runnerMap = runnerMap;
}
public void execute(FlowGraph graph) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
var nodeInputMap = graph.edges().groupBy(FlowEdge::source);
var nodeOutputMap = graph.edges().groupBy(FlowEdge::target);
var startNodes = graph.nodes().select(node -> !nodeInputMap.containsKey(node.id()));
Queue<FlowNode> queue = new LinkedList<>();
for (FlowNode node : startNodes) {
queue.offer(node);
}
while (!queue.isEmpty()) {
var node = queue.poll();
var clazz = runnerMap.get(node.type());
var runner = clazz.getDeclaredConstructor(FlowContext.class).newInstance();
runner.run();
}
public void execute(FlowGraph graph) {
var runner = new FlowGraphRunner(graph, flowStore, runnerMap);
runner.run();
}
}

View File

@@ -0,0 +1,76 @@
package com.lanyuanxiaoyao.service.ai.web.engine;
import com.lanyuanxiaoyao.service.ai.web.engine.entity.FlowContext;
import com.lanyuanxiaoyao.service.ai.web.engine.entity.FlowEdge;
import com.lanyuanxiaoyao.service.ai.web.engine.entity.FlowGraph;
import com.lanyuanxiaoyao.service.ai.web.engine.entity.FlowNode;
import com.lanyuanxiaoyao.service.ai.web.engine.store.FlowStore;
import java.util.LinkedList;
import java.util.Queue;
import lombok.SneakyThrows;
import org.eclipse.collections.api.map.ImmutableMap;
import org.eclipse.collections.api.multimap.set.ImmutableSetMultimap;
/**
* Graph执行器
*
* @author lanyuanxiaoyao
* @version 20250701
*/
public final class FlowGraphRunner implements Runnable {
private final FlowGraph flowGraph;
private final FlowStore flowStore;
private final ImmutableMap<String, Class<? extends FlowNodeRunner>> nodeRunnerClass;
private final Queue<FlowNode> executionQueue = new LinkedList<>();
private final ImmutableSetMultimap<String, FlowEdge> nodeInputMap;
private final ImmutableSetMultimap<String, FlowEdge> nodeOutputMap;
private final ImmutableMap<String, FlowNode> nodeMap;
public FlowGraphRunner(FlowGraph flowGraph, FlowStore flowStore, ImmutableMap<String, Class<? extends FlowNodeRunner>> nodeRunnerClass) {
this.flowGraph = flowGraph;
this.flowStore = flowStore;
this.nodeRunnerClass = nodeRunnerClass;
nodeInputMap = flowGraph.edges().groupBy(FlowEdge::target);
nodeOutputMap = flowGraph.edges().groupBy(FlowEdge::source);
nodeMap = flowGraph.nodes().toImmutableMap(FlowNode::id, node -> node);
}
@SneakyThrows
@Override
public void run() {
flowStore.init(flowGraph);
flowStore.updateGraphToRunning(flowGraph.id());
var context = new FlowContext();
for (FlowNode node : flowGraph.nodes()) {
executionQueue.offer(node);
}
while (!executionQueue.isEmpty()) {
var node = executionQueue.poll();
if (readyForRunning(node)) {
flowStore.updateNodeToRunning(flowGraph.id(), node.id());
var runnerClazz = nodeRunnerClass.get(node.type());
var runner = runnerClazz.getDeclaredConstructor().newInstance();
runner.setNodeId(node.id());
runner.setContext(context);
runner.run();
flowStore.updateNodeToFinished(flowGraph.id(), node.id());
for (FlowEdge edge : nodeOutputMap.get(node.id())) {
flowStore.updateEdgeToExecute(flowGraph.id(), edge.id());
executionQueue.offer(nodeMap.get(edge.target()));
}
}
}
flowStore.updateGraphToFinished(flowGraph.id());
}
private boolean readyForRunning(FlowNode node) {
return (!nodeInputMap.containsKey(node.id()) || nodeInputMap.get(node.id()).allSatisfy(edge -> flowStore.checkEdgeStatus(flowGraph.id(), edge.id(), FlowEdge.Status.EXECUTE, FlowEdge.Status.SKIP)))
&& flowStore.checkNodeStatus(flowGraph.id(), node.id(), FlowNode.Status.INITIAL);
}
}

View File

@@ -0,0 +1,31 @@
package com.lanyuanxiaoyao.service.ai.web.engine;
import com.lanyuanxiaoyao.service.ai.web.engine.entity.FlowContext;
import lombok.Getter;
public abstract class FlowNodeRunner {
@Getter
private String nodeId;
@Getter
private FlowContext context;
public abstract void run();
void setNodeId(String nodeId) {
this.nodeId = nodeId;
}
void setContext(FlowContext context) {
this.context = context;
}
protected <T> T getData(String key) {
var data = context.get(nodeId);
return (T) data.get(key);
}
protected <T> void setData(String key, T value) {
var data = context.get(nodeId);
data.put(key, value);
}
}

View File

@@ -6,5 +6,12 @@ import org.eclipse.collections.api.map.MutableMap;
@Data
public class FlowContext {
private MutableMap<String, Object> data = Maps.mutable.<String, Object>empty().asSynchronized();
private MutableMap<String, MutableMap<String, Object>> data = Maps.mutable.<String, MutableMap<String, Object>>empty().asSynchronized();
public MutableMap<String, Object> get(String key) {
if (!data.containsKey(key)) {
data.put(key, Maps.mutable.<String, Object>empty().asSynchronized());
}
return data.get(key);
}
}

View File

@@ -1,5 +1,7 @@
package com.lanyuanxiaoyao.service.ai.web.engine.entity;
import java.time.LocalDateTime;
/**
* 流程图中的边
*
@@ -13,4 +15,22 @@ public record FlowEdge(
String sourcePoint,
String targetPoint
) {
public enum Status {
INITIAL, EXECUTE, SKIP
}
public record State(
String id,
Status status,
LocalDateTime startingTime,
LocalDateTime finishedTime
) {
public State(String edgeId) {
this(edgeId, Status.INITIAL, LocalDateTime.now(), null);
}
public State(String edgeId, Status status) {
this(edgeId, status, LocalDateTime.now(), null);
}
}
}

View File

@@ -1,5 +1,6 @@
package com.lanyuanxiaoyao.service.ai.web.engine.entity;
import java.time.LocalDateTime;
import org.eclipse.collections.api.set.ImmutableSet;
/**
@@ -13,4 +14,22 @@ public record FlowGraph(
ImmutableSet<FlowNode> nodes,
ImmutableSet<FlowEdge> edges
) {
public enum Status {
INITIAL, RUNNING, FINISHED, ERROR
}
public record State(
String id,
Status status,
LocalDateTime startingTime,
LocalDateTime finishedTime
) {
public State(String id) {
this(id, Status.INITIAL, LocalDateTime.now(), null);
}
public State(String id, Status status) {
this(id, status, LocalDateTime.now(), null);
}
}
}

View File

@@ -1,5 +1,6 @@
package com.lanyuanxiaoyao.service.ai.web.engine.entity;
import java.time.LocalDateTime;
import org.eclipse.collections.api.set.ImmutableSet;
/**
@@ -14,4 +15,22 @@ public record FlowNode(
ImmutableSet<String> inputPoints,
ImmutableSet<String> outputPoints
) {
public enum Status {
INITIAL, RUNNING, FINISHED, SKIPPED
}
public record State(
String id,
Status status,
LocalDateTime startingTime,
LocalDateTime finishedTime
) {
public State(String nodeId) {
this(nodeId, Status.INITIAL, LocalDateTime.now(), null);
}
public State(String nodeId, Status status) {
this(nodeId, status, LocalDateTime.now(), null);
}
}
}

View File

@@ -1,5 +0,0 @@
package com.lanyuanxiaoyao.service.ai.web.engine.entity;
public abstract class FlowNodeRunner implements Runnable {
private final FlowContext context;
}

View File

@@ -0,0 +1,37 @@
package com.lanyuanxiaoyao.service.ai.web.engine.store;
import com.lanyuanxiaoyao.service.ai.web.engine.entity.FlowEdge;
import com.lanyuanxiaoyao.service.ai.web.engine.entity.FlowGraph;
import com.lanyuanxiaoyao.service.ai.web.engine.entity.FlowNode;
/**
* 存储状态
*
* @author lanyuanxiaoyao
* @version 20250701
*/
public interface FlowStore {
void init(FlowGraph flowGraph);
void updateGraphToRunning(String graphId);
void updateGraphToFinished(String graphId);
void updateGraphToError(String graphId);
void updateNodeToRunning(String graphId, String nodeId);
void updateNodeToSkipped(String graphId, String nodeId);
void updateNodeToFinished(String graphId, String nodeId);
void updateEdgeToExecute(String graphId, String edgeId);
void updateEdgeToSkip(String graphId, String edgeId);
boolean checkNodeStatus(String graphId, String nodeId, FlowNode.Status... statuses);
boolean checkEdgeStatus(String graphId, String edgeId, FlowEdge.Status... statuses);
void print();
}

View File

@@ -0,0 +1,138 @@
package com.lanyuanxiaoyao.service.ai.web.engine.store;
import cn.hutool.core.util.ArrayUtil;
import com.lanyuanxiaoyao.service.ai.web.engine.entity.FlowEdge;
import com.lanyuanxiaoyao.service.ai.web.engine.entity.FlowGraph;
import com.lanyuanxiaoyao.service.ai.web.engine.entity.FlowNode;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.collections.api.factory.Maps;
import org.eclipse.collections.api.map.MutableMap;
/**
* 基于内存的流程状态存储
*
* @author lanyuanxiaoyao
* @version 20250701
*/
@Slf4j
public class InMemoryFlowStore implements FlowStore {
private static final MutableMap<String, FlowGraph.State> flowGraphStateMap = Maps.mutable.<String, FlowGraph.State>empty().asSynchronized();
private static final MutableMap<String, FlowNode.State> flowNodeStateMap = Maps.mutable.<String, FlowNode.State>empty().asSynchronized();
private static final MutableMap<String, FlowEdge.State> flowEdgeStateMap = Maps.mutable.<String, FlowEdge.State>empty().asSynchronized();
private String multiKey(String... key) {
return String.join("-", key);
}
@Override
public void init(FlowGraph flowGraph) {
flowGraphStateMap.put(flowGraph.id(), new FlowGraph.State(flowGraph.id()));
for (FlowNode node : flowGraph.nodes()) {
flowNodeStateMap.put(multiKey(flowGraph.id(), node.id()), new FlowNode.State(node.id()));
}
for (FlowEdge edge : flowGraph.edges()) {
flowEdgeStateMap.put(multiKey(flowGraph.id(), edge.id()), new FlowEdge.State(edge.id()));
}
}
@Override
public void updateGraphToRunning(String graphId) {
flowGraphStateMap.updateValue(
graphId,
() -> new FlowGraph.State(graphId, FlowGraph.Status.RUNNING),
old -> new FlowGraph.State(graphId, FlowGraph.Status.RUNNING, old.startingTime(), old.finishedTime())
);
}
@Override
public void updateGraphToFinished(String graphId) {
flowGraphStateMap.updateValue(
graphId,
() -> new FlowGraph.State(graphId, FlowGraph.Status.FINISHED),
old -> new FlowGraph.State(graphId, FlowGraph.Status.FINISHED, old.startingTime(), old.finishedTime())
);
}
@Override
public void updateGraphToError(String graphId) {
flowGraphStateMap.updateValue(
graphId,
() -> new FlowGraph.State(graphId, FlowGraph.Status.ERROR),
old -> new FlowGraph.State(graphId, FlowGraph.Status.ERROR, old.startingTime(), old.finishedTime())
);
}
@Override
public void updateNodeToRunning(String graphId, String nodeId) {
flowNodeStateMap.updateValue(
multiKey(graphId, nodeId),
() -> new FlowNode.State(nodeId, FlowNode.Status.RUNNING),
old -> new FlowNode.State(nodeId, FlowNode.Status.RUNNING, old.startingTime(), old.finishedTime())
);
}
@Override
public void updateNodeToSkipped(String graphId, String nodeId) {
flowNodeStateMap.updateValue(
multiKey(graphId, nodeId),
() -> new FlowNode.State(nodeId, FlowNode.Status.SKIPPED),
old -> new FlowNode.State(nodeId, FlowNode.Status.SKIPPED, old.startingTime(), old.finishedTime())
);
}
@Override
public void updateNodeToFinished(String graphId, String nodeId) {
flowNodeStateMap.updateValue(
multiKey(graphId, nodeId),
() -> new FlowNode.State(nodeId, FlowNode.Status.FINISHED),
old -> new FlowNode.State(nodeId, FlowNode.Status.FINISHED, old.startingTime(), old.finishedTime())
);
}
@Override
public void updateEdgeToExecute(String graphId, String edgeId) {
flowEdgeStateMap.updateValue(
multiKey(graphId, edgeId),
() -> new FlowEdge.State(edgeId, FlowEdge.Status.EXECUTE),
old -> new FlowEdge.State(edgeId, FlowEdge.Status.EXECUTE, old.startingTime(), old.finishedTime())
);
}
@Override
public void updateEdgeToSkip(String graphId, String edgeId) {
flowEdgeStateMap.updateValue(
multiKey(graphId, edgeId),
() -> new FlowEdge.State(edgeId, FlowEdge.Status.SKIP),
old -> new FlowEdge.State(edgeId, FlowEdge.Status.SKIP, old.startingTime(), old.finishedTime())
);
}
@Override
public boolean checkNodeStatus(String graphId, String nodeId, FlowNode.Status... statuses) {
String key = multiKey(graphId, nodeId);
if (flowNodeStateMap.containsKey(key)) {
return ArrayUtil.contains(statuses, flowNodeStateMap.get(key).status());
}
return false;
}
@Override
public boolean checkEdgeStatus(String graphId, String edgeId, FlowEdge.Status... statuses) {
String key = multiKey(graphId, edgeId);
if (flowEdgeStateMap.containsKey(key)) {
return ArrayUtil.contains(statuses, flowEdgeStateMap.get(key).status());
}
return false;
}
@Override
public void print() {
log.info("====== Flow Store ======");
log.info("====== Flow Graph ======");
flowGraphStateMap.forEachKeyValue((key, value) -> log.info("{}: {}", key, value.status()));
log.info("====== Flow Node ======");
flowNodeStateMap.forEachKeyValue((key, value) -> log.info("{}: {}", key, value.status()));
log.info("====== Flow Edge ======");
flowEdgeStateMap.forEachKeyValue((key, value) -> log.info("{}: {}", key, value.status()));
}
}

View File

@@ -0,0 +1,59 @@
package com.lanyuanxiaoyao.service.ai.web;
import com.lanyuanxiaoyao.service.ai.web.engine.FlowExecutor;
import com.lanyuanxiaoyao.service.ai.web.engine.FlowNodeRunner;
import com.lanyuanxiaoyao.service.ai.web.engine.entity.FlowEdge;
import com.lanyuanxiaoyao.service.ai.web.engine.entity.FlowGraph;
import com.lanyuanxiaoyao.service.ai.web.engine.entity.FlowNode;
import com.lanyuanxiaoyao.service.ai.web.engine.store.InMemoryFlowStore;
import java.lang.reflect.InvocationTargetException;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.collections.api.factory.Maps;
import org.eclipse.collections.api.factory.Sets;
/**
* @author lanyuanxiaoyao
* @version 20250701
*/
@Slf4j
public class TestFlow {
public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
var store = new InMemoryFlowStore();
var executor = new FlowExecutor(
store,
Maps.immutable.of(
"plain-node", PlainNode.class
)
);
var graph = new FlowGraph(
"graph-1",
Sets.immutable.of(
new FlowNode("node-1", "plain-node", Sets.immutable.empty(), Sets.immutable.of("target")),
new FlowNode("node-2", "plain-node", Sets.immutable.of("source"), Sets.immutable.of("target")),
new FlowNode("node-4", "plain-node", Sets.immutable.of("source"), Sets.immutable.of("target")),
new FlowNode("node-6", "plain-node", Sets.immutable.of("source"), Sets.immutable.of("target")),
new FlowNode("node-7", "plain-node", Sets.immutable.of("source"), Sets.immutable.of("target")),
new FlowNode("node-5", "plain-node", Sets.immutable.of("source"), Sets.immutable.of("target")),
new FlowNode("node-3", "plain-node", Sets.immutable.of("source"), Sets.immutable.empty())
),
Sets.immutable.of(
new FlowEdge("edge-1", "node-1", "node-2", null, null),
new FlowEdge("edge-2", "node-2", "node-4", null, null),
new FlowEdge("edge-3", "node-2", "node-5", null, null),
new FlowEdge("edge-4", "node-4", "node-6", null, null),
new FlowEdge("edge-5", "node-6", "node-7", null, null),
new FlowEdge("edge-6", "node-7", "node-3", null, null),
new FlowEdge("edge-7", "node-5", "node-3", null, null)
)
);
executor.execute(graph);
store.print();
}
public static class PlainNode extends FlowNodeRunner {
@Override
public void run() {
log.info("run node id: {}", getNodeId());
}
}
}

View File

@@ -1,16 +0,0 @@
package com.lanyuanxiaoyao.service.ai.web.flow;
import com.yomahub.liteflow.core.NodeComponent;
import lombok.extern.slf4j.Slf4j;
/**
* @author lanyuanxiaoyao
* @version 20250625
*/
@Slf4j
public abstract class BaseNode extends NodeComponent {
@Override
public void process() throws Exception {
log.info(getClass().getName());
}
}

View File

@@ -1,12 +0,0 @@
package com.lanyuanxiaoyao.service.ai.web.flow;
/**
* @author lanyuanxiaoyao
* @version 20250625
*/
public class EndNode extends BaseNode {
@Override
public void process() throws Exception {
}
}

View File

@@ -1,304 +0,0 @@
package com.lanyuanxiaoyao.service.ai.web.flow;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.eclipsecollections.EclipseCollectionsModule;
import com.yomahub.liteflow.builder.LiteFlowNodeBuilder;
import com.yomahub.liteflow.core.NodeComponent;
import com.yomahub.liteflow.enums.NodeTypeEnum;
import java.util.LinkedList;
import java.util.Queue;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.collections.api.factory.Lists;
import org.eclipse.collections.api.factory.Maps;
import org.eclipse.collections.api.list.ImmutableList;
import org.eclipse.collections.api.list.MutableList;
import org.eclipse.collections.api.map.ImmutableMap;
/**
* @author lanyuanxiaoyao
* @version 20250625
*/
@Slf4j
public class LiteFlowService {
public LiteFlowService() {
createNode("start-amis-node", NodeTypeEnum.COMMON, StartNode.class);
createNode("end-amis-node", NodeTypeEnum.COMMON, EndNode.class);
createNode("llm-amis-node", NodeTypeEnum.COMMON, LlmNode.class);
}
private static void createNode(String name, NodeTypeEnum type, Class<? extends NodeComponent> clazz) {
LiteFlowNodeBuilder.createNode()
.setId(name)
.setName(name)
.setType(type)
.setClazz(clazz)
.build();
}
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new EclipseCollectionsModule());
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// language=JSON
String source = """
{
"nodes": [
{
"id": "A",
"type": "start",
"position": {
"x": 8,
"y": 272
},
"data": {},
"measured": {
"width": 256,
"height": 75
},
"selected": false,
"dragging": false
},
{
"id": "F",
"type": "end",
"position": {
"x": 1439.5556937134281,
"y": 282.2797340760818
},
"data": {},
"measured": {
"width": 256,
"height": 75
},
"selected": false,
"dragging": false
},
{
"id": "C",
"type": "normal",
"position": {
"x": 902.6781018665707,
"y": 115.31234529524048
},
"data": {},
"measured": {
"width": 256,
"height": 75
},
"selected": false,
"dragging": false
},
{
"id": "B",
"type": "normal",
"position": {
"x": 338,
"y": 287
},
"data": {},
"measured": {
"width": 256,
"height": 75
},
"selected": false,
"dragging": false
},
{
"id": "E",
"type": "normal",
"position": {
"x": 1086.6322978498904,
"y": 371.3061114283591
},
"data": {},
"measured": {
"width": 256,
"height": 75
},
"selected": true,
"dragging": false
},
{
"id": "D",
"type": "normal",
"position": {
"x": 700.0944461714178,
"y": 369.84258971430364
},
"data": {},
"measured": {
"width": 256,
"height": 75
},
"selected": false,
"dragging": false
}
],
"edges": [
{
"source": "A",
"target": "B",
"id": "xy-edge__A-B"
},
{
"source": "B",
"target": "C",
"id": "xy-edge__B-C"
},
{
"source": "C",
"target": "F",
"id": "xy-edge__C-F"
},
{
"source": "D",
"target": "E",
"id": "xy-edge__D-E"
},
{
"source": "B",
"target": "D",
"id": "xy-edge__B-D"
},
{
"source": "E",
"target": "F",
"id": "xy-edge__E-F"
}
],
"data": {
"A": {
"inputs": {
"name": {
"type": "text"
},
"description": {
"type": "text",
"description": "文件描述"
}
}
},
"C": {
"model": "qwen3",
"outputs": {
"text": {
"type": "string"
}
},
"systemPrompt": "你是个沙雕"
},
"B": {
"count": 3,
"score": 0.75,
"knowledgeId": 3585368238960640,
"query": "hello world"
},
"E": {
"type": "python",
"content": "code='hello'\\nprint(code)"
},
"D": {
"model": "qwen3",
"outputs": {
"text": {
"type": "string"
}
},
"systemPrompt": "你是个聪明人"
}
}
}
""";
FlowData root = mapper.readValue(StrUtil.trim(source), FlowData.class);
log.info("\n{}", buildEl(root.nodes, root.edges));
}
public static String buildEl(ImmutableList<FlowData.Node> nodes, ImmutableList<FlowData.Edge> edges) {
var nodeMap = nodes.toMap(FlowData.Node::getId, node -> node);
var adjacencyGraph = Maps.mutable.<String, MutableList<String>>empty();
var reverseAdjacencyGraph = Maps.mutable.<String, MutableList<String>>empty();
var inDegree = Maps.mutable.<String, Integer>empty();
nodes.forEach(node -> {
adjacencyGraph.put(node.getId(), Lists.mutable.empty());
reverseAdjacencyGraph.put(node.getId(), Lists.mutable.empty());
inDegree.put(node.getId(), 0);
});
edges.forEach(edge -> {
adjacencyGraph.get(edge.getSource()).add(edge.getTarget());
reverseAdjacencyGraph.get(edge.getTarget()).add(edge.getSource());
inDegree.put(edge.getTarget(), inDegree.get(edge.getTarget()) + 1);
});
Queue<String> queue = new LinkedList<>();
var topologicalSortedList = Lists.mutable.<String>empty();
inDegree.forEachKeyValue((id, count) -> {
if (count == 0) {
queue.offer(id);
}
});
while (!queue.isEmpty()) {
String id = queue.poll();
topologicalSortedList.add(id);
for (var neighborId : adjacencyGraph.get(id)) {
inDegree.put(neighborId, inDegree.get(neighborId) - 1);
if (inDegree.get(neighborId) == 0) {
queue.offer(neighborId);
}
}
}
topologicalSortedList.forEach(id -> log.info("{} {}", id, adjacencyGraph.get(id)));
topologicalSortedList.forEach(id -> log.info("{} {}", id, reverseAdjacencyGraph.get(id)));
var nodeQueue = new LinkedList<>(topologicalSortedList);
var chains = Lists.mutable.<MutableList<String>>empty();
while (!nodeQueue.isEmpty()) {
String currentId = nodeQueue.poll();
var subChain = Lists.mutable.<String>empty();
while (true) {
subChain.add(currentId);
nodeQueue.remove(currentId);
if (adjacencyGraph.get(currentId).size() != 1) {
break;
}
String nextId = adjacencyGraph.get(currentId).get(0);
if (reverseAdjacencyGraph.get(nextId).size() > 1) {
break;
}
currentId = nextId;
}
chains.add(subChain);
}
log.info("{}", chains);
return StrUtil.join(",", topologicalSortedList);
}
@Data
public static class FlowData {
private ImmutableList<Node> nodes;
private ImmutableList<Edge> edges;
private ImmutableMap<String, Object> data;
@Data
public static class Node {
private String id;
private String type;
}
@Data
public static class Edge {
private String id;
private String source;
private String target;
}
}
}

View File

@@ -1,12 +0,0 @@
package com.lanyuanxiaoyao.service.ai.web.flow;
/**
* @author lanyuanxiaoyao
* @version 20250625
*/
public class LlmNode extends BaseNode {
@Override
public void process() throws Exception {
}
}

View File

@@ -1,12 +0,0 @@
package com.lanyuanxiaoyao.service.ai.web.flow;
/**
* @author lanyuanxiaoyao
* @version 20250625
*/
public class StartNode extends BaseNode {
@Override
public void process() throws Exception {
}
}