modified the step creation to resolve some bugs
This commit is contained in:
parent
c519dab98c
commit
13a61aea03
5
pom.xml
5
pom.xml
|
|
@ -85,6 +85,11 @@
|
|||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
|||
|
|
@ -31,27 +31,53 @@ public class TransformerService {
|
|||
@Autowired
|
||||
NodeDefinitionRepository nodeDefinitionRepository;
|
||||
|
||||
private ExecutionObject execObject;
|
||||
|
||||
public ExecutionObject transform(Flow flow) {
|
||||
execObject = new ExecutionObject(flow);
|
||||
ExecutionStep startStep = createStep(flow, null, execObject.getContext(), flow.getStartNode(), null);
|
||||
execObject.setStartStep(startStep);
|
||||
|
||||
Map<String, ExecutionStep> steps = new HashMap<>();
|
||||
Map<String, IDPair> inputs = new HashMap<>();
|
||||
Map<String, IDPair> outputs = new HashMap<>();
|
||||
|
||||
ExecutionObject execObject = new ExecutionObject(flow);
|
||||
|
||||
for (Node node : flow.getNodes()) {
|
||||
ExecutionStep step = getStep(node, execObject.getContext());
|
||||
steps.put(node.getKey(), step);
|
||||
|
||||
if (node.getKey().equals(flow.getStartNode())) {
|
||||
Map<String, String> mappingInputName = new HashMap<>();
|
||||
step.getNodeDefinition().getInputs().forEach((k, v) -> mappingInputName.put(k, k));
|
||||
log.debug("setting {} to step {}", mappingInputName, step.getId());
|
||||
step.setParentOutputsToInputMapping(mappingInputName);
|
||||
execObject.setStartStep(step);
|
||||
node.getOutputs().forEach(o -> outputs.put(o.getKey(), new IDPair(node.getKey(), o.getName())));
|
||||
} else if (node.getKey().equals(flow.getEndNode())) {
|
||||
execObject.setEndStep(step);
|
||||
node.getInputs().forEach(i -> inputs.put(i.getKey(), new IDPair(node.getKey(), i.getName())));
|
||||
} else {
|
||||
node.getOutputs().forEach(o -> outputs.put(o.getKey(), new IDPair(node.getKey(), o.getName())));
|
||||
node.getInputs().forEach(i -> inputs.put(i.getKey(), new IDPair(node.getKey(), i.getName())));
|
||||
}
|
||||
}
|
||||
|
||||
for (Connection connection : flow.getConnections()) {
|
||||
String from = connection.getFrom();
|
||||
String to = connection.getTo();
|
||||
if (!inputs.containsKey(to))
|
||||
throw new RuntimeException("Input " + to + " not found");
|
||||
if (!outputs.containsKey(from))
|
||||
throw new RuntimeException("Output " + from + " not found");
|
||||
IDPair inputPair = inputs.get(to);
|
||||
IDPair outputPair = outputs.get(from);
|
||||
ExecutionStep outputNode = steps.get(outputPair.nodeId);
|
||||
ExecutionStep inputNode = steps.get(inputPair.nodeId);
|
||||
outputNode.getNextSteps().add(inputNode);
|
||||
inputNode.getParentOutputsToInputMapping().put(outputPair.name, inputPair.name);
|
||||
}
|
||||
|
||||
return execObject;
|
||||
}
|
||||
|
||||
private ExecutionStep createStep(Flow flow, ExecutionStep parent, ExecutionContext context, String nodeId,
|
||||
Map<String, String> mappingNodeOutputNameToChildInputName) {
|
||||
// TODO: cosa succede se ho input da più nodi in un solo nodo ? devo tenere
|
||||
// traccia dei nodi già visitati e non ricreare lo step ma aggiungerlo solamente
|
||||
// ai previous steps
|
||||
|
||||
Node node = flow.getNodes().stream().filter(n -> n.getKey().equals(nodeId)).findFirst()
|
||||
.orElseThrow(() -> new RuntimeException("node " + nodeId + " not found"));
|
||||
|
||||
// prendo tutte le gli id in uscita dal nodo
|
||||
List<IOModel> outputs = node.getOutputs();
|
||||
|
||||
private ExecutionStep getStep(Node node, ExecutionContext context) {
|
||||
// prende l'executor per il tipo di nodo
|
||||
NodeDefinition nodeDefinition = nodeDefinitionRepository.findById(node.getType())
|
||||
.orElseThrow(() -> new RuntimeException("Node definition not found for node type: " + node.getType()));
|
||||
|
|
@ -63,48 +89,16 @@ public class TransformerService {
|
|||
.executor(executors.get(nodeDefinition.getExecutor()))
|
||||
.runtimeParameters(node.getParameters()).nodeDefinition(nodeDefinition).context(context).build();
|
||||
|
||||
if (parent == null) { // se è il primo step devo settare il mapping degli input
|
||||
Map<String, String> mappingInputName = new HashMap<>();
|
||||
nodeDefinition.getInputs().forEach((k, v) -> mappingInputName.put(k, k));
|
||||
log.debug("setting {} to step {}", mappingInputName, step.getId());
|
||||
step.setParentOutputsToInputMapping(mappingInputName);
|
||||
} else {
|
||||
step.setParentOutputsToInputMapping(mappingNodeOutputNameToChildInputName);
|
||||
}
|
||||
|
||||
if (!nodeId.equals(flow.getEndNode())) {
|
||||
|
||||
// mantiene il mapping tra nome output del nodo inviante e nome input del nodo
|
||||
// ricevente
|
||||
Map<String, String> currentMappingNodeOutputNameToChildInputName = new HashMap<>();
|
||||
|
||||
List<Node> childNodes = new ArrayList<>();
|
||||
|
||||
for (IOModel output : outputs) {
|
||||
for (Connection connection : flow.getConnections()) {
|
||||
if (!connection.getFrom().equals(output.getKey()))
|
||||
continue;
|
||||
String inputId = connection.getTo();
|
||||
Node nodeFound = flow.getNodes().stream()
|
||||
.filter(n -> n.getInputs().stream().anyMatch(i -> i.getKey().equals(inputId))).findFirst()
|
||||
.orElseThrow(() -> new RuntimeException("node " + inputId + " not found"));
|
||||
|
||||
// prendo il nome dell'input e lo mappo con l'output
|
||||
IOModel input = nodeFound.getInputs().stream().filter(i -> i.getKey().equals(inputId)).findFirst()
|
||||
.orElseThrow(() -> new RuntimeException("input " + inputId + " not found"));
|
||||
currentMappingNodeOutputNameToChildInputName.put(output.getName(), input.getName());
|
||||
childNodes.add(nodeFound);
|
||||
}
|
||||
}
|
||||
// creo ricorsivamente i prossimi step
|
||||
step.getNextSteps().addAll(childNodes.stream()
|
||||
.map(n -> createStep(flow, step, context, n.getKey(), currentMappingNodeOutputNameToChildInputName))
|
||||
.toList());
|
||||
} else
|
||||
execObject.setEndStep(step);
|
||||
|
||||
return step;
|
||||
|
||||
}
|
||||
|
||||
private static class IDPair {
|
||||
public String nodeId;
|
||||
public String name;
|
||||
|
||||
public IDPair(String nodeId, String name) {
|
||||
this.nodeId = nodeId;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ import org.springframework.boot.SpringApplication;
|
|||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import it.cnr.isti.workflow.manager.executors.ai.AIModel;
|
||||
import it.cnr.isti.workflow.manager.model.types.IOType;
|
||||
|
|
@ -16,7 +19,9 @@ import it.cnr.isti.workflow.manager.model.types.NodeDefinition;
|
|||
import it.cnr.isti.workflow.manager.model.types.ParameterDefinition;
|
||||
import it.cnr.isti.workflow.manager.model.types.ParameterType;
|
||||
import it.cnr.isti.workflow.manager.model.types.Translator;
|
||||
import it.cnr.isti.workflow.manager.model.types.Validation;
|
||||
import it.cnr.isti.workflow.manager.model.ui.Flow;
|
||||
import it.cnr.isti.workflow.manager.model.ui.FlowEntity;
|
||||
import it.cnr.isti.workflow.manager.repositories.FlowRepository;
|
||||
import it.cnr.isti.workflow.manager.repositories.NodeDefinitionRepository;
|
||||
|
||||
@SpringBootApplication
|
||||
|
|
@ -31,7 +36,7 @@ public class WorkflowManagerApplication {
|
|||
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = "app", name = "db.init.enabled", havingValue = "true")
|
||||
CommandLineRunner init(NodeDefinitionRepository repository) {
|
||||
CommandLineRunner init(NodeDefinitionRepository repository, FlowRepository flowRepository) {
|
||||
return args -> {
|
||||
|
||||
JSONObject rPars = new JSONObject().put("options",
|
||||
|
|
@ -39,18 +44,16 @@ public class WorkflowManagerApplication {
|
|||
|
||||
ParameterDefinition rgParams = ParameterDefinition.builder().name("LLM").type(ParameterType.Select)
|
||||
.label("Select the LLM to use").description("The LLM to use for generating requirements")
|
||||
.validation(Validation.builder().name("required").validator("required").build())
|
||||
.specificAttributes(rPars).build();
|
||||
.required(true).specificAttributes(rPars).build();
|
||||
|
||||
JSONObject languageOptions = new JSONObject().put("options",
|
||||
List.of("Franch", "English", "Italian", "spanish", "German", "Chinese", "Japanese"));
|
||||
ParameterDefinition langParam = ParameterDefinition.builder().name("language").type(ParameterType.Select)
|
||||
.label("Language").description("The language to translate to")
|
||||
.validation(Validation.builder().name("required").validator("required").build())
|
||||
.label("Language").description("The language to translate to").required(true)
|
||||
.specificAttributes(languageOptions).build();
|
||||
|
||||
|
||||
Translator inputTranslator = Translator.builder().usedKey(Key.inputKey("phrase")).usedKey(Key.runtimeKey("language"))
|
||||
Translator inputTranslator = Translator.builder()
|
||||
.translation("translate the following phrase ${{phrase}} to ${{language}} returning only the translated").build();
|
||||
|
||||
NodeDefinition translator = NodeDefinition.builder().type("Translator")
|
||||
|
|
@ -63,9 +66,39 @@ public class WorkflowManagerApplication {
|
|||
|
||||
repository.save(translator);
|
||||
|
||||
loadNodeDefinitionAndSave(repository);
|
||||
|
||||
loadFlowsAndSave(flowRepository);
|
||||
};
|
||||
}
|
||||
|
||||
private void loadNodeDefinitionAndSave(NodeDefinitionRepository repository) {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
try{
|
||||
|
||||
List<NodeDefinition> nodes = objectMapper.readValue(ResourceUtils.getFile("classpath:saved-node-type.json"), objectMapper.getTypeFactory().constructCollectionType(List.class,NodeDefinition.class));
|
||||
for (NodeDefinition node: nodes)
|
||||
repository.save(node);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void loadFlowsAndSave(FlowRepository repository) {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
try{
|
||||
|
||||
List<FlowEntity> flows = objectMapper.readValue(ResourceUtils.getFile("classpath:flows.json"), objectMapper.getTypeFactory().constructCollectionType(List.class,FlowEntity.class));
|
||||
for (FlowEntity flow: flows){
|
||||
FlowEntity savedFlow = repository.save(flow);
|
||||
savedFlow.getFlow().setId(savedFlow.getId());
|
||||
repository.save(savedFlow);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import java.util.List;
|
|||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.web.server.WebServerException;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
|
@ -27,6 +26,8 @@ import org.springframework.web.bind.annotation.PathVariable;
|
|||
@RequestMapping("/executions")
|
||||
public class ExecutionController {
|
||||
|
||||
private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(ExecutionController.class);
|
||||
|
||||
@Autowired
|
||||
FlowRepository flowRepository;
|
||||
|
||||
|
|
@ -35,10 +36,16 @@ public class ExecutionController {
|
|||
|
||||
@PostMapping()
|
||||
public ExecutionObject create(@RequestBody String flowId) {
|
||||
logger.info("Creating execution for flow {}", flowId);
|
||||
FlowEntity flow = flowRepository.findById(flowId)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Flow with id " + flowId + " not found"));
|
||||
ExecutionObject eo = executionService.createExecution(flow);
|
||||
return eo;
|
||||
try{
|
||||
ExecutionObject eo = executionService.createExecution(flow);
|
||||
return eo;
|
||||
} catch (Throwable e) {
|
||||
logger.error("Error creating execution for flow {}", flowId, e);
|
||||
throw new WebServerException("Error while creating execution", e);
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping()
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
package it.cnr.isti.workflow.manager.controllers;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
|
|
@ -27,7 +25,7 @@ public class NodeDefinitionController {
|
|||
private NodeDefinitionRepository repository;
|
||||
|
||||
@Autowired
|
||||
private Map<String, Executor> executors;
|
||||
private List<Executor> executors;
|
||||
|
||||
@GetMapping("/nodes")
|
||||
public List<NodeDefinition> getNodes() {
|
||||
|
|
@ -51,8 +49,7 @@ public class NodeDefinitionController {
|
|||
|
||||
@GetMapping("/executors")
|
||||
public List<ExecutorDescriptor> getAvailableExecutors() {
|
||||
return executors.entrySet().stream()
|
||||
.map((entry) -> new ExecutorDescriptor(entry.getKey(), entry.getValue())).toList();
|
||||
return executors.stream().map(entry -> entry.getDescriptor(null)).toList();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
package it.cnr.isti.workflow.manager.exceptions;
|
||||
|
||||
public class ExecutionException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ExecutionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ExecutionException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public ExecutionException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public ExecutionException(String message, Throwable cause, boolean enableSuppression,
|
||||
boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,21 +1,15 @@
|
|||
package it.cnr.isti.workflow.manager.executors;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import it.cnr.isti.workflow.manager.model.types.ParameterDefinition;
|
||||
import it.cnr.isti.workflow.manager.exceptions.ExecutionException;
|
||||
import it.cnr.isti.workflow.manager.model.ExecutorDescriptor;
|
||||
|
||||
public interface Executor {
|
||||
|
||||
Map<String,Object> execute(Map<String, Object> userParameters, Map<String,Object> preExecutionParameters, Map<String, Object> inputsFromParent);
|
||||
Map<String,Object> execute(Map<String, Object> userParameters, Map<String,Object> preExecutionParameters, Map<String, Object> inputsFromParent) throws ExecutionException;
|
||||
|
||||
Map<String, Class<?>> getRequiredPreExecutionParameters(Map<String, Object> userParameters);
|
||||
|
||||
List<String> declaredOutputNames();
|
||||
|
||||
List<String> declaredInputNames();
|
||||
|
||||
List<ParameterDefinition> getMandatoryParameters();
|
||||
|
||||
ExecutorDescriptor getDescriptor(Map<String,Object> executorDescriptorParameters);
|
||||
|
||||
String getName();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,34 @@
|
|||
package it.cnr.isti.workflow.manager.executors;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import it.cnr.isti.workflow.manager.model.ExecutorDescriptor;
|
||||
import it.cnr.isti.workflow.manager.model.Validations;
|
||||
import it.cnr.isti.workflow.manager.model.types.ParameterDefinition;
|
||||
import it.cnr.isti.workflow.manager.model.types.ParameterType;
|
||||
import lombok.ToString;
|
||||
|
||||
@Service("NoOp")
|
||||
@ToString(of = {"name"})
|
||||
public class NoOpExecutor implements Executor {
|
||||
|
||||
private static final String name = "NoOp";
|
||||
|
||||
private static final String name = "No Operation Executor";
|
||||
|
||||
@Override
|
||||
public Map<String, Object> execute(Map<String, Object> userParameters, Map<String, Object> preExecutionParameters,
|
||||
Map<String, Object> inputsFromParent) {
|
||||
return inputsFromParent;
|
||||
return Map.of("output", inputsFromParent.get("input"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutorDescriptor getDescriptor(Map<String, Object> executorDescriptorParameters) {
|
||||
return ExecutorDescriptor.builder().identifier("NoOp").name(name).inputName("input").outputName("output")
|
||||
.description("No operation executor").build();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -23,24 +36,19 @@ public class NoOpExecutor implements Executor {
|
|||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ParameterDefinition> getMandatoryParameters() {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Class<?>> getRequiredPreExecutionParameters(Map<String, Object> userParameters) {
|
||||
return Map.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> declaredOutputNames() {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> declaredInputNames() {
|
||||
return List.of();
|
||||
public List<ParameterDefinition> getDynamicDescriptorParameters() {
|
||||
ParameterDefinition parameterDefinition = ParameterDefinition.builder()
|
||||
.name("IONumber")
|
||||
.description("The Input/Output Number")
|
||||
.type(ParameterType.Number).required(true)
|
||||
.validation(Validations.minValidator(1))
|
||||
.validation(Validations.maxValidator(4))
|
||||
.build();
|
||||
return List.of(parameterDefinition);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ public interface AIModel {
|
|||
|
||||
String getDescription();
|
||||
|
||||
String executePrompt(Map<String, Object> parameters, String prompt);
|
||||
String executePrompt(Map<String, Object> parameters, String prompt) throws Throwable;
|
||||
|
||||
public Map<String, Class<?>> getRequiredPreExecutionParameters();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package it.cnr.isti.workflow.manager.executors.ai;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
|
@ -8,6 +9,9 @@ import org.slf4j.Logger;
|
|||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import it.cnr.isti.workflow.manager.executors.ai.services.GeminiService;
|
||||
|
||||
@Service("google-gemini")
|
||||
|
|
@ -31,33 +35,27 @@ public class GeminiModel implements AIModel {
|
|||
return "Gemini is an LLM model provided by Google";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String executePrompt(Map<String, Object> parameters, String prompt) {
|
||||
public String executePrompt(Map<String, Object> parameters, String prompt) throws Throwable {
|
||||
geminiService.setApiKey("***REMOVED-API-KEY***");
|
||||
//log.info("------- REQUEST ------------");
|
||||
//log.info(prompt);
|
||||
//log.info("-------------------");
|
||||
String result = geminiService.getResponse(prompt).block();
|
||||
log.debug("google gemini called with result {}",result);
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
//TODO: Remove, delay only for testing
|
||||
try {
|
||||
Thread.sleep(5000);
|
||||
} catch (InterruptedException e) {
|
||||
log.error("Error during sleep", e);
|
||||
}
|
||||
// Deserializzazione della risposta JSON in un oggetto GeminiResponse
|
||||
GeminiResponse response = objectMapper.readValue(result, GeminiResponse.class);
|
||||
|
||||
result = parseResponse(result);
|
||||
log.debug("post parsing: {}",result);
|
||||
return result;
|
||||
|
||||
}
|
||||
// Estrazione del testo dal primo candidato
|
||||
String candidateText = response.getCandidates().get(0).getContent().getParts().get(0).getText();
|
||||
|
||||
//log.info("------- RESPONSE ------------");
|
||||
//log.info(candidateText);
|
||||
//log.info("-------------------");
|
||||
|
||||
return candidateText;
|
||||
|
||||
private String parseResponse(String response) {
|
||||
Pattern pattern = Pattern.compile("\"text\"\\s*:\\s*\"(.*?)\"");
|
||||
Matcher matcher = pattern.matcher(response);
|
||||
if (matcher.find()) {
|
||||
return matcher.group(1); // Estratto il testo
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -66,12 +64,56 @@ public class GeminiModel implements AIModel {
|
|||
return Map.of("API-KEY", String.class);
|
||||
}
|
||||
|
||||
/*
|
||||
@Override
|
||||
public Map<String, Object> execute(Map<String, Object> userParameters, Map<String, Object> preExecutionParameters,
|
||||
Map<String, Object> inputsFromParent) {
|
||||
String returnString = (String)executePrompt(preExecutionParameters, (String) inputsFromParent.get("prompt"));
|
||||
return Map.of("response", returnString);
|
||||
}*/
|
||||
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
class GeminiResponse {
|
||||
private java.util.List<Candidate> candidates;
|
||||
|
||||
public java.util.List<Candidate> getCandidates() {
|
||||
return candidates;
|
||||
}
|
||||
|
||||
public void setCandidates(java.util.List<Candidate> candidates) {
|
||||
this.candidates = candidates;
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public static class Candidate {
|
||||
private Content content;
|
||||
|
||||
public Content getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(Content content) {
|
||||
this.content = content;
|
||||
}
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public static class Content {
|
||||
private java.util.List<Part> parts;
|
||||
|
||||
public java.util.List<Part> getParts() {
|
||||
return parts;
|
||||
}
|
||||
|
||||
public void setParts(java.util.List<Part> parts) {
|
||||
this.parts = parts;
|
||||
}
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public static class Part {
|
||||
private String text;
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,14 +3,18 @@ package it.cnr.isti.workflow.manager.executors.ai;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import it.cnr.isti.workflow.manager.exceptions.ExecutionException;
|
||||
import it.cnr.isti.workflow.manager.executors.Executor;
|
||||
import it.cnr.isti.workflow.manager.model.ExecutorDescriptor;
|
||||
import it.cnr.isti.workflow.manager.model.types.ParameterDefinition;
|
||||
import it.cnr.isti.workflow.manager.model.types.ParameterType;
|
||||
import it.cnr.isti.workflow.manager.model.types.Validation;
|
||||
import lombok.ToString;
|
||||
|
||||
@Service("GENERIC-AI")
|
||||
|
|
@ -18,6 +22,7 @@ import lombok.ToString;
|
|||
public class GenericAIExecutor implements Executor {
|
||||
|
||||
//EVERY executor in ai model must return a Map<String, Object> with the key "response" and the value of the response
|
||||
private static final Logger log = LoggerFactory.getLogger(GenericAIExecutor.class);
|
||||
|
||||
private static final String name = "Generic AI Executor";
|
||||
|
||||
|
|
@ -30,13 +35,18 @@ public class GenericAIExecutor implements Executor {
|
|||
|
||||
@Override
|
||||
public Map<String, Object> execute(Map<String, Object> userParameters, Map<String, Object> preExecutionParameters,
|
||||
Map<String, Object> inputsFromParent) {
|
||||
Map<String, Object> inputsFromParent) throws ExecutionException {
|
||||
Objects.requireNonNull(userParameters.get("LLM"), "'LLM' parameter is required for 'GENERIC-AI' executor");
|
||||
Objects.requireNonNull(aiModels.get(userParameters.get("LLM")), "AI model not found: " + userParameters.get("LLM"));
|
||||
AIModel aiModel = aiModels.get(userParameters.get("LLM"));
|
||||
String prompt = (String) inputsFromParent.get("prompt");
|
||||
String result = aiModel.executePrompt(userParameters, prompt);
|
||||
return Map.of("response", result);
|
||||
try{
|
||||
String result = aiModel.executePrompt(userParameters, prompt);
|
||||
return Map.of("response", result);
|
||||
}catch (Throwable e) {
|
||||
log.error("Error executing AI model: {}", e.getMessage(), e);
|
||||
throw new ExecutionException("Error executing AI model: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -47,37 +57,29 @@ public class GenericAIExecutor implements Executor {
|
|||
}
|
||||
|
||||
@Override
|
||||
public ExecutorDescriptor getDescriptor(Map<String, Object> executorDescriptorParameters) {
|
||||
return ExecutorDescriptor.builder().identifier("GENERIC-AI").name(name).inputName("prompt").outputName("response")
|
||||
.description("Generic AI executor").mandatoryParameters(getMandatoryParameters()).build();
|
||||
}
|
||||
|
||||
public Map<String, Class<?>> getRequiredPreExecutionParameters(Map<String, Object> userParameters) {
|
||||
Objects.requireNonNull(userParameters.get("LLM"), "'LLM' parameter is required for 'GENERIC-AI' executor");
|
||||
Objects.requireNonNull(aiModels.get(userParameters.get("LLM")), "AI model not found: " + userParameters.get("LLM"));
|
||||
return aiModels.get(userParameters.get("LLM")).getRequiredPreExecutionParameters();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> declaredOutputNames() {
|
||||
return List.of("response");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> declaredInputNames() {
|
||||
return List.of("prompt");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ParameterDefinition> getMandatoryParameters() {
|
||||
|
||||
JSONObject rPars = new JSONObject().put("options",
|
||||
aiModels.keySet());
|
||||
|
||||
|
||||
ParameterDefinition rgParams = ParameterDefinition.builder().name("LLM").type(ParameterType.Select)
|
||||
.label("Select the LLM to use").description("The LLM to use for generating requirements")
|
||||
.validation(Validation.builder().name("required").validator("required").build())
|
||||
.required(true)
|
||||
.specificAttributes(rPars).build();
|
||||
return List.of(rgParams);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ import org.springframework.web.reactive.function.client.WebClient;
|
|||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.retry.Retry;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
|
@ -34,16 +36,27 @@ public class GeminiService {
|
|||
)));
|
||||
|
||||
WebClient webClient = webClientBuilder.baseUrl(GEMINI_URL).build();
|
||||
Mono<String> result = webClient.post()
|
||||
Mono<String> result = webClient.post()
|
||||
.uri(uriBuilder -> uriBuilder.path("v1beta/models/gemini-2.0-flash:generateContent")
|
||||
.queryParam("key", apiKey)
|
||||
.build())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(requestBody)
|
||||
.retrieve()
|
||||
.bodyToMono(String.class);
|
||||
|
||||
|
||||
return result;
|
||||
.onStatus(
|
||||
status -> status.is5xxServerError(),
|
||||
clientResponse -> clientResponse.bodyToMono(String.class)
|
||||
.defaultIfEmpty("Error: server without body")
|
||||
.flatMap(body -> Mono.error(new RuntimeException("Errore 5xx: " + body))))
|
||||
.bodyToMono(String.class)
|
||||
.timeout(Duration.ofMinutes(2))
|
||||
.retryWhen(
|
||||
Retry.backoff(10, Duration.ofSeconds(30))
|
||||
.filter(throwable -> throwable instanceof RuntimeException
|
||||
|| throwable instanceof java.util.concurrent.TimeoutException)
|
||||
.onRetryExhaustedThrow((retryBackoffSpec, retrySignal) -> new RuntimeException(
|
||||
"Error contacting Gemini API", retrySignal.failure())));
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -9,9 +9,9 @@ import java.util.Set;
|
|||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import it.cnr.isti.workflow.manager.exceptions.ExecutionException;
|
||||
import it.cnr.isti.workflow.manager.executors.Executor;
|
||||
import it.cnr.isti.workflow.manager.model.ExecutionContext.Status;
|
||||
import it.cnr.isti.workflow.manager.model.types.Key;
|
||||
import it.cnr.isti.workflow.manager.model.types.NodeDefinition;
|
||||
import it.cnr.isti.workflow.manager.model.types.Translator;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
|
@ -37,13 +37,13 @@ public class ExecutionStep implements InputReadyListener {
|
|||
boolean startAutomatically = true;
|
||||
|
||||
Executor executor;
|
||||
Map<String, String> parentOutputsToInputMapping;
|
||||
Map<String, String> parentOutputsToInputMapping = new HashMap<>();
|
||||
|
||||
Map<String, Object> inputs = new HashMap<>();
|
||||
|
||||
List<InputReadyListener> nextSteps = new ArrayList<>();
|
||||
|
||||
List<ExecutionStep> previouSteps = new ArrayList<>();
|
||||
//List<ExecutionStep> previouSteps = new ArrayList<>();
|
||||
|
||||
@NonNull
|
||||
NodeDefinition nodeDefinition;
|
||||
|
|
@ -63,8 +63,6 @@ public class ExecutionStep implements InputReadyListener {
|
|||
|
||||
@Override
|
||||
public void inputReady(String inputKey, Object inputValue) {
|
||||
log.debug("Input ready for step {} with key {} and value {}", id, inputKey, inputValue);
|
||||
parentOutputsToInputMapping.forEach((k, v) -> log.debug("mapping {} -> {}", k, v));
|
||||
if (parentOutputsToInputMapping.containsKey(inputKey)) {
|
||||
inputs.put(parentOutputsToInputMapping.get(inputKey), inputValue);
|
||||
if (startAutomatically) startExecution();
|
||||
|
|
@ -72,6 +70,7 @@ public class ExecutionStep implements InputReadyListener {
|
|||
};
|
||||
|
||||
public boolean areAllInputsReady() {
|
||||
log.debug("checking inputs {} - {} ({},{})", inputs.keySet(), parentOutputsToInputMapping.keySet(), inputs.size(), parentOutputsToInputMapping.size());
|
||||
return inputs.size() == parentOutputsToInputMapping.size();
|
||||
}
|
||||
|
||||
|
|
@ -81,26 +80,22 @@ public class ExecutionStep implements InputReadyListener {
|
|||
this.context.getStepsUnderExecution().add(this.id);
|
||||
try {
|
||||
Map<String, Object> realInputs = prepareInputs();
|
||||
log.debug("input translation for step {} is {}", id, realInputs);
|
||||
//log.debug("input translation for step {} is {}", id, realInputs);
|
||||
Map<String, Object> returned = executor.execute(this.runtimeParameters, null, realInputs);
|
||||
log.debug("returned from executor {} for step {} is {}", executor.getClass().getName(), id,
|
||||
returned);
|
||||
this.context.getNodeResult().put(this.id, returned);
|
||||
for (InputReadyListener nextStep : nextSteps)
|
||||
for (Map.Entry<String, Object> entry : returned.entrySet())
|
||||
nextStep.inputReady(!nodeDefinition.getExecutorToOutputTranslationMappings().isEmpty()
|
||||
? nodeDefinition.getExecutorToOutputTranslationMappings().get(entry.getKey())
|
||||
: entry.getKey(), entry.getValue());
|
||||
} catch (Throwable e) {
|
||||
} catch (ExecutionException e) {
|
||||
log.error("error during execution of step {}: {}", id, e.getMessage());
|
||||
this.context.getErrors().add(String.format("[%S] %s", this.id, e.getMessage()));
|
||||
this.context.setStatus(Status.ERROR);
|
||||
return;
|
||||
} finally {
|
||||
this.context.getStepsUnderExecution().remove(this.id);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}).start();
|
||||
else
|
||||
log.debug("not all inputs are ready for step {}", id);
|
||||
|
|
@ -111,23 +106,21 @@ public class ExecutionStep implements InputReadyListener {
|
|||
Map<String, Object> realInputs = new HashMap<>();
|
||||
Set<String> keys = new HashSet<>(inputs.keySet());
|
||||
if (nodeDefinition.getInputTranslators() != null) {
|
||||
log.debug("translators {}", nodeDefinition.getInputTranslators().size());
|
||||
//log.debug("translators {}", nodeDefinition.getInputTranslators().size());
|
||||
for (Map.Entry<String, Translator> entry : nodeDefinition.getInputTranslators().entrySet()) {
|
||||
String translation = entry.getValue().getTranslation();
|
||||
|
||||
for (Key usedKey : entry.getValue().getUsedKeys()) {
|
||||
List<String> usedKeys = entry.getValue().getUsedKeys();
|
||||
log.debug("used keys", usedKeys.toString());
|
||||
for (String usedKey : usedKeys) {
|
||||
String valueToReplace = "";
|
||||
switch (usedKey.getType()) {
|
||||
case Key.Type.RUNTIME:
|
||||
valueToReplace = (String) this.runtimeParameters.get(usedKey.getKey());
|
||||
break;
|
||||
default:
|
||||
valueToReplace = (String) inputs.get(usedKey.getKey());
|
||||
keys.remove(usedKey.getKey());
|
||||
break;
|
||||
}
|
||||
translation = translation.replace(String.format("${{%s}}", usedKey.getKey()), valueToReplace);
|
||||
log.debug("replacing used key {} value {}", usedKey.getKey(), valueToReplace);
|
||||
if (this.runtimeParameters.containsKey(usedKey))
|
||||
valueToReplace = (String) this.runtimeParameters.get(usedKey);
|
||||
else if (inputs.containsKey(usedKey)){
|
||||
valueToReplace = (String) inputs.get(usedKey);
|
||||
keys.remove(usedKey);
|
||||
}
|
||||
translation = translation.replace(String.format("${{%s}}", usedKey), valueToReplace);
|
||||
//log.debug("replacing used key {} value {}", usedKey, valueToReplace);
|
||||
}
|
||||
realInputs.put(entry.getKey(), translation);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,27 +4,33 @@ import java.util.List;
|
|||
|
||||
import it.cnr.isti.workflow.manager.executors.Executor;
|
||||
import it.cnr.isti.workflow.manager.model.types.ParameterDefinition;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NonNull;
|
||||
import lombok.Singular;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class ExecutorDescriptor {
|
||||
|
||||
|
||||
public ExecutorDescriptor(String id, Executor executor){
|
||||
this.identifier = id;
|
||||
this.name = executor.getName();
|
||||
this.inputNames = executor.declaredInputNames();
|
||||
this.outputNames = executor.declaredOutputNames();
|
||||
this.mandatoryParameters = executor.getMandatoryParameters();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
String identifier;
|
||||
@NonNull
|
||||
String name;
|
||||
|
||||
String description;
|
||||
@Singular
|
||||
List<String> inputNames;
|
||||
@Singular
|
||||
List<String> outputNames;
|
||||
@Singular
|
||||
List<ParameterDefinition> mandatoryParameters;
|
||||
|
||||
List<ParameterDefinition> dynamicDescriptorParameter;
|
||||
|
||||
//List<ParameterDescriptor> optionalParameters;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
package it.cnr.isti.workflow.manager.model;
|
||||
|
||||
|
||||
import it.cnr.isti.workflow.manager.model.types.ParameterDefinition;
|
||||
import it.cnr.isti.workflow.manager.model.types.Validation;
|
||||
|
||||
public class Validations {
|
||||
|
||||
|
||||
public static Validation maxValidator(int value){
|
||||
return Validation.builder().name("max").value(value).message("max value is " + value).with((o) -> ((Integer) o)<=value ).build();
|
||||
}
|
||||
public static Validation minValidator(int value){
|
||||
return Validation.builder().name("min").value(value).message("min value is " + value).with((o) -> ((Integer) o)>=value).build();
|
||||
}
|
||||
public static Validation maxLengthValidator(int value){
|
||||
return Validation.builder().name("maxLength").value(value).message("max length is " + value).with((o) -> o.toString().length()<value).build();
|
||||
}
|
||||
public static Validation minLengthValidator(int value){
|
||||
return Validation.builder().name("minLength").value(value).message("min length is " + value).with((o) -> o.toString().length()>value).build();
|
||||
}
|
||||
|
||||
public static Validation regexValidator(String regex){
|
||||
return Validation.builder().name("pattern").value(regex).message("regex is " + regex).with((o) -> o.toString().matches(regex)).build();
|
||||
}
|
||||
|
||||
|
||||
public static boolean isValid(ParameterDefinition parameter, Object value) {
|
||||
for (Validation validation : parameter.getValidations()) {
|
||||
if (!validation.getWith().test(value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package it.cnr.isti.workflow.manager.model.types;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
public enum IOType {
|
||||
Text,
|
||||
|
|
@ -10,4 +11,9 @@ public enum IOType {
|
|||
public static IOType fromString(String key) {
|
||||
return IOType.valueOf(key);
|
||||
}
|
||||
|
||||
@JsonValue
|
||||
public String toString() {
|
||||
return name();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@ public class NodeDefinition {
|
|||
@NonNull
|
||||
private String executor;
|
||||
|
||||
@Builder.Default
|
||||
private String color = "black";
|
||||
|
||||
@Singular
|
||||
@Convert(converter = MapsConverter.class)
|
||||
@Column(columnDefinition = "TEXT")
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@ public class ParameterDefinition {
|
|||
@NonNull
|
||||
private ParameterType type;
|
||||
|
||||
@Builder.Default
|
||||
private boolean required = false;
|
||||
|
||||
@Singular
|
||||
private List<Validation> validations;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,5 +3,7 @@ package it.cnr.isti.workflow.manager.model.types;
|
|||
public enum ParameterType {
|
||||
|
||||
Select,
|
||||
Input
|
||||
Text,
|
||||
Boolean,
|
||||
Number
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,18 @@
|
|||
package it.cnr.isti.workflow.manager.model.types;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.NonNull;
|
||||
import lombok.Singular;
|
||||
|
||||
|
||||
@Data
|
||||
|
|
@ -16,17 +21,32 @@ import lombok.Singular;
|
|||
@Builder
|
||||
public class Translator {
|
||||
|
||||
@Singular
|
||||
@NonNull
|
||||
List<Key> usedKeys;
|
||||
private static final Logger log = org.slf4j.LoggerFactory.getLogger(Translator.class);
|
||||
|
||||
private static final Pattern pattern = Pattern.compile("\\$\\{\\{(.*?)\\}\\}");
|
||||
|
||||
@NonNull
|
||||
String translation;
|
||||
|
||||
public String toString() {
|
||||
return "Translator(usedKeys=" + this.getUsedKeys() + ", translation=" + this.getTranslation() + ")";
|
||||
return "Translator(translation=" + this.getTranslation() + ")";
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public List<String> getUsedKeys() {
|
||||
Matcher matcher = pattern.matcher(translation);
|
||||
|
||||
List<String> variables = new ArrayList<>();
|
||||
|
||||
// Cicla tutti i match trovati
|
||||
while (matcher.find()) {
|
||||
String found =matcher.group(1);
|
||||
log.info("found: {} ",found);
|
||||
variables.add(found); // prende solo il nome della variabile (senza ${{}})
|
||||
}
|
||||
|
||||
return variables;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
package it.cnr.isti.workflow.manager.model.types;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.NonNull;
|
||||
|
||||
|
|
@ -10,6 +13,7 @@ import lombok.NonNull;
|
|||
@AllArgsConstructor
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@EqualsAndHashCode
|
||||
public class Validation {
|
||||
|
||||
@NonNull
|
||||
|
|
@ -18,5 +22,12 @@ public class Validation {
|
|||
@NonNull
|
||||
private String validator;
|
||||
|
||||
private Object value;
|
||||
|
||||
@EqualsAndHashCode.Exclude
|
||||
private String message;
|
||||
|
||||
@NonNull
|
||||
@EqualsAndHashCode.Exclude
|
||||
private Predicate<Object> with;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,289 @@
|
|||
[
|
||||
{
|
||||
"flow": {
|
||||
"name": "testgen",
|
||||
"description": null,
|
||||
"nodes": [
|
||||
{
|
||||
"key": "bbc98e6d-ea70-46d8-93d9-9cd8333b7565",
|
||||
"name": "newNode",
|
||||
"outputs": [
|
||||
{
|
||||
"key": "19f50259-89cf-4dca-aba0-34b7620cb555",
|
||||
"type": "text",
|
||||
"name": "requirements"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"key": "0dc3195a-2682-4a2f-8466-862f29797d6b",
|
||||
"type": "text",
|
||||
"name": "interview"
|
||||
}
|
||||
],
|
||||
"color": "#A8E6CF",
|
||||
"position": {
|
||||
"x": -197,
|
||||
"y": 206
|
||||
},
|
||||
"parameters": {
|
||||
"LLM": "google-gemini"
|
||||
},
|
||||
"description": null,
|
||||
"type": "Requirement Generator"
|
||||
},
|
||||
{
|
||||
"key": "3b0b2489-61a5-4024-98fb-42cdfca2dafa",
|
||||
"name": "newNode",
|
||||
"outputs": [
|
||||
{
|
||||
"key": "b38c0c63-4ac5-4225-b5b6-d1e1b7434842",
|
||||
"type": "text",
|
||||
"name": "requirement-smells"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"key": "b254b9d1-71cf-4c87-9875-eeddf47b89a5",
|
||||
"type": "text",
|
||||
"name": "requirements"
|
||||
}
|
||||
],
|
||||
"color": "#A8E6CF",
|
||||
"position": {
|
||||
"x": -199,
|
||||
"y": 562
|
||||
},
|
||||
"parameters": {
|
||||
"LLM": "google-gemini"
|
||||
},
|
||||
"description": null,
|
||||
"type": "Requirements Analyzer"
|
||||
},
|
||||
{
|
||||
"key": "2bfc55bf-1e15-4e14-8e75-0f1c1cb305f5",
|
||||
"name": "newNode",
|
||||
"outputs": [
|
||||
{
|
||||
"key": "e0fe597b-712d-496e-9780-d3c4a6eb729b",
|
||||
"type": "text",
|
||||
"name": "corrected-requirements"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"key": "0e7016a9-43ef-4ce5-9e54-403811061e2c",
|
||||
"type": "text",
|
||||
"name": "requirement-smells"
|
||||
}
|
||||
],
|
||||
"color": "#A8E6CF",
|
||||
"position": {
|
||||
"x": 398,
|
||||
"y": -113
|
||||
},
|
||||
"parameters": {
|
||||
"LLM": "google-gemini"
|
||||
},
|
||||
"description": null,
|
||||
"type": "Requirements Corrector"
|
||||
},
|
||||
{
|
||||
"key": "a1412e6e-0c32-4615-af3e-c318f8b609f1",
|
||||
"name": "newNode",
|
||||
"outputs": [
|
||||
{
|
||||
"key": "a7eaf186-5fc9-48b0-b76f-fdcad73e4d7a",
|
||||
"type": "text",
|
||||
"name": "output"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"key": "11a14440-8d94-420d-99dc-79bc6fcbab3f",
|
||||
"type": "text",
|
||||
"name": "input"
|
||||
}
|
||||
],
|
||||
"color": "#A8E6CF",
|
||||
"position": {
|
||||
"x": -198,
|
||||
"y": -147
|
||||
},
|
||||
"parameters": null,
|
||||
"description": null,
|
||||
"type": "InputDispatcher"
|
||||
},
|
||||
{
|
||||
"key": "8d3e4cf7-5dc0-48ef-b94e-98d8f0a8ccc7",
|
||||
"name": "newNode",
|
||||
"outputs": [
|
||||
{
|
||||
"key": "caaa6d9a-af43-4501-968e-1bba415818d4",
|
||||
"type": "text",
|
||||
"name": "plant-uml"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"key": "b31f2183-c35e-4a7b-871c-a6fe553687c0",
|
||||
"type": "text",
|
||||
"name": "corrected-requirements"
|
||||
},
|
||||
{
|
||||
"key": "296d4535-8082-4b90-9814-306201887cae",
|
||||
"type": "text",
|
||||
"name": "interview-script"
|
||||
}
|
||||
],
|
||||
"color": "#A8E6CF",
|
||||
"position": {
|
||||
"x": 541,
|
||||
"y": 270
|
||||
},
|
||||
"parameters": {
|
||||
"LLM": "google-gemini"
|
||||
},
|
||||
"description": null,
|
||||
"type": "Class Modeller (UML)"
|
||||
},
|
||||
{
|
||||
"key": "252071ea-2a74-46af-b9e7-08f02771c6c6",
|
||||
"name": "newNode",
|
||||
"outputs": [
|
||||
{
|
||||
"key": "6c9465a6-e1f6-4c76-94fe-eddaf3671250",
|
||||
"type": "text",
|
||||
"name": "plantuml-sequence-diagram"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"key": "94064079-0d19-4cec-9391-6ab1ac6e1e18",
|
||||
"type": "text",
|
||||
"name": "interview"
|
||||
},
|
||||
{
|
||||
"key": "a5bf84d5-7a15-422a-820a-a8490943404c",
|
||||
"type": "text",
|
||||
"name": "plantuml-class-diagram"
|
||||
},
|
||||
{
|
||||
"key": "83a47422-033a-45c9-aac6-ecd4de4b2b9c",
|
||||
"type": "text",
|
||||
"name": "corrected-requirements"
|
||||
}
|
||||
],
|
||||
"color": "#A8E6CF",
|
||||
"position": {
|
||||
"x": 999,
|
||||
"y": 461
|
||||
},
|
||||
"parameters": {
|
||||
"LLM": "google-gemini"
|
||||
},
|
||||
"description": null,
|
||||
"type": "Sequence Modeler"
|
||||
},
|
||||
{
|
||||
"key": "d44d8b30-ab97-4a9c-9475-1f7b2c54a581",
|
||||
"name": "newNode",
|
||||
"outputs": [
|
||||
{
|
||||
"key": "6d37eb52-3bc2-4dd9-8985-0b17f8db92ba",
|
||||
"type": "text",
|
||||
"name": "source-code"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"key": "87c429c4-c876-48df-a1f8-c772e3da63f1",
|
||||
"type": "text",
|
||||
"name": "corrected-requirements"
|
||||
},
|
||||
{
|
||||
"key": "edf43f01-fbe9-4bc3-ae8b-43c5ac3ba0c8",
|
||||
"type": "text",
|
||||
"name": "class-diagram"
|
||||
},
|
||||
{
|
||||
"key": "f0546984-491e-420e-97c2-a54a330c8ba2",
|
||||
"type": "text",
|
||||
"name": "sequence-diagram"
|
||||
}
|
||||
],
|
||||
"color": "#A8E6CF",
|
||||
"position": {
|
||||
"x": 313,
|
||||
"y": 564
|
||||
},
|
||||
"parameters": {
|
||||
"LLM": "google-gemini"
|
||||
},
|
||||
"description": null,
|
||||
"type": "Code Generator"
|
||||
}
|
||||
],
|
||||
"connections": [
|
||||
{
|
||||
"key": "cbe021cf-76af-4675-b2cb-dad023dd0f7f",
|
||||
"from": "19f50259-89cf-4dca-aba0-34b7620cb555",
|
||||
"to": "b254b9d1-71cf-4c87-9875-eeddf47b89a5"
|
||||
},
|
||||
{
|
||||
"key": "8829a219-4985-40e4-8453-c725e2f3c608",
|
||||
"from": "b38c0c63-4ac5-4225-b5b6-d1e1b7434842",
|
||||
"to": "0e7016a9-43ef-4ce5-9e54-403811061e2c"
|
||||
},
|
||||
{
|
||||
"key": "12ce4604-f383-41a6-9292-a0adb04351f5",
|
||||
"from": "a7eaf186-5fc9-48b0-b76f-fdcad73e4d7a",
|
||||
"to": "0dc3195a-2682-4a2f-8466-862f29797d6b"
|
||||
},
|
||||
{
|
||||
"key": "359be251-dfad-4c0f-8759-251fd36b0005",
|
||||
"from": "e0fe597b-712d-496e-9780-d3c4a6eb729b",
|
||||
"to": "b31f2183-c35e-4a7b-871c-a6fe553687c0"
|
||||
},
|
||||
{
|
||||
"key": "9aa48a8c-d56d-49db-bf5f-9ebec0d750ea",
|
||||
"from": "a7eaf186-5fc9-48b0-b76f-fdcad73e4d7a",
|
||||
"to": "296d4535-8082-4b90-9814-306201887cae"
|
||||
},
|
||||
{
|
||||
"key": "4923b0de-5885-4e17-bd8f-ccf3177278a9",
|
||||
"from": "e0fe597b-712d-496e-9780-d3c4a6eb729b",
|
||||
"to": "83a47422-033a-45c9-aac6-ecd4de4b2b9c"
|
||||
},
|
||||
{
|
||||
"key": "a16d869b-7eb6-45d5-8549-53b2c74e9378",
|
||||
"from": "caaa6d9a-af43-4501-968e-1bba415818d4",
|
||||
"to": "a5bf84d5-7a15-422a-820a-a8490943404c"
|
||||
},
|
||||
{
|
||||
"key": "80e8947e-fc2e-4e35-84a0-f4810504bcaa",
|
||||
"from": "a7eaf186-5fc9-48b0-b76f-fdcad73e4d7a",
|
||||
"to": "94064079-0d19-4cec-9391-6ab1ac6e1e18"
|
||||
},
|
||||
{
|
||||
"key": "13861a56-c812-47d1-85d6-9edb55918d6e",
|
||||
"from": "e0fe597b-712d-496e-9780-d3c4a6eb729b",
|
||||
"to": "87c429c4-c876-48df-a1f8-c772e3da63f1"
|
||||
},
|
||||
{
|
||||
"key": "a81ea345-7e44-435b-b9a3-3e875e0305ff",
|
||||
"from": "caaa6d9a-af43-4501-968e-1bba415818d4",
|
||||
"to": "edf43f01-fbe9-4bc3-ae8b-43c5ac3ba0c8"
|
||||
},
|
||||
{
|
||||
"key": "d2ac98b4-66be-4734-919b-753fcd04dd17",
|
||||
"from": "6c9465a6-e1f6-4c76-94fe-eddaf3671250",
|
||||
"to": "f0546984-491e-420e-97c2-a54a330c8ba2"
|
||||
}
|
||||
],
|
||||
"startNode": "a1412e6e-0c32-4615-af3e-c318f8b609f1",
|
||||
"endNode": "d44d8b30-ab97-4a9c-9475-1f7b2c54a581"
|
||||
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,263 @@
|
|||
[
|
||||
{
|
||||
"type": "Requirement Generator",
|
||||
"name": "req gen",
|
||||
"category": "Generators",
|
||||
"executor": "GENERIC-AI",
|
||||
"color": "black",
|
||||
"fixedParameters": {},
|
||||
"runtimeParameters": {
|
||||
"LLM": {
|
||||
"name": "LLM",
|
||||
"label": "Select the LLM to use",
|
||||
"description": "The LLM to use for generating requirements",
|
||||
"type": "Select",
|
||||
"required": true,
|
||||
"validations": [],
|
||||
"specificAttributes": {
|
||||
"options": [
|
||||
"google-gemini",
|
||||
"open-router"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"interview": "Text"
|
||||
},
|
||||
"outputs": {
|
||||
"requirements": "Text"
|
||||
},
|
||||
"executorToOutputTranslationMappings": {
|
||||
"response": "requirements"
|
||||
},
|
||||
"inputToExecutorTranslationMappings": {},
|
||||
"inputTranslators": {
|
||||
"prompt": {
|
||||
"translation": "Analyze the following interview transcript and extract a list of functional requirements. \n\n${{interview}}\n\nFormat the output as a CSV with the following columns: \n1. **ID**: Unique identifier (e.g., F1, F2, F3…). \n2. **Requirement Text**: Clear description of what the system must do (in plain language). \n3. **Customer Quote**: Exact phrase from the transcript that justifies the requirement. \n\n**Rules:** \n- Include **only functional requirements** (no qualities or non-functional requirements). \n- Keep requirements concise and actionable. \n- List requirements in logical order.\n- Make sure requirements are free of smells.\n- Output only the CSV, do not add any text.\n\n**CSV Format Example:**\n\nF1; The system shall ask users to select the arithmetic operation to perform. The possible arithmetic operations are addition, subtraction, multiplication, division; “I need some support to perform different arithmetic operations”.\nF2: The system shall ask users to select the first operand of the operation; “I want to express the entire operation verbally, and get the results”.\nF3: The system shall ask users to select the second operand of the operation; “I want to express the entire operation verbally, and get the results”.\n"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Requirements Analyzer",
|
||||
"name": "req analyzer",
|
||||
"category": "Analyzers",
|
||||
"executor": "GENERIC-AI",
|
||||
"color": "black",
|
||||
"fixedParameters": {},
|
||||
"runtimeParameters": {
|
||||
"LLM": {
|
||||
"name": "LLM",
|
||||
"label": "Select the LLM to use",
|
||||
"description": "The LLM to use for generating requirements",
|
||||
"type": "Select",
|
||||
"required": true,
|
||||
"validations": [],
|
||||
"specificAttributes": {
|
||||
"options": [
|
||||
"google-gemini",
|
||||
"open-router"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"requirements": "Text"
|
||||
},
|
||||
"outputs": {
|
||||
"requirement-smells": "Text"
|
||||
},
|
||||
"executorToOutputTranslationMappings": {
|
||||
"response": "requirement-smells"
|
||||
},
|
||||
"inputToExecutorTranslationMappings": {},
|
||||
"inputTranslators": {
|
||||
"prompt": {
|
||||
"translation": "giving the following requirements as csv:\n${{requirements}}\nAct as a **Requirements Analyst** tasked with reviewing a set of functional requirements derived from a customer interview. Analyze each requirement for potential **requirement smells** (e.g., ambiguity, incompleteness, inconsistency, or vagueness) and generate a CSV report with the following columns: \n\n1. **ID**: Unique identifier of the requirement (e.g., F1, F2). \n2. **Requirement**: The exact text of the requirement. \n3. **Customer Quote**: Supporting quote from the stakeholder. \n4. **Smell Description**: \n - If a smell is detected, briefly describe it (e.g., \"Ambiguous: 'user-friendly' is subjective\"). \n - If no smell is found, leave this field empty (null). \n\n**Rules:** \n- Read the requirement carefully, and think whether it can be tested\n- If it cannot be tested, understand what the smell is that makes it not testable. \n- Typical smells are: \nAmbiguity: Subjective terms (e.g., \"easy,\" \"fast\").\nIncompleteness: Missing steps/triggers (e.g., \"remind users\" but no timing specified).\nInconsistency: Conflicts with other requirements.\nVagueness: Unquantifiable metrics (e.g., \"often,\" \"sometimes\").\n- Output only the CSV, do not add any text.\n\n**Output CSV Format Example:** \n\nF1; The system shall ask users to select the arithmetic operation to perform. The possible arithmetic operations are addition, subtraction, multiplication, division; “I need some support to perform different arithmetic operations”; null\nF2; The system shall ask users to select the first operand of the operation; “I want to express the entire operation verbally, and get the results”; users is plural, can be ambiguous\n"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Requirements Corrector",
|
||||
"name": "Req corrector",
|
||||
"category": "Correctors",
|
||||
"executor": "GENERIC-AI",
|
||||
"color": "black",
|
||||
"fixedParameters": { },
|
||||
"runtimeParameters": {
|
||||
"LLM": {
|
||||
"name": "LLM",
|
||||
"label": "Select the LLM to use",
|
||||
"description": "The LLM to use for generating requirements",
|
||||
"type": "Select",
|
||||
"required": true,
|
||||
"validations": [ ],
|
||||
"specificAttributes": {
|
||||
"options": [
|
||||
"google-gemini",
|
||||
"open-router"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"requirement-smells": "Text"
|
||||
},
|
||||
"outputs": {
|
||||
"corrected-requirements": "Text"
|
||||
},
|
||||
"executorToOutputTranslationMappings": {
|
||||
"response": "corrected-requirements"
|
||||
},
|
||||
"inputToExecutorTranslationMappings": { },
|
||||
"inputTranslators": {
|
||||
"prompt": {
|
||||
"translation": "giving the input:\n\n${{requirement-smells}}\n\nthat is a CSV of requirements with identified smells (columns: `ID`, `Requirement`, `Customer Quote`, `Smell Description`). \n\nAct as a **Requirements Corrector**. Your task is to **revise smelly requirements** (ambiguity, vagueness, inconsistency, incompleteness) into **clear, actionable, and testable** functional requirements. \n\n**Output Rules:** \n- Generate a **new CSV** with columns: \n 1. **ID**: Original requirement identifier (e.g., `F1`, `F2`). \n 2. **Corrected Requirement**: Rewritten version that resolves the smell. \n 3. **Customer Quote**: Original supporting quote from the transcript. \n- **Remove the \"Smell Description\" column** (corrections should speak for themselves). \n- **Preserve intent**: Ensure the Corrected requirement aligns with the stakeholder’s original need. \n- **For non-smelly requirements**: Copy them verbatim to the new CSV.\n- Output only the CSV, do not add any text.\n\n**Output CSV Format Example:** \n\nF1; The system shall ask users to select the arithmetic operation to perform. The possible arithmetic operations are addition, subtraction, multiplication, division; “I need some support to perform different arithmetic operations”\nF2; The system shall ask the user to select the first operand of the operation; “I want to express the entire operation verbally, and get the results”;\n"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Class Modeller (UML)",
|
||||
"name": "req modeller (class)",
|
||||
"category": "Modellers",
|
||||
"executor": "GENERIC-AI",
|
||||
"color": "black",
|
||||
"fixedParameters": { },
|
||||
"runtimeParameters": {
|
||||
"LLM": {
|
||||
"name": "LLM",
|
||||
"label": "Select the LLM to use",
|
||||
"description": "The LLM to use for generating requirements",
|
||||
"type": "Select",
|
||||
"required": true,
|
||||
"validations": [ ],
|
||||
"specificAttributes": {
|
||||
"options": [
|
||||
"google-gemini",
|
||||
"open-router"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"corrected-requirements": "Text",
|
||||
"interview-script": "Text"
|
||||
},
|
||||
"outputs": {
|
||||
"plant-uml": "Text"
|
||||
},
|
||||
"executorToOutputTranslationMappings": {
|
||||
"response": "plant-uml"
|
||||
},
|
||||
"inputToExecutorTranslationMappings": { },
|
||||
"inputTranslators": {
|
||||
"prompt": {
|
||||
"translation": "Act as a **Requirements Modeler** specializing in structural design. Create a PlantUML class diagram that captures the core domain model implied by the functional requirements and interview transcript.\n\n**Input:** \n1. Corrected requirements (CSV with `ID`, `Corrected Requirement`, `Customer Quote`) :\n\n${{corrected-requirements}}\n \n2. Interview transcript for context :\n\n${{interview-script}}\n\n**Output Rules:** \n1. Generate **one concise class diagram** as a textual output in PlantUML syntax \n2. Focus on: \n - Key entities (classes) and their attributes \n - Critical relationships (associations, compositions) \n - Basic operations/methods implied by requirements \n3. Output only the model, do not add any text.\n\n**Example Output (PlantUML):**\n\n@startuml Calculator_Class_Diagram\n\nclass Calculator {\n + currentInput: Number\n + storedValue: Number\n + selectedOperation: OperationType\n + calculate(): Number\n + clear(): void\n}\n\nclass InputHandler {\n + handleDigitPress(digit: Integer): void\n + handleOperator(op: OperationType): void\n + handleEquals(): void\n}\n\nclass Display {\n + updateScreen(value: String): void\n}\n\nenum OperationType <<enum>> {\n ADD\n SUBTRACT\n MULTIPLY\n DIVIDE\n}\n\nCalculator \"1\" *-- \"1\" OperationType\nCalculator --> Display : updates\nInputHandler --> Calculator : commands\n\nnote top of Calculator : \"Must support basic arithmetic\\noperations (F2)\"\nnote right of InputHandler : \"User quote: 'I need buttons for\\nnumbers and functions...'\"\n\n@enduml\n"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "InputDispatcher",
|
||||
"name": "Input",
|
||||
"category": "Dispachers",
|
||||
"executor": "NoOp",
|
||||
"color": "black",
|
||||
"fixedParameters": { },
|
||||
"runtimeParameters": { },
|
||||
"inputs": {
|
||||
"input": "Text"
|
||||
},
|
||||
"outputs": {
|
||||
"output": "Text"
|
||||
},
|
||||
"executorToOutputTranslationMappings": {
|
||||
"output": "output"
|
||||
},
|
||||
"inputToExecutorTranslationMappings": {
|
||||
"input": "input"
|
||||
},
|
||||
"inputTranslators": { }
|
||||
},
|
||||
{
|
||||
"type": "Sequence Modeler",
|
||||
"name": "Seq Req modeller",
|
||||
"category": "Modelers",
|
||||
"executor": "GENERIC-AI",
|
||||
"color": "black",
|
||||
"fixedParameters": { },
|
||||
"runtimeParameters": {
|
||||
"LLM": {
|
||||
"name": "LLM",
|
||||
"label": "Select the LLM to use",
|
||||
"description": "The LLM to use for generating requirements",
|
||||
"type": "Select",
|
||||
"required": true,
|
||||
"validations": [ ],
|
||||
"specificAttributes": {
|
||||
"options": [
|
||||
"google-gemini",
|
||||
"open-router"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"interview": "Text",
|
||||
"plantuml-class-diagram": "Text",
|
||||
"corrected-requirements": "Text"
|
||||
},
|
||||
"outputs": {
|
||||
"plantuml-sequence-diagram": "Text"
|
||||
},
|
||||
"executorToOutputTranslationMappings": {
|
||||
"response": "plantuml-sequence-diagram"
|
||||
},
|
||||
"inputToExecutorTranslationMappings": { },
|
||||
"inputTranslators": {
|
||||
"prompt": {
|
||||
"translation": "Act as a **Requirements Modeler** specializing in interaction design. Create a PlantUML sequence diagram that visually captures the key system interactions described in the functional requirements and interview transcript.\n\n**Input:**\n1. Cleaned requirements (CSV format with ID, Corrected Requirement, Customer Quote):\n\n${{corrected-requirements}}\n\n2. Original interview transcript for context:\n\n${{interview}}\n\n3. Class diagram:\n\n\n\n**Output Rules:**\n1. Generate **one comprehensive sequence diagram** in PlantUML syntax\n2. Consider only the classes from the input class diagram\n3. Focus on showing:\n - Primary user-to-system interactions\n - Critical system responses\n - Important timing constraints (where specified)\n4. Include only the most essential interactions (5-10 steps max)\n5. Output only the model, do not add any text.\n\n**Example Output (PlantUML):**\n\n@startuml Calculator_Sequence_Diagram \n\nactor User \nparticipant InputHandler \nparticipant Calculator \nparticipant Display \n\nUser -> InputHandler : Press digit \"5\" \nnote right of User: \"I need buttons for numbers...\" (Requirement F1) \nInputHandler -> Calculator : Store input (currentInput = 5) \nCalculator -> Display : Update screen (\"5\") \n\nUser -> InputHandler : Press operator \"+\" \nnote right of User: \"Basic arithmetic is essential\" (F3) \nInputHandler -> Calculator : Set operation (ADD) \n\nUser -> InputHandler : Press digit \"3\" \nInputHandler -> Calculator : Store input (currentInput = 3) \nCalculator -> Display : Update screen (\"3\") \n\nUser -> InputHandler : Press \"=\" \nInputHandler -> Calculator : calculate() \nCalculator -> Calculator : Compute 5 + 3 \nCalculator -> Display : Update screen (\"8\") \n\n@enduml \n"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Code Generator",
|
||||
"name": "Code Generator",
|
||||
"category": "Generators",
|
||||
"executor": "GENERIC-AI",
|
||||
"color": "black",
|
||||
"fixedParameters": { },
|
||||
"runtimeParameters": {
|
||||
"LLM": {
|
||||
"name": "LLM",
|
||||
"label": "Select the LLM to use",
|
||||
"description": "The LLM to use for generating requirements",
|
||||
"type": "Select",
|
||||
"required": true,
|
||||
"validations": [ ],
|
||||
"specificAttributes": {
|
||||
"options": [
|
||||
"google-gemini",
|
||||
"open-router"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"corrected-requirements": "Text",
|
||||
"class-diagram": "Text",
|
||||
"sequence-diagram": "Text"
|
||||
},
|
||||
"outputs": {
|
||||
"source-code": "Text"
|
||||
},
|
||||
"executorToOutputTranslationMappings": {
|
||||
"response": "source-code"
|
||||
},
|
||||
"inputToExecutorTranslationMappings": { },
|
||||
"inputTranslators": {
|
||||
"prompt": {
|
||||
"translation": "Act as a **Senior Software Engineer** specializing in **model-driven development**. Generate clean, maintainable code that:\n1. Implements all validated requirements (from CSV) \n2. Matches the structure of the provided class diagram \n3. Follows the interaction flows from the sequence diagram \n\n**Input:** \n1. requirements (ID, Corrected Requirement, Customer Quote):\n\n${{corrected-requirements}}\n\n2. class diagram (PlantUML) :\n\n${{class-diagram}}\n\n3. sequence diagram (PlantUML) :\n\n\n\n\n**Output Rules:** \n- **Language**: Python\n- **Architecture**: \n - 1:1 mapping of PlantUML classes to code \n - Sequence diagram steps → method logic \n- **Quality**: \n - Type hints (Python)\n - Docstrings referencing requirement IDs (e.g., `# F1`) \n - Appropriate error handling \n- **Format**:\n - Output only executable code, do not add any text, besides comments\n\n**Example Output (Python Calculator):** \n\nfrom enum import Enum, auto\n\nclass OperationType(Enum):\n \"\"\"Supported operations (matches class diagram <<enum>>)\"\"\"\n ADD = auto() # F3: Basic arithmetic\n SUBTRACT = auto() # F3\n MULTIPLY = auto() # F3\n DIVIDE = auto() # F3\n\nclass Calculator:\n \"\"\"Core calculator logic (from class diagram)\"\"\"\n def __init__(self):\n self.current_input = 0 # F1: Digit entry\n self.stored_value = None # For multi-step operations\n self.selected_op = None # F3: Operation selection\n \n def calculate(self) -> float:\n \"\"\"Execute the current operation (sequence diagram '=' press)\"\"\"\n if not self.selected_op:\n return self.current_input\n \n # Sequence diagram compute step\n match self.selected_op:\n case OperationType.ADD:\n result = self.stored_value + self.current_input\n case OperationType.SUBTRACT:\n result = self.stored_value - self.current_input\n # ... other operations (F3)\n \n self.current_input = result\n return result\n\nclass InputHandler:\n \"\"\"Manages user input flow (sequence diagram interactions)\"\"\"\n def __init__(self, calculator: Calculator, display: Display):\n self.calc = calculator\n self.display = display\n \n def handle_digit(self, digit: int): # F1\n \"\"\"Process digit press (sequence diagram step 1)\"\"\"\n self.calc.current_input = self.calc.current_input * 10 + digit\n self.display.update(str(self.calc.current_input))\n\n def handle_operator(self, op: OperationType): # F3\n \"\"\"Process +,-,*,/ press (sequence diagram step 2)\"\"\"\n self.calc.stored_value = self.calc.current_input\n self.calc.selected_op = op\n self.calc.current_input = 0\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -8,17 +8,18 @@ import org.junit.jupiter.api.Test;
|
|||
import org.slf4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import it.cnr.isti.workflow.manager.executors.ExecutionObject;
|
||||
import it.cnr.isti.workflow.manager.model.ExecutionStep;
|
||||
import it.cnr.isti.workflow.manager.model.InputReadyListener;
|
||||
import it.cnr.isti.workflow.manager.model.ui.Flow;
|
||||
import it.cnr.isti.workflow.manager.model.ui.FlowEntity;
|
||||
import it.cnr.isti.workflow.manager.repositories.FlowRepository;
|
||||
|
||||
@SpringBootTest
|
||||
@TestPropertySource(locations="classpath:test.properties")
|
||||
public class FlowTest {
|
||||
|
||||
private static final Logger log = org.slf4j.LoggerFactory.getLogger(FlowTest.class);
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@ package it.cnr.isti.workflow.manager;
|
|||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
|
||||
@SpringBootTest
|
||||
@TestPropertySource(locations="classpath:test.properties")
|
||||
class NodeDefinitionControllerTest {
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -18,19 +18,80 @@ import static org.junit.jupiter.api.Assertions.*;
|
|||
import java.io.StringWriter;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
|
||||
@SpringBootTest
|
||||
@TestPropertySource(locations="classpath:test.properties")
|
||||
class NodeDefRepoTest {
|
||||
|
||||
private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(NodeDefRepoTest.class);
|
||||
|
||||
private static final String NODE_EXAMPLE_JSON = """
|
||||
{
|
||||
"type": "Translator",
|
||||
"name": "translator_copy",
|
||||
"category": "TRANSLATOR",
|
||||
"color": "black",
|
||||
"executor": "Generic AI Executor",
|
||||
"executorToOutputTranslationMappings": {
|
||||
"response": "translated"
|
||||
},
|
||||
"inputToExecutorTranslationMappings": {},
|
||||
"inputTranslators": {
|
||||
"prompt": {
|
||||
"usedKeys": [
|
||||
{
|
||||
"key": null,
|
||||
"type": "STANDARD"
|
||||
}
|
||||
],
|
||||
"translation": "translate the following phrase ${{phrase}} to ${{language}} returning only the translated"
|
||||
}
|
||||
},
|
||||
"outputs": {
|
||||
"translated": "Text"
|
||||
},
|
||||
"inputs": {
|
||||
"phrase": "Text"
|
||||
},
|
||||
"fixedParameters": {},
|
||||
"runtimeParameters": {
|
||||
"LLM": {
|
||||
"name": "LLM",
|
||||
"label": "Select the LLM to use",
|
||||
"description": "The LLM to use for generating requirements",
|
||||
"type": "Select",
|
||||
"required": true,
|
||||
"validations": [],
|
||||
"specificAttributes": {
|
||||
"options": [
|
||||
"google-gemini",
|
||||
"open-router"
|
||||
]
|
||||
}
|
||||
},
|
||||
"language": {
|
||||
"name": "language",
|
||||
"type": "Number",
|
||||
"description": "The language to translate to",
|
||||
"label": "Language",
|
||||
"required": true,
|
||||
"specificAttributes": {
|
||||
"options": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
@Autowired
|
||||
private NodeDefinitionRepository nodeRepository;
|
||||
|
||||
@Test
|
||||
void testStoreAndFindNode() {
|
||||
NodeDefinition node = NodeDefinition.builder().type("DefectDetection").name("defect detection").executor("GENERIC-AI").category("DefectDetection").input("file",IOType.Csv)
|
||||
.fixedParameter("LLM", ParameterDefinition.builder().name("param").label("param").type(ParameterType.Input).build()) .output("file",IOType.Csv).build();
|
||||
NodeDefinition node = NodeDefinition.builder().type("DefectDetection").name("defect detection")
|
||||
.executor("GENERIC-AI").category("DefectDetection").input("file", IOType.Csv)
|
||||
.fixedParameter("LLM",
|
||||
ParameterDefinition.builder().name("param").label("param").type(ParameterType.Text).build())
|
||||
.output("file", IOType.Csv).build();
|
||||
nodeRepository.save(node);
|
||||
NodeDefinition foundNode = nodeRepository.findById(node.getType()).orElse(null);
|
||||
assertNotNull(foundNode);
|
||||
|
|
@ -40,25 +101,51 @@ class NodeDefRepoTest {
|
|||
|
||||
@Test
|
||||
void testStoreAndFindAll() {
|
||||
NodeDefinition node = NodeDefinition.builder().type("DefectDetection").name("defect detection").executor("GENERIC-AI").category("DefectDetection").input("file",IOType.Csv) .output("file",IOType.Csv).build();
|
||||
NodeDefinition node = NodeDefinition.builder().type("DefectDetection").name("defect detection")
|
||||
.executor("GENERIC-AI").category("DefectDetection").input("file", IOType.Csv).output("file", IOType.Csv)
|
||||
.build();
|
||||
nodeRepository.save(node);
|
||||
NodeDefinition returnNode = StreamSupport.stream(nodeRepository.findAll().spliterator(), false).filter(n->n.getType().equals(node.getType())).findFirst().orElse(null) ;
|
||||
NodeDefinition returnNode = StreamSupport.stream(nodeRepository.findAll().spliterator(), false)
|
||||
.filter(n -> n.getType().equals(node.getType())).findFirst().orElse(null);
|
||||
assertNotNull(returnNode);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testStoreAndSerializeAsJson() throws Exception {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
NodeDefinition node = NodeDefinition.builder().type("DefectDetection").name("defect detection").executor("GENERIC-AI").category("DefectDetection").input("file",IOType.Csv)
|
||||
.fixedParameter("LLM", ParameterDefinition.builder().name("param").label("param").type(ParameterType.Input).build()) .output("file",IOType.Csv).build();
|
||||
NodeDefinition node = NodeDefinition.builder().type("DefectDetection").name("defect detection")
|
||||
.executor("GENERIC-AI").category("DefectDetection").input("file", IOType.Csv)
|
||||
.fixedParameter("LLM",
|
||||
ParameterDefinition.builder().name("param").label("param").type(ParameterType.Text).build())
|
||||
.output("file", IOType.Csv).build();
|
||||
nodeRepository.save(node);
|
||||
NodeDefinition foundNode = nodeRepository.findById(node.getType()).orElse(null);
|
||||
assertNotNull(foundNode);
|
||||
foundNode.getInputs().forEach((k,v) -> logger.info("input: {} {}", k, v ));
|
||||
StringWriter sw= new StringWriter();
|
||||
foundNode.getInputs().forEach((k, v) -> logger.info("input: {} {}", k, v));
|
||||
StringWriter sw = new StringWriter();
|
||||
mapper.writeValue(sw, foundNode);
|
||||
String json = sw.toString();
|
||||
assertNotNull(json);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nodeTypeDeserialization(){
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
try {
|
||||
NodeDefinition node = mapper.readValue(NODE_EXAMPLE_JSON, NodeDefinition.class);
|
||||
assertNotNull(node);
|
||||
assertEquals("Translator", node.getType());
|
||||
assertEquals("translator_copy", node.getName());
|
||||
assertEquals("TRANSLATOR", node.getCategory());
|
||||
assertEquals("Generic AI Executor", node.getExecutor());
|
||||
assertNotNull(node.getInputs());
|
||||
assertNotNull(node.getOutputs());
|
||||
assertNotNull(node.getRuntimeParameters());
|
||||
assertNotNull(node.getFixedParameters());
|
||||
|
||||
} catch (Exception e) {
|
||||
fail("Deserialization failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
spring.datasource.url=jdbc:h2:mem:test;MODE=MySQL;
|
||||
spring.datasource.driver-class-name=org.h2.Driver
|
||||
spring.datasource.username=
|
||||
spring.datasource.password=
|
||||
# We add the MySQL Dialect so that it understands and generates the query based on MySQL
|
||||
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
|
||||
Loading…
Reference in New Issue