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 com.lanyuanxiaoyao.service.ai.web.engine.entity.FlowContext;
|
||||
import java.util.Map;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.collections.api.factory.Maps;
|
||||
import org.eclipse.collections.api.map.ImmutableMap;
|
||||
|
||||
@@ -13,6 +14,7 @@ import org.eclipse.collections.api.map.ImmutableMap;
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250711
|
||||
*/
|
||||
@Slf4j
|
||||
public class FlowHelper {
|
||||
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.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.FlowNodeRunner;
|
||||
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.PythonCodeExecutor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
@@ -16,14 +19,15 @@ import lombok.extern.slf4j.Slf4j;
|
||||
public class CodeNode extends FlowNodeRunner {
|
||||
@Override
|
||||
public void run() {
|
||||
var mapper = SpringBeanGetter.getBean(ObjectMapper.class);
|
||||
var inputVariablesMap = FlowHelper.generateInputVariablesMap(getNodeId(), getContext());
|
||||
var type = this.<String>getData("type");
|
||||
var script = this.<String>getData("content");
|
||||
CodeExecutor executor = null;
|
||||
switch (type) {
|
||||
case "javascript":
|
||||
executor = new JavaScriptCodeExecutor();
|
||||
}
|
||||
CodeExecutor executor = switch (type) {
|
||||
case "javascript" -> new JavaScriptCodeExecutor(mapper);
|
||||
case "python" -> new PythonCodeExecutor(mapper);
|
||||
default -> null;
|
||||
};
|
||||
if (ObjectUtil.isNull(executor)) {
|
||||
throw new RuntimeException(StrUtil.format("Unsupported type: {}", type));
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package com.lanyuanxiaoyao.service.ai.web.engine.node;
|
||||
|
||||
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 com.lanyuanxiaoyao.service.ai.web.engine.FlowHelper;
|
||||
import com.lanyuanxiaoyao.service.ai.web.engine.FlowNodeOptionalRunner;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -44,22 +47,41 @@ public class SwitchNode extends FlowNodeOptionalRunner {
|
||||
var left = FlowHelper.generateVariable(leftVariable, getContext());
|
||||
var operator = (String) condition.get("op");
|
||||
var right = condition.get("right");
|
||||
if (right instanceof Map<?, ?>) {
|
||||
var rightVariable = (String) BeanUtil.getProperty(condition, "right.field");
|
||||
if (StrUtil.isNotBlank(rightVariable)) {
|
||||
right = FlowHelper.generateVariable(rightVariable, getContext());
|
||||
}
|
||||
if (left instanceof CharSequence || left instanceof Boolean) {
|
||||
String source = StrUtil.toStringOrNull(left);
|
||||
String target = StrUtil.toStringOrNull(right);
|
||||
return switch (operator) {
|
||||
case "equal" -> StrUtil.equals(source, target);
|
||||
case "not_equal" -> !StrUtil.equals(source, target);
|
||||
case "is_empty" -> StrUtil.isBlank(source);
|
||||
case "is_not_empty" -> StrUtil.isNotBlank(source);
|
||||
case "like" -> StrUtil.contains(source, target);
|
||||
case "not_like" -> !StrUtil.contains(source, target);
|
||||
case "starts_with" -> StrUtil.startWith(source, target);
|
||||
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;
|
||||
};
|
||||
}
|
||||
return switch (operator) {
|
||||
case "equal" -> ObjectUtil.equals(left, right);
|
||||
case "not_equal" -> ObjectUtil.notEqual(left, right);
|
||||
case "is_empty" -> ObjectUtil.isEmpty(left);
|
||||
case "is_not_empty" -> ObjectUtil.isNotEmpty(left);
|
||||
case "like" -> StrUtil.contains((String) left, (String) right);
|
||||
case "not_like" -> !StrUtil.contains((String) left, (String) right);
|
||||
case "starts_with" -> StrUtil.startWith((String) left, (String) right);
|
||||
case "ends_with" -> StrUtil.endWith((String) left, (String) right);
|
||||
default -> false;
|
||||
};
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,29 @@
|
||||
package com.lanyuanxiaoyao.service.ai.web.engine.node.code;
|
||||
|
||||
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.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
|
||||
*/
|
||||
public class JavaScriptCodeExecutor implements CodeExecutor{
|
||||
@Slf4j
|
||||
public class JavaScriptCodeExecutor implements CodeExecutor {
|
||||
private final ObjectMapper mapper;
|
||||
|
||||
public JavaScriptCodeExecutor(ObjectMapper mapper) {
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public ImmutableMap<String, Object> execute(String script, ImmutableMap<String, Object> inputVariablesMap) {
|
||||
if (StrUtil.isBlank(script)) {
|
||||
@@ -26,7 +37,7 @@ public class JavaScriptCodeExecutor implements CodeExecutor{
|
||||
.build()
|
||||
) {
|
||||
var bindings = context.getBindings("js");
|
||||
inputVariablesMap.forEachKeyValue(bindings::putMember);
|
||||
bindings.putMember("context", inputVariablesMap);
|
||||
var result = context.eval(
|
||||
Source.create(
|
||||
"js",
|
||||
@@ -34,11 +45,12 @@ public class JavaScriptCodeExecutor implements CodeExecutor{
|
||||
function process() {
|
||||
%s
|
||||
}
|
||||
process();
|
||||
var result = process();
|
||||
JSON.stringify(result? result: {})
|
||||
""".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