feat(ai-web): 完成代码节点的执行
This commit is contained in:
@@ -6,6 +6,7 @@ import cn.hutool.extra.template.TemplateEngine;
|
|||||||
import cn.hutool.extra.template.TemplateUtil;
|
import cn.hutool.extra.template.TemplateUtil;
|
||||||
import com.lanyuanxiaoyao.service.ai.web.engine.entity.FlowContext;
|
import com.lanyuanxiaoyao.service.ai.web.engine.entity.FlowContext;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.eclipse.collections.api.factory.Maps;
|
import org.eclipse.collections.api.factory.Maps;
|
||||||
import org.eclipse.collections.api.map.ImmutableMap;
|
import org.eclipse.collections.api.map.ImmutableMap;
|
||||||
|
|
||||||
@@ -13,6 +14,7 @@ import org.eclipse.collections.api.map.ImmutableMap;
|
|||||||
* @author lanyuanxiaoyao
|
* @author lanyuanxiaoyao
|
||||||
* @version 20250711
|
* @version 20250711
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class FlowHelper {
|
public class FlowHelper {
|
||||||
private static final TemplateEngine TEMPLATE_ENGINE = TemplateUtil.createEngine();
|
private static final TemplateEngine TEMPLATE_ENGINE = TemplateUtil.createEngine();
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,13 @@ package com.lanyuanxiaoyao.service.ai.web.engine.node;
|
|||||||
|
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.lanyuanxiaoyao.service.ai.web.configuration.SpringBeanGetter;
|
||||||
import com.lanyuanxiaoyao.service.ai.web.engine.FlowHelper;
|
import com.lanyuanxiaoyao.service.ai.web.engine.FlowHelper;
|
||||||
import com.lanyuanxiaoyao.service.ai.web.engine.FlowNodeRunner;
|
import com.lanyuanxiaoyao.service.ai.web.engine.FlowNodeRunner;
|
||||||
import com.lanyuanxiaoyao.service.ai.web.engine.node.code.CodeExecutor;
|
import com.lanyuanxiaoyao.service.ai.web.engine.node.code.CodeExecutor;
|
||||||
import com.lanyuanxiaoyao.service.ai.web.engine.node.code.JavaScriptCodeExecutor;
|
import com.lanyuanxiaoyao.service.ai.web.engine.node.code.JavaScriptCodeExecutor;
|
||||||
|
import com.lanyuanxiaoyao.service.ai.web.engine.node.code.PythonCodeExecutor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -16,14 +19,15 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
public class CodeNode extends FlowNodeRunner {
|
public class CodeNode extends FlowNodeRunner {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
var mapper = SpringBeanGetter.getBean(ObjectMapper.class);
|
||||||
var inputVariablesMap = FlowHelper.generateInputVariablesMap(getNodeId(), getContext());
|
var inputVariablesMap = FlowHelper.generateInputVariablesMap(getNodeId(), getContext());
|
||||||
var type = this.<String>getData("type");
|
var type = this.<String>getData("type");
|
||||||
var script = this.<String>getData("content");
|
var script = this.<String>getData("content");
|
||||||
CodeExecutor executor = null;
|
CodeExecutor executor = switch (type) {
|
||||||
switch (type) {
|
case "javascript" -> new JavaScriptCodeExecutor(mapper);
|
||||||
case "javascript":
|
case "python" -> new PythonCodeExecutor(mapper);
|
||||||
executor = new JavaScriptCodeExecutor();
|
default -> null;
|
||||||
}
|
};
|
||||||
if (ObjectUtil.isNull(executor)) {
|
if (ObjectUtil.isNull(executor)) {
|
||||||
throw new RuntimeException(StrUtil.format("Unsupported type: {}", type));
|
throw new RuntimeException(StrUtil.format("Unsupported type: {}", type));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
package com.lanyuanxiaoyao.service.ai.web.engine.node;
|
package com.lanyuanxiaoyao.service.ai.web.engine.node;
|
||||||
|
|
||||||
import cn.hutool.core.bean.BeanUtil;
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
|
import cn.hutool.core.util.NumberUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.lanyuanxiaoyao.service.ai.web.engine.FlowHelper;
|
import com.lanyuanxiaoyao.service.ai.web.engine.FlowHelper;
|
||||||
import com.lanyuanxiaoyao.service.ai.web.engine.FlowNodeOptionalRunner;
|
import com.lanyuanxiaoyao.service.ai.web.engine.FlowNodeOptionalRunner;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -44,22 +47,41 @@ public class SwitchNode extends FlowNodeOptionalRunner {
|
|||||||
var left = FlowHelper.generateVariable(leftVariable, getContext());
|
var left = FlowHelper.generateVariable(leftVariable, getContext());
|
||||||
var operator = (String) condition.get("op");
|
var operator = (String) condition.get("op");
|
||||||
var right = condition.get("right");
|
var right = condition.get("right");
|
||||||
if (right instanceof Map<?, ?>) {
|
if (left instanceof CharSequence || left instanceof Boolean) {
|
||||||
var rightVariable = (String) BeanUtil.getProperty(condition, "right.field");
|
String source = StrUtil.toStringOrNull(left);
|
||||||
if (StrUtil.isNotBlank(rightVariable)) {
|
String target = StrUtil.toStringOrNull(right);
|
||||||
right = FlowHelper.generateVariable(rightVariable, getContext());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return switch (operator) {
|
return switch (operator) {
|
||||||
case "equal" -> ObjectUtil.equals(left, right);
|
case "equal" -> StrUtil.equals(source, target);
|
||||||
case "not_equal" -> ObjectUtil.notEqual(left, right);
|
case "not_equal" -> !StrUtil.equals(source, target);
|
||||||
case "is_empty" -> ObjectUtil.isEmpty(left);
|
case "is_empty" -> StrUtil.isBlank(source);
|
||||||
case "is_not_empty" -> ObjectUtil.isNotEmpty(left);
|
case "is_not_empty" -> StrUtil.isNotBlank(source);
|
||||||
case "like" -> StrUtil.contains((String) left, (String) right);
|
case "like" -> StrUtil.contains(source, target);
|
||||||
case "not_like" -> !StrUtil.contains((String) left, (String) right);
|
case "not_like" -> !StrUtil.contains(source, target);
|
||||||
case "starts_with" -> StrUtil.startWith((String) left, (String) right);
|
case "starts_with" -> StrUtil.startWith(source, target);
|
||||||
case "ends_with" -> StrUtil.endWith((String) left, (String) right);
|
case "ends_with" -> StrUtil.endWith(source, target);
|
||||||
|
default -> false;
|
||||||
|
};
|
||||||
|
} else if (left instanceof Number source) {
|
||||||
|
var sourceNumber = new BigDecimal(StrUtil.toString(source));
|
||||||
|
var targetNumber = new BigDecimal(StrUtil.toString(right));
|
||||||
|
return switch (operator) {
|
||||||
|
case "equal" -> NumberUtil.equals(sourceNumber, targetNumber);
|
||||||
|
case "not_equal" -> !NumberUtil.equals(sourceNumber, targetNumber);
|
||||||
|
case "greater" -> NumberUtil.isGreater(sourceNumber, targetNumber);
|
||||||
|
case "greater_equal" -> NumberUtil.isGreaterOrEqual(sourceNumber, targetNumber);
|
||||||
|
case "less" -> NumberUtil.isLess(sourceNumber, targetNumber);
|
||||||
|
case "less_equal" -> NumberUtil.isLessOrEqual(sourceNumber, targetNumber);
|
||||||
|
default -> false;
|
||||||
|
};
|
||||||
|
} else if (left instanceof Collection<?> source) {
|
||||||
|
return switch (operator) {
|
||||||
|
case "is_empty" -> CollectionUtil.isEmpty(source);
|
||||||
|
case "is_not_empty" -> CollectionUtil.isNotEmpty(source);
|
||||||
|
case "contain" -> CollectionUtil.safeContains(source, right);
|
||||||
|
case "not_contain" -> !CollectionUtil.safeContains(source, right);
|
||||||
default -> false;
|
default -> false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,29 @@
|
|||||||
package com.lanyuanxiaoyao.service.ai.web.engine.node.code;
|
package com.lanyuanxiaoyao.service.ai.web.engine.node.code;
|
||||||
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.eclipse.collections.api.factory.Maps;
|
import org.eclipse.collections.api.factory.Maps;
|
||||||
import org.eclipse.collections.api.map.ImmutableMap;
|
import org.eclipse.collections.api.map.ImmutableMap;
|
||||||
import org.graalvm.polyglot.Context;
|
import org.graalvm.polyglot.Context;
|
||||||
import org.graalvm.polyglot.Engine;
|
import org.graalvm.polyglot.Engine;
|
||||||
import org.graalvm.polyglot.Source;
|
import org.graalvm.polyglot.Source;
|
||||||
import org.graalvm.polyglot.TypeLiteral;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author lanyuanxiaoyao
|
* @author lanyuanxiaoyao
|
||||||
* @version 20250717
|
* @version 20250717
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class JavaScriptCodeExecutor implements CodeExecutor {
|
public class JavaScriptCodeExecutor implements CodeExecutor {
|
||||||
|
private final ObjectMapper mapper;
|
||||||
|
|
||||||
|
public JavaScriptCodeExecutor(ObjectMapper mapper) {
|
||||||
|
this.mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
@Override
|
@Override
|
||||||
public ImmutableMap<String, Object> execute(String script, ImmutableMap<String, Object> inputVariablesMap) {
|
public ImmutableMap<String, Object> execute(String script, ImmutableMap<String, Object> inputVariablesMap) {
|
||||||
if (StrUtil.isBlank(script)) {
|
if (StrUtil.isBlank(script)) {
|
||||||
@@ -26,7 +37,7 @@ public class JavaScriptCodeExecutor implements CodeExecutor{
|
|||||||
.build()
|
.build()
|
||||||
) {
|
) {
|
||||||
var bindings = context.getBindings("js");
|
var bindings = context.getBindings("js");
|
||||||
inputVariablesMap.forEachKeyValue(bindings::putMember);
|
bindings.putMember("context", inputVariablesMap);
|
||||||
var result = context.eval(
|
var result = context.eval(
|
||||||
Source.create(
|
Source.create(
|
||||||
"js",
|
"js",
|
||||||
@@ -34,11 +45,12 @@ public class JavaScriptCodeExecutor implements CodeExecutor{
|
|||||||
function process() {
|
function process() {
|
||||||
%s
|
%s
|
||||||
}
|
}
|
||||||
process();
|
var result = process();
|
||||||
|
JSON.stringify(result? result: {})
|
||||||
""".formatted(script)
|
""".formatted(script)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
return Maps.immutable.ofAll(result.as(new TypeLiteral<>() {}));
|
return mapper.readValue(result.asString(), new TypeReference<>() {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.lanyuanxiaoyao.service.ai.web.engine.node.code;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.eclipse.collections.api.map.ImmutableMap;
|
||||||
|
import org.python.core.PySystemState;
|
||||||
|
import org.python.util.PythonInterpreter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lanyuanxiaoyao
|
||||||
|
* @version 20250718
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class PythonCodeExecutor implements CodeExecutor {
|
||||||
|
private final ObjectMapper mapper;
|
||||||
|
|
||||||
|
public PythonCodeExecutor(ObjectMapper mapper) {
|
||||||
|
this.mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
@Override
|
||||||
|
public ImmutableMap<String, Object> execute(String script, ImmutableMap<String, Object> inputVariablesMap) {
|
||||||
|
try (var systemState = new PySystemState()) {
|
||||||
|
systemState.setdefaultencoding("UTF-8");
|
||||||
|
var interpreter = new PythonInterpreter(null, systemState);
|
||||||
|
script = Arrays.stream(script.split("\n"))
|
||||||
|
.map(line -> " " + line)
|
||||||
|
.collect(Collectors.joining("\n"));
|
||||||
|
interpreter.set("context", inputVariablesMap);
|
||||||
|
var pythonScript = interpreter.compile(
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
|
||||||
|
def process():
|
||||||
|
%s
|
||||||
|
|
||||||
|
result = json.dumps(process())
|
||||||
|
""".formatted(script)
|
||||||
|
);
|
||||||
|
interpreter.exec(pythonScript);
|
||||||
|
var result = interpreter.get("result");
|
||||||
|
return mapper.readValue((String) result.__tojava__(String.class), new TypeReference<>() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.lanyuanxiaoyao.service.ai.web;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.datatype.eclipsecollections.EclipseCollectionsModule;
|
||||||
|
import com.lanyuanxiaoyao.service.ai.web.engine.node.code.JavaScriptCodeExecutor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.eclipse.collections.api.factory.Maps;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lanyuanxiaoyao
|
||||||
|
* @version 20250717
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class TestCodeExecutor {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
var mapper = new ObjectMapper();
|
||||||
|
mapper.registerModule(new EclipseCollectionsModule());
|
||||||
|
var executor = new JavaScriptCodeExecutor(mapper);
|
||||||
|
var result = executor.execute(
|
||||||
|
"""
|
||||||
|
return {'code': 1, 'text': context.get('text')}
|
||||||
|
""",
|
||||||
|
Maps.immutable.of(
|
||||||
|
"text", "hello world"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
log.info("Result: {}", result);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package com.lanyuanxiaoyao.service.ai.web;
|
|
||||||
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.eclipse.collections.api.factory.Maps;
|
|
||||||
import org.eclipse.collections.api.map.ImmutableMap;
|
|
||||||
import org.graalvm.polyglot.Context;
|
|
||||||
import org.graalvm.polyglot.Engine;
|
|
||||||
import org.graalvm.polyglot.Source;
|
|
||||||
import org.graalvm.polyglot.TypeLiteral;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author lanyuanxiaoyao
|
|
||||||
* @version 20250717
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
public class TestJsExecutor {
|
|
||||||
public static void main(String[] args) {
|
|
||||||
var result = executeJavascript(
|
|
||||||
// language=JavaScript
|
|
||||||
"return 'hello'",
|
|
||||||
Maps.immutable.of(
|
|
||||||
"code", 1
|
|
||||||
)
|
|
||||||
);
|
|
||||||
log.info("Result: {}", result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ImmutableMap<String, Object> executeJavascript(String script, ImmutableMap<String, Object> inputVariablesMap) {
|
|
||||||
if (StrUtil.isBlank(script)) {
|
|
||||||
return Maps.immutable.empty();
|
|
||||||
}
|
|
||||||
try (var engin = Engine.create()) {
|
|
||||||
try (
|
|
||||||
var context = Context.newBuilder()
|
|
||||||
.allowAllAccess(true)
|
|
||||||
.engine(engin)
|
|
||||||
.build()
|
|
||||||
) {
|
|
||||||
var bindings = context.getBindings("js");
|
|
||||||
inputVariablesMap.forEachKeyValue(bindings::putMember);
|
|
||||||
var result = context.eval(
|
|
||||||
Source.create(
|
|
||||||
"js",
|
|
||||||
"""
|
|
||||||
function process() {
|
|
||||||
%s
|
|
||||||
}
|
|
||||||
process();
|
|
||||||
""".formatted(script)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return Maps.immutable.ofAll(result.as(new TypeLiteral<>() {}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user