Merge branch 'work_flow_v3.0' into v3.0.0

# Conflicts:
#	ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatServiceFacade.java
This commit is contained in:
zhang
2026-02-26 14:48:19 +08:00
85 changed files with 1012 additions and 299 deletions

View File

@@ -6,7 +6,9 @@ import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.ruoyi.service.chat.IChatMessageService;
import org.ruoyi.common.chat.domain.bo.chat.ChatMessageBo;
import org.ruoyi.common.chat.domain.vo.chat.ChatMessageVo;
import org.ruoyi.common.chat.service.chatMessage.IChatMessageService;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import org.ruoyi.common.idempotent.annotation.RepeatSubmit;
@@ -18,8 +20,6 @@ import org.ruoyi.common.core.validate.AddGroup;
import org.ruoyi.common.core.validate.EditGroup;
import org.ruoyi.common.log.enums.BusinessType;
import org.ruoyi.common.excel.utils.ExcelUtil;
import org.ruoyi.domain.vo.chat.ChatMessageVo;
import org.ruoyi.domain.bo.chat.ChatMessageBo;
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
/**

View File

@@ -6,7 +6,7 @@ import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.ruoyi.common.chat.Service.IChatModelService;
import org.ruoyi.common.chat.service.chat.IChatModelService;
import org.ruoyi.common.chat.domain.bo.chat.ChatModelBo;
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
import org.ruoyi.enums.ModelType;

View File

@@ -1,76 +0,0 @@
package org.ruoyi.domain.bo.chat;
import org.ruoyi.common.core.validate.AddGroup;
import org.ruoyi.common.core.validate.EditGroup;
import org.ruoyi.domain.entity.chat.ChatMessage;
import org.ruoyi.common.mybatis.core.domain.BaseEntity;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
/**
* 聊天消息业务对象 chat_message
*
* @author ageerle
* @date 2025-12-14
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = ChatMessage.class, reverseConvertGenerate = false)
public class ChatMessageBo extends BaseEntity {
/**
* 主键
*/
@NotNull(message = "主键不能为空", groups = { EditGroup.class })
private Long id;
/**
* 会话id
*/
private Long sessionId;
/**
* 用户id
*/
@NotNull(message = "用户id不能为空", groups = { AddGroup.class, EditGroup.class })
private Long userId;
/**
* 消息内容
*/
private String content;
/**
* 对话角色
*/
private String role;
/**
* 扣除金额
*/
private Long deductCost;
/**
* 累计 Tokens
*/
private Long totalTokens;
/**
* 模型名称
*/
private String modelName;
/**
* 计费类型1-token计费2-次数计费)
*/
private String billingType;
/**
* 备注
*/
private String remark;
}

View File

@@ -1,76 +0,0 @@
package org.ruoyi.domain.entity.chat;
import org.ruoyi.common.tenant.core.TenantEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
/**
* 聊天消息对象 chat_message
*
* @author ageerle
* @date 2025-12-14
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("chat_message")
public class ChatMessage extends TenantEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id")
private Long id;
/**
* 会话id
*/
private Long sessionId;
/**
* 用户id
*/
private Long userId;
/**
* 消息内容
*/
private String content;
/**
* 对话角色
*/
private String role;
/**
* 扣除金额
*/
private Long deductCost;
/**
* 累计 Tokens
*/
private Long totalTokens;
/**
* 模型名称
*/
private String modelName;
/**
* 计费类型1-token计费2-次数计费)
*/
private String billingType;
/**
* 备注
*/
private String remark;
}

View File

