From 2a349befa52ceffd012bb0464854ce9a09ac69bd Mon Sep 17 00:00:00 2001 From: Lucio Lelii Date: Thu, 23 Jul 2026 14:54:59 +0200 Subject: [PATCH] fix: let HumanDecision blocks support execution simulation HumanDecisionExecutor explicitly disabled supportsSimulation(), so any execution containing a HumanDecision node lost the aggregate simulationAvailable flag entirely, hiding the simulate feature from clients even though the /simulate endpoint itself was untouched. simulate() now prompts the simulator LLM to pick one of the configured options (and a rationale when required), validates the choice, and returns the same output shape a real human interaction would produce. Co-Authored-By: Claude Sonnet 5 --- .../blocks/HumanDecisionExecutor.java | 104 +++++++++++++++++- 1 file changed, 99 insertions(+), 5 deletions(-) diff --git a/src/main/java/it/cnr/isti/workflow/manager/executions/executors/blocks/HumanDecisionExecutor.java b/src/main/java/it/cnr/isti/workflow/manager/executions/executors/blocks/HumanDecisionExecutor.java index d20d1f7..0f86cc3 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/executions/executors/blocks/HumanDecisionExecutor.java +++ b/src/main/java/it/cnr/isti/workflow/manager/executions/executors/blocks/HumanDecisionExecutor.java @@ -3,11 +3,15 @@ package it.cnr.isti.workflow.manager.executions.executors.blocks; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; import it.cnr.isti.workflow.manager.blocks.Block; import it.cnr.isti.workflow.manager.blocks.configurations.HumanDecisionBlockConfiguration; +import it.cnr.isti.workflow.manager.blocks.configurations.HumanDecisionOption; import it.cnr.isti.workflow.manager.blocks.factories.HumanDecisionBlockFactory; import it.cnr.isti.workflow.manager.blocks.types.HumanDecisionBlockType; import it.cnr.isti.workflow.manager.executions.ExecutionEventLogger; @@ -15,10 +19,15 @@ import it.cnr.isti.workflow.manager.executions.ExecutionEventType; import it.cnr.isti.workflow.manager.executions.ExecutionVariableDescriptor; import it.cnr.isti.workflow.manager.executions.InteractionResult; 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 HumanDecisionExecutor implements BlockExecutor { + @Autowired + private Map llmProviders; + @Override public Map execute(Block block, List inputs, Map authorizations, Map executionVariables, @@ -26,6 +35,96 @@ public class HumanDecisionExecutor implements BlockExecutor simulate(Block block, List inputs, + Map authorizations, Map executionVariables, + Map executionVariableDescriptors, + LLMDescriptor simulatorDescriptor, ExecutionEventLogger eventLogger) { + HumanDecisionBlockConfiguration configuration = + (HumanDecisionBlockConfiguration) block.getSpecificConfiguration(); + if (simulatorDescriptor == null) { + throw new IllegalArgumentException("Missing simulation descriptor for HumanDecision execution"); + } + LLMProvider llmProvider = llmProviders.get(simulatorDescriptor.provider()); + if (llmProvider == null) { + llmProvider = llmProviders.values().stream() + .filter(p -> p.getName().equals(simulatorDescriptor.provider())) + .findFirst() + .orElse(null); + } + if (llmProvider == null) { + throw new IllegalArgumentException("Provider not found: " + simulatorDescriptor.provider()); + } + String authKey = llmProvider.authorizationKey(); + if (llmProvider.requiresAuthorization() + && (!authorizations.containsKey(authKey) || !StringUtils.hasText(String.valueOf(authorizations.get(authKey))))) { + throw new IllegalArgumentException("Missing authorization for provider: " + simulatorDescriptor.provider()); + } + + String context = inputs.stream() + .map(input -> "%s= %s".formatted(input.getDescriptor().getName(), input.getValue())) + .collect(Collectors.joining(", ")); + String optionsDescription = configuration.getOptions().stream() + .map(option -> "- %s: %s".formatted(option.name(), option.label())) + .collect(Collectors.joining("\n")); + String prompt = """ + Given the following context as input { %s }, answer this decision question: "%s" + + Choose exactly one of the following options (use its exact name): + %s + + Respond in exactly this format: + CHOICE: