diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/workflow/NodeProcessResult.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/workflow/NodeProcessResult.java index d78e1c4b..a62d4218 100644 --- a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/workflow/NodeProcessResult.java +++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/workflow/NodeProcessResult.java @@ -21,4 +21,14 @@ public class NodeProcessResult { * 条件执行时使用 */ private String nextNodeUuid; + + /** + * 是否发生错误 + */ + private boolean error = false; + + /** + * 错误或提示信息 + */ + private String message; } diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/workflow/WfNodeFactory.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/workflow/WfNodeFactory.java index 92095f0e..a6328e64 100644 --- a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/workflow/WfNodeFactory.java +++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/workflow/WfNodeFactory.java @@ -5,7 +5,10 @@ import org.ruoyi.workflow.entity.WorkflowNode; import org.ruoyi.workflow.workflow.node.AbstractWfNode; import org.ruoyi.workflow.workflow.node.EndNode; import org.ruoyi.workflow.workflow.node.answer.LLMAnswerNode; +import org.ruoyi.workflow.workflow.node.keywordExtractor.KeywordExtractorNode; +import org.ruoyi.workflow.workflow.node.mailSend.MailSendNode; import org.ruoyi.workflow.workflow.node.start.StartNode; +import org.ruoyi.workflow.workflow.node.switcher.SwitcherNode; public class WfNodeFactory { public static AbstractWfNode create(WorkflowComponent wfComponent, WorkflowNode nodeDefinition, @@ -14,7 +17,10 @@ public class WfNodeFactory { switch (WfComponentNameEnum.getByName(wfComponent.getName())) { case START -> wfNode = new StartNode(wfComponent, nodeDefinition, wfState, nodeState); case LLM_ANSWER -> wfNode = new LLMAnswerNode(wfComponent, nodeDefinition, wfState, nodeState); + case KEYWORD_EXTRACTOR -> wfNode = new KeywordExtractorNode(wfComponent, nodeDefinition, wfState, nodeState); case END -> wfNode = new EndNode(wfComponent, nodeDefinition, wfState, nodeState); + case MAIL_SEND -> wfNode = new MailSendNode(wfComponent, nodeDefinition, wfState, nodeState); + case SWITCHER -> wfNode = new SwitcherNode(wfComponent, nodeDefinition, wfState, nodeState); default -> { } } diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/workflow/WorkflowUtil.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/workflow/WorkflowUtil.java index adbd581b..7f9e89c3 100644 --- a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/workflow/WorkflowUtil.java +++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/workflow/WorkflowUtil.java @@ -20,12 +20,10 @@ import org.ruoyi.workflow.workflow.data.NodeIOData; import org.ruoyi.workflow.workflow.data.NodeIODataContent; import org.ruoyi.workflow.workflow.def.WfNodeParamRef; import org.springframework.stereotype.Component; - import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; - import static org.ruoyi.workflow.cosntant.AdiConstant.WorkflowConstant.DEFAULT_OUTPUT_PARAM_NAME; @Slf4j @@ -35,22 +33,48 @@ public class WorkflowUtil { @Resource private ChatServiceFactory chatServiceFactory; - @SuppressWarnings("unchecked") public static String renderTemplate(String template, List values) { + // 🔒 关键修复:如果 template 为 null,直接返回 null 或空字符串 + if (template == null) { + return null; // 或 return ""; 根据业务需求 + } + String result = template; + + // 防御 values 为 null + if (values == null) { + return result; + } + for (NodeIOData next : values) { + if (next == null || next.getName() == null) { + continue; + } + String name = next.getName(); NodeIODataContent dataContent = next.getContent(); - if (dataContent.getType().equals(WfIODataTypeEnum.FILES.getValue())) { - List value = (List) dataContent.getValue(); - result = result.replace("{" + name + "}", String.join(",", value)); - } else if (dataContent.getType().equals(WfIODataTypeEnum.OPTIONS.getValue())) { - Map value = (Map) dataContent.getValue(); - result = result.replace("{" + name + "}", value.toString()); - } else { - result = result.replace("{" + name + "}", dataContent.getValue().toString()); + if (dataContent == null || dataContent.getValue() == null) { + // 变量值为 null,替换为空字符串 + result = result.replace("{" + name + "}", ""); + continue; } + + String replacement; + if (dataContent.getType().equals(WfIODataTypeEnum.FILES.getValue())) { + @SuppressWarnings("unchecked") + List value = (List) dataContent.getValue(); + replacement = String.join(",", value); + } else if (dataContent.getType().equals(WfIODataTypeEnum.OPTIONS.getValue())) { + @SuppressWarnings("unchecked") + Map value = (Map) dataContent.getValue(); + replacement = value.toString(); + } else { + replacement = dataContent.getValue().toString(); + } + + result = result.replace("{" + name + "}", replacement); } + return result; } @@ -81,8 +105,15 @@ public class WorkflowUtil { .mapResult(response -> { String responseTxt = response.aiMessage().text(); log.info("llm response:{}", responseTxt); - NodeIOData output = NodeIOData.createByText(DEFAULT_OUTPUT_PARAM_NAME, "", responseTxt); - wfState.getNodeStateByNodeUuid(node.getUuid()).ifPresent(item -> item.getOutputs().add(output)); + + // 传递所有输入数据 + 添加 LLM 输出 + wfState.getNodeStateByNodeUuid(node.getUuid()).ifPresent(item -> { + List outputs = new ArrayList<>(item.getInputs()); + NodeIOData output = NodeIOData.createByText(DEFAULT_OUTPUT_PARAM_NAME, "", responseTxt); + outputs.add(output); + item.setOutputs(outputs); + }); + return Map.of("completeResult", response.aiMessage().text()); }) .startingNode(node.getUuid()) @@ -141,9 +172,10 @@ public class WorkflowUtil { * @return */ private Message getMessage(String role, Object value) { + log.info("Creating message with role: {}, content: {}", role, value); // 🔥 Message message = new Message(); - message.setContent(String.valueOf(value)); message.setRole(role); + message.setContent(value); return message; } @@ -154,9 +186,13 @@ public class WorkflowUtil { * @param messages */ private void addSystemMessage(List systemMessage, List messages) { + log.info("addSystemMessage received: {}", systemMessage); // 🔥 加这一行 + if (CollUtil.isEmpty(systemMessage)) { return; } - systemMessage.stream().map(userMsg -> getMessage("system", userMsg.singleText())).forEach(messages::add); + systemMessage.stream() + .map(userMsg -> getMessage("system", userMsg.singleText())) + .forEach(messages::add); } } diff --git a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/workflow/node/AbstractWfNode.java b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/workflow/node/AbstractWfNode.java index d6119e5c..9deeb5e8 100644 --- a/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/workflow/node/AbstractWfNode.java +++ b/ruoyi-modules-api/ruoyi-workflow-api/src/main/java/org/ruoyi/workflow/workflow/node/AbstractWfNode.java @@ -166,17 +166,40 @@ public abstract class AbstractWfNode { protected abstract NodeProcessResult onProcess(); protected String getFirstInputText() { + // 检查输入是否为空 + if (state.getInputs() == null || state.getInputs().isEmpty()) { + log.warn("No inputs available for node: {}", state.getUuid()); + return ""; + } + + // 优先查找 output 参数(LLM 节点的输出) + Optional outputParam = state.getInputs() + .stream() + .filter(item -> DEFAULT_OUTPUT_PARAM_NAME.equals(item.getName())) + .map(NodeIOData::valueToString) + .findFirst(); + + if (outputParam.isPresent()) { + log.debug("Found output parameter for node: {}", state.getUuid()); + return outputParam.get(); + } + + // 如果没有 output,查找其他文本类型参数(排除 input) String firstInputText; if (state.getInputs().size() > 1) { firstInputText = state.getInputs() .stream() - .filter(item -> WfIODataTypeEnum.TEXT.getValue().equals(item.getContent().getType()) && !DEFAULT_INPUT_PARAM_NAME.equals(item.getName())) + .filter(item -> WfIODataTypeEnum.TEXT.getValue().equals(item.getContent().getType()) + && !DEFAULT_INPUT_PARAM_NAME.equals(item.getName())) .map(NodeIOData::valueToString) .findFirst() .orElse(""); } else { firstInputText = state.getInputs().get(0).valueToString(); } + + log.debug("Using first input text for node: {}, value: {}", state.getUuid(), + firstInputText.length() > 50 ? firstInputText.substring(0, 50) + "..." : firstInputText); return firstInputText; }