@@ -1,92 +0,0 @@
package org.ruoyi.domain.vo.chat;
import org.ruoyi.domain.entity.chat.ChatMessage;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import org.ruoyi.common.excel.annotation.ExcelDictFormat;
import org.ruoyi.common.excel.convert.ExcelDictConvert;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 聊天消息视图对象 chat_message
*
* @author ageerle
* @date 2025-12-14
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = ChatMessage.class)
public class ChatMessageVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@ExcelProperty(value = "主键")
private Long id;
/**
* 会话id
*/
@ExcelProperty(value = "会话id")
private Long sessionId;
/**
* 用户id
*/
@ExcelProperty(value = "用户id")
private Long userId;
/**
* 消息内容
*/
@ExcelProperty(value = "消息内容")
private String content;
/**
* 对话角色
*/
@ExcelProperty(value = "对话角色")
private String role;
/**
* 扣除金额
*/
@ExcelProperty(value = "扣除金额")
private Long deductCost;
/**
* 累计 Tokens
*/
@ExcelProperty(value = "累计 Tokens")
private Long totalTokens;
/**
* 模型名称
*/
@ExcelProperty(value = "模型名称")
private String modelName;
/**
* 计费类型1-token计费2-次数计费)
*/
@ExcelProperty(value = "计费类型", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "1=-token计费2-次数计费")
private String billingType;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
}

View File

@@ -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;
}
}

View File

@@ -1,25 +0,0 @@
package org.ruoyi.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 角色枚举
*
* @author ageerle@163.com
* @date 2025-12-17
*/
@Getter
@AllArgsConstructor
public enum RoleType {
SYSTEM("system"),
USER("user"),
ASSISTANT("assistant"),
FUNCTION("function"),
TOOL("tool"),
;
private final String name;
}

View File

@@ -2,7 +2,7 @@ package org.ruoyi.factory;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.chat.Service.IChatModelService;
import org.ruoyi.common.chat.service.chat.IChatModelService;
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
import org.ruoyi.service.embed.BaseEmbedModelService;
import org.ruoyi.service.embed.MultiModalEmbedModelService;

View File

@@ -1,7 +1,7 @@
package org.ruoyi.mapper.chat;
import org.ruoyi.domain.entity.chat.ChatMessage;
import org.ruoyi.domain.vo.chat.ChatMessageVo;
import org.ruoyi.common.chat.domain.vo.chat.ChatMessageVo;
import org.ruoyi.common.chat.entity.chat.ChatMessage;
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
/**

View File

@@ -1,6 +1,6 @@
package org.ruoyi.mapper.chat;
import org.ruoyi.common.chat.domain.entity.chat.ChatModel;
import org.ruoyi.common.chat.entity.chat.ChatModel;
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;

View File

@@ -1,87 +0,0 @@
package org.ruoyi.service.chat;
import org.ruoyi.common.chat.domain.dto.ChatMessageDTO;
import org.ruoyi.common.mybatis.core.page.PageQuery;
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
import org.ruoyi.domain.bo.chat.ChatMessageBo;
import org.ruoyi.domain.vo.chat.ChatMessageVo;
import java.util.Collection;
import java.util.List;
/**
* 聊天消息Service接口
*
* @author ageerle
* @date 2025-12-14
*/
public interface IChatMessageService {
/**
* 查询聊天消息
*
* @param id 主键
* @return 聊天消息
*/
ChatMessageVo queryById(Long id);
/**
* 分页查询聊天消息列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 聊天消息分页列表
*/
TableDataInfo<ChatMessageVo> queryPageList(ChatMessageBo bo, PageQuery pageQuery);
/**
* 查询符合条件的聊天消息列表
*
* @param bo 查询条件
* @return 聊天消息列表
*/
List<ChatMessageVo> queryList(ChatMessageBo bo);
/**
* 新增聊天消息
*
* @param bo 聊天消息
* @return 是否新增成功
*/
Boolean insertByBo(ChatMessageBo bo);
/**
* 修改聊天消息
*
* @param bo 聊天消息
* @return 是否修改成功
*/
Boolean updateByBo(ChatMessageBo bo);
/**
* 校验并批量删除聊天消息信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 根据会话ID获取所有消息
* 用于长期记忆功能
*
* @param sessionId 会话ID
* @return 消息DTO列表
*/
List<ChatMessageDTO> getMessagesBySessionId(Long sessionId);
/**
* 根据会话ID删除所有消息
* 用于清理会话历史
*
* @param sessionId 会话ID
* @return 是否删除成功
*/
Boolean deleteBySessionId(Long sessionId);
}

