Rename checker block to conditional block

This commit is contained in:
Lucio Lelii 2026-03-10 16:59:52 +01:00
parent 6207f9e50c
commit 330e7a6b62
5 changed files with 63 additions and 50 deletions

View File

@ -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<CheckerBlockType> {
public class ConditionalBlockConfiguration extends BlockConfiguration<ConditionalBlockType> {
@UiDependency(field = "useLlm", equals = "false")
@Structural
@ -45,26 +45,36 @@ public class CheckerBlockConfiguration extends BlockConfiguration<CheckerBlockTy
acceptVariableAsPlaceholder = true)
String prompt;
@Structural
@LongText(
placeholder = "Define the output to propagate, for example: Approved request for ${{customer}}",
tip = "The selected branch receives the rendered output template.",
acceptVariableAsPlaceholder = true)
@JsonProperty(required = true)
String outputTemplate;
@Builder
public CheckerBlockConfiguration(@NonNull String name, String condition,
boolean useLlm, LLMDescriptor llmDescriptor, String prompt) {
public ConditionalBlockConfiguration(@NonNull String name, String condition,
boolean useLlm, LLMDescriptor llmDescriptor, String prompt, String outputTemplate) {
super(name);
this.condition = condition;
this.useLlm = useLlm;
this.llmDescriptor = llmDescriptor;
this.prompt = prompt;
this.outputTemplate = outputTemplate;
}
@Override
public Class<CheckerBlockType> getBlockType() {
return CheckerBlockType.class;
public Class<ConditionalBlockType> 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<CheckerBlockTy
boolean isLlmPromptConfigurationValid() {
return !useLlm || (prompt != null && !prompt.isBlank());
}
@AssertTrue(message = "outputTemplate is required")
@JsonIgnore
boolean isOutputTemplateValid() {
return outputTemplate != null && !outputTemplate.isBlank();
}
}

View File

@ -10,13 +10,13 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import it.cnr.isti.workflow.manager.blocks.Block;
import it.cnr.isti.workflow.manager.blocks.configurations.CheckerBlockConfiguration;
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.types.ConditionalBlockType;
import it.cnr.isti.workflow.manager.ios.IODescriptor;
import it.cnr.isti.workflow.manager.ios.IOType;
@Component
public class CheckerBlockFactory implements BlockFactory<CheckerBlockType, CheckerBlockConfiguration> {
public class ConditionalBlockFactory implements BlockFactory<ConditionalBlockType, ConditionalBlockConfiguration> {
public static final String TRUE_OUTPUT = "true";
public static final String FALSE_OUTPUT = "false";
@ -24,15 +24,15 @@ public class CheckerBlockFactory implements BlockFactory<CheckerBlockType, Check
private static final Pattern SPEL_VARIABLE_PATTERN = Pattern.compile("#([a-zA-Z_][a-zA-Z0-9_]*)");
@Autowired
CheckerBlockType blockType;
ConditionalBlockType blockType;
@Override
public Block<CheckerBlockType> create(CheckerBlockConfiguration configuration) {
public Block<ConditionalBlockType> create(ConditionalBlockConfiguration configuration) {
List<IODescriptor> inputs = retrieveInputs(configuration).stream()
.map(inputName -> IODescriptor.of(inputName, IOType.TEXT))
.toList();
return Block.<CheckerBlockType>builder()
return Block.<ConditionalBlockType>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<CheckerBlockType, Check
}
@Override
public Block<CheckerBlockType> createEmpty() {
return create(CheckerBlockConfiguration.empty());
public Block<ConditionalBlockType> createEmpty() {
return create(ConditionalBlockConfiguration.empty());
}
@Override
public Class<CheckerBlockType> getBlockType() {
return CheckerBlockType.class;
public Class<ConditionalBlockType> getBlockType() {
return ConditionalBlockType.class;
}
private List<String> retrieveInputs(CheckerBlockConfiguration configuration) {
private List<String> retrieveInputs(ConditionalBlockConfiguration configuration) {
Set<String> inputNames = new LinkedHashSet<>();
if (configuration.isUseLlm()) {
collectMatches(inputNames, configuration.getPrompt(), PLACEHOLDER_PATTERN, 1);
@ -59,6 +59,7 @@ public class CheckerBlockFactory implements BlockFactory<CheckerBlockType, Check
collectMatches(inputNames, configuration.getCondition(), PLACEHOLDER_PATTERN, 1);
collectMatches(inputNames, configuration.getCondition(), SPEL_VARIABLE_PATTERN, 1);
}
collectMatches(inputNames, configuration.getOutputTemplate(), PLACEHOLDER_PATTERN, 1);
return List.copyOf(inputNames);
}

View File

@ -3,12 +3,12 @@ package it.cnr.isti.workflow.manager.blocks.types;
import org.springframework.stereotype.Component;
import it.cnr.isti.workflow.manager.blocks.configurations.BlockConfiguration;
import it.cnr.isti.workflow.manager.blocks.configurations.CheckerBlockConfiguration;
import it.cnr.isti.workflow.manager.blocks.configurations.ConditionalBlockConfiguration;
@Component(CheckerBlockType.TYPE)
public class CheckerBlockType implements BlockType {
@Component(ConditionalBlockType.TYPE)
public class ConditionalBlockType implements BlockType {
public static final String TYPE = "CheckerBlock";
public static final String TYPE = "ConditionalBlock";
@Override
public String getName() {
@ -32,6 +32,6 @@ public class CheckerBlockType implements BlockType {
@Override
public Class<? extends BlockConfiguration<?>> getBlockConfigurationClass() {
return CheckerBlockConfiguration.class;
return ConditionalBlockConfiguration.class;
}
}

View File

@ -28,6 +28,8 @@ public class ExecutionObject {
ExecutionContext context;
List<Connection> 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<Step<?>> steps = getStepsFromFlow(flow);

View File

@ -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<CheckerBlockType> {
public class ConditionalExecutor implements BlockExecutor<ConditionalBlockType> {
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<CheckerBlockType> {
private Map<String, LLMProvider> llmProviders;
@Override
public Map<String, Object> execute(Block<CheckerBlockType> block, List<Input> inputs,
public Map<String, Object> execute(Block<ConditionalBlockType> block, List<Input> inputs,
Map<String, Object> authorizations) {
CheckerBlockConfiguration config = (CheckerBlockConfiguration) block.getSpecificConfiguration();
ConditionalBlockConfiguration config = (ConditionalBlockConfiguration) block.getSpecificConfiguration();
Map<String, Object> 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<CheckerBlockType> getBlockType() {
return CheckerBlockType.class;
public Class<ConditionalBlockType> getBlockType() {
return ConditionalBlockType.class;
}
private Map<String, Object> collectInputValues(List<Input> inputs) {
@ -69,13 +69,6 @@ public class CheckerExecutor implements BlockExecutor<CheckerBlockType> {
return values;
}
private Object buildPayload(Map<String, Object> inputValues) {
if (inputValues.size() == 1) {
return inputValues.values().iterator().next();
}
return inputValues;
}
private boolean evaluateWithExpression(String expression, Map<String, Object> inputValues) {
String normalizedExpression = normalizeExpression(expression);
StandardEvaluationContext context = new StandardEvaluationContext(inputValues);
@ -87,7 +80,7 @@ public class CheckerExecutor implements BlockExecutor<CheckerBlockType> {
return result;
}
private boolean evaluateWithLlm(CheckerBlockConfiguration config, Map<String, Object> inputValues,
private boolean evaluateWithLlm(ConditionalBlockConfiguration config, Map<String, Object> inputValues,
Map<String, Object> authorizations) {
LLMDescriptor llmDescriptor = config.getLlmDescriptor();
LLMProvider llmProvider = resolveProvider(llmDescriptor.provider());
@ -115,7 +108,7 @@ public class CheckerExecutor implements BlockExecutor<CheckerBlockType> {
.orElseThrow(() -> new IllegalArgumentException("Provider not found: " + providerName));
}
private String buildLlmPrompt(CheckerBlockConfiguration config, Map<String, Object> inputValues) {
private String buildLlmPrompt(ConditionalBlockConfiguration config, Map<String, Object> 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<CheckerBlockType> {
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<CheckerBlockType> {
}
}
} 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");
}
}