Add File
This commit is contained in:
@@ -0,0 +1,987 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023-2025, Agents-Flex (fuhai999@gmail.com).
|
||||||
|
* <p>
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* <p>
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* <p>
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.agentsflex.core.chain;
|
||||||
|
|
||||||
|
import com.agentsflex.core.chain.event.*;
|
||||||
|
import com.agentsflex.core.chain.listener.*;
|
||||||
|
import com.agentsflex.core.prompt.template.TextPromptTemplate;
|
||||||
|
import com.agentsflex.core.util.CollectionUtil;
|
||||||
|
import com.agentsflex.core.util.MapUtil;
|
||||||
|
import com.agentsflex.core.util.NamedThreadPools;
|
||||||
|
import com.agentsflex.core.util.StringUtil;
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.fastjson.JSONPath;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Phaser;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
|
||||||
|
public class Chain extends ChainNode {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(Chain.class);
|
||||||
|
|
||||||
|
protected List<ChainNode> nodes;
|
||||||
|
protected List<ChainEdge> edges;
|
||||||
|
|
||||||
|
protected Map<String, Object> executeResult = null;
|
||||||
|
|
||||||
|
protected Map<Class<?>, List<ChainEventListener>> eventListeners = new HashMap<>(0);
|
||||||
|
protected List<ChainOutputListener> outputListeners = new ArrayList<>();
|
||||||
|
protected List<ChainErrorListener> chainErrorListeners = new ArrayList<>();
|
||||||
|
protected List<NodeErrorListener> nodeErrorListeners = new ArrayList<>();
|
||||||
|
protected List<ChainSuspendListener> suspendListeners = new ArrayList<>();
|
||||||
|
|
||||||
|
|
||||||
|
protected ExecutorService asyncNodeExecutors = NamedThreadPools.newFixedThreadPool("chain-executor");
|
||||||
|
protected Phaser phaser = new Phaser(1);
|
||||||
|
protected Map<String, NodeContext> nodeContexts = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
protected Map<String, ChainNode> suspendNodes = new ConcurrentHashMap<>();
|
||||||
|
protected List<Parameter> suspendForParameters;
|
||||||
|
protected ChainStatus status = ChainStatus.READY;
|
||||||
|
protected Exception exception;
|
||||||
|
protected String message;
|
||||||
|
|
||||||
|
|
||||||
|
public Chain() {
|
||||||
|
this.id = UUID.randomUUID().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Chain(ChainHolder holder) {
|
||||||
|
this.id = holder.getId();
|
||||||
|
this.name = holder.getName();
|
||||||
|
this.description = holder.getDescription();
|
||||||
|
|
||||||
|
this.nodes = holder.getNodes();
|
||||||
|
this.edges = holder.getEdges();
|
||||||
|
|
||||||
|
this.executeResult = holder.getExecuteResult();
|
||||||
|
this.nodeContexts = holder.getNodeContexts();
|
||||||
|
this.suspendNodes = holder.getSuspendNodes();
|
||||||
|
this.suspendForParameters = holder.getSuspendForParameters();
|
||||||
|
this.status = holder.getStatus();
|
||||||
|
this.message = holder.getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Map<Class<?>, List<ChainEventListener>> getEventListeners() {
|
||||||
|
return eventListeners;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEventListeners(Map<Class<?>, List<ChainEventListener>> eventListeners) {
|
||||||
|
this.eventListeners = eventListeners;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void addEventListener(Class<? extends ChainEvent> eventClass, ChainEventListener listener) {
|
||||||
|
List<ChainEventListener> chainEventListeners = eventListeners.computeIfAbsent(eventClass, k -> new ArrayList<>());
|
||||||
|
chainEventListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void addEventListener(ChainEventListener listener) {
|
||||||
|
List<ChainEventListener> chainEventListeners = eventListeners.computeIfAbsent(ChainEvent.class, k -> new ArrayList<>());
|
||||||
|
chainEventListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public synchronized void removeEventListener(ChainEventListener listener) {
|
||||||
|
for (List<ChainEventListener> list : eventListeners.values()) {
|
||||||
|
list.removeIf(item -> item == listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void removeEventListener(Class<? extends ChainEvent> eventClass, ChainEventListener listener) {
|
||||||
|
List<ChainEventListener> list = eventListeners.get(eventClass);
|
||||||
|
if (list != null && !list.isEmpty()) {
|
||||||
|
list.removeIf(item -> item == listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void addErrorListener(ChainErrorListener listener) {
|
||||||
|
this.chainErrorListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void removeErrorListener(ChainErrorListener listener) {
|
||||||
|
this.chainErrorListeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void addNodeErrorListener(NodeErrorListener listener) {
|
||||||
|
this.nodeErrorListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void removeNodeErrorListener(NodeErrorListener listener) {
|
||||||
|
this.nodeErrorListeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void addSuspendListener(ChainSuspendListener listener) {
|
||||||
|
this.suspendListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void removeSuspendListener(ChainSuspendListener listener) {
|
||||||
|
this.suspendListeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ChainOutputListener> getOutputListeners() {
|
||||||
|
return outputListeners;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOutputListeners(List<ChainOutputListener> outputListeners) {
|
||||||
|
this.outputListeners = outputListeners;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addOutputListener(ChainOutputListener outputListener) {
|
||||||
|
if (this.outputListeners == null) {
|
||||||
|
this.outputListeners = new ArrayList<>();
|
||||||
|
}
|
||||||
|
this.outputListeners.add(outputListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ChainNode> getNodes() {
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNodes(List<ChainNode> chainNodes) {
|
||||||
|
this.nodes = chainNodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addNode(ChainNode chainNode) {
|
||||||
|
if (nodes == null) {
|
||||||
|
this.nodes = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chainNode instanceof ChainEventListener) {
|
||||||
|
addEventListener((ChainEventListener) chainNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chainNode.getId() == null) {
|
||||||
|
chainNode.setId(UUID.randomUUID().toString());
|
||||||
|
}
|
||||||
|
nodes.add(chainNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ChainStatus getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(ChainStatus status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatusAndNotifyEvent(ChainStatus status) {
|
||||||
|
ChainStatus before = this.status;
|
||||||
|
this.status = status;
|
||||||
|
|
||||||
|
if (before != status) {
|
||||||
|
notifyEvent(new ChainStatusChangeEvent(this, this.status, before));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void notifyEvent(ChainEvent event) {
|
||||||
|
for (Map.Entry<Class<?>, List<ChainEventListener>> entry : eventListeners.entrySet()) {
|
||||||
|
if (entry.getKey().isInstance(event)) {
|
||||||
|
for (ChainEventListener chainEventListener : entry.getValue()) {
|
||||||
|
chainEventListener.onEvent(event, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Map<String, Object> getExecuteResult() {
|
||||||
|
return executeResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExecuteResult(Map<String, Object> executeResult) {
|
||||||
|
this.executeResult = executeResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ChainErrorListener> getChainErrorListeners() {
|
||||||
|
return chainErrorListeners;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setChainErrorListeners(List<ChainErrorListener> chainErrorListeners) {
|
||||||
|
this.chainErrorListeners = chainErrorListeners;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<NodeErrorListener> getNodeErrorListeners() {
|
||||||
|
return nodeErrorListeners;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNodeErrorListeners(List<NodeErrorListener> nodeErrorListeners) {
|
||||||
|
this.nodeErrorListeners = nodeErrorListeners;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ChainSuspendListener> getSuspendListeners() {
|
||||||
|
return suspendListeners;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSuspendListeners(List<ChainSuspendListener> suspendListeners) {
|
||||||
|
this.suspendListeners = suspendListeners;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, NodeContext> getNodeContexts() {
|
||||||
|
return nodeContexts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNodeContexts(Map<String, NodeContext> nodeContexts) {
|
||||||
|
this.nodeContexts = nodeContexts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, ChainNode> getSuspendNodes() {
|
||||||
|
return suspendNodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSuspendNodes(Map<String, ChainNode> suspendNodes) {
|
||||||
|
this.suspendNodes = suspendNodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Exception getException() {
|
||||||
|
return exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setException(Exception exception) {
|
||||||
|
this.exception = exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Phaser getPhaser() {
|
||||||
|
return phaser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPhaser(Phaser phaser) {
|
||||||
|
this.phaser = phaser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(String key, Object value) {
|
||||||
|
this.memory.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Object get(String key) {
|
||||||
|
if (StringUtil.noText(key)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object result = memory.get(key);
|
||||||
|
if (result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> parts = Arrays.asList(key.split("\\."));
|
||||||
|
if (parts.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int matchedLevels = 0;
|
||||||
|
for (int i = parts.size(); i > 0; i--) {
|
||||||
|
String tryKey = String.join(".", parts.subList(0, i));
|
||||||
|
Object tempResult = memory.get(tryKey);
|
||||||
|
if (tempResult != null) {
|
||||||
|
result = tempResult;
|
||||||
|
matchedLevels = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result instanceof Collection) {
|
||||||
|
List<Object> results = new ArrayList<>();
|
||||||
|
for (Object item : ((Collection<?>) result)) {
|
||||||
|
results.add(getResult(parts, matchedLevels, item));
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getResult(parts, matchedLevels, result);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object getResult(List<String> parts, int matchedLevels, Object result) {
|
||||||
|
List<String> remainingParts = parts.subList(matchedLevels, parts.size());
|
||||||
|
String jsonPath = "$." + String.join(".", remainingParts);
|
||||||
|
try {
|
||||||
|
return JSONPath.eval(result, jsonPath);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.toString(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Map<String, Object> execute(Chain parent) {
|
||||||
|
return executeForResult(parent.getMemory().getAll());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void execute(Map<String, Object> variables) {
|
||||||
|
runInLifeCycle(variables,
|
||||||
|
new ChainStartEvent(this, variables),
|
||||||
|
this::executeInternal);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Map<String, Object> executeForResult(Map<String, Object> variables) {
|
||||||
|
return executeForResult(variables, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> executeForResult(Map<String, Object> variables, boolean ignoreError) {
|
||||||
|
if (this.status == ChainStatus.SUSPEND) {
|
||||||
|
this.resume(variables);
|
||||||
|
} else {
|
||||||
|
runInLifeCycle(variables, new ChainStartEvent(this, variables), this::executeInternal);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ignoreError) {
|
||||||
|
if (this.status == ChainStatus.FINISHED_ABNORMAL) {
|
||||||
|
if (this.exception != null) {
|
||||||
|
if (this.exception instanceof RuntimeException) {
|
||||||
|
throw (RuntimeException) this.exception;
|
||||||
|
} else {
|
||||||
|
throw new ChainException(this.exception);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.message == null) this.message = "Chain execute error";
|
||||||
|
throw new ChainException(this.message);
|
||||||
|
}
|
||||||
|
} else if (this.status == ChainStatus.SUSPEND && this.exception != null) {
|
||||||
|
throw (ChainSuspendException) this.exception;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.executeResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Parameter> getParameters() {
|
||||||
|
List<ChainNode> startNodes = this.getStartNodes();
|
||||||
|
if (startNodes == null || startNodes.isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Parameter> parameters = new ArrayList<>();
|
||||||
|
for (ChainNode node : startNodes) {
|
||||||
|
List<Parameter> nodeParameters = node.getParameters();
|
||||||
|
if (nodeParameters != null) parameters.addAll(nodeParameters);
|
||||||
|
}
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getParameterValues(ChainNode node) {
|
||||||
|
return getParameterValues(node, node.getParameters());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getParameterValues(ChainNode node, List<? extends Parameter> parameters) {
|
||||||
|
return getParameterValues(node, parameters, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getParameterValues(ChainNode node, List<? extends Parameter> parameters, Map<String, Object> formatArgs) {
|
||||||
|
return getParameterValues(node, parameters, formatArgs, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isNullOrBlank(Object value) {
|
||||||
|
return value == null || value instanceof String && StringUtil.noText((String) value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getParameterValues(ChainNode node, List<? extends Parameter> parameters, Map<String, Object> formatArgs
|
||||||
|
, boolean getValueOnly) {
|
||||||
|
if (parameters == null || parameters.isEmpty()) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
Map<String, Object> variables = new HashMap<>();
|
||||||
|
List<Parameter> suspendParameters = null;
|
||||||
|
for (Parameter parameter : parameters) {
|
||||||
|
RefType refType = parameter.getRefType();
|
||||||
|
Object value;
|
||||||
|
if (refType == RefType.FIXED) {
|
||||||
|
if (formatArgs != null && !formatArgs.isEmpty()) {
|
||||||
|
value = TextPromptTemplate.of(parameter.getValue()).formatToString(formatArgs);
|
||||||
|
} else {
|
||||||
|
value = parameter.getValue();
|
||||||
|
}
|
||||||
|
} else if (refType == RefType.REF) {
|
||||||
|
value = this.get(parameter.getRef());
|
||||||
|
} else {
|
||||||
|
value = this.get(parameter.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value == null && parameter.getDefaultValue() != null) {
|
||||||
|
value = parameter.getDefaultValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (refType == RefType.INPUT && isNullOrBlank(value)) {
|
||||||
|
if (!getValueOnly) {
|
||||||
|
if (suspendParameters == null) {
|
||||||
|
suspendParameters = new ArrayList<>();
|
||||||
|
}
|
||||||
|
suspendParameters.add(parameter);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameter.isRequired() && isNullOrBlank(value)) {
|
||||||
|
if (!getValueOnly) {
|
||||||
|
throw new ChainException(node.getName() + " Missing required parameter:" + parameter.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value instanceof String) {
|
||||||
|
value = ((String) value).trim();
|
||||||
|
if (parameter.getDataType() == DataType.Boolean) {
|
||||||
|
value = "true".equalsIgnoreCase((String) value) || "1".equalsIgnoreCase((String) value);
|
||||||
|
} else if (parameter.getDataType() == DataType.Number) {
|
||||||
|
value = Long.parseLong((String) value);
|
||||||
|
} else if (parameter.getDataType() == DataType.Array) {
|
||||||
|
value = JSON.parseArray((String) value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variables.put(parameter.getName(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (suspendParameters != null && !suspendParameters.isEmpty()) {
|
||||||
|
this.setSuspendForParameters(suspendParameters);
|
||||||
|
this.suspend(node);
|
||||||
|
|
||||||
|
// 构建参数名称列表
|
||||||
|
String missingParams = suspendParameters.stream()
|
||||||
|
.map(Parameter::getName)
|
||||||
|
.collect(Collectors.joining("', '", "'", "'"));
|
||||||
|
|
||||||
|
String errorMessage = String.format(
|
||||||
|
"Node '%s' (type: %s) is suspended. Waiting for input parameters: %s.",
|
||||||
|
StringUtil.getFirstWithText(node.getName(), node.getId()),
|
||||||
|
node.getClass().getSimpleName(),
|
||||||
|
missingParams
|
||||||
|
);
|
||||||
|
|
||||||
|
throw new ChainSuspendException(errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return variables;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NodeContext getNodeContext(String nodeId) {
|
||||||
|
return MapUtil.computeIfAbsent(nodeContexts, nodeId, k -> new NodeContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void executeInternal() {
|
||||||
|
List<ChainNode> currentNodes = getStartNodes();
|
||||||
|
if (currentNodes == null || currentNodes.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ExecuteNode> executeNodes = new ArrayList<>();
|
||||||
|
for (ChainNode currentNode : currentNodes) {
|
||||||
|
executeNodes.add(new ExecuteNode(currentNode, null, ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
doExecuteNodes(executeNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void doExecuteNodes(List<ExecuteNode> executeNodes) {
|
||||||
|
for (ExecuteNode executeNode : executeNodes) {
|
||||||
|
ChainNode currentNode = executeNode.currentNode;
|
||||||
|
if (currentNode.isAsync()) {
|
||||||
|
phaser.register();
|
||||||
|
asyncNodeExecutors.execute(() -> {
|
||||||
|
try {
|
||||||
|
doExecuteNode(executeNode);
|
||||||
|
} finally {
|
||||||
|
phaser.arriveAndDeregister();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
doExecuteNode(executeNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取节点执行结果
|
||||||
|
*
|
||||||
|
* @param nodeId 节点ID
|
||||||
|
* @return 执行结果
|
||||||
|
*/
|
||||||
|
public Map<String, Object> getNodeExecuteResult(String nodeId) {
|
||||||
|
Map<String, Object> all = getMemory().getAll();
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
all.forEach((k, v) -> {
|
||||||
|
if (k.startsWith(nodeId)) {
|
||||||
|
String newKey = k.substring(nodeId.length() + 1);
|
||||||
|
result.put(newKey, v);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void addComputeCost(long computeCost) {
|
||||||
|
this.computeCost += computeCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void doExecuteNode(ExecuteNode executeNode) {
|
||||||
|
if (this.getStatus() != ChainStatus.RUNNING) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ChainNode currentNode = executeNode.currentNode;
|
||||||
|
NodeContext nodeContext = getNodeContext(currentNode.id);
|
||||||
|
|
||||||
|
Map<String, Object> executeResult = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
onNodeExecuteBefore(nodeContext);
|
||||||
|
|
||||||
|
if (shouldSkipCurrentNode(executeNode, nodeContext, currentNode)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ChainContext.setNode(currentNode);
|
||||||
|
notifyEvent(new NodeStartEvent(this, currentNode));
|
||||||
|
if (this.getStatus() != ChainStatus.RUNNING) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentNode.setNodeStatus(ChainNodeStatus.RUNNING);
|
||||||
|
onNodeExecuteStart(nodeContext);
|
||||||
|
try {
|
||||||
|
suspendNodes.remove(currentNode.getId());
|
||||||
|
executeResult = currentNode.execute(this);
|
||||||
|
addComputeCost(currentNode.getComputeCost());
|
||||||
|
} finally {
|
||||||
|
nodeContext.recordExecute(executeNode);
|
||||||
|
this.executeResult = executeResult;
|
||||||
|
}
|
||||||
|
} catch (Throwable error) {
|
||||||
|
currentNode.setNodeStatus(ChainNodeStatus.ERROR);
|
||||||
|
notifyNodeError(error, currentNode, executeResult);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
currentNode.setNodeStatusFinished();
|
||||||
|
onNodeExecuteEnd(nodeContext);
|
||||||
|
ChainContext.clearNode();
|
||||||
|
notifyEvent(new NodeEndEvent(this, currentNode, executeResult));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (executeResult != null && !executeResult.isEmpty()) {
|
||||||
|
executeResult.forEach((s, o) -> {
|
||||||
|
Chain.this.memory.put(currentNode.id + "." + s, o);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
onNodeExecuteAfter(nodeContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.getStatus() != ChainStatus.RUNNING) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 继续执行下一个节点
|
||||||
|
if (!currentNode.isLoopEnable()) {
|
||||||
|
doExecuteNextNodes(currentNode, executeResult);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 检查是否达到最大循环次数
|
||||||
|
if (currentNode.getMaxLoopCount() > 0 && nodeContext.getExecuteCount() >= currentNode.getMaxLoopCount()) {
|
||||||
|
doExecuteNextNodes(currentNode, executeResult);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查跳出条件
|
||||||
|
NodeCondition breakCondition = currentNode.getLoopBreakCondition();
|
||||||
|
if (breakCondition != null && breakCondition.check(this, nodeContext, executeResult)) {
|
||||||
|
doExecuteNextNodes(currentNode, executeResult);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 等待间隔
|
||||||
|
long loopIntervalMs = currentNode.getLoopIntervalMs();
|
||||||
|
if (loopIntervalMs > 0) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(loopIntervalMs);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt(); // 恢复中断状态
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 继续执行当前节点
|
||||||
|
doExecuteNode(executeNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录节点触发,并检查当前节点的执行条件是否未通过。
|
||||||
|
* 若条件未通过,则返回 true,表示应跳过该节点的执行。
|
||||||
|
*
|
||||||
|
* @param executeNode 当前正在执行的节点
|
||||||
|
* @param nodeContext 节点上下文,用于记录触发信息
|
||||||
|
* @param currentNode 当前链路节点配置
|
||||||
|
* @return 如果条件不满足(需要跳过),返回 true;否则返回 false
|
||||||
|
*/
|
||||||
|
private synchronized boolean shouldSkipCurrentNode(ExecuteNode executeNode, NodeContext nodeContext, ChainNode currentNode) {
|
||||||
|
|
||||||
|
// record trigger 和 check 必须在同步块内执行,
|
||||||
|
// 否则会导致并发问题:全部节点触发了 trigger,但是 check 还未开始执行
|
||||||
|
nodeContext.recordTrigger(executeNode);
|
||||||
|
|
||||||
|
NodeCondition condition = currentNode.getCondition();
|
||||||
|
if (condition == null) {
|
||||||
|
return false; // 无条件则不应跳过
|
||||||
|
}
|
||||||
|
|
||||||
|
ChainNode prevNode = executeNode.prevNode;
|
||||||
|
Map<String, Object> prevNodeExecuteResult = prevNode != null ? getNodeExecuteResult(prevNode.id) : Collections.emptyMap();
|
||||||
|
|
||||||
|
// 返回 true 表示条件不满足,应跳过当前节点
|
||||||
|
return !condition.check(this, nodeContext, prevNodeExecuteResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行后续节点(可能有多个)
|
||||||
|
*
|
||||||
|
* @param currentNode 当前节点
|
||||||
|
* @param executeResult 执行结果
|
||||||
|
*/
|
||||||
|
private void doExecuteNextNodes(ChainNode currentNode, Map<String, Object> executeResult) {
|
||||||
|
List<ChainEdge> outwardEdges = currentNode.getOutwardEdges();
|
||||||
|
if (CollectionUtil.hasItems(outwardEdges)) {
|
||||||
|
List<ExecuteNode> nextExecuteNodes = new ArrayList<>(outwardEdges.size());
|
||||||
|
for (ChainEdge chainEdge : outwardEdges) {
|
||||||
|
ChainNode nextNode = getNodeById(chainEdge.getTarget());
|
||||||
|
if (nextNode == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
EdgeCondition condition = chainEdge.getCondition();
|
||||||
|
if (condition == null || condition.check(this, chainEdge, executeResult)) {
|
||||||
|
nextExecuteNodes.add(new ExecuteNode(nextNode, currentNode, chainEdge.getId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
doExecuteNodes(nextExecuteNodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onNodeExecuteAfter(NodeContext nodeContext) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onNodeExecuteEnd(NodeContext nodeContext) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onNodeExecuteStart(NodeContext nodeContext) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onNodeExecuteBefore(NodeContext nodeContext) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ChainNode> getStartNodes() {
|
||||||
|
if (this.nodes == null || this.nodes.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.suspendNodes.isEmpty()) {
|
||||||
|
return new ArrayList<>(suspendNodes.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ChainNode> nodes = new ArrayList<>();
|
||||||
|
|
||||||
|
for (ChainNode node : this.nodes) {
|
||||||
|
if (CollectionUtil.noItems(node.getInwardEdges())) {
|
||||||
|
nodes.add(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private ChainNode getNodeById(String id) {
|
||||||
|
if (id == null || StringUtil.noText(id)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ChainNode node : this.nodes) {
|
||||||
|
if (id.equals(node.getId())) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void runInLifeCycle(Map<String, Object> variables, ChainEvent startEvent, Runnable runnable) {
|
||||||
|
if (variables != null) {
|
||||||
|
this.memory.putAll(variables);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
ChainContext.setChain(this);
|
||||||
|
notifyEvent(startEvent);
|
||||||
|
try {
|
||||||
|
setStatusAndNotifyEvent(ChainStatus.RUNNING);
|
||||||
|
runnable.run();
|
||||||
|
} catch (ChainSuspendException cse) {
|
||||||
|
notifySuspend();
|
||||||
|
this.exception = cse;
|
||||||
|
} catch (Exception e) {
|
||||||
|
this.exception = e;
|
||||||
|
setStatusAndNotifyEvent(ChainStatus.ERROR);
|
||||||
|
notifyError(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.phaser.arriveAndAwaitAdvance();
|
||||||
|
|
||||||
|
if (status == ChainStatus.RUNNING) {
|
||||||
|
setStatusAndNotifyEvent(ChainStatus.FINISHED_NORMAL);
|
||||||
|
} else if (status == ChainStatus.ERROR) {
|
||||||
|
setStatusAndNotifyEvent(ChainStatus.FINISHED_ABNORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
ChainContext.clearChain();
|
||||||
|
notifyEvent(new ChainEndEvent(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void notifyOutput(ChainNode node, Object response) {
|
||||||
|
for (ChainOutputListener inputListener : outputListeners) {
|
||||||
|
inputListener.onOutput(this, node, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void notifySuspend() {
|
||||||
|
for (ChainSuspendListener suspendListener : suspendListeners) {
|
||||||
|
suspendListener.onSuspend(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void notifyError(Throwable error) {
|
||||||
|
for (ChainErrorListener errorListener : chainErrorListeners) {
|
||||||
|
errorListener.onError(error, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void notifyNodeError(Throwable error, ChainNode node, Map<String, Object> executeResult) {
|
||||||
|
for (NodeErrorListener errorListener : nodeErrorListeners) {
|
||||||
|
errorListener.onError(error, node, executeResult, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void stopNormal(String message) {
|
||||||
|
this.message = message;
|
||||||
|
setStatusAndNotifyEvent(ChainStatus.FINISHED_NORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void stopError(String message) {
|
||||||
|
this.message = message;
|
||||||
|
setStatusAndNotifyEvent(ChainStatus.FINISHED_ABNORMAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void output(ChainNode node, Object response) {
|
||||||
|
notifyOutput(node, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public List<ChainEdge> getEdges() {
|
||||||
|
return edges;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEdges(List<ChainEdge> edges) {
|
||||||
|
this.edges = edges;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addEdge(ChainEdge edge) {
|
||||||
|
if (this.edges == null) {
|
||||||
|
this.edges = new ArrayList<>();
|
||||||
|
}
|
||||||
|
this.edges.add(edge);
|
||||||
|
|
||||||
|
boolean findSource = false, findTarget = false;
|
||||||
|
|
||||||
|
for (ChainNode node : this.nodes) {
|
||||||
|
if (node.getId().equals(edge.getSource())) {
|
||||||
|
node.addOutwardEdge(edge);
|
||||||
|
findSource = true;
|
||||||
|
} else if (node.getId().equals(edge.getTarget())) {
|
||||||
|
node.addInwardEdge(edge);
|
||||||
|
findTarget = true;
|
||||||
|
}
|
||||||
|
if (findSource && findTarget) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessage(String message) {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExecutorService getAsyncNodeExecutors() {
|
||||||
|
return asyncNodeExecutors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAsyncNodeExecutors(ExecutorService asyncNodeExecutors) {
|
||||||
|
this.asyncNodeExecutors = asyncNodeExecutors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Parameter> getSuspendForParameters() {
|
||||||
|
return suspendForParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSuspendForParameters(List<Parameter> suspendForParameters) {
|
||||||
|
this.suspendForParameters = suspendForParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void addSuspendForParameter(Parameter suspendForParameter) {
|
||||||
|
if (this.suspendForParameters == null) {
|
||||||
|
this.suspendForParameters = new ArrayList<>();
|
||||||
|
}
|
||||||
|
this.suspendForParameters.add(suspendForParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void suspend(ChainNode node) {
|
||||||
|
try {
|
||||||
|
suspendNodes.putIfAbsent(node.getId(), node);
|
||||||
|
} finally {
|
||||||
|
setStatusAndNotifyEvent(ChainStatus.SUSPEND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resume(Map<String, Object> variables) {
|
||||||
|
runInLifeCycle(variables,
|
||||||
|
new ChainResumeEvent(this, variables),
|
||||||
|
this::executeInternal);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class ExecuteNode {
|
||||||
|
|
||||||
|
final ChainNode currentNode;
|
||||||
|
final ChainNode prevNode;
|
||||||
|
final String fromEdgeId;
|
||||||
|
|
||||||
|
public ExecuteNode(ChainNode currentNode, ChainNode prevNode, String fromEdgeId) {
|
||||||
|
this.currentNode = currentNode;
|
||||||
|
this.prevNode = prevNode;
|
||||||
|
this.fromEdgeId = fromEdgeId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
//node
|
||||||
|
this.memory.clear();
|
||||||
|
this.nodeStatus = ChainNodeStatus.READY;
|
||||||
|
|
||||||
|
|
||||||
|
//chain
|
||||||
|
this.status = ChainStatus.READY;
|
||||||
|
this.executeResult = null;
|
||||||
|
this.message = null;
|
||||||
|
this.exception = null;
|
||||||
|
this.nodeContexts.clear();
|
||||||
|
|
||||||
|
//算力消耗
|
||||||
|
this.computeCost = 0;
|
||||||
|
|
||||||
|
if (this.suspendNodes != null) {
|
||||||
|
this.suspendNodes.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.suspendForParameters != null) {
|
||||||
|
this.suspendForParameters.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.asyncNodeExecutors = NamedThreadPools.newFixedThreadPool("chain-executor");
|
||||||
|
this.phaser = new Phaser(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toJSON() {
|
||||||
|
return ChainHolder.fromChain(this).toJSON();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Chain fromJSON(String jsonString) {
|
||||||
|
return ChainHolder.fromJSON(jsonString).toChain();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChainNodeValidResult validate() throws Exception {
|
||||||
|
if (this.validator != null) {
|
||||||
|
return this.validator.validate(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.nodes == null || this.nodes.isEmpty()) {
|
||||||
|
return ChainNodeValidResult.fail("Chain nodes can not be empty.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> details = new HashMap<>();
|
||||||
|
for (ChainNode node : this.nodes) {
|
||||||
|
ChainNodeValidResult nodeResult = node.validate();
|
||||||
|
if (nodeResult != null && !nodeResult.isSuccess()) {
|
||||||
|
details.put(node.getId(), nodeResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return details.isEmpty() ? ChainNodeValidResult.ok() : ChainNodeValidResult.fail("", details);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Chain{" +
|
||||||
|
"nodes=" + nodes +
|
||||||
|
", edges=" + edges +
|
||||||
|
", executeResult=" + executeResult +
|
||||||
|
", eventListeners=" + eventListeners +
|
||||||
|
", outputListeners=" + outputListeners +
|
||||||
|
", chainErrorListeners=" + chainErrorListeners +
|
||||||
|
", nodeErrorListeners=" + nodeErrorListeners +
|
||||||
|
", suspendListeners=" + suspendListeners +
|
||||||
|
", asyncNodeExecutors=" + asyncNodeExecutors +
|
||||||
|
", phaser=" + phaser +
|
||||||
|
", nodeContexts=" + nodeContexts +
|
||||||
|
", suspendNodes=" + suspendNodes +
|
||||||
|
", suspendForParameters=" + suspendForParameters +
|
||||||
|
", status=" + status +
|
||||||
|
", exception=" + exception +
|
||||||
|
", message='" + message + '\'' +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user