refactor(executions): extract loop guard helpers into LoopGuardSupport
Cluster J from the structural analysis: LoopContainer guard evaluation helpers (feedback-input resolution, guard template values, guard output extraction/parsing, event logging) - all field-free, called only from runLoopFrom/reconcileLoopSubflow. 1790 -> 1717 lines. Behavior-preserving: pure extraction, no logic changes (logLoopGuardEvaluation was instance-scoped but touched no field, so it moves cleanly as a static method). Verified with `mvn test`: 462 tests, 0 failures, 0 errors. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
4cabbe90d3
commit
4c627c6c76
|
|
@ -50,7 +50,6 @@ import it.cnr.isti.workflow.manager.executions.persistence.ExecutionSnapshot;
|
|||
import it.cnr.isti.workflow.manager.executions.persistence.ContainerContinuationPhase;
|
||||
import it.cnr.isti.workflow.manager.executions.persistence.ContainerContinuationSnapshot;
|
||||
import it.cnr.isti.workflow.manager.executions.steps.Step;
|
||||
import it.cnr.isti.workflow.manager.executions.executors.BooleanLlmResponseParser;
|
||||
import it.cnr.isti.workflow.manager.ios.IODescriptor;
|
||||
import it.cnr.isti.workflow.manager.executions.bias.BiasActivation;
|
||||
import it.cnr.isti.workflow.manager.executions.bias.BiasApiException;
|
||||
|
|
@ -915,14 +914,14 @@ public class ExecutionsService {
|
|||
Map<String, ContainerFlowInterfaceResolver.ExposedHandle> inputPortsByName = ContainerFlowInterfaceResolver
|
||||
.getExposedInputs(configuration.getSubFlow()).stream()
|
||||
.collect(Collectors.toMap(ContainerFlowInterfaceResolver.ExposedHandle::publicName, handle -> handle));
|
||||
String feedbackInput = resolveFeedbackInput(configuration, inputPortsByName);
|
||||
String feedbackInput = LoopGuardSupport.resolveFeedbackInput(configuration, inputPortsByName);
|
||||
List<ContainerFlowInterfaceResolver.ExposedHandle> guardOutputHandles = ContainerFlowInterfaceResolver
|
||||
.getExposedOutputs(configuration.getGuardSubFlow());
|
||||
Map<String, Object> guardResult = ContainerExecutionSupport.collectExposedOutputsAsMap(completedChild, guardOutputHandles);
|
||||
boolean shouldContinue = parseLoopGuardResponse(
|
||||
String.valueOf(requireGuardOutput(guardResult, LoopContainerConfiguration.GUARD_OUTPUT)));
|
||||
String feedback = String.valueOf(requireGuardOutput(guardResult, LoopContainerConfiguration.FEEDBACK_OUTPUT));
|
||||
logLoopGuardEvaluation(eventLogger, iterationIndex, shouldContinue);
|
||||
boolean shouldContinue = LoopGuardSupport.parseLoopGuardResponse(
|
||||
String.valueOf(LoopGuardSupport.requireGuardOutput(guardResult, LoopContainerConfiguration.GUARD_OUTPUT)));
|
||||
String feedback = String.valueOf(LoopGuardSupport.requireGuardOutput(guardResult, LoopContainerConfiguration.FEEDBACK_OUTPUT));
|
||||
LoopGuardSupport.logLoopGuardEvaluation(eventLogger, iterationIndex, shouldContinue);
|
||||
if (!shouldContinue) {
|
||||
setContainerContinuation(parent.getId(), parentStep.getId(), null);
|
||||
parent.completeContainerSubflow(parentStep.getId(), NodeExecutionResult.completed(latestOutputs));
|
||||
|
|
@ -948,7 +947,7 @@ public class ExecutionsService {
|
|||
Map<String, ContainerFlowInterfaceResolver.ExposedHandle> inputPortsByName = ContainerFlowInterfaceResolver
|
||||
.getExposedInputs(configuration.getSubFlow()).stream()
|
||||
.collect(Collectors.toMap(ContainerFlowInterfaceResolver.ExposedHandle::publicName, handle -> handle));
|
||||
String feedbackInput = resolveFeedbackInput(configuration, inputPortsByName);
|
||||
String feedbackInput = LoopGuardSupport.resolveFeedbackInput(configuration, inputPortsByName);
|
||||
List<ContainerFlowInterfaceResolver.ExposedHandle> mainOutputHandles = ContainerFlowInterfaceResolver
|
||||
.getExposedOutputs(configuration.getSubFlow());
|
||||
Map<String, ContainerFlowInterfaceResolver.ExposedHandle> guardInputsByName = ContainerFlowInterfaceResolver
|
||||
|
|
@ -990,9 +989,9 @@ public class ExecutionsService {
|
|||
continue;
|
||||
}
|
||||
|
||||
Map<String, Object> guardTemplateValues = buildGuardTemplateValues(inputs, latestOutputs, iterationIndex);
|
||||
Map<String, Object> guardTemplateValues = LoopGuardSupport.buildGuardTemplateValues(inputs, latestOutputs, iterationIndex);
|
||||
Map<String, Object> nextInputsIfContinuing = new LinkedHashMap<>(inputs);
|
||||
updateInputsForNextIteration(nextInputsIfContinuing, latestOutputs, inputPortsByName);
|
||||
LoopGuardSupport.updateInputsForNextIteration(nextInputsIfContinuing, latestOutputs, inputPortsByName);
|
||||
|
||||
ExecutionObject guardChild = createAndStartSubflowChild(parent, parentStepId, container,
|
||||
container.getName() + " guard iteration " + iterationIndex, configuration.getGuardSubFlow(),
|
||||
|
|
@ -1008,10 +1007,10 @@ public class ExecutionsService {
|
|||
}
|
||||
parent.setExecutionVariableDescriptors(guardChild.getContext().getExecutionVariableDescriptors());
|
||||
Map<String, Object> guardResult = ContainerExecutionSupport.collectExposedOutputsAsMap(guardChild, guardOutputHandles);
|
||||
boolean shouldContinue = parseLoopGuardResponse(
|
||||
String.valueOf(requireGuardOutput(guardResult, LoopContainerConfiguration.GUARD_OUTPUT)));
|
||||
String feedback = String.valueOf(requireGuardOutput(guardResult, LoopContainerConfiguration.FEEDBACK_OUTPUT));
|
||||
logLoopGuardEvaluation(eventLogger, iterationIndex, shouldContinue);
|
||||
boolean shouldContinue = LoopGuardSupport.parseLoopGuardResponse(
|
||||
String.valueOf(LoopGuardSupport.requireGuardOutput(guardResult, LoopContainerConfiguration.GUARD_OUTPUT)));
|
||||
String feedback = String.valueOf(LoopGuardSupport.requireGuardOutput(guardResult, LoopContainerConfiguration.FEEDBACK_OUTPUT));
|
||||
LoopGuardSupport.logLoopGuardEvaluation(eventLogger, iterationIndex, shouldContinue);
|
||||
if (!shouldContinue) {
|
||||
setContainerContinuation(parent.getId(), parentStepId, null);
|
||||
return ContainerAdvanceOutcome.completed(new LinkedHashMap<>(latestOutputs));
|
||||
|
|
@ -1024,78 +1023,6 @@ public class ExecutionsService {
|
|||
}
|
||||
}
|
||||
|
||||
private void logLoopGuardEvaluation(ExecutionEventLogger eventLogger, int iterationIndex, boolean shouldContinue) {
|
||||
if (eventLogger == null) {
|
||||
return;
|
||||
}
|
||||
eventLogger.info(ExecutionEventType.CONTAINER_CONDITION_EVALUATED, "Evaluated loop guard",
|
||||
Map.of("containerType", LoopContainerType.TYPE, "iteration", iterationIndex, "continue", shouldContinue));
|
||||
eventLogger.info(ExecutionEventType.CONTAINER_ITERATION_COMPLETED,
|
||||
shouldContinue ? "Completed loop iteration " + iterationIndex : "Loop completed at iteration " + iterationIndex,
|
||||
Map.of("containerType", LoopContainerType.TYPE, "iteration", iterationIndex, "continue", shouldContinue));
|
||||
}
|
||||
|
||||
private static String resolveFeedbackInput(LoopContainerConfiguration configuration,
|
||||
Map<String, ContainerFlowInterfaceResolver.ExposedHandle> inputPortsByName) {
|
||||
if (configuration.getFeedbackInput() != null && !configuration.getFeedbackInput().isBlank()) {
|
||||
String configuredInput = configuration.getFeedbackInput();
|
||||
ContainerFlowInterfaceResolver.ExposedHandle handle = inputPortsByName.get(configuredInput);
|
||||
if (handle == null || handle.handle().io().isMultiple()) {
|
||||
throw new IllegalArgumentException(
|
||||
"LoopContainer feedbackInput must target an open non-multiple subFlow input: " + configuredInput);
|
||||
}
|
||||
return configuredInput;
|
||||
}
|
||||
List<ContainerFlowInterfaceResolver.ExposedHandle> eligibleInputs = inputPortsByName.values().stream()
|
||||
.filter(handle -> !handle.handle().io().isMultiple())
|
||||
.toList();
|
||||
if (eligibleInputs.size() == 1) {
|
||||
return eligibleInputs.getFirst().publicName();
|
||||
}
|
||||
if (eligibleInputs.isEmpty()) {
|
||||
throw new IllegalArgumentException(
|
||||
"LoopContainer subFlow must expose at least one open non-multiple input to receive guard feedback");
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
"LoopContainer feedbackInput is required when subFlow exposes more than one open non-multiple input");
|
||||
}
|
||||
|
||||
private static void updateInputsForNextIteration(Map<String, Object> currentInputs, Map<String, Object> latestOutputs,
|
||||
Map<String, ContainerFlowInterfaceResolver.ExposedHandle> inputPortsByName) {
|
||||
for (Map.Entry<String, Object> output : latestOutputs.entrySet()) {
|
||||
if (inputPortsByName.containsKey(output.getKey())) {
|
||||
currentInputs.put(output.getKey(), output.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, Object> buildGuardTemplateValues(Map<String, Object> currentInputs,
|
||||
Map<String, Object> latestOutputs, int iteration) {
|
||||
Map<String, Object> values = new LinkedHashMap<>();
|
||||
if (currentInputs != null) {
|
||||
values.putAll(currentInputs);
|
||||
currentInputs.forEach((key, value) -> values.put("inputs." + key, value));
|
||||
}
|
||||
if (latestOutputs != null) {
|
||||
values.putAll(latestOutputs);
|
||||
latestOutputs.forEach((key, value) -> values.put("outputs." + key, value));
|
||||
}
|
||||
values.put("iteration", iteration);
|
||||
return values;
|
||||
}
|
||||
|
||||
private static Object requireGuardOutput(Map<String, Object> guardResult, String outputName) {
|
||||
Object value = guardResult.get(outputName);
|
||||
if (value == null) {
|
||||
throw new IllegalArgumentException("LoopContainer guardSubFlow must return output: " + outputName);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private static boolean parseLoopGuardResponse(String response) {
|
||||
return BooleanLlmResponseParser.parse(response, "loop guard subflow", "Loop");
|
||||
}
|
||||
|
||||
// ---- Shared container-child helpers ----
|
||||
|
||||
private NodeExecutionResult toNodeExecutionResult(ContainerAdvanceOutcome outcome, String parentExecutionId,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,88 @@
|
|||
package it.cnr.isti.workflow.manager.executions;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import it.cnr.isti.workflow.manager.containers.configurations.LoopContainerConfiguration;
|
||||
import it.cnr.isti.workflow.manager.containers.iresolvers.ContainerFlowInterfaceResolver;
|
||||
import it.cnr.isti.workflow.manager.containers.types.LoopContainerType;
|
||||
import it.cnr.isti.workflow.manager.executions.executors.BooleanLlmResponseParser;
|
||||
|
||||
final class LoopGuardSupport {
|
||||
|
||||
private LoopGuardSupport() {
|
||||
}
|
||||
|
||||
static void logLoopGuardEvaluation(ExecutionEventLogger eventLogger, int iterationIndex, boolean shouldContinue) {
|
||||
if (eventLogger == null) {
|
||||
return;
|
||||
}
|
||||
eventLogger.info(ExecutionEventType.CONTAINER_CONDITION_EVALUATED, "Evaluated loop guard",
|
||||
Map.of("containerType", LoopContainerType.TYPE, "iteration", iterationIndex, "continue", shouldContinue));
|
||||
eventLogger.info(ExecutionEventType.CONTAINER_ITERATION_COMPLETED,
|
||||
shouldContinue ? "Completed loop iteration " + iterationIndex : "Loop completed at iteration " + iterationIndex,
|
||||
Map.of("containerType", LoopContainerType.TYPE, "iteration", iterationIndex, "continue", shouldContinue));
|
||||
}
|
||||
|
||||
static String resolveFeedbackInput(LoopContainerConfiguration configuration,
|
||||
Map<String, ContainerFlowInterfaceResolver.ExposedHandle> inputPortsByName) {
|
||||
if (configuration.getFeedbackInput() != null && !configuration.getFeedbackInput().isBlank()) {
|
||||
String configuredInput = configuration.getFeedbackInput();
|
||||
ContainerFlowInterfaceResolver.ExposedHandle handle = inputPortsByName.get(configuredInput);
|
||||
if (handle == null || handle.handle().io().isMultiple()) {
|
||||
throw new IllegalArgumentException(
|
||||
"LoopContainer feedbackInput must target an open non-multiple subFlow input: " + configuredInput);
|
||||
}
|
||||
return configuredInput;
|
||||
}
|
||||
List<ContainerFlowInterfaceResolver.ExposedHandle> eligibleInputs = inputPortsByName.values().stream()
|
||||
.filter(handle -> !handle.handle().io().isMultiple())
|
||||
.toList();
|
||||
if (eligibleInputs.size() == 1) {
|
||||
return eligibleInputs.getFirst().publicName();
|
||||
}
|
||||
if (eligibleInputs.isEmpty()) {
|
||||
throw new IllegalArgumentException(
|
||||
"LoopContainer subFlow must expose at least one open non-multiple input to receive guard feedback");
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
"LoopContainer feedbackInput is required when subFlow exposes more than one open non-multiple input");
|
||||
}
|
||||
|
||||
static void updateInputsForNextIteration(Map<String, Object> currentInputs, Map<String, Object> latestOutputs,
|
||||
Map<String, ContainerFlowInterfaceResolver.ExposedHandle> inputPortsByName) {
|
||||
for (Map.Entry<String, Object> output : latestOutputs.entrySet()) {
|
||||
if (inputPortsByName.containsKey(output.getKey())) {
|
||||
currentInputs.put(output.getKey(), output.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Map<String, Object> buildGuardTemplateValues(Map<String, Object> currentInputs,
|
||||
Map<String, Object> latestOutputs, int iteration) {
|
||||
Map<String, Object> values = new LinkedHashMap<>();
|
||||
if (currentInputs != null) {
|
||||
values.putAll(currentInputs);
|
||||
currentInputs.forEach((key, value) -> values.put("inputs." + key, value));
|
||||
}
|
||||
if (latestOutputs != null) {
|
||||
values.putAll(latestOutputs);
|
||||
latestOutputs.forEach((key, value) -> values.put("outputs." + key, value));
|
||||
}
|
||||
values.put("iteration", iteration);
|
||||
return values;
|
||||
}
|
||||
|
||||
static Object requireGuardOutput(Map<String, Object> guardResult, String outputName) {
|
||||
Object value = guardResult.get(outputName);
|
||||
if (value == null) {
|
||||
throw new IllegalArgumentException("LoopContainer guardSubFlow must return output: " + outputName);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static boolean parseLoopGuardResponse(String response) {
|
||||
return BooleanLlmResponseParser.parse(response, "loop guard subflow", "Loop");
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue