diff --git a/assistant-agent-autoconfigure/src/main/java/com/alibaba/assistant/agent/autoconfigure/subagent/node/CodeGeneratorNode.java b/assistant-agent-autoconfigure/src/main/java/com/alibaba/assistant/agent/autoconfigure/subagent/node/CodeGeneratorNode.java index e7d5e68b..c10ef3b6 100644 --- a/assistant-agent-autoconfigure/src/main/java/com/alibaba/assistant/agent/autoconfigure/subagent/node/CodeGeneratorNode.java +++ b/assistant-agent-autoconfigure/src/main/java/com/alibaba/assistant/agent/autoconfigure/subagent/node/CodeGeneratorNode.java @@ -405,10 +405,11 @@ private String buildSystemPrompt(Language language, List codeactToo sb.append("2. 函数名和参数必须与要求完全一致\n"); sb.append("3. 调用工具使用 实例名.方法名() 格式\n"); sb.append("4. 只返回纯代码,不要 ```python 标记\n"); + sb.append("5. ⚠️【禁止生成注释】不要生成任何注释,包括:函数注释(docstring)、行注释(#开头)、多行注释。直接输出纯净的可执行代码。\n"); if (isCondition) { - sb.append("5. 条件函数必须返回 True 或 False\n"); + sb.append("6. 条件函数必须返回 True 或 False\n"); } else { - sb.append("5. 【重要】每个函数必须有 return 语句返回结果\n"); + sb.append("6. 【重要】每个函数必须有 return 语句返回结果\n"); sb.append(" - 查询/搜索类:return 搜索结果\n"); sb.append(" - 处理/计算类:return 处理结果\n"); sb.append(" - 通知/回复类:先执行操作,再 return 操作结果或状态\n"); @@ -616,15 +617,15 @@ private ReturnSchema getReturnSchema(CodeactTool tool) { // 优先从 registry 获取(包含观测到的 schema) if (returnSchemaRegistry != null) { - logger.info("CodeGeneratorNode#getReturnSchema - reason=开始查询schema, registryHashCode={}, toolName={}, allToolsWithSchema={}", + logger.debug("CodeGeneratorNode#getReturnSchema - reason=开始查询schema, registryHashCode={}, toolName={}, allToolsWithSchema={}", System.identityHashCode(returnSchemaRegistry), toolName, returnSchemaRegistry.getToolsWithSchema()); ReturnSchema observed = returnSchemaRegistry.getSchema(toolName).orElse(null); if (observed != null) { - logger.info("CodeGeneratorNode#getReturnSchema - reason=从registry获取到schema, toolName={}, sampleCount={}, hasSuccessShape={}", + logger.debug("CodeGeneratorNode#getReturnSchema - reason=从registry获取到schema, toolName={}, sampleCount={}, hasSuccessShape={}", toolName, observed.getSampleCount(), observed.getSuccessShape() != null); return observed; } else { - logger.info("CodeGeneratorNode#getReturnSchema - reason=registry中未找到schema, toolName={}", toolName); + logger.debug("CodeGeneratorNode#getReturnSchema - reason=registry中未找到schema, toolName={}", toolName); } } else { logger.warn("CodeGeneratorNode#getReturnSchema - reason=returnSchemaRegistry为null, toolName={}", toolName); diff --git a/assistant-agent-core/src/main/java/com/alibaba/assistant/agent/core/executor/GraalCodeExecutor.java b/assistant-agent-core/src/main/java/com/alibaba/assistant/agent/core/executor/GraalCodeExecutor.java index 4e8378d4..13531a00 100644 --- a/assistant-agent-core/src/main/java/com/alibaba/assistant/agent/core/executor/GraalCodeExecutor.java +++ b/assistant-agent-core/src/main/java/com/alibaba/assistant/agent/core/executor/GraalCodeExecutor.java @@ -41,6 +41,9 @@ import org.springframework.ai.chat.model.ToolContext; import org.springframework.ai.tool.ToolCallback; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.nio.charset.StandardCharsets; @@ -438,8 +441,24 @@ private String generateCustomVariables(ToolContext toolContext) { return sb.toString(); } + /** + * 用于序列化复杂对象的 ObjectMapper + */ + private static final ObjectMapper JSON_MAPPER = new ObjectMapper(); + /** * 将 Java 对象转换为 Python 字面量 + * + *

支持的类型转换: + *

*/ @SuppressWarnings("unchecked") private String toPythonLiteral(Object value) { @@ -486,8 +505,38 @@ private String toPythonLiteral(Object value) { sb.append("}"); return sb.toString(); } - // 其他类型转为字符串 - return "\"" + value.toString().replace("\\", "\\\\").replace("\"", "\\\"") + "\""; + // 其他复杂对象类型:尝试使用 Jackson 序列化为 JSON,然后在 Python 中解析 + // 这样可以正确处理 Attachment 等 POJO 对象,而不是简单地调用 toString() + return convertComplexObjectToPythonLiteral(value); + } + + /** + * 将复杂对象转换为 Python 字面量 + * + *

先尝试使用 Jackson 将对象序列化为 JSON,然后转换为 Python 字典/列表格式。 + * 如果序列化失败,则退化为字符串表示。 + * + * @param value 要转换的对象 + * @return Python 字面量表示 + */ + private String convertComplexObjectToPythonLiteral(Object value) { + try { + // 使用 Jackson 将对象序列化为 JSON 字符串 + String jsonStr = JSON_MAPPER.writeValueAsString(value); + + // 将 JSON 反序列化为 Map 或 List,然后递归转换为 Python 字面量 + // 这样可以复用已有的 Map/List 处理逻辑 + Object parsed = JSON_MAPPER.readValue(jsonStr, Object.class); + + // 递归调用 toPythonLiteral 处理反序列化后的对象(Map 或 List) + return toPythonLiteral(parsed); + } catch (JsonProcessingException e) { + // JSON 序列化失败,退化为字符串表示 + logger.warn("GraalCodeExecutor#convertComplexObjectToPythonLiteral - reason=JSON序列化失败, " + + "valueType={}, error={}, 退化为toString()", value.getClass().getName(), e.getMessage()); + String strValue = value.toString(); + return "\"" + strValue.replace("\\", "\\\\").replace("\"", "\\\"") + "\""; + } } /** diff --git a/assistant-agent-core/src/main/java/com/alibaba/assistant/agent/core/tool/schema/DefaultReturnSchemaRegistry.java b/assistant-agent-core/src/main/java/com/alibaba/assistant/agent/core/tool/schema/DefaultReturnSchemaRegistry.java index 896bff55..47f9d942 100644 --- a/assistant-agent-core/src/main/java/com/alibaba/assistant/agent/core/tool/schema/DefaultReturnSchemaRegistry.java +++ b/assistant-agent-core/src/main/java/com/alibaba/assistant/agent/core/tool/schema/DefaultReturnSchemaRegistry.java @@ -137,7 +137,7 @@ public Optional getSchema(String toolName) { return Optional.empty(); } ReturnSchema schema = mergedSchemas.get(toolName); - logger.info("DefaultReturnSchemaRegistry#getSchema - reason=查询schema, hashCode={}, toolName={}, found={}, allTools={}", + logger.debug("DefaultReturnSchemaRegistry#getSchema - reason=查询schema, hashCode={}, toolName={}, found={}, allTools={}", System.identityHashCode(this), toolName, schema != null, mergedSchemas.keySet()); return Optional.ofNullable(schema); } diff --git a/assistant-agent-evaluation/src/main/java/com/alibaba/assistant/agent/evaluation/builder/EvaluationCriterionBuilder.java b/assistant-agent-evaluation/src/main/java/com/alibaba/assistant/agent/evaluation/builder/EvaluationCriterionBuilder.java index 04f817bc..1a6ce3b6 100644 --- a/assistant-agent-evaluation/src/main/java/com/alibaba/assistant/agent/evaluation/builder/EvaluationCriterionBuilder.java +++ b/assistant-agent-evaluation/src/main/java/com/alibaba/assistant/agent/evaluation/builder/EvaluationCriterionBuilder.java @@ -223,6 +223,44 @@ public EvaluationCriterionBuilder multimodalForImages(String attachmentsPath, St return this; } + /** + * Set the timeout for this criterion execution (in milliseconds). + * When timeout occurs, the criterion returns TIMEOUT status with defaultValue. + * + * @param timeoutMs timeout in milliseconds + * @return this builder + */ + public EvaluationCriterionBuilder timeoutMs(long timeoutMs) { + criterion.setTimeoutMs(timeoutMs); + return this; + } + + /** + * Set the default value to use when the criterion times out or errors. + * This ensures evaluation can continue even when individual criteria fail. + * + * @param defaultValue the default value to use on timeout/error + * @return this builder + */ + public EvaluationCriterionBuilder defaultValue(Object defaultValue) { + criterion.setDefaultValue(defaultValue); + return this; + } + + /** + * Configure timeout and default value together. + * A convenience method for setting both timeout and fallback behavior. + * + * @param timeoutMs timeout in milliseconds + * @param defaultValue the default value to use on timeout/error + * @return this builder + */ + public EvaluationCriterionBuilder withTimeout(long timeoutMs, Object defaultValue) { + criterion.setTimeoutMs(timeoutMs); + criterion.setDefaultValue(defaultValue); + return this; + } + /** * Ensure the dependent criterion is in the dependsOn list */ diff --git a/assistant-agent-evaluation/src/main/java/com/alibaba/assistant/agent/evaluation/evaluator/LLMBasedEvaluator.java b/assistant-agent-evaluation/src/main/java/com/alibaba/assistant/agent/evaluation/evaluator/LLMBasedEvaluator.java index 7c6aff30..4f907003 100644 --- a/assistant-agent-evaluation/src/main/java/com/alibaba/assistant/agent/evaluation/evaluator/LLMBasedEvaluator.java +++ b/assistant-agent-evaluation/src/main/java/com/alibaba/assistant/agent/evaluation/evaluator/LLMBasedEvaluator.java @@ -27,6 +27,7 @@ import org.slf4j.LoggerFactory; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.ai.chat.prompt.Prompt; import java.util.HashMap; @@ -48,11 +49,17 @@ public class LLMBasedEvaluator implements Evaluator { private final ChatModel chatModel; private final String evaluatorId; private final ObjectMapper objectMapper; + private final ChatOptions chatOptions; public LLMBasedEvaluator(ChatModel chatModel, String evaluatorId) { + this(chatModel, evaluatorId, null); + } + + public LLMBasedEvaluator(ChatModel chatModel, String evaluatorId, ChatOptions chatOptions) { this.chatModel = chatModel; this.evaluatorId = evaluatorId; this.objectMapper = new ObjectMapper(); + this.chatOptions = chatOptions; } @Override @@ -71,8 +78,8 @@ public CriterionResult evaluate(CriterionExecutionContext executionContext) { logger.debug("Evaluating criterion {} with LLM, prompt: {}, chatModel: {} ({})", executionContext.getCriterion().getName(), promptText, chatModel.getClass().getSimpleName(), chatModel); - // Call LLM - Prompt prompt = new Prompt(promptText); + // Call LLM with optional ChatOptions + Prompt prompt = chatOptions != null ? new Prompt(promptText, chatOptions) : new Prompt(promptText); ChatResponse chatResponse = chatModel.call(prompt); String response = chatResponse.getResult().getOutput().getText(); diff --git a/assistant-agent-evaluation/src/main/java/com/alibaba/assistant/agent/evaluation/evaluator/MultimodalLLMBasedEvaluator.java b/assistant-agent-evaluation/src/main/java/com/alibaba/assistant/agent/evaluation/evaluator/MultimodalLLMBasedEvaluator.java index 50af1a42..3f078b8a 100644 --- a/assistant-agent-evaluation/src/main/java/com/alibaba/assistant/agent/evaluation/evaluator/MultimodalLLMBasedEvaluator.java +++ b/assistant-agent-evaluation/src/main/java/com/alibaba/assistant/agent/evaluation/evaluator/MultimodalLLMBasedEvaluator.java @@ -29,6 +29,7 @@ import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.content.Media; @@ -49,6 +50,7 @@ public class MultimodalLLMBasedEvaluator extends LLMBasedEvaluator { private static final Logger logger = LoggerFactory.getLogger(MultimodalLLMBasedEvaluator.class); private final ChatModel multimodalChatModel; + private final ChatOptions multimodalChatOptions; /** * 用于将 Map 转换为 MediaConvertible 实现类的 ObjectMapper @@ -69,7 +71,21 @@ public class MultimodalLLMBasedEvaluator extends LLMBasedEvaluator { * @param evaluatorId 评估器ID */ public MultimodalLLMBasedEvaluator(ChatModel textModel, ChatModel multimodalModel, String evaluatorId) { - this(textModel, multimodalModel, evaluatorId, createDefaultObjectMapper()); + this(textModel, multimodalModel, evaluatorId, null, null, createDefaultObjectMapper()); + } + + /** + * 构造函数(带 ChatOptions) + * + * @param textModel 纯文本模型,用于普通评估 + * @param multimodalModel 多模态模型,用于处理图片等多模态输入 + * @param evaluatorId 评估器ID + * @param textChatOptions 纯文本模型的ChatOptions(可选) + * @param multimodalChatOptions 多模态模型的ChatOptions(可选) + */ + public MultimodalLLMBasedEvaluator(ChatModel textModel, ChatModel multimodalModel, String evaluatorId, + ChatOptions textChatOptions, ChatOptions multimodalChatOptions) { + this(textModel, multimodalModel, evaluatorId, textChatOptions, multimodalChatOptions, createDefaultObjectMapper()); } /** @@ -93,8 +109,24 @@ private static ObjectMapper createDefaultObjectMapper() { */ public MultimodalLLMBasedEvaluator(ChatModel textModel, ChatModel multimodalModel, String evaluatorId, ObjectMapper objectMapper) { - super(textModel, evaluatorId); + this(textModel, multimodalModel, evaluatorId, null, null, objectMapper); + } + + /** + * 构造函数(完整参数) + * + * @param textModel 纯文本模型,用于普通评估 + * @param multimodalModel 多模态模型,用于处理图片等多模态输入 + * @param evaluatorId 评估器ID + * @param textChatOptions 纯文本模型的ChatOptions(可选) + * @param multimodalChatOptions 多模态模型的ChatOptions(可选) + * @param objectMapper 用于类型转换的 ObjectMapper + */ + public MultimodalLLMBasedEvaluator(ChatModel textModel, ChatModel multimodalModel, String evaluatorId, + ChatOptions textChatOptions, ChatOptions multimodalChatOptions, ObjectMapper objectMapper) { + super(textModel, evaluatorId, textChatOptions); this.multimodalChatModel = multimodalModel; + this.multimodalChatOptions = multimodalChatOptions; this.objectMapper = objectMapper != null ? objectMapper : new ObjectMapper(); } @@ -316,8 +348,10 @@ protected CriterionResult evaluateWithMultimodal(CriterionExecutionContext conte .media(mediaList) .build(); - // 调用多模态模型 - Prompt prompt = new Prompt(List.of(userMessage)); + // 调用多模态模型(支持自定义ChatOptions) + Prompt prompt = multimodalChatOptions != null + ? new Prompt(List.of(userMessage), multimodalChatOptions) + : new Prompt(List.of(userMessage)); ChatResponse chatResponse = multimodalChatModel.call(prompt); String responseText = chatResponse.getResult().getOutput().getText(); diff --git a/assistant-agent-evaluation/src/main/java/com/alibaba/assistant/agent/evaluation/executor/CriterionEvaluationAction.java b/assistant-agent-evaluation/src/main/java/com/alibaba/assistant/agent/evaluation/executor/CriterionEvaluationAction.java index aa6a7ff5..3c2c0845 100644 --- a/assistant-agent-evaluation/src/main/java/com/alibaba/assistant/agent/evaluation/executor/CriterionEvaluationAction.java +++ b/assistant-agent-evaluation/src/main/java/com/alibaba/assistant/agent/evaluation/executor/CriterionEvaluationAction.java @@ -41,6 +41,8 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.function.Function; import java.util.stream.Collectors; @@ -79,6 +81,7 @@ public CriterionEvaluationAction(EvaluationCriterion criterion, @Override public Map apply(OverAllState state) { Map updates = new HashMap<>(); + long startTime = System.currentTimeMillis(); try { // Extract essential components directly from state @@ -106,19 +109,50 @@ public Map apply(OverAllState state) { logger.debug("Conditional execution check passed for criterion: {}", criterion.getName()); } + // Determine timeout: criterion-specific > suite default + long timeoutMs = criterion.getTimeoutMs() != null + ? criterion.getTimeoutMs() + : suite.getDefaultCriterionTimeoutMs(); + // Check if batching is enabled CriterionBatchingConfig batchingConfig = criterion.getBatchingConfig(); boolean batchingEnabled = batchingConfig != null && batchingConfig.isEnabled(); CriterionResult result; - if (batchingEnabled) { - // Execute with batching - logger.debug("Batching enabled for criterion: {}", criterion.getName()); - result = executeWithBatching(evaluationContext, dependencyResults, batchingConfig); - } else { - // Execute without batching (original logic) - logger.debug("Batching not enabled for criterion: {}", criterion.getName()); - result = executeWithoutBatching(evaluationContext, dependencyResults); + + // Execute criterion with timeout protection + try { + if (executorService != null && timeoutMs > 0) { + // Use CompletableFuture with timeout for criterion execution + final EvaluationContext finalContext = evaluationContext; + final Map finalDepResults = dependencyResults; + final CriterionBatchingConfig finalBatchConfig = batchingConfig; + final boolean finalBatchEnabled = batchingEnabled; + + result = CompletableFuture.supplyAsync(() -> { + if (finalBatchEnabled) { + return executeWithBatching(finalContext, finalDepResults, finalBatchConfig); + } else { + return executeWithoutBatching(finalContext, finalDepResults); + } + }, executorService).get(timeoutMs, TimeUnit.MILLISECONDS); + } else { + // No executor or timeout disabled, execute directly + if (batchingEnabled) { + logger.debug("Batching enabled for criterion: {}", criterion.getName()); + result = executeWithBatching(evaluationContext, dependencyResults, batchingConfig); + } else { + logger.debug("Batching not enabled for criterion: {}", criterion.getName()); + result = executeWithoutBatching(evaluationContext, dependencyResults); + } + } + } catch (TimeoutException te) { + // Criterion timed out - return timeout result with default value + long elapsedMs = System.currentTimeMillis() - startTime; + logger.warn("Criterion '{}' timed out after {}ms (timeout={}ms), using default value: {}", + criterion.getName(), elapsedMs, timeoutMs, criterion.getDefaultValue()); + + result = buildTimeoutResult(startTime, timeoutMs); } // Store result as an independent state key @@ -137,29 +171,75 @@ public Map apply(OverAllState state) { com.alibaba.assistant.agent.evaluation.observation.EvaluationObservationLifecycleListener .registerCriterionResult(criterion.getName(), result); - logger.info("Criterion {} completed with result: {}", criterion.getName(), result.getValue()); + logger.info("Criterion {} completed with status: {}, value: {}", + criterion.getName(), result.getStatus(), result.getValue()); } catch (Exception e) { logger.error("Error executing criterion {}: {}", criterion.getName(), e.getMessage(), e); - // Create error result - CriterionResult errorResult = new CriterionResult(); - errorResult.setCriterionName(criterion.getName()); - errorResult.setStatus(CriterionStatus.ERROR); - errorResult.setErrorMessage(e.getMessage()); - errorResult.setStartTimeMillis(System.currentTimeMillis()); - errorResult.setEndTimeMillis(System.currentTimeMillis()); + // Create error result with default value if available + CriterionResult errorResult = buildErrorResult(startTime, e.getMessage()); // Store error result as an independent state key updates.put(criterion.getName() + "_result", errorResult); - updates.put(criterion.getName() + "_status", "ERROR"); + updates.put(criterion.getName() + "_status", errorResult.getStatus().toString()); updates.put(criterion.getName() + "_error", e.getMessage()); + updates.put(criterion.getName() + "_completed", true); updates.put("timestamp", System.currentTimeMillis()); + + if (errorResult.getValue() != null) { + updates.put(criterion.getName() + "_value", errorResult.getValue()); + } + + // Register error result for observation + com.alibaba.assistant.agent.evaluation.observation.EvaluationObservationLifecycleListener + .registerCriterionResult(criterion.getName(), errorResult); } return updates; } + /** + * Build a timeout result with the configured default value + */ + private CriterionResult buildTimeoutResult(long startTime, long timeoutMs) { + CriterionResult result = new CriterionResult(); + result.setCriterionName(criterion.getName()); + result.setStatus(CriterionStatus.TIMEOUT); + result.setValue(criterion.getDefaultValue()); + result.setReason(String.format("Criterion execution timed out after %dms", timeoutMs)); + result.setStartTimeMillis(startTime); + result.setEndTimeMillis(System.currentTimeMillis()); + + Map metadata = new HashMap<>(); + metadata.put("timeoutMs", timeoutMs); + metadata.put("defaultValueUsed", true); + result.setMetadata(metadata); + + return result; + } + + /** + * Build an error result with the configured default value + */ + private CriterionResult buildErrorResult(long startTime, String errorMessage) { + CriterionResult result = new CriterionResult(); + result.setCriterionName(criterion.getName()); + result.setStatus(CriterionStatus.ERROR); + result.setValue(criterion.getDefaultValue()); + result.setErrorMessage(errorMessage); + result.setStartTimeMillis(startTime); + result.setEndTimeMillis(System.currentTimeMillis()); + + if (criterion.getDefaultValue() != null) { + Map metadata = new HashMap<>(); + metadata.put("defaultValueUsed", true); + result.setMetadata(metadata); + } + + return result; + } + /** * Build dependency results map from individual state keys * This reads _result keys from state for all dependencies diff --git a/assistant-agent-evaluation/src/main/java/com/alibaba/assistant/agent/evaluation/model/EvaluationCriterion.java b/assistant-agent-evaluation/src/main/java/com/alibaba/assistant/agent/evaluation/model/EvaluationCriterion.java index 8f9294cc..45e9e46e 100644 --- a/assistant-agent-evaluation/src/main/java/com/alibaba/assistant/agent/evaluation/model/EvaluationCriterion.java +++ b/assistant-agent-evaluation/src/main/java/com/alibaba/assistant/agent/evaluation/model/EvaluationCriterion.java @@ -109,6 +109,21 @@ public class EvaluationCriterion { */ private MultimodalConfig multimodalConfig; + /** + * Timeout configuration for this criterion (in milliseconds). + * If not set, will use the suite-level default timeout. + * When timeout occurs, the criterion returns TIMEOUT status with defaultValue. + */ + private Long timeoutMs; + + /** + * Default value to use when: + * - The criterion times out + * - The criterion execution fails with an error + * This ensures evaluation can continue even when individual criteria fail. + */ + private Object defaultValue; + public String getName() { return name; } @@ -237,6 +252,22 @@ public void setMultimodalConfig(MultimodalConfig multimodalConfig) { this.multimodalConfig = multimodalConfig; } + public Long getTimeoutMs() { + return timeoutMs; + } + + public void setTimeoutMs(Long timeoutMs) { + this.timeoutMs = timeoutMs; + } + + public Object getDefaultValue() { + return defaultValue; + } + + public void setDefaultValue(Object defaultValue) { + this.defaultValue = defaultValue; + } + /** * Few-shot example for LLM guidance */ diff --git a/assistant-agent-evaluation/src/main/java/com/alibaba/assistant/agent/evaluation/model/EvaluationSuite.java b/assistant-agent-evaluation/src/main/java/com/alibaba/assistant/agent/evaluation/model/EvaluationSuite.java index 840d6073..ecb81a3a 100644 --- a/assistant-agent-evaluation/src/main/java/com/alibaba/assistant/agent/evaluation/model/EvaluationSuite.java +++ b/assistant-agent-evaluation/src/main/java/com/alibaba/assistant/agent/evaluation/model/EvaluationSuite.java @@ -60,6 +60,13 @@ public class EvaluationSuite { */ private List criteria = new ArrayList<>(); + /** + * Default timeout for individual criterion execution (in milliseconds). + * This is used when a criterion does not specify its own timeoutMs. + * Default is 10000ms (10 seconds). + */ + private long defaultCriterionTimeoutMs = 10000; + /** * Compiled evaluation graph representation based on criteria dependencies. * This is built by EvaluationSuiteBuilder using graph-core and treated as an internal implementation detail for execution. @@ -124,4 +131,12 @@ public CompiledGraph getCompiledGraph() { public void setCompiledGraph(CompiledGraph compiledGraph) { this.compiledGraph = compiledGraph; } + + public long getDefaultCriterionTimeoutMs() { + return defaultCriterionTimeoutMs; + } + + public void setDefaultCriterionTimeoutMs(long defaultCriterionTimeoutMs) { + this.defaultCriterionTimeoutMs = defaultCriterionTimeoutMs; + } }