fix: 展示 JavaScript 代码节点执行错误
- 配置脚本执行超时并保留可定位的 JavaScript 异常信息 - 在试运行步骤和终态结果中展示并展开失败节点
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
package tech.easyflow.ai.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* JavaScript 代码执行引擎配置。
|
||||
*/
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "node.code-engine.javascript")
|
||||
public class JavascriptCodeEngineProps {
|
||||
|
||||
/**
|
||||
* 单次脚本执行超时时间,单位毫秒。
|
||||
*/
|
||||
private long timeoutMs = 5000L;
|
||||
|
||||
/**
|
||||
* 获取单次脚本执行超时时间。
|
||||
*
|
||||
* @return 超时时间,单位毫秒
|
||||
*/
|
||||
public long getTimeoutMs() {
|
||||
return timeoutMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置单次脚本执行超时时间。
|
||||
*
|
||||
* @param timeoutMs 超时时间,单位毫秒
|
||||
*/
|
||||
public void setTimeoutMs(long timeoutMs) {
|
||||
if (timeoutMs <= 0L) {
|
||||
throw new IllegalArgumentException(
|
||||
"node.code-engine.javascript.timeout-ms 必须大于 0");
|
||||
}
|
||||
this.timeoutMs = timeoutMs;
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
import tech.easyflow.ai.config.BochaaiProps;
|
||||
import tech.easyflow.ai.config.CodeEngineProps;
|
||||
import tech.easyflow.ai.config.JavascriptCodeEngineProps;
|
||||
import tech.easyflow.ai.easyagentsflow.code.PythonRuntimeEngine;
|
||||
import tech.easyflow.ai.easyagentsflow.code.UnavailableCodeRuntimeEngine;
|
||||
import tech.easyflow.ai.node.*;
|
||||
@@ -42,6 +43,8 @@ public class TinyFlowConfigService {
|
||||
@Resource
|
||||
private CodeEngineProps codeEngineProps;
|
||||
@Resource
|
||||
private JavascriptCodeEngineProps javascriptCodeEngineProps;
|
||||
@Resource
|
||||
private CodeEngineCapabilityService codeEngineCapabilityService;
|
||||
|
||||
public void initProvidersAndNodeParsers(ChainParser chainParser) {
|
||||
@@ -107,6 +110,9 @@ public class TinyFlowConfigService {
|
||||
|
||||
public void setCodeRuntimeEngineProvider() {
|
||||
CodeRuntimeEngineManager manager = CodeRuntimeEngineManager.getInstance();
|
||||
manager.configureJavascriptRuntimeEngine(javascriptCodeEngineProps.getTimeoutMs());
|
||||
log.info("已配置 JavaScript 代码执行引擎: timeoutMs={}",
|
||||
javascriptCodeEngineProps.getTimeoutMs());
|
||||
|
||||
PythonRuntimeEngine.ProbeResult probeResult;
|
||||
String pythonCommand = codeEngineProps.getCommand();
|
||||
|
||||
@@ -7,9 +7,11 @@ import com.easyagents.flow.core.chain.NodeStatus;
|
||||
import com.easyagents.flow.core.chain.repository.ChainStateRepository;
|
||||
import com.easyagents.flow.core.chain.repository.NodeStateRepository;
|
||||
import com.easyagents.flow.core.chain.runtime.ChainExecutor;
|
||||
import com.easyagents.flow.core.code.impl.JavascriptExecutionException;
|
||||
import org.springframework.stereotype.Component;
|
||||
import tech.easyflow.ai.easyagentsflow.entity.ChainInfo;
|
||||
import tech.easyflow.ai.easyagentsflow.entity.NodeInfo;
|
||||
import tech.easyflow.common.util.StringUtil;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
@@ -89,7 +91,7 @@ public class TinyFlowService {
|
||||
res.setStatus(chainState.getStatus().getValue());
|
||||
ExceptionSummary chainError = chainState.getError();
|
||||
if (chainError != null) {
|
||||
res.setMessage(chainError.getRootCauseClass() + " --> " + chainError.getRootCauseMessage());
|
||||
res.setMessage(formatError(chainError));
|
||||
}
|
||||
Map<String, Object> executeResult = chainState.getExecuteResult();
|
||||
if (executeResult != null && !executeResult.isEmpty()) {
|
||||
@@ -118,7 +120,7 @@ public class TinyFlowService {
|
||||
if (nodeState != null) {
|
||||
ExceptionSummary error = nodeState.getError();
|
||||
if (error != null) {
|
||||
node.setMessage(error.getRootCauseClass() + " --> " + error.getRootCauseMessage());
|
||||
node.setMessage(formatError(error));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,4 +137,31 @@ public class TinyFlowService {
|
||||
node.setSuspendForParameters(chainState.getSuspendForParameters());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将执行异常转换为试运行界面可读的错误信息。
|
||||
*
|
||||
* @param error 持久化的异常摘要
|
||||
* @return 可展示的错误信息
|
||||
*/
|
||||
private String formatError(ExceptionSummary error) {
|
||||
if (JavascriptExecutionException.class.getName()
|
||||
.equals(error.getExceptionClass())
|
||||
&& StringUtil.hasText(error.getMessage())) {
|
||||
return error.getMessage();
|
||||
}
|
||||
String rootClass = StringUtil.hasText(error.getRootCauseClass())
|
||||
? error.getRootCauseClass()
|
||||
: error.getExceptionClass();
|
||||
String rootMessage = StringUtil.hasText(error.getRootCauseMessage())
|
||||
? error.getRootCauseMessage()
|
||||
: error.getMessage();
|
||||
if (StringUtil.noText(rootClass)) {
|
||||
return rootMessage;
|
||||
}
|
||||
if (StringUtil.noText(rootMessage)) {
|
||||
return rootClass;
|
||||
}
|
||||
return rootClass + " --> " + rootMessage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package tech.easyflow.ai.config;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* JavaScript 代码执行引擎配置测试。
|
||||
*/
|
||||
public class JavascriptCodeEnginePropsTest {
|
||||
|
||||
/**
|
||||
* 验证正数超时配置可以正常写入。
|
||||
*/
|
||||
@Test
|
||||
public void shouldAcceptPositiveTimeout() {
|
||||
JavascriptCodeEngineProps props =
|
||||
new JavascriptCodeEngineProps();
|
||||
|
||||
props.setTimeoutMs(1000L);
|
||||
|
||||
Assert.assertEquals(1000L, props.getTimeoutMs());
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证非正数超时配置会立即失败。
|
||||
*/
|
||||
@Test
|
||||
public void shouldRejectNonPositiveTimeout() {
|
||||
assertInvalidTimeout(0L);
|
||||
assertInvalidTimeout(-1L);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证指定超时值无法写入配置。
|
||||
*
|
||||
* @param timeoutMs 非法超时值
|
||||
*/
|
||||
private void assertInvalidTimeout(long timeoutMs) {
|
||||
JavascriptCodeEngineProps props =
|
||||
new JavascriptCodeEngineProps();
|
||||
try {
|
||||
props.setTimeoutMs(timeoutMs);
|
||||
Assert.fail("非正数超时配置应执行失败");
|
||||
} catch (IllegalArgumentException exception) {
|
||||
Assert.assertTrue(exception.getMessage().contains(
|
||||
"必须大于 0"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,13 @@ package tech.easyflow.ai.easyagentsflow.service;
|
||||
|
||||
import com.easyagents.flow.core.chain.ChainState;
|
||||
import com.easyagents.flow.core.chain.ChainStatus;
|
||||
import com.easyagents.flow.core.chain.ExceptionSummary;
|
||||
import com.easyagents.flow.core.chain.NodeState;
|
||||
import com.easyagents.flow.core.chain.NodeStatus;
|
||||
import com.easyagents.flow.core.chain.repository.ChainStateRepository;
|
||||
import com.easyagents.flow.core.chain.repository.NodeStateRepository;
|
||||
import com.easyagents.flow.core.chain.runtime.ChainExecutor;
|
||||
import com.easyagents.flow.core.code.impl.JavascriptExecutionException;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import tech.easyflow.ai.easyagentsflow.entity.ChainInfo;
|
||||
@@ -137,6 +139,50 @@ public class TinyFlowServiceTest {
|
||||
.load(EXECUTE_ID, NODE_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证 JavaScript 执行错误使用面向试运行用户的定位信息。
|
||||
*
|
||||
* @throws Exception 测试依赖注入失败时抛出
|
||||
*/
|
||||
@Test
|
||||
public void shouldExposeJavascriptExecutionMessage()
|
||||
throws Exception {
|
||||
ChainExecutor chainExecutor = mock(ChainExecutor.class);
|
||||
ChainStateRepository chainStateRepository =
|
||||
mock(ChainStateRepository.class);
|
||||
NodeStateRepository nodeStateRepository =
|
||||
mock(NodeStateRepository.class);
|
||||
String message =
|
||||
"JavaScript 语法错误(第 2 行,第 3 列):Unexpected token";
|
||||
ExceptionSummary error = new ExceptionSummary(
|
||||
new JavascriptExecutionException(
|
||||
message,
|
||||
new IllegalArgumentException("raw parser error")));
|
||||
ChainState chainState = new ChainState();
|
||||
chainState.setStatus(ChainStatus.FAILED);
|
||||
chainState.setError(error);
|
||||
NodeState nodeState = new NodeState();
|
||||
nodeState.setStatus(NodeStatus.FAILED);
|
||||
nodeState.setError(error);
|
||||
when(chainExecutor.getChainStateRepository())
|
||||
.thenReturn(chainStateRepository);
|
||||
when(chainExecutor.getNodeStateRepository())
|
||||
.thenReturn(nodeStateRepository);
|
||||
when(chainStateRepository.load(EXECUTE_ID))
|
||||
.thenReturn(chainState);
|
||||
when(nodeStateRepository.load(EXECUTE_ID, NODE_ID))
|
||||
.thenReturn(nodeState);
|
||||
TinyFlowService service = service(chainExecutor);
|
||||
|
||||
ChainInfo result = service.getChainStatus(
|
||||
EXECUTE_ID, List.of(node(NodeStatus.READY)));
|
||||
|
||||
Assert.assertEquals(message, result.getMessage());
|
||||
Assert.assertEquals(
|
||||
message,
|
||||
result.getNodes().get(NODE_ID).getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建带指定初始状态的设计器节点。
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user