View File

@@ -25,17 +25,20 @@ import org.ruoyi.agent.WebSearchAgent;
import org.ruoyi.agent.tool.ExecuteSqlQueryTool;
import org.ruoyi.agent.tool.QueryAllTablesTool;
import org.ruoyi.agent.tool.QueryTableSchemaTool;
import org.ruoyi.common.chat.Service.IChatService;
import org.ruoyi.common.chat.base.ThreadContext;
import org.ruoyi.common.chat.domain.dto.request.ReSumeRunner;
import org.ruoyi.common.chat.domain.dto.request.WorkFlowRunner;
import org.ruoyi.common.chat.enums.RoleType;
import org.ruoyi.common.chat.service.chat.IChatService;
import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
import org.ruoyi.common.chat.domain.entity.chat.ChatContext;
import org.ruoyi.common.chat.entity.chat.ChatContext;
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
import org.ruoyi.common.chat.service.chatMessage.AbstractChatMessageService;
import org.ruoyi.common.chat.service.workFlow.IWorkFlowStarterService;
import org.ruoyi.common.core.utils.ObjectUtils;
import org.ruoyi.common.core.utils.SpringUtils;
import org.ruoyi.common.core.utils.StringUtils;
import org.ruoyi.common.sse.utils.SseMessageUtils;
import org.ruoyi.domain.bo.chat.ChatMessageBo;
import org.ruoyi.enums.RoleType;
import org.ruoyi.service.chat.IChatMessageService;
import org.ruoyi.service.chat.impl.memory.PersistentChatMemoryStore;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.annotation.Validated;
@@ -59,7 +62,7 @@ import java.util.concurrent.ConcurrentHashMap;
*/
@Slf4j
@Validated
public abstract class AbstractStreamingChatService implements IChatService {
public abstract class AbstractStreamingChatService extends AbstractChatMessageService implements IChatService {
/**
* 默认保留的消息窗口大小(用于长期记忆)
@@ -77,6 +80,11 @@ public abstract class AbstractStreamingChatService implements IChatService {
*/
private static final Map<Object, MessageWindowChatMemory> memoryCache = new ConcurrentHashMap<>();
/**
* 获取工作流启用Bean对象
*/
private static final IWorkFlowStarterService starterService = SpringUtils.getBean(IWorkFlowStarterService.class);
/**
* 定义聊天流程骨架
*/
@@ -109,9 +117,28 @@ public abstract class AbstractStreamingChatService implements IChatService {
// 保存用户消息
saveChatMessage(chatRequest, userId, content, RoleType.USER.getName(), chatModelVo);
// 判断用户是否重新输入
boolean isResume = chatRequest.getIsResume() != null && chatRequest.getIsResume();
if (isResume){
ReSumeRunner reSumeRunner = chatRequest.getReSumeRunner();
if (ObjectUtils.isNotEmpty(reSumeRunner)){
starterService.resumeFlow(reSumeRunner.getRuntimeUuid(), reSumeRunner.getFeedbackContent(), emitter);
return emitter;
}
}
// 判断用户是否开启工作流
boolean enableWorkFlow = chatRequest.getEnableWorkFlow() != null && chatRequest.getEnableWorkFlow();
if (enableWorkFlow) {
WorkFlowRunner runner = chatRequest.getWorkFlowRunner();
if (ObjectUtils.isNotEmpty(runner)){
return starterService.streaming(ThreadContext.getCurrentUser(), runner.getUuid(), runner.getInputs(), chatRequest.getSessionId());
}
}
// 使用长期记忆增强的消息列表
List<ChatMessage> messagesWithMemory = buildMessagesWithMemory(chatRequest);
if (chatRequest.getEnableThinking()) {
String msg = doAgent(content, chatModelVo);
SseMessageUtils.sendMessage(userId, msg);
@@ -120,13 +147,10 @@ public abstract class AbstractStreamingChatService implements IChatService {
saveChatMessage(chatRequest, userId, msg, RoleType.ASSISTANT.getName(), chatModelVo);
} else {
// 创建包含内存管理的响应处理器
if (ObjectUtils.isEmpty(handler)) {
handler = createResponseHandler(chatRequest, userId, tokenValue, chatModelVo);
}
handler = ObjectUtils.isEmpty(handler) ? createResponseHandler(chatRequest, userId, tokenValue, chatModelVo) : handler;
// 调用具体实现的聊天方法
doChat(chatModelVo, chatRequest, messagesWithMemory, handler);
}
} catch (Exception e) {
SseMessageUtils.sendMessage(userId, "对话出错:" + e.getMessage());
SseMessageUtils.completeConnection(userId, tokenValue);
@@ -145,6 +169,12 @@ public abstract class AbstractStreamingChatService implements IChatService {
*/
protected List<ChatMessage> buildMessagesWithMemory(ChatRequest chatRequest) {
List<ChatMessage> messages = new ArrayList<>();
// 工作流对话消息
List<ChatMessage> chatMessages = chatRequest.getChatMessages();
if (!CollectionUtils.isEmpty(chatMessages)){
messages.addAll(chatMessages);
}
// 开启长期记忆
if (enablePersistentMemory && chatRequest.getSessionId() != null) {
MessageWindowChatMemory memory = createChatMemory(chatRequest.getSessionId());
if (memory != null) {
@@ -156,11 +186,6 @@ public abstract class AbstractStreamingChatService implements IChatService {
}
return messages;
}
// 工作流方式
List<ChatMessage> chatMessages = chatRequest.getChatMessages();
if (!CollectionUtils.isEmpty(chatMessages)){
messages.addAll(chatMessages);
}
return messages;
}
@@ -277,40 +302,6 @@ public abstract class AbstractStreamingChatService implements IChatService {
};
}
/**
* 保存聊天消息到数据库
*
* @param chatRequest 聊天请求
* @param userId 用户ID
* @param content 消息内容
* @param role 消息角色
* @param chatModelVo 模型配置
*/
private void saveChatMessage(ChatRequest chatRequest, Long userId, String content, String role, ChatModelVo chatModelVo) {
try {
// 验证必要的上下文信息
if (chatRequest == null || userId == null) {
log.warn("缺少必要的聊天上下文信息,无法保存消息");
return;
}
// 创建ChatMessageBo对象
ChatMessageBo messageBO = new ChatMessageBo();
messageBO.setUserId(userId);
messageBO.setSessionId(chatRequest.getSessionId());
messageBO.setContent(content);
messageBO.setRole(role);
messageBO.setModelName(chatRequest.getModel());
messageBO.setBillingType(chatModelVo.getModelType());
messageBO.setRemark(null);
IChatMessageService chatMessageService = SpringUtils.getBean(IChatMessageService.class);
chatMessageService.insertByBo(messageBO);
} catch (Exception e) {
log.error("保存{}聊天消息时出错: {}", getProviderName(), e.getMessage(), e);
}
}
/**
* 构建具体厂商的 StreamingChatModel
* 子类必须实现此方法,返回对应厂商的模型实例

View File

@@ -1,6 +1,10 @@
package org.ruoyi.service.chat.impl;
import org.ruoyi.common.chat.domain.bo.chat.ChatMessageBo;
import org.ruoyi.common.chat.domain.dto.ChatMessageDTO;
import org.ruoyi.common.chat.domain.vo.chat.ChatMessageVo;
import org.ruoyi.common.chat.entity.chat.ChatMessage;
import org.ruoyi.common.chat.service.chatMessage.IChatMessageService;
import org.ruoyi.common.core.utils.MapstructUtils;
import org.ruoyi.common.core.utils.StringUtils;
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
@@ -10,11 +14,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.service.chat.IChatMessageService;
import org.springframework.stereotype.Service;
import org.ruoyi.domain.bo.chat.ChatMessageBo;
import org.ruoyi.domain.vo.chat.ChatMessageVo;
import org.ruoyi.domain.entity.chat.ChatMessage;
import org.ruoyi.mapper.chat.ChatMessageMapper;
import java.util.List;

View File

@@ -1,8 +1,8 @@
package org.ruoyi.service.chat.impl;
import org.ruoyi.common.chat.Service.IChatModelService;
import org.ruoyi.common.chat.service.chat.IChatModelService;
import org.ruoyi.common.chat.domain.bo.chat.ChatModelBo;
import org.ruoyi.common.chat.domain.entity.chat.ChatModel;
import org.ruoyi.common.chat.entity.chat.ChatModel;
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
import org.ruoyi.common.core.utils.MapstructUtils;
import org.ruoyi.common.core.utils.StringUtils;

View File

@@ -1,27 +1,27 @@
package org.ruoyi.service.chat.impl;
import java.util.List;
import org.ruoyi.common.chat.Service.IChatModelService;
import org.ruoyi.common.chat.Service.IChatService;
import cn.dev33.satoken.stp.StpUtil;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.chat.service.chat.IChatModelService;
import org.ruoyi.common.chat.service.chat.IChatService;
import org.ruoyi.common.chat.domain.dto.ChatMessageDTO;
import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
import org.ruoyi.common.chat.domain.entity.chat.ChatContext;
import org.ruoyi.common.chat.entity.chat.ChatContext;
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
import org.ruoyi.common.chat.factory.ChatServiceFactory;
import org.ruoyi.common.satoken.utils.LoginHelper;
import org.ruoyi.common.sse.core.SseEmitterManager;
import org.ruoyi.domain.bo.vector.QueryVectorBo;
import org.ruoyi.domain.vo.knowledge.KnowledgeInfoVo;
;
import org.ruoyi.service.knowledge.IKnowledgeInfoService;
import org.ruoyi.service.vector.VectorStoreService;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import cn.dev33.satoken.stp.StpUtil;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
/**
* 聊天服务业务实现
@@ -52,7 +52,6 @@ public class ChatServiceFacade {
* @return SseEmitter
*/
public SseEmitter sseChat(ChatRequest chatRequest, HttpServletRequest request) {
// 1. 根据模型名称查询完整配置
ChatModelVo chatModelVo = chatModelService.selectModelByName(chatRequest.getModel());
if (chatModelVo == null) {

View File

@@ -108,7 +108,7 @@ public class ChatMemoryUsageExample {
log.info("=== 示例4清理过期消息 ===");
/*
// 假设已有IChatMessageService实例
IChatMessageService chatMessageService = getBean(IChatMessageService.class);
AbstractChatMessageService chatMessageService = getBean(AbstractChatMessageService.class);
// 场景:用户要求"忘记我们之前的对话"
Long sessionId = 789L;

View File

@@ -4,8 +4,8 @@ import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.store.memory.chat.ChatMemoryStore;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.chat.domain.dto.ChatMessageDTO;
import org.ruoyi.common.chat.service.chatMessage.IChatMessageService;
import org.ruoyi.common.core.utils.SpringUtils;
import org.ruoyi.service.chat.IChatMessageService;
import java.util.ArrayList;
import java.util.List;

View File

@@ -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

View File

@@ -3,7 +3,7 @@ package org.ruoyi.service.graph.impl;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.chat.Service.IChatModelService;
import org.ruoyi.common.chat.service.chat.IChatModelService;
import org.ruoyi.common.chat.domain.bo.chat.ChatModelBo;
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
import org.ruoyi.config.GraphExtractPrompt;

View File

@@ -0,0 +1,43 @@
package org.ruoyi.service.image;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.chat.service.image.IImageGenerationService;
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
import org.ruoyi.common.chat.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);
}

View File

@@ -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();
}
}

View File

@@ -2,7 +2,7 @@ package org.ruoyi.service.knowledge.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.RandomUtil;
import org.ruoyi.common.chat.Service.IChatModelService;
import org.ruoyi.common.chat.service.chat.IChatModelService;
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
import org.ruoyi.common.core.domain.dto.OssDTO;
import org.ruoyi.common.core.service.OssService;