feat(ai-web): 完成流程引擎雏形
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
package com.lanyuanxiaoyao.service.ai.web.engine.entity;
|
||||
|
||||
public abstract class FlowNodeRunner implements Runnable {
|
||||
private final FlowContext context;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user