feat: 支持工作流代码节点 Python 执行引擎
- easyflow-module-ai: 新增 PythonRuntimeEngine、不可用引擎降级实现与引擎能力服务 - easyflow-module-ai: 在 TinyFlowConfigService 注册 python/py 引擎并增加启动探测与可用性日志 - easyflow-api: 新增 /api/v1/workflow/supportedCodeEngines 能力查询接口 - easyflow-starter: 增加 node.code-engine.python 配置项默认值 - Dockerfile: 安装 python3 运行时以支持容器内执行 - test: 增加 PythonRuntimeEngineTest 覆盖成功、语法错误、超时、输出限制、命令缺失场景 - chore(ui-admin): 更新 cspell 词典
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
package tech.easyflow.ai.easyagentsflow.code;
|
||||
|
||||
import com.easyagents.flow.core.chain.Chain;
|
||||
import com.easyagents.flow.core.chain.ChainDefinition;
|
||||
import com.easyagents.flow.core.chain.repository.InMemoryChainStateRepository;
|
||||
import com.easyagents.flow.core.node.CodeNode;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Assume;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class PythonRuntimeEngineTest {
|
||||
|
||||
private static final String PYTHON_COMMAND = "python3";
|
||||
|
||||
@Test
|
||||
public void testExecuteSuccess() {
|
||||
Assume.assumeTrue(PythonRuntimeEngine.probe(PYTHON_COMMAND, 1500L).isAvailable());
|
||||
|
||||
Chain chain = createChain();
|
||||
CodeNode node = (CodeNode) chain.getDefinition().getNodeById("code-test");
|
||||
chain.getState().getMemory().put("a", 1);
|
||||
chain.getState().getMemory().put("b", 2);
|
||||
|
||||
PythonRuntimeEngine engine = new PythonRuntimeEngine(PYTHON_COMMAND, 3000L, 65536, System.getProperty("java.io.tmpdir"));
|
||||
Map<String, Object> result = engine.execute("_result['sum'] = a + b\n_result['env_type'] = type(_env).__name__", node, chain);
|
||||
|
||||
Assert.assertEquals(3, ((Number) result.get("sum")).intValue());
|
||||
Assert.assertEquals("dict", result.get("env_type"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSyntaxError() {
|
||||
Assume.assumeTrue(PythonRuntimeEngine.probe(PYTHON_COMMAND, 1500L).isAvailable());
|
||||
|
||||
Chain chain = createChain();
|
||||
CodeNode node = (CodeNode) chain.getDefinition().getNodeById("code-test");
|
||||
PythonRuntimeEngine engine = new PythonRuntimeEngine(PYTHON_COMMAND, 3000L, 65536, System.getProperty("java.io.tmpdir"));
|
||||
|
||||
assertExecuteFail(engine, node, chain, "if True print('broken')", "执行失败");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTimeout() {
|
||||
Assume.assumeTrue(PythonRuntimeEngine.probe(PYTHON_COMMAND, 1500L).isAvailable());
|
||||
|
||||
Chain chain = createChain();
|
||||
CodeNode node = (CodeNode) chain.getDefinition().getNodeById("code-test");
|
||||
PythonRuntimeEngine engine = new PythonRuntimeEngine(PYTHON_COMMAND, 100L, 65536, System.getProperty("java.io.tmpdir"));
|
||||
|
||||
assertExecuteFail(engine, node, chain, "import time\ntime.sleep(1)\n_result['ok'] = 1", "超时");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOutputTooLarge() {
|
||||
Assume.assumeTrue(PythonRuntimeEngine.probe(PYTHON_COMMAND, 1500L).isAvailable());
|
||||
|
||||
Chain chain = createChain();
|
||||
CodeNode node = (CodeNode) chain.getDefinition().getNodeById("code-test");
|
||||
PythonRuntimeEngine engine = new PythonRuntimeEngine(PYTHON_COMMAND, 3000L, 64, System.getProperty("java.io.tmpdir"));
|
||||
|
||||
assertExecuteFail(engine, node, chain, "_result['text'] = 'a' * 2048", "输出超出限制");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCommandNotFound() {
|
||||
Chain chain = createChain();
|
||||
CodeNode node = (CodeNode) chain.getDefinition().getNodeById("code-test");
|
||||
PythonRuntimeEngine engine = new PythonRuntimeEngine("python3_not_exists_for_test", 1000L, 65536, System.getProperty("java.io.tmpdir"));
|
||||
|
||||
assertExecuteFail(engine, node, chain, "_result['ok'] = 1", "执行失败");
|
||||
}
|
||||
|
||||
private Chain createChain() {
|
||||
ChainDefinition definition = new ChainDefinition();
|
||||
CodeNode node = new CodeNode();
|
||||
node.setId("code-test");
|
||||
node.setEngine("python");
|
||||
definition.addNode(node);
|
||||
|
||||
Chain chain = new Chain(definition, UUID.randomUUID().toString());
|
||||
chain.setChainStateRepository(new InMemoryChainStateRepository());
|
||||
return chain;
|
||||
}
|
||||
|
||||
private void assertExecuteFail(PythonRuntimeEngine engine, CodeNode node, Chain chain, String code, String messageContains) {
|
||||
try {
|
||||
engine.execute(code, node, chain);
|
||||
Assert.fail("expected execute fail");
|
||||
} catch (RuntimeException e) {
|
||||
Assert.assertTrue(e.getMessage().contains(messageContains));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user