diff --git a/src/main/java/it/cnr/isti/workflow/manager/blocks/configurations/CheckerBlockConfiguration.java b/src/main/java/it/cnr/isti/workflow/manager/blocks/configurations/ConditionalBlockConfiguration.java similarity index 69% rename from src/main/java/it/cnr/isti/workflow/manager/blocks/configurations/CheckerBlockConfiguration.java rename to src/main/java/it/cnr/isti/workflow/manager/blocks/configurations/ConditionalBlockConfiguration.java index fcc2640..89e300a 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/blocks/configurations/CheckerBlockConfiguration.java +++ b/src/main/java/it/cnr/isti/workflow/manager/blocks/configurations/ConditionalBlockConfiguration.java @@ -6,7 +6,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import it.cnr.isti.workflow.manager.blocks.configurations.annotations.LongText; import it.cnr.isti.workflow.manager.blocks.configurations.annotations.Structural; import it.cnr.isti.workflow.manager.blocks.configurations.annotations.UiDependency; -import it.cnr.isti.workflow.manager.blocks.types.CheckerBlockType; +import it.cnr.isti.workflow.manager.blocks.types.ConditionalBlockType; import it.cnr.isti.workflow.manager.llms.LLMDescriptor; import jakarta.validation.Valid; import jakarta.validation.constraints.AssertTrue; @@ -19,7 +19,7 @@ import lombok.NonNull; @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) @Getter @EqualsAndHashCode(callSuper = true) -public class CheckerBlockConfiguration extends BlockConfiguration { +public class ConditionalBlockConfiguration extends BlockConfiguration { @UiDependency(field = "useLlm", equals = "false") @Structural @@ -45,26 +45,36 @@ public class CheckerBlockConfiguration extends BlockConfiguration getBlockType() { - return CheckerBlockType.class; + public Class getBlockType() { + return ConditionalBlockType.class; } - public static CheckerBlockConfiguration empty() { - CheckerBlockConfiguration configuration = new CheckerBlockConfiguration(); - configuration.name = CheckerBlockType.TYPE; + public static ConditionalBlockConfiguration empty() { + ConditionalBlockConfiguration configuration = new ConditionalBlockConfiguration(); + configuration.name = ConditionalBlockType.TYPE; configuration.condition = ""; configuration.useLlm = false; + configuration.outputTemplate = ""; return configuration; } @@ -85,4 +95,10 @@ public class CheckerBlockConfiguration extends BlockConfiguration { +public class ConditionalBlockFactory implements BlockFactory { public static final String TRUE_OUTPUT = "true"; public static final String FALSE_OUTPUT = "false"; @@ -24,15 +24,15 @@ public class CheckerBlockFactory implements BlockFactory create(CheckerBlockConfiguration configuration) { + public Block create(ConditionalBlockConfiguration configuration) { List inputs = retrieveInputs(configuration).stream() .map(inputName -> IODescriptor.of(inputName, IOType.TEXT)) .toList(); - return Block.builder() + return Block.builder() .inputs(inputs) .output(IODescriptor.of(TRUE_OUTPUT, IOType.TEXT)) .output(IODescriptor.of(FALSE_OUTPUT, IOType.TEXT)) @@ -42,16 +42,16 @@ public class CheckerBlockFactory implements BlockFactory createEmpty() { - return create(CheckerBlockConfiguration.empty()); + public Block createEmpty() { + return create(ConditionalBlockConfiguration.empty()); } @Override - public Class getBlockType() { - return CheckerBlockType.class; + public Class getBlockType() { + return ConditionalBlockType.class; } - private List retrieveInputs(CheckerBlockConfiguration configuration) { + private List retrieveInputs(ConditionalBlockConfiguration configuration) { Set inputNames = new LinkedHashSet<>(); if (configuration.isUseLlm()) { collectMatches(inputNames, configuration.getPrompt(), PLACEHOLDER_PATTERN, 1); @@ -59,6 +59,7 @@ public class CheckerBlockFactory implements BlockFactory> getBlockConfigurationClass() { - return CheckerBlockConfiguration.class; + return ConditionalBlockConfiguration.class; } } diff --git a/src/main/java/it/cnr/isti/workflow/manager/executions/ExecutionObject.java b/src/main/java/it/cnr/isti/workflow/manager/executions/ExecutionObject.java index 081097e..50e88d7 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/executions/ExecutionObject.java +++ b/src/main/java/it/cnr/isti/workflow/manager/executions/ExecutionObject.java @@ -28,6 +28,8 @@ public class ExecutionObject { ExecutionContext context; + List stepConnections = new ArrayList<>(); + long creationTime = System.currentTimeMillis(); String name; @@ -38,6 +40,7 @@ public class ExecutionObject { @Builder public ExecutionObject(String executionName, FlowData flow) { this.name = executionName; + this.stepConnections = flow.getConnections() == null ? List.of() : List.copyOf(flow.getConnections()); List> steps = getStepsFromFlow(flow); diff --git a/src/main/java/it/cnr/isti/workflow/manager/executions/executors/CheckerExecutor.java b/src/main/java/it/cnr/isti/workflow/manager/executions/executors/ConditionalExecutor.java similarity index 81% rename from src/main/java/it/cnr/isti/workflow/manager/executions/executors/CheckerExecutor.java rename to src/main/java/it/cnr/isti/workflow/manager/executions/executors/ConditionalExecutor.java index e57d658..833b834 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/executions/executors/CheckerExecutor.java +++ b/src/main/java/it/cnr/isti/workflow/manager/executions/executors/ConditionalExecutor.java @@ -17,20 +17,20 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import it.cnr.isti.workflow.manager.blocks.Block; -import it.cnr.isti.workflow.manager.blocks.configurations.CheckerBlockConfiguration; -import it.cnr.isti.workflow.manager.blocks.factories.CheckerBlockFactory; -import it.cnr.isti.workflow.manager.blocks.types.CheckerBlockType; +import it.cnr.isti.workflow.manager.blocks.configurations.ConditionalBlockConfiguration; +import it.cnr.isti.workflow.manager.blocks.factories.ConditionalBlockFactory; +import it.cnr.isti.workflow.manager.blocks.types.ConditionalBlockType; import it.cnr.isti.workflow.manager.executions.steps.Input; import it.cnr.isti.workflow.manager.llms.LLMDescriptor; import it.cnr.isti.workflow.manager.llms.providers.LLMProvider; @Component -public class CheckerExecutor implements BlockExecutor { +public class ConditionalExecutor implements BlockExecutor { - private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(CheckerExecutor.class); + private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(ConditionalExecutor.class); private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\$\\{\\{(.*?)}}"); private static final String LLM_SYSTEM_PROMPT = """ - You are a workflow checker. + You are a workflow conditional evaluator. Decide whether the provided prompt evaluates to true or false using the given inputs. Reply strictly as JSON in the form {"result":true} or {"result":false}. Do not add any extra text. @@ -43,22 +43,22 @@ public class CheckerExecutor implements BlockExecutor { private Map llmProviders; @Override - public Map execute(Block block, List inputs, + public Map execute(Block block, List inputs, Map authorizations) { - CheckerBlockConfiguration config = (CheckerBlockConfiguration) block.getSpecificConfiguration(); + ConditionalBlockConfiguration config = (ConditionalBlockConfiguration) block.getSpecificConfiguration(); Map inputValues = collectInputValues(inputs); boolean matches = config.isUseLlm() ? evaluateWithLlm(config, inputValues, authorizations) : evaluateWithExpression(config.getCondition(), inputValues); - Object payload = buildPayload(inputValues); + String payload = resolvePlaceholders(config.getOutputTemplate(), inputValues); return matches - ? Map.of(CheckerBlockFactory.TRUE_OUTPUT, payload) - : Map.of(CheckerBlockFactory.FALSE_OUTPUT, payload); + ? Map.of(ConditionalBlockFactory.TRUE_OUTPUT, payload) + : Map.of(ConditionalBlockFactory.FALSE_OUTPUT, payload); } @Override - public Class getBlockType() { - return CheckerBlockType.class; + public Class getBlockType() { + return ConditionalBlockType.class; } private Map collectInputValues(List inputs) { @@ -69,13 +69,6 @@ public class CheckerExecutor implements BlockExecutor { return values; } - private Object buildPayload(Map inputValues) { - if (inputValues.size() == 1) { - return inputValues.values().iterator().next(); - } - return inputValues; - } - private boolean evaluateWithExpression(String expression, Map inputValues) { String normalizedExpression = normalizeExpression(expression); StandardEvaluationContext context = new StandardEvaluationContext(inputValues); @@ -87,7 +80,7 @@ public class CheckerExecutor implements BlockExecutor { return result; } - private boolean evaluateWithLlm(CheckerBlockConfiguration config, Map inputValues, + private boolean evaluateWithLlm(ConditionalBlockConfiguration config, Map inputValues, Map authorizations) { LLMDescriptor llmDescriptor = config.getLlmDescriptor(); LLMProvider llmProvider = resolveProvider(llmDescriptor.provider()); @@ -115,7 +108,7 @@ public class CheckerExecutor implements BlockExecutor { .orElseThrow(() -> new IllegalArgumentException("Provider not found: " + providerName)); } - private String buildLlmPrompt(CheckerBlockConfiguration config, Map inputValues) { + private String buildLlmPrompt(ConditionalBlockConfiguration config, Map inputValues) { StringBuilder builder = new StringBuilder(); builder.append(LLM_SYSTEM_PROMPT).append("\n"); builder.append("Decision prompt: ").append(resolvePlaceholders(config.getPrompt(), inputValues)).append("\n"); @@ -143,7 +136,7 @@ public class CheckerExecutor implements BlockExecutor { private boolean parseBooleanResponse(String response) { if (!StringUtils.hasText(response)) { - throw new IllegalArgumentException("Empty response from LLM checker"); + throw new IllegalArgumentException("Empty response from LLM conditional evaluator"); } String trimmed = response.trim(); @@ -170,9 +163,9 @@ public class CheckerExecutor implements BlockExecutor { } } } catch (Exception e) { - logger.debug("Unable to parse checker LLM response as JSON: {}", trimmed, e); + logger.debug("Unable to parse conditional LLM response as JSON: {}", trimmed, e); } - throw new IllegalArgumentException("Checker LLM response must be true, false, or JSON containing result"); + throw new IllegalArgumentException("Conditional LLM response must be true, false, or JSON containing result"); } }