mirror of
https://gitcode.com/ageerle/ruoyi-ai.git
synced 2026-03-13 20:53:42 +08:00
Merge branch 'vFuture3.0.0' into v3.0.0
This commit is contained in:
@@ -3657,7 +3657,16 @@ INSERT INTO `t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `di
|
||||
INSERT INTO `t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (23, 'af9d6d7b9c9b47f990ad25ec84912b73', 'Tongyiwanx', '阿里图像生成', '使用通义万相生成图像', 0, 1, '2025-12-26 16:32:25', '2025-12-26 16:32:25', 0, '000000');
|
||||
INSERT INTO `t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (24, 'a1e2c9d4b8f04e1a9c3d6f8e2a7b1c9d', 'MailSend', '发送邮箱', '发送邮箱', 0, 1, '2025-12-30 17:37:14', '2025-12-30 17:37:14', 0, '000000');
|
||||
INSERT INTO `t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (25, 'f1e2d3c4b5a67890f1e2d3c4b5a6f1e2', 'HttpRequest', '请求节点', '请求节点', 0, 1, '2025-12-30 17:37:14', '2025-12-30 17:37:14', 0, '000000');
|
||||
INSERT INTO `chat_model` (`id`, `category`, `model_name`, `provider_code`, `model_describe`, `model_price`, `model_type`, `model_show`, `model_free`, `priority`, `api_host`, `api_key`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`, `tenant_id`) VALUES (2022565766560468994, 'Tongyiwanx', 'wan2.5-t2i-preview', 'Tongyiwanx', 'wan2.5-t2i-preview', 1, '1', 'Y', 'Y', 1, 'https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation', 'skxxxx', 103, 1, '2026-02-14 14:57:11', 1, '2026-02-14 14:57:11', '通义万相文生图', 0);
|
||||
INSERT INTO `chat_model` (`id`, `category`, `model_name`, `provider_code`, `model_describe`, `model_price`, `model_type`, `model_show`, `model_free`, `priority`, `api_host`, `api_key`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`, `tenant_id`) VALUES (2022565766560468994, 'image', 'wan2.5-t2i-preview', 'Tongyiwanx', 'wan2.5-t2i-preview', 1, '1', 'Y', 'Y', 1, 'https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation', 'skxxxx', 103, 1, '2026-02-14 14:57:11', 1, '2026-02-14 14:57:11', '通义万相文生图', 0);
|
||||
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query_param`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2021046920636690433, '流程管理', 0, 0, 'flow', '', NULL, 1, 0, 'M', '0', '0', NULL, 'ph:user-fill', 103, 1, '2026-02-10 10:21:50', 1, '2026-02-10 15:59:28', '');
|
||||
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query_param`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2021047050391678978, '工作流编排', 2021046920636690433, 0, 'aiflowengine', 'aiflow/index', NULL, 1, 0, 'C', '0', '0', '', 'ph:user-fill', 103, 1, '2026-02-10 10:22:21', 1, '2026-02-10 16:04:41', '');
|
||||
INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027192921483309058, '000000', 'HTTP请求节点响应模板', 'node.httpRequest.template', '✅ HTTP请求节点:结束响应 - ', 'Y', 103, 1, '2026-02-27 09:23:51', 1, '2026-02-27 09:31:41', NULL);
|
||||
INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027193296990957569, '000000', '文生图节点响应模板', 'node.image.template', '🎨 文生图节点:结束响应 - 图片URL: ', 'Y', 103, 1, '2026-02-27 09:25:20', 1, '2026-02-27 09:31:52', NULL);
|
||||
INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027193820393959425, '000000', '发送邮箱节点响应模板', 'node.mailsend.template', '📧 发送邮箱节点:结束响应 - ', 'Y', 103, 1, '2026-02-27 09:27:25', 1, '2026-02-27 09:32:05', NULL);
|
||||
INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027194134438277122, '000000', '结束节点响应模板', 'node.end.template', '🔚 流程已执行完毕,如果您有其他需求,请随时重新发起请求。', 'Y', 103, 1, '2026-02-27 09:28:40', 1, '2026-02-27 09:32:53', NULL);
|
||||
INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027206492573335554, '000000', '人机交互节点响应模板', 'node.humanFeedback.template', '👤 人机交互节点:等待用户操作 - ', 'Y', 103, 1, '2026-02-27 10:17:46', 1, '2026-02-27 10:17:46', NULL);
|
||||
INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027208880369647617, '000000', '条件分支节点响应模板', 'node.switch.template', '🔀 条件分支节点:触发 -> 跳转到节点 ', 'Y', 103, 1, '2026-02-27 10:27:15', 1, '2026-02-27 10:35:54', NULL);
|
||||
INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027213914603995137, '000000', '大模型回答节点响应模板', 'node.llmAnswer.template', '🤖 LLM 节点 生成回答:', 'Y', 103, 1, '2026-02-27 10:47:16', 1, '2026-02-27 10:52:40', NULL);
|
||||
INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027214387000066050, '000000', '关键词提取响应模板', 'node.keywordExtractor.template', '🔑 关键词提取节点 处理完成 : ', 'Y', 103, 1, '2026-02-27 10:49:08', 1, '2026-02-27 10:52:08', NULL);
|
||||
INSERT INTO `sys_config` (`config_id`, `tenant_id`, `config_name`, `config_key`, `config_value`, `config_type`, `create_dept`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2027217577397391361, '000000', '工作流异常响应模板', 'node.exception.template', '🛑 工作流发生异常:', 'N', 103, 1, '2026-02-27 11:01:49', 1, '2026-02-27 11:02:01', NULL);
|
||||
|
||||
|
||||
1
pom.xml
1
pom.xml
@@ -56,6 +56,7 @@
|
||||
<!-- AI 相关依赖 -->
|
||||
<langchain4j.version>1.11.0</langchain4j.version>
|
||||
<langchain4j.community.version>1.11.0-beta19</langchain4j.community.version>
|
||||
<langchain4j.community.zhipu.ai.version>1.1.0-beta7</langchain4j.community.zhipu.ai.version>
|
||||
<langgraph4j.version>1.5.3</langgraph4j.version>
|
||||
<weaviate.version>1.19.6</weaviate.version>
|
||||
<dify.version>1.0.7</dify.version>
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.ruoyi.workflow.dto.node;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 为大模型返回信息封装的信息DTO(发送邮箱)
|
||||
*/
|
||||
@Data
|
||||
public class LLmMailSendNodeConfigDto {
|
||||
|
||||
/**
|
||||
* 主题
|
||||
*/
|
||||
private String subject;
|
||||
|
||||
/**
|
||||
* 内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 收件邮箱
|
||||
*/
|
||||
@JsonProperty("to_mails")
|
||||
private String toMails;
|
||||
|
||||
/**
|
||||
* 抄送邮箱
|
||||
*/
|
||||
@JsonProperty("cc_mails")
|
||||
private String ccMails;
|
||||
|
||||
/**
|
||||
* 发送类型
|
||||
*/
|
||||
private Integer senderType;
|
||||
}
|
||||
@@ -4,9 +4,15 @@ import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
|
||||
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||
import org.ruoyi.common.chat.enums.RoleType;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.ruoyi.common.core.exception.ServiceException;
|
||||
import org.ruoyi.common.core.service.ConfigService;
|
||||
import org.ruoyi.common.core.utils.SpringUtils;
|
||||
import org.ruoyi.common.core.utils.StringUtils;
|
||||
import org.ruoyi.workflow.entity.WorkflowNode;
|
||||
import org.ruoyi.workflow.helper.SSEEmitterHelper;
|
||||
import org.ruoyi.workflow.workflow.WfState;
|
||||
import org.ruoyi.workflow.workflow.WorkflowUtil;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
/**
|
||||
* 工作流消息工具类
|
||||
@@ -17,6 +23,34 @@ import org.ruoyi.workflow.workflow.WorkflowUtil;
|
||||
@Slf4j
|
||||
public class WorkflowMessageUtil {
|
||||
|
||||
|
||||
/**
|
||||
* 通知并存储消息(对话使用)
|
||||
* @param wfState 工作流实例状态
|
||||
* @param sseEmitter SSE连接对象
|
||||
* @param node 工作流节点
|
||||
* @param message 消息
|
||||
*/
|
||||
public static void notifyAndStoreMessage(WfState wfState, SseEmitter sseEmitter, WorkflowNode node, String message){
|
||||
saveWorkflowMessage(wfState, message);
|
||||
sendEmitterMessage(sseEmitter, node, message);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取节点的响应模板
|
||||
* @param configKey 参数Key
|
||||
* @return 返回模板样式
|
||||
*/
|
||||
public static String getNodeMessageTemplate(String configKey){
|
||||
ConfigService configService = SpringUtil.getBean(ConfigService.class);
|
||||
String configValue = configService.getConfigValue(configKey);
|
||||
if (StringUtils.isEmpty(configValue)) {
|
||||
throw new ServiceException("请先配置该节点的响应模板");
|
||||
}
|
||||
return configValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存工作流消息公共方法(对话使用)
|
||||
* @param wfState 工作流实例状态
|
||||
@@ -34,4 +68,15 @@ public class WorkflowMessageUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送SSE消息
|
||||
* @param sseEmitter 连接对象
|
||||
* @param node 工作流定义
|
||||
* @param message 消息
|
||||
*/
|
||||
public static void sendEmitterMessage(SseEmitter sseEmitter, WorkflowNode node, String message) {
|
||||
String nodeUuid = node.getUuid();
|
||||
SSEEmitterHelper.parseAndSendPartialMsg(sseEmitter,"[NODE_CHUNK_" + nodeUuid + "]", message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.ruoyi.workflow.workflow.data.NodeIOData;
|
||||
import org.ruoyi.workflow.workflow.def.WfNodeIO;
|
||||
import org.ruoyi.workflow.workflow.def.WfNodeParamRef;
|
||||
import org.ruoyi.workflow.workflow.node.AbstractWfNode;
|
||||
import org.ruoyi.workflow.workflow.node.enmus.NodeMessageTemplateEnum;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.util.*;
|
||||
@@ -54,6 +55,7 @@ public class WorkflowEngine {
|
||||
@Setter
|
||||
private SseEmitter sseEmitter;
|
||||
private User user;
|
||||
@Getter
|
||||
private WfState wfState;
|
||||
private WfRuntimeResp wfRuntimeResp;
|
||||
|
||||
@@ -125,7 +127,10 @@ public class WorkflowEngine {
|
||||
String nextNode = stateSnapshot.config().nextNode().orElse("");
|
||||
//还有下个节点,表示进入中断状态,等待用户输入后继续执<E7BBAD>?
|
||||
if (StringUtils.isNotBlank(nextNode) && !nextNode.equalsIgnoreCase(END)) {
|
||||
String intTip = WorkflowUtil.getHumanFeedbackTip(nextNode, wfNodes);
|
||||
// 获取提示模板
|
||||
String nodeMessageTemplate = WorkflowMessageUtil.getNodeMessageTemplate(NodeMessageTemplateEnum.HUMAN_FEED_BACK.getValue());
|
||||
// 获取人机交互提示信息
|
||||
String intTip = nodeMessageTemplate + WorkflowUtil.getHumanFeedbackTip(nextNode, wfNodes);
|
||||
//将等待输入信息[事件与提示词]发送到到客户端
|
||||
SSEEmitterHelper.parseAndSendPartialMsg(sseEmitter, "[NODE_WAIT_FEEDBACK_BY_" + nextNode + "]", intTip);
|
||||
// 保存提示信息到Chat信息记录中(对话使用)
|
||||
@@ -136,7 +141,17 @@ public class WorkflowEngine {
|
||||
workflowRuntimeService.updateOutput(wfRuntimeResp.getId(), wfState);
|
||||
} else {
|
||||
WorkflowRuntime updatedRuntime = workflowRuntimeService.updateOutput(wfRuntimeResp.getId(), wfState);
|
||||
// 保存成功会话信息
|
||||
wfNodes.stream().filter(item -> stateSnapshot.node().equals(item.getUuid()))
|
||||
.findFirst().ifPresent(wfNode -> {
|
||||
// 获取节点模板提示词信息
|
||||
String nodeMessageTemplate = WorkflowMessageUtil.getNodeMessageTemplate(NodeMessageTemplateEnum.END.getValue());
|
||||
// 发送SSE消息驱动事件和保存会话
|
||||
WorkflowMessageUtil.notifyAndStoreMessage(wfState, sseEmitter, wfNode, nodeMessageTemplate);
|
||||
});
|
||||
// 发送结束消息
|
||||
sseEmitterHelper.sendComplete(user.getId(), sseEmitter, updatedRuntime.getOutput());
|
||||
// 发送驱动消息事件
|
||||
InterruptedFlow.RUNTIME_TO_GRAPH.remove(wfState.getUuid());
|
||||
}
|
||||
}
|
||||
@@ -163,10 +178,12 @@ public class WorkflowEngine {
|
||||
|
||||
private void errorWhenExe(Exception e) {
|
||||
log.error("error", e);
|
||||
String nodeMessageTemplate = WorkflowMessageUtil.getNodeMessageTemplate(NodeMessageTemplateEnum.EXCEPTION.getValue());
|
||||
String errorMsg = e.getMessage();
|
||||
if (errorMsg.contains("parallel node doesn't support conditional branch")) {
|
||||
errorMsg = "并行节点中不能包含条件分<EFBFBD>?";
|
||||
}
|
||||
errorMsg = nodeMessageTemplate + errorMsg;
|
||||
sseEmitterHelper.sendErrorAndComplete(user.getId(), sseEmitter, errorMsg);
|
||||
workflowRuntimeService.updateStatus(wfRuntimeResp.getId(), WORKFLOW_PROCESS_STATUS_FAIL, errorMsg);
|
||||
}
|
||||
|
||||
@@ -103,6 +103,8 @@ public class WorkflowStarter implements IWorkFlowStarterService {
|
||||
// 如果SSE连接对象不为空传入该对象(Chat调用工作流对话使用)
|
||||
if (null != sseEmitter){
|
||||
workflowEngine.setSseEmitter(sseEmitter);
|
||||
// 为了让每个节点都可以发送模板消息 保持SSE对象一致(以防出现向已关闭的SSE对象发送消息)
|
||||
workflowEngine.getWfState().setSseEmitter(sseEmitter);
|
||||
}
|
||||
workflowEngine.resume(userInput);
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ public class WorkflowUtil extends AbstractChatMessageService {
|
||||
}
|
||||
|
||||
public void streamingInvokeLLM(WfState wfState, WfNodeState state, WorkflowNode node, String modelName,
|
||||
List<SystemMessage> systemMessage) {
|
||||
List<SystemMessage> systemMessage, String nodeMessageTemplate) {
|
||||
log.info("stream invoke, modelName: {}", modelName);
|
||||
|
||||
// 根据模型名称查询模型信息
|
||||
@@ -153,8 +153,10 @@ public class WorkflowUtil extends AbstractChatMessageService {
|
||||
|
||||
// 会话ID不为空时插入数据库
|
||||
if (sessionId != null){
|
||||
// 获取模板消息拼接信息体
|
||||
String message = nodeMessageTemplate + responseTxt;
|
||||
// 保存助手回复消息
|
||||
saveChatMessage(chatRequest, userId, responseTxt, RoleType.ASSISTANT.getName(), chatModelVo);
|
||||
saveChatMessage(chatRequest, userId, message, RoleType.ASSISTANT.getName(), chatModelVo);
|
||||
log.info("{}消息结束,已保存到数据库", getProviderName());
|
||||
}
|
||||
|
||||
|
||||
@@ -6,15 +6,11 @@ import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.SerializationUtils;
|
||||
import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
|
||||
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||
import org.ruoyi.common.chat.enums.RoleType;
|
||||
import org.ruoyi.common.core.exception.base.BaseException;
|
||||
import org.ruoyi.workflow.base.NodeInputConfigTypeHandler;
|
||||
import org.ruoyi.workflow.entity.WorkflowComponent;
|
||||
import org.ruoyi.workflow.entity.WorkflowNode;
|
||||
import org.ruoyi.workflow.enums.WfIODataTypeEnum;
|
||||
import org.ruoyi.workflow.helper.SSEEmitterHelper;
|
||||
import org.ruoyi.workflow.util.JsonUtil;
|
||||
import org.ruoyi.workflow.util.SpringUtil;
|
||||
import org.ruoyi.workflow.util.WorkflowMessageUtil;
|
||||
@@ -229,16 +225,16 @@ public abstract class AbstractWfNode {
|
||||
/**
|
||||
* 会话消息保存方法
|
||||
*/
|
||||
public void saveSessionMessage(WfState wfState, String message) {
|
||||
WorkflowMessageUtil.saveWorkflowMessage(wfState, message);
|
||||
public void notifyAndStoreMessage(WfState wfState, String message) {
|
||||
WorkflowMessageUtil.notifyAndStoreMessage(wfState, wfState.getSseEmitter(), node, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送SSe消息
|
||||
* @param message 信息
|
||||
* 获取节点的响应模板
|
||||
* @param configKey 参数Key
|
||||
* @return 返回模板样式
|
||||
*/
|
||||
public void sendSseEvent(String message){
|
||||
String nodeUuid = node.getUuid();
|
||||
SSEEmitterHelper.parseAndSendPartialMsg(wfState.getSseEmitter(), "[NODE_CHUNK_" + nodeUuid + "]", message);
|
||||
public String getNodeMessageTemplate(String configKey){
|
||||
return WorkflowMessageUtil.getNodeMessageTemplate(configKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,13 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.ruoyi.workflow.entity.WorkflowComponent;
|
||||
import org.ruoyi.workflow.entity.WorkflowNode;
|
||||
import org.ruoyi.workflow.util.SpringUtil;
|
||||
import org.ruoyi.workflow.util.WorkflowMessageUtil;
|
||||
import org.ruoyi.workflow.workflow.NodeProcessResult;
|
||||
import org.ruoyi.workflow.workflow.WfNodeState;
|
||||
import org.ruoyi.workflow.workflow.WfState;
|
||||
import org.ruoyi.workflow.workflow.WorkflowUtil;
|
||||
import org.ruoyi.workflow.workflow.node.AbstractWfNode;
|
||||
import org.ruoyi.workflow.workflow.node.enmus.NodeMessageTemplateEnum;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -46,7 +48,11 @@ public class LLMAnswerNode extends AbstractWfNode {
|
||||
String modelName = nodeConfigObj.getModelName();
|
||||
// 转换系统信息结构
|
||||
List<SystemMessage> systemMessage = List.of(new SystemMessage(prompt));
|
||||
workflowUtil.streamingInvokeLLM(wfState, state, node, modelName, systemMessage);
|
||||
// 获取节点模板提示词信息
|
||||
String nodeMessageTemplate = WorkflowMessageUtil.getNodeMessageTemplate(NodeMessageTemplateEnum.LLM_RESPONSE.getValue());
|
||||
// 发送SSE驱动事件消息
|
||||
WorkflowMessageUtil.sendEmitterMessage(wfState.getSseEmitter(), node, nodeMessageTemplate);
|
||||
workflowUtil.streamingInvokeLLM(wfState, state, node, modelName, systemMessage, nodeMessageTemplate);
|
||||
return new NodeProcessResult();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.ruoyi.workflow.workflow.node.enmus;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 节点消息模板ConfigKey枚举
|
||||
*/
|
||||
@Getter
|
||||
public enum NodeMessageTemplateEnum {
|
||||
HTTP_REQUEST("node.httpRequest.template"),
|
||||
MAIL_SEND("node.mailsend.template"),
|
||||
IMAGE("node.image.template"),
|
||||
HUMAN_FEED_BACK("node.humanFeedback.template"),
|
||||
SWITCH("node.switch.template"),
|
||||
LLM_RESPONSE("node.llmAnswer.template"),
|
||||
KEYWORD_EXTRACTOR("node.keywordExtractor.template"),
|
||||
EXCEPTION("node.exception.template"),
|
||||
END("node.end.template");
|
||||
|
||||
private final String value;
|
||||
|
||||
NodeMessageTemplateEnum(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import org.ruoyi.workflow.workflow.WfNodeState;
|
||||
import org.ruoyi.workflow.workflow.WfState;
|
||||
import org.ruoyi.workflow.workflow.data.NodeIOData;
|
||||
import org.ruoyi.workflow.workflow.node.AbstractWfNode;
|
||||
import org.ruoyi.workflow.workflow.node.enmus.NodeMessageTemplateEnum;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
@@ -32,6 +33,8 @@ public class HttpRequestNode extends AbstractWfNode {
|
||||
|
||||
@Override
|
||||
public NodeProcessResult onProcess() {
|
||||
// 获取节点模板提示词信息
|
||||
String nodeMessageTemplate = getNodeMessageTemplate(NodeMessageTemplateEnum.HTTP_REQUEST.getValue());
|
||||
try {
|
||||
HttpRequestNodeConfig config = checkAndGetConfig(HttpRequestNodeConfig.class);
|
||||
List<NodeIOData> inputs = state.getInputs();
|
||||
@@ -63,11 +66,9 @@ public class HttpRequestNode extends AbstractWfNode {
|
||||
List<NodeIOData> outputs = new ArrayList<>();
|
||||
outputs.add(NodeIOData.createByText("output", "HTTP响应", response));
|
||||
|
||||
// 保存成功会话信息
|
||||
String message = "HTTP响应:" + response;
|
||||
saveSessionMessage(wfState, message);
|
||||
// 发送驱动消息事件
|
||||
sendSseEvent(message);
|
||||
// 保存成功会话信息且发送驱动消息事件
|
||||
String message = nodeMessageTemplate + response;
|
||||
notifyAndStoreMessage(wfState, message);
|
||||
return NodeProcessResult.builder().content(outputs).build();
|
||||
|
||||
} catch (Exception e) {
|
||||
@@ -78,11 +79,9 @@ public class HttpRequestNode extends AbstractWfNode {
|
||||
errorOutputs.add(NodeIOData.createByText("output", "错误", ""));
|
||||
errorOutputs.add(NodeIOData.createByText("error", "HTTP请求错误", e.getMessage()));
|
||||
|
||||
// 保存失败会话信息
|
||||
String message = "HTTP响应失败:" + e.getMessage();
|
||||
saveSessionMessage(wfState, message);
|
||||
// 发送驱动消息事件
|
||||
sendSseEvent(message);
|
||||
// 保存失败会话信息且发送驱动消息事件
|
||||
String message = nodeMessageTemplate + e.getMessage();
|
||||
notifyAndStoreMessage(wfState, message);
|
||||
return NodeProcessResult.builder().content(errorOutputs).build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import org.ruoyi.workflow.workflow.WfState;
|
||||
import org.ruoyi.workflow.workflow.WorkflowUtil;
|
||||
import org.ruoyi.workflow.workflow.data.NodeIOData;
|
||||
import org.ruoyi.workflow.workflow.node.AbstractWfNode;
|
||||
import org.ruoyi.workflow.workflow.node.enmus.NodeMessageTemplateEnum;
|
||||
|
||||
import static org.ruoyi.workflow.cosntant.AdiConstant.WorkflowConstant.NODE_PROCESS_STATUS_SUCCESS;
|
||||
|
||||
@@ -51,11 +52,11 @@ public class ImageNode extends AbstractWfNode {
|
||||
Integer seed = nodeConfigObj.getSeed();
|
||||
// 调用LLM生成图片(后续可以将图片保存到OSS中)
|
||||
String imageUrl = workflowUtil.buildTextToImage(modelName, prompt, size, seed);
|
||||
// 保存成功信息
|
||||
String message = "图片生成地址:" + imageUrl;
|
||||
saveSessionMessage(wfState, message);
|
||||
// 发送驱动消息事件
|
||||
sendSseEvent(message);
|
||||
// 获取节点模板提示词信息
|
||||
String nodeMessageTemplate = getNodeMessageTemplate(NodeMessageTemplateEnum.IMAGE.getValue());
|
||||
// 保存成功信息且发送驱动消息事件
|
||||
String message = nodeMessageTemplate + imageUrl;
|
||||
notifyAndStoreMessage(wfState, message);
|
||||
// 创建节点参数对象
|
||||
NodeIOData nodeIOData = NodeIOData.createByText("output", "image", imageUrl);
|
||||
// 添加到输出列表以便给后续节点使用
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
package org.ruoyi.workflow.workflow.node.keywordExtractor;
|
||||
|
||||
import dev.langchain4j.data.message.SystemMessage;
|
||||
import dev.langchain4j.data.message.UserMessage;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.ruoyi.workflow.entity.WorkflowComponent;
|
||||
import org.ruoyi.workflow.entity.WorkflowNode;
|
||||
import org.ruoyi.workflow.util.SpringUtil;
|
||||
import org.ruoyi.workflow.util.WorkflowMessageUtil;
|
||||
import org.ruoyi.workflow.workflow.NodeProcessResult;
|
||||
import org.ruoyi.workflow.workflow.WfNodeState;
|
||||
import org.ruoyi.workflow.workflow.WfState;
|
||||
import org.ruoyi.workflow.workflow.WorkflowUtil;
|
||||
import org.ruoyi.workflow.workflow.data.NodeIOData;
|
||||
import org.ruoyi.workflow.workflow.node.AbstractWfNode;
|
||||
import org.ruoyi.workflow.workflow.node.enmus.NodeMessageTemplateEnum;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -67,8 +68,12 @@ public class KeywordExtractorNode extends AbstractWfNode {
|
||||
WorkflowUtil workflowUtil = SpringUtil.getBean(WorkflowUtil.class);
|
||||
String modelName = config.getModelName();
|
||||
List<SystemMessage> systemMessage = List.of(new SystemMessage(prompt));
|
||||
// 获取节点模板提示词信息
|
||||
String nodeMessageTemplate = WorkflowMessageUtil.getNodeMessageTemplate(NodeMessageTemplateEnum.KEYWORD_EXTRACTOR.getValue());
|
||||
// 发送SSE事件消息
|
||||
WorkflowMessageUtil.sendEmitterMessage(wfState.getSseEmitter(), node, nodeMessageTemplate);
|
||||
// 使用流式调用
|
||||
workflowUtil.streamingInvokeLLM(wfState, state, node, modelName, systemMessage);
|
||||
workflowUtil.streamingInvokeLLM(wfState, state, node, modelName, systemMessage, nodeMessageTemplate);
|
||||
return new NodeProcessResult();
|
||||
}
|
||||
|
||||
|
||||
@@ -161,7 +161,8 @@ public class KnowledgeRetrievalNode extends AbstractWfNode {
|
||||
tempState,
|
||||
tempNode,
|
||||
modelName,
|
||||
systemMessage
|
||||
systemMessage,
|
||||
""
|
||||
);
|
||||
|
||||
// 等待LLM响应完成(最多等待30秒)
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.alibaba.fastjson.JSONValidator;
|
||||
import jakarta.mail.internet.MimeMessage;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.ruoyi.workflow.dto.node.LLmMailSendNodeConfigDto;
|
||||
import org.ruoyi.workflow.entity.WorkflowComponent;
|
||||
import org.ruoyi.workflow.entity.WorkflowNode;
|
||||
import org.ruoyi.workflow.workflow.NodeProcessResult;
|
||||
@@ -13,6 +14,8 @@ import org.ruoyi.workflow.workflow.WfState;
|
||||
import org.ruoyi.workflow.workflow.WorkflowUtil;
|
||||
import org.ruoyi.workflow.workflow.data.NodeIOData;
|
||||
import org.ruoyi.workflow.workflow.node.AbstractWfNode;
|
||||
import org.ruoyi.workflow.workflow.node.enmus.NodeMessageTemplateEnum;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
import org.springframework.mail.javamail.JavaMailSenderImpl;
|
||||
import org.springframework.mail.javamail.MimeMessageHelper;
|
||||
@@ -30,6 +33,8 @@ public class MailSendNode extends AbstractWfNode {
|
||||
|
||||
@Override
|
||||
public NodeProcessResult onProcess() {
|
||||
// 获取节点模板提示词信息
|
||||
String nodeMessageTemplate = getNodeMessageTemplate(NodeMessageTemplateEnum.MAIL_SEND.getValue());
|
||||
try {
|
||||
MailSendNodeConfig config = checkAndGetConfig(MailSendNodeConfig.class);
|
||||
List<NodeIOData> inputs = state.getInputs();
|
||||
@@ -37,7 +42,9 @@ public class MailSendNode extends AbstractWfNode {
|
||||
String input = getDataFromInput(inputs);
|
||||
// 判断是否为JSON格式(LLM输出转换 由LLM生成格式)
|
||||
if (StringUtils.isNotBlank(input) && isJson(input)) {
|
||||
config = JSONObject.parseObject(input, MailSendNodeConfig.class);
|
||||
LLmMailSendNodeConfigDto lLmMailSendNodeConfigDto = JSONObject.parseObject(input, LLmMailSendNodeConfigDto.class);
|
||||
// 保留原本Sender和Smtp对象
|
||||
BeanUtils.copyProperties(lLmMailSendNodeConfigDto, config);
|
||||
}
|
||||
|
||||
// 安全获取模板(使用 defaultString 避免 null)
|
||||
@@ -111,11 +118,9 @@ public class MailSendNode extends AbstractWfNode {
|
||||
mailSender.send(message);
|
||||
log.info("Email sent successfully to: {}", toMails);
|
||||
|
||||
// 保存成功会话信息
|
||||
String resultMessage = "发送邮箱成功";
|
||||
saveSessionMessage(wfState, resultMessage);
|
||||
// 发送驱动消息事件
|
||||
sendSseEvent(resultMessage);
|
||||
// 保存成功会话信息且发送驱动消息事件
|
||||
String resultMessage = nodeMessageTemplate + "发送邮箱成功";
|
||||
notifyAndStoreMessage(wfState, resultMessage);
|
||||
|
||||
// 构造输出:统一输出为 output 参数
|
||||
List<NodeIOData> outputs = new java.util.ArrayList<>();
|
||||
@@ -144,11 +149,9 @@ public class MailSendNode extends AbstractWfNode {
|
||||
// 异常时也统一输出为 output 参数,添加错误信息
|
||||
List<NodeIOData> errorOutputs = new java.util.ArrayList<>();
|
||||
|
||||
// 保存失败会话信息
|
||||
String resultMessage = "发送邮箱失败: " + e.getMessage();
|
||||
saveSessionMessage(wfState, resultMessage);
|
||||
// 发送驱动消息事件
|
||||
sendSseEvent(resultMessage);
|
||||
// 保存失败会话信息且发送驱动消息事件
|
||||
String resultMessage = nodeMessageTemplate + "发送邮箱失败: " + e.getMessage();
|
||||
notifyAndStoreMessage(wfState, resultMessage);
|
||||
|
||||
state.getInputs().stream()
|
||||
.filter(item -> "output".equals(item.getName()))
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.ruoyi.workflow.workflow.WfNodeState;
|
||||
import org.ruoyi.workflow.workflow.WfState;
|
||||
import org.ruoyi.workflow.workflow.data.NodeIOData;
|
||||
import org.ruoyi.workflow.workflow.node.AbstractWfNode;
|
||||
import org.ruoyi.workflow.workflow.node.enmus.NodeMessageTemplateEnum;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
@@ -43,6 +44,9 @@ public class SwitcherNode extends AbstractWfNode {
|
||||
log.info("条件分支节点处理中,分支数量: {}",
|
||||
config.getCases() != null ? config.getCases().size() : 0);
|
||||
|
||||
// 获取提示模板
|
||||
String nodeMessageTemplate = getNodeMessageTemplate(NodeMessageTemplateEnum.SWITCH.getValue());
|
||||
|
||||
// 按顺序评估每个分支
|
||||
if (config.getCases() != null) {
|
||||
for (int i = 0; i < config.getCases().size(); i++) {
|
||||
@@ -52,13 +56,17 @@ public class SwitcherNode extends AbstractWfNode {
|
||||
|
||||
if (evaluateCase(switcherCase, inputs)) {
|
||||
// 检查目标节点UUID是否为空
|
||||
if (StringUtils.isBlank(switcherCase.getTargetNodeUuid())) {
|
||||
String targetNodeUuid = switcherCase.getTargetNodeUuid();
|
||||
if (StringUtils.isBlank(targetNodeUuid)) {
|
||||
log.warn("分支 {} 匹配但目标节点UUID为空,跳过到下一个分支", i + 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 根据目标节点UUID获取对应节点名称
|
||||
findNodeAndNotify(targetNodeUuid, nodeMessageTemplate);
|
||||
|
||||
log.info("分支 {} 匹配,跳转到节点: {}",
|
||||
i + 1, switcherCase.getTargetNodeUuid());
|
||||
i + 1, targetNodeUuid);
|
||||
|
||||
// 构造输出:只保留 output 和其他非 input 参数 + 添加分支匹配信息
|
||||
List<NodeIOData> outputs = new java.util.ArrayList<>();
|
||||
@@ -85,12 +93,12 @@ public class SwitcherNode extends AbstractWfNode {
|
||||
|
||||
outputs.add(NodeIOData.createByText("matched_case", "switcher", String.valueOf(i + 1)));
|
||||
outputs.add(NodeIOData.createByText("case_uuid", "switcher", switcherCase.getUuid()));
|
||||
outputs.add(NodeIOData.createByText("target_node", "switcher", switcherCase.getTargetNodeUuid()));
|
||||
outputs.add(NodeIOData.createByText("target_node", "switcher", targetNodeUuid));
|
||||
|
||||
// WorkflowEngine 会自动将 nextNodeUuid 放入 resultMap 的 "next" 键中
|
||||
return NodeProcessResult.builder()
|
||||
.content(outputs)
|
||||
.nextNodeUuid(switcherCase.getTargetNodeUuid())
|
||||
.nextNodeUuid(targetNodeUuid)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -99,6 +107,9 @@ public class SwitcherNode extends AbstractWfNode {
|
||||
// 所有分支都不满足,使用默认分支
|
||||
log.info("没有分支匹配,使用默认分支: {}", config.getDefaultTargetNodeUuid());
|
||||
|
||||
// 根据默认目标节点UUID获取对应节点名称
|
||||
findNodeAndNotify(config.getDefaultTargetNodeUuid(), nodeMessageTemplate);
|
||||
|
||||
if (StringUtils.isBlank(config.getDefaultTargetNodeUuid())) {
|
||||
log.warn("默认目标节点UUID为空,工作流可能在此停止");
|
||||
}
|
||||
@@ -154,6 +165,21 @@ public class SwitcherNode extends AbstractWfNode {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据节点ID查询对应节点
|
||||
* @param targetNodeUuid 节点UUID
|
||||
* @param nodeMessageTemplate 节点消息模板
|
||||
*/
|
||||
private void findNodeAndNotify(String targetNodeUuid, String nodeMessageTemplate) {
|
||||
// 根据目标节点UUID获取对应节点名称
|
||||
WorkflowNode workflowNode = workflowNodeService.lambdaQuery().eq(WorkflowNode::getUuid, targetNodeUuid).one();
|
||||
if (null != workflowNode){
|
||||
// 获取节点名称
|
||||
String message = nodeMessageTemplate + workflowNode.getTitle();
|
||||
notifyAndStoreMessage(wfState, message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 评估单个分支的条件
|
||||
*
|
||||
|
||||
@@ -48,6 +48,12 @@
|
||||
<version>${langchain4j.community.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-community-zhipu-ai</artifactId>
|
||||
<version>${langchain4j.community.zhipu.ai.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>dev.langchain4j</groupId>-->
|
||||
<!-- <artifactId>langchain4j-community-zhipu-ai</artifactId>-->
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.ruoyi.service.chat.impl.provider;
|
||||
|
||||
import dev.langchain4j.community.model.zhipu.ZhipuAiStreamingChatModel;
|
||||
import dev.langchain4j.data.message.ChatMessage;
|
||||
import dev.langchain4j.model.chat.StreamingChatModel;
|
||||
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
|
||||
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||
import org.ruoyi.enums.ChatModeType;
|
||||
import org.ruoyi.service.chat.impl.AbstractStreamingChatService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 智谱AI服务调用
|
||||
*
|
||||
* @author zengxb
|
||||
* @date 2026/02/26
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class ZhiPuChatServiceImpl extends AbstractStreamingChatService {
|
||||
@Override
|
||||
protected void doChat(ChatModelVo chatModelVo, ChatRequest chatRequest, List<ChatMessage> messagesWithMemory, StreamingChatResponseHandler handler) {
|
||||
StreamingChatModel streamingChatModel = buildStreamingChatModel(chatModelVo,chatRequest);
|
||||
streamingChatModel.chat(messagesWithMemory, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected StreamingChatModel buildStreamingChatModel(ChatModelVo chatModelVo, ChatRequest chatRequest) {
|
||||
return ZhipuAiStreamingChatModel.builder()
|
||||
.apiKey(chatModelVo.getApiKey())
|
||||
.model(chatModelVo.getModelName())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProviderName() {
|
||||
return ChatModeType.ZHI_PU.getCode();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user