mirror of
https://gitcode.com/ageerle/ruoyi-ai.git
synced 2026-04-07 08:47:32 +00:00
context:通义万相文生图节点功能以及发送邮箱和HTTP请求节点调研
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
INSERT INTO `mysql`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (17, '5cd68dccbbb411f0bb7840c2ba9a7fbc', 'Start', '开始', '流程由此开始', 0, 1, '2025-11-07 16:32:49', '2025-11-07 16:32:49', 0, '000000');
|
||||
INSERT INTO `mysql`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (18, '5cd6ac69bbb411f0bb7840c2ba9a7fbc', 'End', '结束', '流程由此结束', 0, 1, '2025-11-07 16:32:49', '2025-11-07 16:32:49', 0, '000000');
|
||||
INSERT INTO `mysql`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (19, '5cd6c8eabbb411f0bb7840c2ba9a7fbc', 'Answer', '生成回答', '调用大语言模型回答问题', 0, 1, '2025-11-07 16:32:49', '2025-11-07 16:32:49', 0, '000000');
|
||||
INSERT INTO `mysql`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (25, '0b4369bb60dc46d6bd84ceb4e36184dc', 'KeywordExtractor', '关键词提取', '从文本中提取关键词', 0, 1, '2025-12-26 16:30:05', '2025-12-26 16:30:05', 0, '000000');
|
||||
INSERT INTO `mysql`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (26, 'bb00fc2f52c74fec82ee3f99725b56bb', 'Switcher', '条件分支', '根据条件执行不同分支', 0, 1, '2025-12-26 16:30:46', '2025-12-26 16:30:46', 0, '000000');
|
||||
INSERT INTO `mysql`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (36, 'f37dbcb8f0d5464d90fbb22774490a56', 'HumanFeedback', '人类', '人机沟通', 0, 1, '2025-12-30 17:37:14', '2025-12-30 17:37:14', 0, '000000');
|
||||
INSERT INTO `ruoyi-ai-v3`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (17, '5cd68dccbbb411f0bb7840c2ba9a7fbc', 'Start', '开始', '流程由此开始', 0, 1, '2025-11-07 16:32:49', '2025-11-07 16:32:49', 0, '000000');
|
||||
INSERT INTO `ruoyi-ai-v3`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (18, '5cd6ac69bbb411f0bb7840c2ba9a7fbc', 'End', '结束', '流程由此结束', 0, 1, '2025-11-07 16:32:49', '2025-11-07 16:32:49', 0, '000000');
|
||||
INSERT INTO `ruoyi-ai-v3`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (19, '5cd6c8eabbb411f0bb7840c2ba9a7fbc', 'Answer', '生成回答', '调用大语言模型回答问题', 0, 1, '2025-11-07 16:32:49', '2025-11-07 16:32:49', 0, '000000');
|
||||
INSERT INTO `ruoyi-ai-v3`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (20, '0b4369bb60dc46d6bd84ceb4e36184dc', 'KeywordExtractor', '关键词提取', '从文本中提取关键词', 0, 1, '2025-12-26 16:30:05', '2025-12-26 16:30:05', 0, '000000');
|
||||
INSERT INTO `ruoyi-ai-v3`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (21, 'bb00fc2f52c74fec82ee3f99725b56bb', 'Switcher', '条件分支', '根据条件执行不同分支', 0, 1, '2025-12-26 16:30:46', '2025-12-26 16:30:46', 0, '000000');
|
||||
INSERT INTO `ruoyi-ai-v3`.`t_workflow_component` (`id`, `uuid`, `name`, `title`, `remark`, `display_order`, `is_enable`, `create_time`, `update_time`, `is_deleted`, `tenant_id`) VALUES (22, 'f37dbcb8f0d5464d90fbb22774490a56', 'HumanFeedback', '人类', '人机沟通', 0, 1, '2025-12-30 17:37:14', '2025-12-30 17:37:14', 0, '000000');
|
||||
INSERT INTO `ruoyi-ai-v3`.`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 `ruoyi-ai-v3`.`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 `ruoyi-ai-v3`.`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 `ruoyi-ai-v3`.`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', 'Bearer sk-f4550b0e138c488cbfcafe3d61f800a5', 103, 1, '2026-02-14 14:57:11', 1, '2026-02-14 14:57:11', '通义万相文生图', 0);
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.ruoyi.common.chat.Service;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import org.ruoyi.common.chat.domain.entity.image.ImageContext;
|
||||
|
||||
/**
|
||||
* 公共文生图接口
|
||||
*/
|
||||
public interface IImageGenerationService {
|
||||
|
||||
/**
|
||||
* 根据文字生成图片
|
||||
*/
|
||||
String generateImage(@Valid ImageContext imageContext);
|
||||
|
||||
/**
|
||||
* 获取服务提供商名称
|
||||
*/
|
||||
String getProviderName();
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.ruoyi.common.chat.domain.entity.image;
|
||||
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||
|
||||
/**
|
||||
* 文生图对话上下文对象
|
||||
*
|
||||
* @author zengxb
|
||||
* @date 2026-02-14
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Builder
|
||||
public class ImageContext {
|
||||
|
||||
/**
|
||||
* 模型管理视图对象
|
||||
*/
|
||||
@NotNull(message = "模型管理视图对象不能为空")
|
||||
private ChatModelVo chatModelVo;
|
||||
|
||||
/**
|
||||
* 提示词
|
||||
*/
|
||||
@NotNull(message = "提示词不能为空")
|
||||
private String prompt;
|
||||
|
||||
/**
|
||||
* 图片尺寸大小
|
||||
*/
|
||||
private String size;
|
||||
|
||||
/**
|
||||
* 随机数种子
|
||||
*/
|
||||
@Min(value = 0, message = "随机数种子不能小于0")
|
||||
@Max(value = 2147483647, message = "随机数种子不能大于2147483647")
|
||||
private Integer seed;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.ruoyi.common.chat.factory;
|
||||
|
||||
import org.ruoyi.common.chat.Service.IImageGenerationService;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 文生图服务工厂类
|
||||
*
|
||||
* @author zengxb
|
||||
* @date 2026-02-14
|
||||
*/
|
||||
@Component
|
||||
public class ImageServiceFactory implements ApplicationContextAware {
|
||||
|
||||
private final Map<String, IImageGenerationService> imageSerivceMap = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
// 初始化时收集所有IImageGenerationService的实现
|
||||
Map<String, IImageGenerationService> serviceMap = applicationContext.getBeansOfType(IImageGenerationService.class);
|
||||
for (IImageGenerationService service : serviceMap.values()) {
|
||||
if (service != null ) {
|
||||
imageSerivceMap.put(service.getProviderName(), service);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取原始服务(不包装代理)
|
||||
*/
|
||||
public IImageGenerationService getOriginalService(String category) {
|
||||
IImageGenerationService service = imageSerivceMap.get(category);
|
||||
if (service == null) {
|
||||
throw new IllegalArgumentException("不支持的模型类别: " + category);
|
||||
}
|
||||
return service;
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,10 @@ import org.ruoyi.workflow.entity.WorkflowComponent;
|
||||
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.HumanFeedbackNode;
|
||||
import org.ruoyi.workflow.workflow.node.humanFeedBack.HumanFeedbackNode;
|
||||
import org.ruoyi.workflow.workflow.node.answer.LLMAnswerNode;
|
||||
import org.ruoyi.workflow.workflow.node.httpRequest.HttpRequestNode;
|
||||
import org.ruoyi.workflow.workflow.node.image.ImageNode;
|
||||
import org.ruoyi.workflow.workflow.node.keywordExtractor.KeywordExtractorNode;
|
||||
import org.ruoyi.workflow.workflow.node.knowledgeRetrieval.KnowledgeRetrievalNode;
|
||||
import org.ruoyi.workflow.workflow.node.mailSend.MailSendNode;
|
||||
@@ -21,6 +22,7 @@ public class WfNodeFactory {
|
||||
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 TONGYI_WANX -> wfNode = new ImageNode(wfComponent, nodeDefinition, wfState, nodeState);
|
||||
case KNOWLEDGE_RETRIEVER -> wfNode = new KnowledgeRetrievalNode(wfComponent, nodeDefinition, wfState, nodeState);
|
||||
case END -> wfNode = new EndNode(wfComponent, nodeDefinition, wfState, nodeState);
|
||||
case MAIL_SEND -> wfNode = new MailSendNode(wfComponent, nodeDefinition, wfState, nodeState);
|
||||
|
||||
@@ -13,10 +13,13 @@ import org.bsc.langgraph4j.langchain4j.generators.StreamingChatGenerator;
|
||||
import org.bsc.langgraph4j.state.AgentState;
|
||||
import org.ruoyi.common.chat.Service.IChatModelService;
|
||||
import org.ruoyi.common.chat.Service.IChatService;
|
||||
import org.ruoyi.common.chat.Service.IImageGenerationService;
|
||||
import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
|
||||
import org.ruoyi.common.chat.domain.entity.chat.ChatContext;
|
||||
import org.ruoyi.common.chat.domain.entity.image.ImageContext;
|
||||
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||
import org.ruoyi.common.chat.factory.ChatServiceFactory;
|
||||
import org.ruoyi.common.chat.factory.ImageServiceFactory;
|
||||
import org.ruoyi.workflow.base.NodeInputConfigTypeHandler;
|
||||
import org.ruoyi.workflow.entity.WorkflowNode;
|
||||
import org.ruoyi.workflow.enums.WfIODataTypeEnum;
|
||||
@@ -38,6 +41,9 @@ public class WorkflowUtil {
|
||||
@Resource
|
||||
private ChatServiceFactory chatServiceFactory;
|
||||
|
||||
@Resource
|
||||
private ImageServiceFactory imageServiceFactory;
|
||||
|
||||
@Resource
|
||||
private IChatModelService chatModelService;
|
||||
|
||||
@@ -213,4 +219,30 @@ public class WorkflowUtil {
|
||||
.filter(item -> nameSet.contains(item.getName()))
|
||||
.map(item -> getMessage("user", item.getContent().getValue().toString())).toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用LLM 根据文字生成图片
|
||||
*/
|
||||
public String buildTextToImage(String modelName, String prompt, String size, Integer seed){
|
||||
log.info("Generate image invoke, modelName: {}", modelName);
|
||||
// 根据模型名称查询模型信息
|
||||
ChatModelVo chatModelVo = chatModelService.selectModelByName(modelName);
|
||||
if (chatModelVo == null) {
|
||||
throw new IllegalArgumentException("模型不存在: " + modelName);
|
||||
}
|
||||
// 根据模型名称找到模型实体
|
||||
String modelVoCategory = chatModelVo.getCategory();
|
||||
// 根据 category 获取对应的 IImageGenerationService(不使用计费代理,工作流场景单独计费)
|
||||
IImageGenerationService imageService = imageServiceFactory.getOriginalService(modelVoCategory);
|
||||
// 构建文生图上下文对象
|
||||
ImageContext imageContext = ImageContext.builder()
|
||||
.chatModelVo(chatModelVo)
|
||||
.prompt(prompt)
|
||||
.size(size)
|
||||
.seed(seed)
|
||||
.build();
|
||||
// 调用LLM 生成图片
|
||||
return imageService.generateImage(imageContext);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,12 +12,6 @@ public class LLMAnswerNodeConfig {
|
||||
@NotBlank
|
||||
private String prompt;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
// @NotBlank
|
||||
private String category;
|
||||
|
||||
@NotNull
|
||||
@JsonProperty("model_name")
|
||||
private String modelName;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.ruoyi.workflow.workflow.node;
|
||||
package org.ruoyi.workflow.workflow.node.humanFeedBack;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@@ -8,6 +8,7 @@ import org.ruoyi.workflow.workflow.NodeProcessResult;
|
||||
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 static org.ruoyi.workflow.cosntant.AdiConstant.WorkflowConstant.*;
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.ruoyi.workflow.workflow.node.image;
|
||||
|
||||
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.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 static org.ruoyi.workflow.cosntant.AdiConstant.WorkflowConstant.NODE_PROCESS_STATUS_SUCCESS;
|
||||
|
||||
/**
|
||||
* 【节点】文生图 <br/>
|
||||
* 节点内容固定格式:ImageNodeConfig
|
||||
*/
|
||||
@Slf4j
|
||||
public class ImageNode extends AbstractWfNode {
|
||||
|
||||
public ImageNode(WorkflowComponent wfComponent, WorkflowNode nodeDef, WfState wfState, WfNodeState nodeState) {
|
||||
super(wfComponent, nodeDef, wfState, nodeState);
|
||||
}
|
||||
|
||||
/**
|
||||
* nodeConfig格式:
|
||||
* {"prompt": "{input}","model_name":"wan2.5-t2i-preview","size":"1024*1024"}
|
||||
*
|
||||
* @return 图片地址URL
|
||||
*/
|
||||
@Override
|
||||
public NodeProcessResult onProcess() {
|
||||
ImageNodeConfig nodeConfigObj = checkAndGetConfig(ImageNodeConfig.class);
|
||||
String inputText = getFirstInputText();
|
||||
log.info("Image node config:{}", nodeConfigObj);
|
||||
String prompt = inputText;
|
||||
if (StringUtils.isNotBlank(nodeConfigObj.getPrompt())) {
|
||||
prompt = WorkflowUtil.renderTemplate(nodeConfigObj.getPrompt(), state.getInputs());
|
||||
}
|
||||
log.info("Image prompt:{}", prompt);
|
||||
// 获取工作流实例
|
||||
WorkflowUtil workflowUtil = SpringUtil.getBean(WorkflowUtil.class);
|
||||
// 获取模型名称
|
||||
String modelName = nodeConfigObj.getModelName();
|
||||
// 获取图片大小
|
||||
String size = nodeConfigObj.getSize();
|
||||
// 获取随机数种子
|
||||
Integer seed = nodeConfigObj.getSeed();
|
||||
// 调用LLM生成图片(后续可以将图片保存到OSS中)
|
||||
String imageUrl = workflowUtil.buildTextToImage(modelName, prompt, size, seed);
|
||||
// 创建节点参数对象
|
||||
NodeIOData nodeIOData = NodeIOData.createByText("output", "image", imageUrl);
|
||||
// 添加到输出列表以便给后续节点使用
|
||||
state.getOutputs().add(nodeIOData);
|
||||
// 设置为成功状态
|
||||
state.setProcessStatus(NODE_PROCESS_STATUS_SUCCESS);
|
||||
return new NodeProcessResult();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.ruoyi.workflow.workflow.node.image;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@EqualsAndHashCode
|
||||
@Data
|
||||
public class ImageNodeConfig {
|
||||
|
||||
/**
|
||||
* 提示词
|
||||
*/
|
||||
private String prompt;
|
||||
|
||||
/**
|
||||
* 模型名称
|
||||
*/
|
||||
@NotNull
|
||||
@JsonProperty("model_name")
|
||||
private String modelName;
|
||||
|
||||
/**
|
||||
* 图片尺寸大小
|
||||
*/
|
||||
private String size;
|
||||
|
||||
/**
|
||||
* 随机数种子
|
||||
*/
|
||||
@Min(value = 0, message = "随机数种子不能小于0")
|
||||
@Max(value = 2147483647, message = "随机数种子不能大于2147483647")
|
||||
private Integer seed;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.ruoyi.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 文生图模型分类
|
||||
*
|
||||
* @author Zengxb
|
||||
* @date 2026-02-14
|
||||
*/
|
||||
@Getter
|
||||
public enum ImageModeType {
|
||||
|
||||
TONGYI_WANX("Tongyiwanx", "万相");
|
||||
|
||||
private final String code;
|
||||
private final String description;
|
||||
|
||||
ImageModeType(String code, String description) {
|
||||
this.code = code;
|
||||
this.description = description;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.ruoyi.service.chat.impl.provider;
|
||||
|
||||
import dev.langchain4j.agent.tool.ToolSpecification;
|
||||
import dev.langchain4j.agentic.AgenticServices;
|
||||
import dev.langchain4j.agentic.supervisor.SupervisorAgent;
|
||||
import dev.langchain4j.agentic.supervisor.SupervisorResponseStrategy;
|
||||
@@ -30,6 +29,8 @@ import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* qianWenAI服务调用
|
||||
@@ -44,14 +45,20 @@ public class QianWenChatServiceImpl extends AbstractStreamingChatService {
|
||||
@Autowired
|
||||
private McpSseConfig mcpSseConfig;
|
||||
|
||||
/**
|
||||
* 千问开发者默认地址
|
||||
*/
|
||||
private static final String QWEN_API_HOST = "https://dashscope.aliyuncs.com/api/v1";
|
||||
|
||||
// 添加文档解析的前缀字段
|
||||
private static final String UPLOAD_FILE_API_PREFIX = "fileid";
|
||||
|
||||
// 缓存不同API Key和模型的MCP智能体实例
|
||||
private final ConcurrentHashMap<String, SupervisorAgent> supervisorCache = new ConcurrentHashMap<>();
|
||||
|
||||
// 缓存不同API Key和模型的MCP客户端实例
|
||||
private final ConcurrentHashMap<String, McpClient> mcpClientCache = new ConcurrentHashMap<>();
|
||||
|
||||
// 缓存不同API Key和模型的MCP工具提供者实例
|
||||
private final ConcurrentHashMap<String, ToolProvider> toolProviderCache = new ConcurrentHashMap<>();
|
||||
// 用于线程安全的锁
|
||||
private final ReentrantLock cacheLock = new ReentrantLock();
|
||||
|
||||
@Override
|
||||
protected StreamingChatModel buildStreamingChatModel(ChatModelVo chatModelVo,ChatRequest chatRequest) {
|
||||
return QwenStreamingChatModel.builder()
|
||||
@@ -102,17 +109,16 @@ public class QianWenChatServiceImpl extends AbstractStreamingChatService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用MCP服务(智能体)
|
||||
* @param userMessage 用户信息
|
||||
* @param chatModelVo 模型信息
|
||||
* @return 返回LLM信息
|
||||
* 获取缓存键
|
||||
*/
|
||||
protected String doAgent(String userMessage,ChatModelVo chatModelVo) {
|
||||
// 判断是否开启MCP服务
|
||||
if (!mcpSseConfig.isEnabled()) {
|
||||
return "";
|
||||
}
|
||||
private String getCacheKey(ChatModelVo chatModelVo) {
|
||||
return chatModelVo.getApiKey() + ":" + chatModelVo.getModelName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化MCP客户端连接
|
||||
*/
|
||||
private McpClient initializeMcpClient() {
|
||||
// 步骤1:根据SSE对外暴露端点连接
|
||||
McpTransport httpMcpTransport = new StreamableHttpMcpTransport.Builder().
|
||||
url(mcpSseConfig.getUrl()).
|
||||
@@ -120,42 +126,74 @@ public class QianWenChatServiceImpl extends AbstractStreamingChatService {
|
||||
build();
|
||||
|
||||
// 步骤2:开启客户端连接
|
||||
McpClient mcpClient = new DefaultMcpClient.Builder()
|
||||
return new DefaultMcpClient.Builder()
|
||||
.transport(httpMcpTransport)
|
||||
.build();
|
||||
}
|
||||
|
||||
// 获取所有mcp工具
|
||||
List<ToolSpecification> toolSpecifications = mcpClient.listTools();
|
||||
System.out.println(toolSpecifications);
|
||||
/**
|
||||
* 调用MCP服务(智能体)
|
||||
* @param userMessage 用户信息
|
||||
* @param chatModelVo 模型信息
|
||||
* @return 返回LLM信息
|
||||
*/
|
||||
protected String doAgent(String userMessage, ChatModelVo chatModelVo) {
|
||||
// 判断是否开启MCP服务
|
||||
if (!mcpSseConfig.isEnabled()) {
|
||||
return "";
|
||||
}
|
||||
// 生成缓存键
|
||||
String cacheKey = getCacheKey(chatModelVo);
|
||||
// 尝试从缓存获取监督智能体
|
||||
SupervisorAgent cachedSupervisor = supervisorCache.get(cacheKey);
|
||||
if (cachedSupervisor != null) {
|
||||
// 如果已存在缓存的监督智能体,直接使用
|
||||
return cachedSupervisor.invoke(userMessage);
|
||||
}
|
||||
cacheLock.lock();
|
||||
try {
|
||||
// 双重检查,防止并发情况下的重复初始化
|
||||
cachedSupervisor = supervisorCache.get(cacheKey);
|
||||
if (cachedSupervisor != null) {
|
||||
return cachedSupervisor.invoke(userMessage);
|
||||
}
|
||||
|
||||
// 步骤3:将mcp对象包装
|
||||
ToolProvider toolProvider = McpToolProvider.builder()
|
||||
.mcpClients(List.of(mcpClient))
|
||||
.build();
|
||||
// 获取或初始化MCP客户端
|
||||
McpClient mcpClient = mcpClientCache.computeIfAbsent(cacheKey, k -> initializeMcpClient());
|
||||
|
||||
// 步骤4:加载LLM模型对话
|
||||
QwenChatModel qwenChatModel = QwenChatModel.builder()
|
||||
.baseUrl(QWEN_API_HOST)
|
||||
.apiKey(chatModelVo.getApiKey())
|
||||
.modelName(chatModelVo.getModelName())
|
||||
.build();
|
||||
// 步骤3:将mcp对象包装
|
||||
ToolProvider toolProvider = toolProviderCache.computeIfAbsent(cacheKey, k -> McpToolProvider.builder()
|
||||
.mcpClients(List.of(mcpClient))
|
||||
.build());
|
||||
|
||||
// 步骤5:将MCP对象由智能体Agent管控
|
||||
McpAgent mcpAgent = AgenticServices.agentBuilder(McpAgent.class)
|
||||
.chatModel(qwenChatModel)
|
||||
.toolProvider(toolProvider)
|
||||
.build();
|
||||
// 步骤4:加载LLM模型对话
|
||||
QwenChatModel qwenChatModel = QwenChatModel.builder()
|
||||
.apiKey(chatModelVo.getApiKey())
|
||||
.modelName(chatModelVo.getModelName())
|
||||
.build();
|
||||
|
||||
// 步骤6:将所有MCP对象由超级智能体管控
|
||||
SupervisorAgent supervisor = AgenticServices
|
||||
.supervisorBuilder()
|
||||
.chatModel(qwenChatModel)
|
||||
.subAgents(mcpAgent)
|
||||
.responseStrategy(SupervisorResponseStrategy.LAST)
|
||||
.build();
|
||||
// 步骤5:将MCP对象由智能体Agent管控
|
||||
McpAgent mcpAgent = AgenticServices.agentBuilder(McpAgent.class)
|
||||
.chatModel(qwenChatModel)
|
||||
.toolProvider(toolProvider)
|
||||
.build();
|
||||
|
||||
// 步骤7:调用大模型LLM
|
||||
return supervisor.invoke(userMessage);
|
||||
// 步骤6:将所有MCP对象由超级智能体管控
|
||||
SupervisorAgent supervisor = AgenticServices
|
||||
.supervisorBuilder()
|
||||
.chatModel(qwenChatModel)
|
||||
.subAgents(mcpAgent)
|
||||
.responseStrategy(SupervisorResponseStrategy.LAST)
|
||||
.build();
|
||||
|
||||
// 缓存监督智能体
|
||||
supervisorCache.put(cacheKey, supervisor);
|
||||
|
||||
// 步骤7:调用大模型LLM
|
||||
return supervisor.invoke(userMessage);
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.ruoyi.service.image;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.ruoyi.common.chat.Service.IImageGenerationService;
|
||||
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||
import org.ruoyi.common.chat.domain.entity.image.ImageContext;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
@Slf4j
|
||||
@Validated
|
||||
public abstract class AbstractImageGenerationService implements IImageGenerationService {
|
||||
|
||||
/**
|
||||
* 根据文字生成图片
|
||||
* @param imageContext 文生图上下文对象
|
||||
* @return 生成的图片URL
|
||||
*/
|
||||
@Override
|
||||
public String generateImage(ImageContext imageContext){
|
||||
// 获取模型管理视图对象
|
||||
ChatModelVo chatModelVo = imageContext.getChatModelVo();
|
||||
// 获取提示词
|
||||
String prompt = imageContext.getPrompt();
|
||||
// 获取图片尺寸大小
|
||||
String size = imageContext.getSize();
|
||||
// 获取随机数种子
|
||||
Integer seed = imageContext.getSeed();
|
||||
return doGenerateImage(chatModelVo, prompt, size, seed);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行生成图片(钩子方法 - 子类必须实现)
|
||||
*
|
||||
* @param prompt 提示词
|
||||
*/
|
||||
protected abstract String doGenerateImage(ChatModelVo chatModelVo, String prompt, String size, Integer seed);
|
||||
|
||||
/**
|
||||
* 构建具体厂商的 ImageModel(原生SDK 非langchain4j-dashscope版)
|
||||
* 子类必须实现此方法,返回对应厂商的模型实例
|
||||
*/
|
||||
protected abstract Object buildImageModel(ChatModelVo chatModelVo);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package org.ruoyi.service.image.provider;
|
||||
|
||||
import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesis;
|
||||
import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesisParam;
|
||||
import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesisResult;
|
||||
import com.alibaba.dashscope.exception.ApiException;
|
||||
import com.alibaba.dashscope.exception.NoApiKeyException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
|
||||
import org.ruoyi.common.core.utils.StringUtils;
|
||||
import org.ruoyi.enums.ImageModeType;
|
||||
import org.ruoyi.service.image.AbstractImageGenerationService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 万相文生图AI调用
|
||||
*
|
||||
* @author Zengxb
|
||||
* @date 2026/02/14
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class TongYiWanxImageServiceImpl extends AbstractImageGenerationService {
|
||||
|
||||
/**
|
||||
* 默认图片数量(1张)
|
||||
*/
|
||||
private final static int IMAGE_DEFAULT_SIZE = 1;
|
||||
|
||||
/**
|
||||
* 默认图片分辨率(1280*1280)
|
||||
*/
|
||||
private final static String IMAGE_DEFAULT_RESOLUTION = "1280*1280";
|
||||
|
||||
@Override
|
||||
protected String doGenerateImage(ChatModelVo chatModelVo, String prompt, String size, Integer seed) {
|
||||
// 构建万相模型对象
|
||||
var param = (ImageSynthesisParam) buildImageModel(chatModelVo);
|
||||
// 设置图片大小和提示词以及随机数种子
|
||||
param.setSize(StringUtils.isEmpty(size) ? IMAGE_DEFAULT_RESOLUTION : size);
|
||||
param.setPrompt(prompt);
|
||||
param.setSeed(seed);
|
||||
// 同步调用 AI 大模型,生成图片
|
||||
var imageSynthesis = new ImageSynthesis();
|
||||
ImageSynthesisResult result;
|
||||
try {
|
||||
log.info("同步调用通义万相文生图接口中....");
|
||||
result = imageSynthesis.call(param);
|
||||
} catch (ApiException | NoApiKeyException e) {
|
||||
log.error("同步调用通义万相文生图接口失败", e);
|
||||
return "";
|
||||
}
|
||||
// 直接提取图片URL
|
||||
var output = result.getOutput();
|
||||
var results = output.getResults();
|
||||
return results.isEmpty() ? "" : results.get(0).get("url");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object buildImageModel(ChatModelVo chatModelVo) {
|
||||
return ImageSynthesisParam.builder()
|
||||
.prompt("")
|
||||
.apiKey(chatModelVo.getApiKey())
|
||||
.model(chatModelVo.getModelName())
|
||||
.n(IMAGE_DEFAULT_SIZE)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProviderName() {
|
||||
return ImageModeType.TONGYI_WANX.getCode();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user