diff --git a/main/manager-api/src/main/java/xiaozhi/common/utils/ResultUtils.java b/main/manager-api/src/main/java/xiaozhi/common/utils/ResultUtils.java new file mode 100644 index 00000000..3f9fe27d --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/common/utils/ResultUtils.java @@ -0,0 +1,31 @@ +package xiaozhi.common.utils; + +/** + * 返回响应体工具类 + */ +public class ResultUtils +{ + public static Result success(T data) { + return new Result().ok(data); + } + + public static Result error() { + return new Result().error(); + } + + public static Result error(String msg) { + return new Result().error(msg); + } + + public static Result error(int errorCode, String msg) { + return new Result().error(errorCode, msg); + } + + public static Result error(int errorCode) { + return new Result().error(errorCode); + } + + public static Result empty() { + return new Result(); + } +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java index 8aa912a4..bfd82e41 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java @@ -1,6 +1,5 @@ package xiaozhi.modules.agent.controller; -import java.util.Date; import java.util.List; import java.util.Map; import java.util.UUID; @@ -33,8 +32,8 @@ import xiaozhi.common.page.PageData; import xiaozhi.common.redis.RedisKeys; import xiaozhi.common.redis.RedisUtils; import xiaozhi.common.user.UserDetail; -import xiaozhi.common.utils.ConvertUtils; import xiaozhi.common.utils.Result; +import xiaozhi.common.utils.ResultUtils; import xiaozhi.modules.agent.dto.AgentChatHistoryDTO; import xiaozhi.modules.agent.dto.AgentChatSessionDTO; import xiaozhi.modules.agent.dto.AgentCreateDTO; @@ -45,8 +44,10 @@ import xiaozhi.modules.agent.entity.AgentEntity; import xiaozhi.modules.agent.entity.AgentTemplateEntity; import xiaozhi.modules.agent.service.AgentChatAudioService; import xiaozhi.modules.agent.service.AgentChatHistoryService; +import xiaozhi.modules.agent.service.AgentPluginMappingService; import xiaozhi.modules.agent.service.AgentService; import xiaozhi.modules.agent.service.AgentTemplateService; +import xiaozhi.modules.agent.vo.AgentInfoVO; import xiaozhi.modules.device.entity.DeviceEntity; import xiaozhi.modules.device.service.DeviceService; import xiaozhi.modules.security.user.SecurityUser; @@ -61,6 +62,7 @@ public class AgentController { private final DeviceService deviceService; private final AgentChatHistoryService agentChatHistoryService; private final AgentChatAudioService agentChatAudioService; + private final AgentPluginMappingService agentPluginMappingService; private final RedisUtils redisUtils; @GetMapping("/list") @@ -88,46 +90,17 @@ public class AgentController { @GetMapping("/{id}") @Operation(summary = "获取智能体详情") @RequiresPermissions("sys:role:normal") - public Result getAgentById(@PathVariable("id") String id) { - AgentEntity agent = agentService.getAgentById(id); - return new Result().ok(agent); + public Result getAgentById(@PathVariable("id") String id) { + AgentInfoVO agent = agentService.getAgentById(id); + return ResultUtils.success(agent); } @PostMapping @Operation(summary = "创建智能体") @RequiresPermissions("sys:role:normal") public Result save(@RequestBody @Valid AgentCreateDTO dto) { - AgentEntity entity = ConvertUtils.sourceToTarget(dto, AgentEntity.class); - - // 获取默认模板 - AgentTemplateEntity template = agentTemplateService.getDefaultTemplate(); - if (template != null) { - // 设置模板中的默认值 - entity.setAsrModelId(template.getAsrModelId()); - entity.setVadModelId(template.getVadModelId()); - entity.setLlmModelId(template.getLlmModelId()); - entity.setVllmModelId(template.getVllmModelId()); - entity.setTtsModelId(template.getTtsModelId()); - entity.setTtsVoiceId(template.getTtsVoiceId()); - entity.setMemModelId(template.getMemModelId()); - entity.setIntentModelId(template.getIntentModelId()); - entity.setSystemPrompt(template.getSystemPrompt()); - entity.setSummaryMemory(template.getSummaryMemory()); - entity.setChatHistoryConf(template.getChatHistoryConf()); - entity.setLangCode(template.getLangCode()); - entity.setLanguage(template.getLanguage()); - } - - // 设置用户ID和创建者信息 - UserDetail user = SecurityUser.getUser(); - entity.setUserId(user.getId()); - entity.setCreator(user.getId()); - entity.setCreatedAt(new Date()); - - // ID、智能体编码和排序会在Service层自动生成 - agentService.insert(entity); - - return new Result().ok(entity.getId()); + String agentId = agentService.createAgent(dto); + return new Result().ok(agentId); } @PutMapping("/saveMemory/{macAddress}") @@ -139,88 +112,15 @@ public class AgentController { } AgentUpdateDTO agentUpdateDTO = new AgentUpdateDTO(); agentUpdateDTO.setSummaryMemory(dto.getSummaryMemory()); - return updateAgentById(device.getAgentId(), agentUpdateDTO); + agentService.updateAgentById(device.getAgentId(), agentUpdateDTO); + return new Result<>(); } @PutMapping("/{id}") @Operation(summary = "更新智能体") @RequiresPermissions("sys:role:normal") public Result update(@PathVariable String id, @RequestBody @Valid AgentUpdateDTO dto) { - return updateAgentById(id, dto); - } - - private Result updateAgentById(String id, AgentUpdateDTO dto) { - // 先查询现有实体 - AgentEntity existingEntity = agentService.getAgentById(id); - if (existingEntity == null) { - return new Result().error("智能体不存在"); - } - - // 只更新提供的非空字段 - if (dto.getAgentName() != null) { - existingEntity.setAgentName(dto.getAgentName()); - } - if (dto.getAgentCode() != null) { - existingEntity.setAgentCode(dto.getAgentCode()); - } - if (dto.getAsrModelId() != null) { - existingEntity.setAsrModelId(dto.getAsrModelId()); - } - if (dto.getVadModelId() != null) { - existingEntity.setVadModelId(dto.getVadModelId()); - } - if (dto.getLlmModelId() != null) { - existingEntity.setLlmModelId(dto.getLlmModelId()); - } - if (dto.getVllmModelId() != null) { - existingEntity.setVllmModelId(dto.getVllmModelId()); - } - if (dto.getTtsModelId() != null) { - existingEntity.setTtsModelId(dto.getTtsModelId()); - } - if (dto.getTtsVoiceId() != null) { - existingEntity.setTtsVoiceId(dto.getTtsVoiceId()); - } - if (dto.getMemModelId() != null) { - existingEntity.setMemModelId(dto.getMemModelId()); - } - if (dto.getIntentModelId() != null) { - existingEntity.setIntentModelId(dto.getIntentModelId()); - } - if (dto.getSystemPrompt() != null) { - existingEntity.setSystemPrompt(dto.getSystemPrompt()); - } - if (dto.getSummaryMemory() != null) { - existingEntity.setSummaryMemory(dto.getSummaryMemory()); - } - if (dto.getChatHistoryConf() != null) { - existingEntity.setChatHistoryConf(dto.getChatHistoryConf()); - } - if (dto.getLangCode() != null) { - existingEntity.setLangCode(dto.getLangCode()); - } - if (dto.getLanguage() != null) { - existingEntity.setLanguage(dto.getLanguage()); - } - if (dto.getSort() != null) { - existingEntity.setSort(dto.getSort()); - } - - // 设置更新者信息 - UserDetail user = SecurityUser.getUser(); - existingEntity.setUpdater(user.getId()); - existingEntity.setUpdatedAt(new Date()); - - // 更新记忆策略 - if (existingEntity.getMemModelId() == null || existingEntity.getMemModelId().equals(Constant.MEMORY_NO_MEM)) { - // 删除所有记录 - agentChatHistoryService.deleteByAgentId(existingEntity.getId(), true, true); - existingEntity.setSummaryMemory(""); - } else if (existingEntity.getChatHistoryConf() != null && existingEntity.getChatHistoryConf() == 1) { - // 删除音频数据 - agentChatHistoryService.deleteByAgentId(existingEntity.getId(), true, false); - } - agentService.updateById(existingEntity); + agentService.updateAgentById(id, dto); return new Result<>(); } @@ -232,6 +132,8 @@ public class AgentController { deviceService.deleteByAgentId(id); // 删除关联的聊天记录 agentChatHistoryService.deleteByAgentId(id, true, true); + // 删除关联的插件 + agentPluginMappingService.deleteByAgentId(id); // 再删除智能体 agentService.deleteById(id); return new Result<>(); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentDao.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentDao.java index 3b4be452..03e569cd 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentDao.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentDao.java @@ -6,6 +6,7 @@ import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import xiaozhi.common.dao.BaseDao; import xiaozhi.modules.agent.entity.AgentEntity; +import xiaozhi.modules.agent.vo.AgentInfoVO; @Mapper public interface AgentDao extends BaseDao { @@ -28,4 +29,11 @@ public interface AgentDao extends BaseDao { " WHERE d.mac_address = #{macAddress} " + " ORDER BY d.id DESC LIMIT 1") AgentEntity getDefaultAgentByMacAddress(@Param("macAddress") String macAddress); + + /** + * 根据id查询agent信息,包括插件信息 + * + * @param agentId 智能体ID + */ + AgentInfoVO selectAgentInfoById(@Param("agentId") String agentId); } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentPluginMappingMapper.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentPluginMappingMapper.java new file mode 100644 index 00000000..2fe81753 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentPluginMappingMapper.java @@ -0,0 +1,22 @@ +package xiaozhi.modules.agent.dao; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import xiaozhi.modules.agent.entity.AgentPluginMapping; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +import java.util.List; + +/** +* @description 针对表【ai_agent_plugin_mapping(Agent与插件的唯一映射表)】的数据库操作Mapper +* @createDate 2025-05-25 22:33:17 +* @Entity xiaozhi.modules.agent.entity.AgentPluginMapping +*/ +@Mapper +public interface AgentPluginMappingMapper extends BaseMapper { + List selectPluginsByAgentId(@Param("agentId") String agentId); +} + + + + diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentUpdateDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentUpdateDTO.java index 32190d98..0e3d9bc3 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentUpdateDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentUpdateDTO.java @@ -1,6 +1,8 @@ package xiaozhi.modules.agent.dto; import java.io.Serializable; +import java.util.HashMap; +import java.util.List; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -15,19 +17,19 @@ import lombok.Data; public class AgentUpdateDTO implements Serializable { private static final long serialVersionUID = 1L; - @Schema(description = "智能体编码", example = "AGT_1234567890", required = false) + @Schema(description = "智能体编码", example = "AGT_1234567890", nullable = true) private String agentCode; - @Schema(description = "智能体名称", example = "客服助手", required = false) + @Schema(description = "智能体名称", example = "客服助手", nullable = true) private String agentName; - @Schema(description = "语音识别模型标识", example = "asr_model_02", required = false) + @Schema(description = "语音识别模型标识", example = "asr_model_02", nullable = true) private String asrModelId; - @Schema(description = "语音活动检测标识", example = "vad_model_02", required = false) + @Schema(description = "语音活动检测标识", example = "vad_model_02", nullable = true) private String vadModelId; - @Schema(description = "大语言模型标识", example = "llm_model_02", required = false) + @Schema(description = "大语言模型标识", example = "llm_model_02", nullable = true) private String llmModelId; @Schema(description = "VLLM模型标识", example = "vllm_model_02", required = false) @@ -36,31 +38,46 @@ public class AgentUpdateDTO implements Serializable { @Schema(description = "语音合成模型标识", example = "tts_model_02", required = false) private String ttsModelId; - @Schema(description = "音色标识", example = "voice_02", required = false) + @Schema(description = "音色标识", example = "voice_02", nullable = true) private String ttsVoiceId; - @Schema(description = "记忆模型标识", example = "mem_model_02", required = false) + @Schema(description = "记忆模型标识", example = "mem_model_02", nullable = true) private String memModelId; - @Schema(description = "意图模型标识", example = "intent_model_02", required = false) + @Schema(description = "意图模型标识", example = "intent_model_02", nullable = true) private String intentModelId; - @Schema(description = "角色设定参数", example = "你是一个专业的客服助手,负责回答用户问题并提供帮助", required = false) + @Schema(description = "插件函数信息", nullable = true) + private List functions; + + @Schema(description = "角色设定参数", example = "你是一个专业的客服助手,负责回答用户问题并提供帮助", nullable = true) private String systemPrompt; - @Schema(description = "总结记忆", example = "构建可生长的动态记忆网络,在有限空间内保留关键信息的同时,智能维护信息演变轨迹\n" + - "根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", required = false) + @Schema(description = "总结记忆", example = "构建可生长的动态记忆网络,在有限空间内保留关键信息的同时,智能维护信息演变轨迹\n" + + "根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", nullable = true) private String summaryMemory; - @Schema(description = "聊天记录配置(0不记录 1仅记录文本 2记录文本和语音)", example = "3", required = false) + @Schema(description = "聊天记录配置(0不记录 1仅记录文本 2记录文本和语音)", example = "3", nullable = true) private Integer chatHistoryConf; - @Schema(description = "语言编码", example = "zh_CN", required = false) + @Schema(description = "语言编码", example = "zh_CN", nullable = true) private String langCode; - @Schema(description = "交互语种", example = "中文", required = false) + @Schema(description = "交互语种", example = "中文", nullable = true) private String language; - @Schema(description = "排序", example = "1", required = false) + @Schema(description = "排序", example = "1", nullable = true) private Integer sort; + + @Data + @Schema(description = "插件函数信息") + public static class FunctionInfo implements Serializable { + @Schema(description = "插件ID", example = "plugin_01") + private String pluginId; + + @Schema(description = "函数参数信息", nullable = true) + private HashMap paramInfo; + + private static final long serialVersionUID = 1L; + } } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentPluginMapping.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentPluginMapping.java new file mode 100644 index 00000000..ae99b0eb --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/entity/AgentPluginMapping.java @@ -0,0 +1,54 @@ +package xiaozhi.modules.agent.entity; + +import java.io.Serializable; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * Agent与插件的唯一映射表 + * + * @TableName ai_agent_plugin_mapping + */ +@Data +@TableName(value = "ai_agent_plugin_mapping") +@Schema(description = "Agent与插件的唯一映射表") +public class AgentPluginMapping implements Serializable { + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + @Schema(description = "映射信息主键ID") + private Long id; + + /** + * 智能体ID + */ + @Schema(description = "智能体ID") + private String agentId; + + /** + * 插件ID + */ + @Schema(description = "插件ID") + private String pluginId; + + /** + * 插件参数(Json)格式 + */ + @Schema(description = "插件参数(Json)格式") + private String paramInfo; + + // 冗余字段,用于方便在根据id查询插件时,对照查出插件的Provider_code,详见dao层xml文件 + @TableField(exist = false) + @Schema(description = "插件provider_code, 对应表ai_model_provider") + private String providerCode; + + @TableField(exist = false) + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentPluginMappingService.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentPluginMappingService.java new file mode 100644 index 00000000..79d97d2e --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentPluginMappingService.java @@ -0,0 +1,29 @@ +package xiaozhi.modules.agent.service; + +import java.util.List; + +import com.baomidou.mybatisplus.extension.service.IService; + +import xiaozhi.modules.agent.entity.AgentPluginMapping; + +/** + * @description 针对表【ai_agent_plugin_mapping(Agent与插件的唯一映射表)】的数据库操作Service + * @createDate 2025-05-25 22:33:17 + */ +public interface AgentPluginMappingService extends IService { + + /** + * 根据智能体id获取插件参数 + * + * @param agentId + * @return + */ + List agentPluginParamsByAgentId(String agentId); + + /** + * 根据智能体id删除插件参数 + * + * @param agentId + */ + void deleteByAgentId(String agentId); +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentService.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentService.java index 2104d353..a9d4e5bb 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentService.java @@ -5,8 +5,11 @@ import java.util.Map; import xiaozhi.common.page.PageData; import xiaozhi.common.service.BaseService; +import xiaozhi.modules.agent.dto.AgentCreateDTO; import xiaozhi.modules.agent.dto.AgentDTO; +import xiaozhi.modules.agent.dto.AgentUpdateDTO; import xiaozhi.modules.agent.entity.AgentEntity; +import xiaozhi.modules.agent.vo.AgentInfoVO; /** * 智能体表处理service @@ -30,7 +33,7 @@ public interface AgentService extends BaseService { * @param id 智能体ID * @return 智能体实体 */ - AgentEntity getAgentById(String id); + AgentInfoVO getAgentById(String id); /** * 插入智能体 @@ -79,4 +82,20 @@ public interface AgentService extends BaseService { * @return 是否有权限 */ boolean checkAgentPermission(String agentId, Long userId); + + /** + * 更新智能体 + * + * @param agentId 智能体ID + * @param dto 更新智能体所需的信息 + */ + void updateAgentById(String agentId, AgentUpdateDTO dto); + + /** + * 创建智能体 + * + * @param dto 创建智能体所需的信息 + * @return 创建的智能体ID + */ + String createAgent(AgentCreateDTO dto); } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentPluginMappingServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentPluginMappingServiceImpl.java new file mode 100644 index 00000000..2a53d474 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentPluginMappingServiceImpl.java @@ -0,0 +1,37 @@ +package xiaozhi.modules.agent.service.impl; + +import java.util.List; + +import org.springframework.stereotype.Service; + +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; + +import lombok.RequiredArgsConstructor; +import xiaozhi.modules.agent.dao.AgentPluginMappingMapper; +import xiaozhi.modules.agent.entity.AgentPluginMapping; +import xiaozhi.modules.agent.service.AgentPluginMappingService; + +/** + * @description 针对表【ai_agent_plugin_mapping(Agent与插件的唯一映射表)】的数据库操作Service实现 + * @createDate 2025-05-25 22:33:17 + */ +@Service +@RequiredArgsConstructor +public class AgentPluginMappingServiceImpl extends ServiceImpl + implements AgentPluginMappingService { + private final AgentPluginMappingMapper agentPluginMappingMapper; + + @Override + public List agentPluginParamsByAgentId(String agentId) { + return agentPluginMappingMapper.selectPluginsByAgentId(agentId); + } + + @Override + public void deleteByAgentId(String agentId) { + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("agent_id", agentId); + agentPluginMappingMapper.delete(updateWrapper); + } + +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java index ad6e75c3..36aa1192 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java @@ -1,12 +1,17 @@ package xiaozhi.modules.agent.service.impl; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.function.Function; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; @@ -14,16 +19,30 @@ import com.baomidou.mybatisplus.core.metadata.IPage; import lombok.AllArgsConstructor; import xiaozhi.common.constant.Constant; +import xiaozhi.common.exception.RenException; import xiaozhi.common.page.PageData; import xiaozhi.common.redis.RedisKeys; import xiaozhi.common.redis.RedisUtils; import xiaozhi.common.service.impl.BaseServiceImpl; +import xiaozhi.common.user.UserDetail; +import xiaozhi.common.utils.ConvertUtils; +import xiaozhi.common.utils.JsonUtils; import xiaozhi.modules.agent.dao.AgentDao; +import xiaozhi.modules.agent.dto.AgentCreateDTO; import xiaozhi.modules.agent.dto.AgentDTO; +import xiaozhi.modules.agent.dto.AgentUpdateDTO; import xiaozhi.modules.agent.entity.AgentEntity; +import xiaozhi.modules.agent.entity.AgentPluginMapping; +import xiaozhi.modules.agent.entity.AgentTemplateEntity; +import xiaozhi.modules.agent.service.AgentChatHistoryService; +import xiaozhi.modules.agent.service.AgentPluginMappingService; import xiaozhi.modules.agent.service.AgentService; +import xiaozhi.modules.agent.service.AgentTemplateService; +import xiaozhi.modules.agent.vo.AgentInfoVO; import xiaozhi.modules.device.service.DeviceService; +import xiaozhi.modules.model.dto.ModelProviderDTO; import xiaozhi.modules.model.service.ModelConfigService; +import xiaozhi.modules.model.service.ModelProviderService; import xiaozhi.modules.security.user.SecurityUser; import xiaozhi.modules.sys.enums.SuperAdminEnum; import xiaozhi.modules.timbre.service.TimbreService; @@ -36,6 +55,10 @@ public class AgentServiceImpl extends BaseServiceImpl imp private final ModelConfigService modelConfigService; private final RedisUtils redisUtils; private final DeviceService deviceService; + private final AgentPluginMappingService agentPluginMappingService; + private final AgentChatHistoryService agentChatHistoryService; + private final AgentTemplateService agentTemplateService; + private final ModelProviderService modelProviderService; @Override public PageData adminAgentList(Map params) { @@ -46,15 +69,20 @@ public class AgentServiceImpl extends BaseServiceImpl imp } @Override - public AgentEntity getAgentById(String id) { - AgentEntity agent = agentDao.selectById(id); - if (agent != null && agent.getMemModelId() != null && agent.getMemModelId().equals(Constant.MEMORY_NO_MEM)) { - agent.setChatHistoryConf(Constant.ChatHistoryConfEnum.IGNORE.getCode()); - } else if (agent != null && agent.getMemModelId() != null - && !agent.getMemModelId().equals(Constant.MEMORY_NO_MEM) - && agent.getChatHistoryConf() == null) { - agent.setChatHistoryConf(Constant.ChatHistoryConfEnum.RECORD_TEXT_AUDIO.getCode()); + public AgentInfoVO getAgentById(String id) { + AgentInfoVO agent = agentDao.selectAgentInfoById(id); + + if (agent == null) { + throw new RenException("智能体不存在"); } + + if (agent.getMemModelId() != null && agent.getMemModelId().equals(Constant.MEMORY_NO_MEM)) { + agent.setChatHistoryConf(Constant.ChatHistoryConfEnum.IGNORE.getCode()); + if (agent.getChatHistoryConf() == null) { + agent.setChatHistoryConf(Constant.ChatHistoryConfEnum.RECORD_TEXT_AUDIO.getCode()); + } + } + // 无需额外查询插件列表,已通过SQL查询出来 return agent; } @@ -167,4 +195,198 @@ public class AgentServiceImpl extends BaseServiceImpl imp // 检查是否是智能体的所有者 return userId.equals(agent.getUserId()); } + + // 根据id更新智能体信息 + @Override + @Transactional(rollbackFor = Exception.class) + public void updateAgentById(String agentId, AgentUpdateDTO dto) { + // 先查询现有实体 + AgentEntity existingEntity = this.getAgentById(agentId); + if (existingEntity == null) { + throw new RuntimeException("智能体不存在"); + } + + // 只更新提供的非空字段 + if (dto.getAgentName() != null) { + existingEntity.setAgentName(dto.getAgentName()); + } + if (dto.getAgentCode() != null) { + existingEntity.setAgentCode(dto.getAgentCode()); + } + if (dto.getAsrModelId() != null) { + existingEntity.setAsrModelId(dto.getAsrModelId()); + } + if (dto.getVadModelId() != null) { + existingEntity.setVadModelId(dto.getVadModelId()); + } + if (dto.getLlmModelId() != null) { + existingEntity.setLlmModelId(dto.getLlmModelId()); + } + if (dto.getVllmModelId() != null) { + existingEntity.setVllmModelId(dto.getVllmModelId()); + } + if (dto.getTtsModelId() != null) { + existingEntity.setTtsModelId(dto.getTtsModelId()); + } + if (dto.getTtsVoiceId() != null) { + existingEntity.setTtsVoiceId(dto.getTtsVoiceId()); + } + if (dto.getMemModelId() != null) { + existingEntity.setMemModelId(dto.getMemModelId()); + } + if (dto.getIntentModelId() != null) { + existingEntity.setIntentModelId(dto.getIntentModelId()); + } + if (dto.getSystemPrompt() != null) { + existingEntity.setSystemPrompt(dto.getSystemPrompt()); + } + if (dto.getSummaryMemory() != null) { + existingEntity.setSummaryMemory(dto.getSummaryMemory()); + } + if (dto.getChatHistoryConf() != null) { + existingEntity.setChatHistoryConf(dto.getChatHistoryConf()); + } + if (dto.getLangCode() != null) { + existingEntity.setLangCode(dto.getLangCode()); + } + if (dto.getLanguage() != null) { + existingEntity.setLanguage(dto.getLanguage()); + } + if (dto.getSort() != null) { + existingEntity.setSort(dto.getSort()); + } + + // 更新函数插件信息 + List functions = dto.getFunctions(); + if (functions != null) { + // 1. 收集本次提交的 pluginId + List newPluginIds = functions.stream() + .map(AgentUpdateDTO.FunctionInfo::getPluginId) + .toList(); + + // 2. 查询当前agent现有的所有映射 + List existing = agentPluginMappingService.list( + new QueryWrapper() + .eq("agent_id", agentId)); + Map existMap = existing.stream() + .collect(Collectors.toMap(AgentPluginMapping::getPluginId, Function.identity())); + + // 3. 构造所有要 保存或更新 的实体 + List allToPersist = functions.stream().map(info -> { + AgentPluginMapping m = new AgentPluginMapping(); + m.setAgentId(agentId); + m.setPluginId(info.getPluginId()); + m.setParamInfo(JsonUtils.toJsonString(info.getParamInfo())); + AgentPluginMapping old = existMap.get(info.getPluginId()); + if (old != null) { + // 已存在,设置id表示更新 + m.setId(old.getId()); + } + return m; + }).toList(); + + // 4. 拆分:已有ID的走更新,无ID的走插入 + List toUpdate = allToPersist.stream() + .filter(m -> m.getId() != null) + .toList(); + List toInsert = allToPersist.stream() + .filter(m -> m.getId() == null) + .toList(); + + if (!toUpdate.isEmpty()) { + agentPluginMappingService.updateBatchById(toUpdate); + } + if (!toInsert.isEmpty()) { + agentPluginMappingService.saveBatch(toInsert); + } + + // 5. 删除本次不在提交列表里的插件映射 + List toDelete = existing.stream() + .filter(old -> !newPluginIds.contains(old.getPluginId())) + .map(AgentPluginMapping::getId) + .toList(); + if (!toDelete.isEmpty()) { + agentPluginMappingService.removeBatchByIds(toDelete); + } + } + + // 设置更新者信息 + UserDetail user = SecurityUser.getUser(); + existingEntity.setUpdater(user.getId()); + existingEntity.setUpdatedAt(new Date()); + + // 更新记忆策略 + if (existingEntity.getMemModelId() == null || existingEntity.getMemModelId().equals(Constant.MEMORY_NO_MEM)) { + // 删除所有记录 + agentChatHistoryService.deleteByAgentId(existingEntity.getId(), true, true); + existingEntity.setSummaryMemory(""); + } else if (existingEntity.getChatHistoryConf() != null && existingEntity.getChatHistoryConf() == 1) { + // 删除音频数据 + agentChatHistoryService.deleteByAgentId(existingEntity.getId(), true, false); + } + this.updateById(existingEntity); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public String createAgent(AgentCreateDTO dto) { + // 转换为实体 + AgentEntity entity = ConvertUtils.sourceToTarget(dto, AgentEntity.class); + + // 获取默认模板 + AgentTemplateEntity template = agentTemplateService.getDefaultTemplate(); + if (template != null) { + // 设置模板中的默认值 + entity.setAsrModelId(template.getAsrModelId()); + entity.setVadModelId(template.getVadModelId()); + entity.setLlmModelId(template.getLlmModelId()); + entity.setVllmModelId(template.getVllmModelId()); + entity.setTtsModelId(template.getTtsModelId()); + entity.setTtsVoiceId(template.getTtsVoiceId()); + entity.setMemModelId(template.getMemModelId()); + entity.setIntentModelId(template.getIntentModelId()); + entity.setSystemPrompt(template.getSystemPrompt()); + entity.setSummaryMemory(template.getSummaryMemory()); + entity.setChatHistoryConf(template.getChatHistoryConf()); + entity.setLangCode(template.getLangCode()); + entity.setLanguage(template.getLanguage()); + } + + // 设置用户ID和创建者信息 + UserDetail user = SecurityUser.getUser(); + entity.setUserId(user.getId()); + entity.setCreator(user.getId()); + entity.setCreatedAt(new Date()); + + // 保存智能体 + insert(entity); + + // 设置默认插件 + List toInsert = new ArrayList<>(); + // 播放音乐、查天气、查新闻 + String[] pluginIds = new String[] { "SYSTEM_PLUGIN_MUSIC", "SYSTEM_PLUGIN_WEATHER", + "SYSTEM_PLUGIN_NEWS_NEWSNOW" }; + for (String pluginId : pluginIds) { + ModelProviderDTO provider = modelProviderService.getById(pluginId); + if (provider == null) { + continue; + } + AgentPluginMapping mapping = new AgentPluginMapping(); + mapping.setPluginId(pluginId); + + Map paramInfo = new HashMap<>(); + List> fields = JsonUtils.parseObject(provider.getFields(), List.class); + if (fields != null) { + for (Map field : fields) { + paramInfo.put((String) field.get("key"), field.get("default")); + } + } + mapping.setParamInfo(JsonUtils.toJsonString(paramInfo)); + mapping.setAgentId(entity.getId()); + toInsert.add(mapping); + } + // 保存默认插件 + agentPluginMappingService.saveBatch(toInsert); + return entity.getId(); + } } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/vo/AgentInfoVO.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/vo/AgentInfoVO.java new file mode 100644 index 00000000..d56c8bb4 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/vo/AgentInfoVO.java @@ -0,0 +1,24 @@ +package xiaozhi.modules.agent.vo; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import xiaozhi.modules.agent.entity.AgentEntity; +import xiaozhi.modules.agent.entity.AgentPluginMapping; + +import java.util.List; + +/** + * Agent信息返回体VO + * 这里直接extend了Agent实体类AgentEntity,后续需要规范返回字段可以copy字段出来 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class AgentInfoVO extends AgentEntity +{ + @Schema(description = "插件列表Id") + @TableField(typeHandler = JacksonTypeHandler.class) + private List functions; +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/config/controller/ConfigController.java b/main/manager-api/src/main/java/xiaozhi/modules/config/controller/ConfigController.java index f83efb3a..f21795a3 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/config/controller/ConfigController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/config/controller/ConfigController.java @@ -27,7 +27,7 @@ public class ConfigController { private final ConfigService configService; @PostMapping("server-base") - @Operation(summary = "获取配置") + @Operation(summary = "服务端获取配置接口") public Result getConfig() { Object config = configService.getConfig(true); return new Result().ok(config); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/config/service/impl/ConfigServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/config/service/impl/ConfigServiceImpl.java index b9a7918c..e56cb381 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/config/service/impl/ConfigServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/config/service/impl/ConfigServiceImpl.java @@ -1,9 +1,6 @@ package xiaozhi.modules.config.service.impl; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; @@ -16,7 +13,9 @@ import xiaozhi.common.redis.RedisKeys; import xiaozhi.common.redis.RedisUtils; import xiaozhi.common.utils.JsonUtils; import xiaozhi.modules.agent.entity.AgentEntity; +import xiaozhi.modules.agent.entity.AgentPluginMapping; import xiaozhi.modules.agent.entity.AgentTemplateEntity; +import xiaozhi.modules.agent.service.AgentPluginMappingService; import xiaozhi.modules.agent.service.AgentService; import xiaozhi.modules.agent.service.AgentTemplateService; import xiaozhi.modules.config.service.ConfigService; @@ -39,6 +38,7 @@ public class ConfigServiceImpl implements ConfigService { private final AgentTemplateService agentTemplateService; private final RedisUtils redisUtils; private final TimbreService timbreService; + private final AgentPluginMappingService agentPluginMappingService; @Override public Object getConfig(Boolean isCache) { @@ -132,6 +132,19 @@ public class ConfigServiceImpl implements ConfigService { agent.setAsrModelId(null); } + // 添加函数调用参数信息 + if (!Objects.equals(agent.getIntentModelId(), "Intent_nointent")) { + String agentId = agent.getId(); + List pluginMappings = agentPluginMappingService.agentPluginParamsByAgentId(agentId); + if (pluginMappings != null && !pluginMappings.isEmpty()) { + Map pluginParams = new HashMap<>(); + for (AgentPluginMapping pluginMapping : pluginMappings) { + pluginParams.put(pluginMapping.getProviderCode(), pluginMapping.getParamInfo()); + } + result.put("plugins", pluginParams); + } + } + // 构建模块配置 buildModuleConfig( agent.getAgentName(), @@ -284,6 +297,7 @@ public class ConfigServiceImpl implements ConfigService { map.put("functions", functions); } } + System.out.println("map: " + map); } if ("Memory".equals(modelTypes[i])) { Map map = (Map) model.getConfigJson(); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/controller/ModelProviderController.java b/main/manager-api/src/main/java/xiaozhi/modules/model/controller/ModelProviderController.java index fc534a0a..24a7c7ca 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/controller/ModelProviderController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/controller/ModelProviderController.java @@ -18,6 +18,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import lombok.AllArgsConstructor; import xiaozhi.common.page.PageData; import xiaozhi.common.utils.Result; +import xiaozhi.common.utils.ResultUtils; import xiaozhi.common.validator.group.UpdateGroup; import xiaozhi.modules.model.dto.ModelProviderDTO; import xiaozhi.modules.model.service.ModelProviderService; @@ -65,4 +66,10 @@ public class ModelProviderController { return new Result<>(); } + @GetMapping("/plugin/names") + @Tag(name = "获取插件名称列表") + public Result> getPluginNameList() { + return ResultUtils.success(modelProviderService.getPluginList()); + } + } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelProviderService.java b/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelProviderService.java index d41766f6..829f86a8 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelProviderService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelProviderService.java @@ -1,5 +1,6 @@ package xiaozhi.modules.model.service; +import java.util.Collection; import java.util.List; import xiaozhi.common.page.PageData; @@ -7,7 +8,11 @@ import xiaozhi.modules.model.dto.ModelProviderDTO; public interface ModelProviderService { - // List getModelNames(String modelType, String modelName); + List getPluginList(); + + ModelProviderDTO getById(String id); + + List getPluginListByIds(Collection ids); List getListByModelType(String modelType); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelProviderServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelProviderServiceImpl.java index ffeec288..ccab6256 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelProviderServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelProviderServiceImpl.java @@ -1,5 +1,6 @@ package xiaozhi.modules.model.service.impl; +import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -8,6 +9,7 @@ import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; @@ -32,6 +34,29 @@ public class ModelProviderServiceImpl extends BaseServiceImpl getPluginList() { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(ModelProviderEntity::getModelType, "Plugin"); + List providerEntities = modelProviderDao.selectList(queryWrapper); + return ConvertUtils.sourceToTarget(providerEntities, ModelProviderDTO.class); + } + + @Override + public ModelProviderDTO getById(String id) { + ModelProviderEntity entity = modelProviderDao.selectById(id); + return ConvertUtils.sourceToTarget(entity, ModelProviderDTO.class); + } + + @Override + public List getPluginListByIds(Collection ids) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(ModelProviderEntity::getId, ids); + queryWrapper.eq(ModelProviderEntity::getModelType, "Plugin"); + List providerEntities = modelProviderDao.selectList(queryWrapper); + return ConvertUtils.sourceToTarget(providerEntities, ModelProviderDTO.class); + } + @Override public List getListByModelType(String modelType) { diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/enums/ServerActionEnum.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/enums/ServerActionEnum.java index 94e2672f..ec3ce2f5 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/enums/ServerActionEnum.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/enums/ServerActionEnum.java @@ -2,36 +2,29 @@ package xiaozhi.modules.sys.enums; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; -import xiaozhi.common.exception.RenException; /** * 服务端动作枚举 */ -public enum ServerActionEnum -{ +public enum ServerActionEnum { RESTART("restart"), UPDATE_CONFIG("update_config"); private final String value; - ServerActionEnum(String value) - { + ServerActionEnum(String value) { this.value = value; } @JsonValue - public String getValue() - { + public String getValue() { return value; } @JsonCreator - public static ServerActionEnum fromValue(String value) - { - for (ServerActionEnum action : ServerActionEnum.values()) - { - if (action.value.equalsIgnoreCase(value)) - { + public static ServerActionEnum fromValue(String value) { + for (ServerActionEnum action : ServerActionEnum.values()) { + if (action.value.equalsIgnoreCase(value)) { return action; } } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/enums/ServerActionResponseEnum.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/enums/ServerActionResponseEnum.java index 9e4c8307..040b2085 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/enums/ServerActionResponseEnum.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/enums/ServerActionResponseEnum.java @@ -1,16 +1,16 @@ package xiaozhi.modules.sys.enums; +import org.apache.commons.lang3.StringUtils; + import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; -import lombok.Getter; -import org.apache.commons.lang3.StringUtils; /** * 服务端调用响应枚举 */ -public enum ServerActionResponseEnum -{ +public enum ServerActionResponseEnum { SUCCESS("success"), FAIL("fail"); + private final String value; ServerActionResponseEnum(String value) { @@ -18,8 +18,7 @@ public enum ServerActionResponseEnum } @JsonValue - public String getValue() - { + public String getValue() { return value; } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/utils/WebSocketClientManager.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/utils/WebSocketClientManager.java index 0c49aead..45ac488e 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/utils/WebSocketClientManager.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/utils/WebSocketClientManager.java @@ -1,13 +1,5 @@ package xiaozhi.modules.sys.utils; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.extern.slf4j.Slf4j; -import org.springframework.util.StopWatch; -import org.springframework.web.socket.*; -import org.springframework.web.socket.client.standard.StandardWebSocketClient; -import org.springframework.web.socket.handler.AbstractWebSocketHandler; -import xiaozhi.common.utils.DateUtils; - import java.io.Closeable; import java.io.IOException; import java.net.URI; @@ -15,30 +7,51 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.concurrent.*; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.function.Predicate; +import org.springframework.util.StopWatch; +import org.springframework.web.socket.BinaryMessage; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketHttpHeaders; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.client.standard.StandardWebSocketClient; +import org.springframework.web.socket.handler.AbstractWebSocketHandler; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import lombok.extern.slf4j.Slf4j; +import xiaozhi.common.utils.DateUtils; + /** * WebSocketClientResource:支持 try-with-resources 模式 */ @Slf4j -public class WebSocketClientManager implements Closeable -{ +public class WebSocketClientManager implements Closeable { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); // 全局回调线程池 - private static final ExecutorService CALLBACK_EXECUTOR = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() { - private final AtomicInteger cnt = new AtomicInteger(); + private static final ExecutorService CALLBACK_EXECUTOR = Executors + .newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() { + private final AtomicInteger cnt = new AtomicInteger(); - public Thread newThread(Runnable r) - { - Thread t = new Thread(r, "ws-callback-" + cnt.getAndIncrement()); - t.setDaemon(true); - return t; - } - }); + public Thread newThread(Runnable r) { + Thread t = new Thread(r, "ws-callback-" + cnt.getAndIncrement()); + t.setDaemon(true); + return t; + } + }); private volatile WebSocketSession session; private final BlockingQueue textMessageQueue; @@ -51,18 +64,10 @@ public class WebSocketClientManager implements Closeable private volatile Consumer onBinary; private volatile Consumer onError; - private final String uri; - private final WebSocketHttpHeaders headers; - private final long connectTimeout; - private final TimeUnit connectUnit; private final int queueCapacity; // 私有构造,仅由 Builder 调用 private WebSocketClientManager(Builder b) { - this.uri = b.uri; - this.headers = b.headers != null ? b.headers : new WebSocketHttpHeaders(); - this.connectTimeout = b.connectTimeout; - this.connectUnit = b.connectUnit; this.maxSessionDuration = b.maxSessionDuration; this.maxSessionDurationUnit = b.maxSessionDurationUnit; this.queueCapacity = b.queueCapacity; @@ -71,13 +76,14 @@ public class WebSocketClientManager implements Closeable this.errorFuture = new CompletableFuture<>(); } - public static WebSocketClientManager build(Builder b) throws InterruptedException, ExecutionException, TimeoutException, IOException { + public static WebSocketClientManager build(Builder b) + throws InterruptedException, ExecutionException, TimeoutException, IOException { WebSocketClientManager ws = new WebSocketClientManager(b); StandardWebSocketClient client = new StandardWebSocketClient(); - CompletableFuture future = client.execute(ws.new InternalHandler(b.uri), b.headers, URI.create(b.uri)); + CompletableFuture future = client.execute(ws.new InternalHandler(b.uri), b.headers, + URI.create(b.uri)); WebSocketSession sess = future.get(b.connectTimeout, b.connectUnit); - if (sess == null || !sess.isOpen()) - { + if (sess == null || !sess.isOpen()) { throw new IOException("握手失败或会话未打开"); } ws.session = sess; @@ -100,13 +106,10 @@ public class WebSocketClientManager implements Closeable session.sendMessage(new TextMessage(json)); } - - private List listenerCustom( BlockingQueue queue, Predicate predicate) - throws InterruptedException, TimeoutException, ExecutionException - { + throws InterruptedException, TimeoutException, ExecutionException { List collected = new ArrayList<>(); long deadline = System.currentTimeMillis() + maxSessionDurationUnit.toMillis(maxSessionDuration); @@ -136,17 +139,16 @@ public class WebSocketClientManager implements Closeable /** * 同步接收多条消息,直到 predicate 为 true 或超时抛异常; + * * @return 返回监听期间的所有消息列表 */ public List listener(Predicate predicate) - throws InterruptedException, TimeoutException, ExecutionException - { + throws InterruptedException, TimeoutException, ExecutionException { return listenerCustom(textMessageQueue, predicate); } public List listenerBinary(Predicate predicate) - throws InterruptedException, TimeoutException, ExecutionException - { + throws InterruptedException, TimeoutException, ExecutionException { return listenerCustom(binaryMessageQueue, predicate); } @@ -183,8 +185,8 @@ public class WebSocketClientManager implements Closeable if (session != null && session.isOpen()) { session.close(CloseStatus.NORMAL); } + } catch (IOException ignored) { } - catch (IOException ignored) {} textMessageQueue.clear(); binaryMessageQueue.clear(); errorFuture.completeExceptionally(new IOException("WebSocket 已关闭")); @@ -207,7 +209,8 @@ public class WebSocketClientManager implements Closeable // 保存会话 WebSocketClientManager.this.session = session; this.stopWatch.start(); - log.info("ws连接成功, 目标URI: {}, 连接时间: {}", targetUri, DateUtils.getDateTimeNow(DateUtils.DATE_TIME_MILLIS_PATTERN)); + log.info("ws连接成功, 目标URI: {}, 连接时间: {}", targetUri, + DateUtils.getDateTimeNow(DateUtils.DATE_TIME_MILLIS_PATTERN)); } /** @@ -264,18 +267,19 @@ public class WebSocketClientManager implements Closeable stopWatch.stop(); } log.info("ws连接关闭, 目标URI: {}, 关闭时间: {}, 连接总时长: {}s", - targetUri, DateUtils.getDateTimeNow(DateUtils.DATE_TIME_MILLIS_PATTERN), DateUtils.millsToSecond(stopWatch.getTotalTimeMillis())); + targetUri, DateUtils.getDateTimeNow(DateUtils.DATE_TIME_MILLIS_PATTERN), + DateUtils.millsToSecond(stopWatch.getTotalTimeMillis())); } } public static class Builder { - private String uri; // 目标 WS URI - private long connectTimeout = 3; // 请求连接等待时间 - private TimeUnit connectUnit = TimeUnit.SECONDS; // 请求连接等待时间单位 - private long maxSessionDuration = 5; // 最大连线时间,默认5秒 + private String uri; // 目标 WS URI + private long connectTimeout = 3; // 请求连接等待时间 + private TimeUnit connectUnit = TimeUnit.SECONDS; // 请求连接等待时间单位 + private long maxSessionDuration = 5; // 最大连线时间,默认5秒 private TimeUnit maxSessionDurationUnit = TimeUnit.SECONDS; // 最大连线时间单位 - private int queueCapacity = 100; // 消息队列容量 - private WebSocketHttpHeaders headers; // 请求头 + private int queueCapacity = 100; // 消息队列容量 + private WebSocketHttpHeaders headers; // 请求头 /** * 目标 WS URI @@ -307,7 +311,8 @@ public class WebSocketClientManager implements Closeable return this; } - public WebSocketClientManager build() throws InterruptedException, ExecutionException, TimeoutException, IOException { + public WebSocketClientManager build() + throws InterruptedException, ExecutionException, TimeoutException, IOException { return WebSocketClientManager.build(this); } diff --git a/main/manager-api/src/main/resources/db/changelog/202505091409.sql b/main/manager-api/src/main/resources/db/changelog/202505091409.sql deleted file mode 100644 index 76ea8ae0..00000000 --- a/main/manager-api/src/main/resources/db/changelog/202505091409.sql +++ /dev/null @@ -1,6 +0,0 @@ --- 更新intent_llmM供应器 -update `ai_model_provider` set fields = '[{"key":"llm","label":"LLM模型","type":"string"},{"key":"functions","label":"函数列表","type":"dict","dict_name":"functions"}]' where id = 'SYSTEM_Intent_intent_llm'; --- 更新ChatGLMLLM的意图识别配置 -update `ai_model_config` set config_json = '{\"type\": \"intent_llm\", \"llm\": \"LLM_ChatGLMLLM\", \"functions\": \"get_weather;get_news_from_newsnow;play_music\"}' where id = 'Intent_intent_llm'; --- 更新函数调用意图识别配置 -UPDATE `ai_model_config` SET config_json = REPLACE(config_json, ';get_news;', ';get_news_from_newsnow;') WHERE id = 'Intent_function_call'; diff --git a/main/manager-api/src/main/resources/db/changelog/202505292203.sql b/main/manager-api/src/main/resources/db/changelog/202505292203.sql new file mode 100644 index 00000000..4102c7fa --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202505292203.sql @@ -0,0 +1,189 @@ +-- =============================== +-- 一、在ai_model_provider中插入plugin 记录 +-- =============================== +START TRANSACTION; + + +-- intent_llm和function_call不设置函数列表 +update `ai_model_provider` set fields = '[{"key":"llm","label":"LLM模型","type":"string"}]' where id = 'SYSTEM_Intent_intent_llm'; +update `ai_model_provider` set fields = '[]' where id = 'SYSTEM_Intent_function_call'; +update `ai_model_config` set config_json = '{\"type\": \"intent_llm\", \"llm\": \"LLM_ChatGLMLLM\"}' where id = 'Intent_intent_llm'; +UPDATE `ai_model_config` SET config_json = '{\"type\": \"function_call\"}' WHERE id = 'Intent_function_call'; + + +delete from ai_model_provider where model_type = 'Plugin'; +-- 1. 天气查询 +INSERT INTO ai_model_provider (id, model_type, provider_code, name, fields, + sort, creator, create_date, updater, update_date) +VALUES ('SYSTEM_PLUGIN_WEATHER', + 'Plugin', + 'get_weather', + '天气查询', + JSON_ARRAY( + JSON_OBJECT( + 'key', 'api_key', + 'type', 'string', + 'label', '天气插件 API 密钥', + 'default', (SELECT param_value FROM sys_params WHERE param_code = 'plugins.get_weather.api_key') + ), + JSON_OBJECT( + 'key', 'default_location', + 'type', 'string', + 'label', '默认查询城市', + 'default', + (SELECT param_value FROM sys_params WHERE param_code = 'plugins.get_weather.default_location') + ), + JSON_OBJECT( + 'key', 'api_host', + 'type', 'string', + 'label', '开发者 API Host', + 'default', + (SELECT param_value FROM sys_params WHERE param_code = 'plugins.get_weather.api_host') + ) + ), + 10, 0, NOW(), 0, NOW()); + +-- 6. 本地播放音乐 +INSERT INTO ai_model_provider (id, model_type, provider_code, name, fields, + sort, creator, create_date, updater, update_date) +VALUES ('SYSTEM_PLUGIN_MUSIC', + 'Plugin', + 'play_music', + '服务器音乐播放', + JSON_ARRAY(), + 20, 0, NOW(), 0, NOW()); + +-- 2. 新闻订阅 +INSERT INTO ai_model_provider (id, model_type, provider_code, name, fields, + sort, creator, create_date, updater, update_date) +VALUES ('SYSTEM_PLUGIN_NEWS_CHINANEWS', + 'Plugin', + 'get_news_from_chinanews', + '中新网新闻', + JSON_ARRAY( + JSON_OBJECT( + 'key', 'default_rss_url', + 'type', 'string', + 'label', '默认 RSS 源', + 'default', + (SELECT param_value FROM sys_params WHERE param_code = 'plugins.get_news.default_rss_url') + ), + JSON_OBJECT( + 'key', 'society_rss_url', + 'type', 'string', + 'label', '社会新闻 RSS 地址', + 'default', + 'https://www.chinanews.com.cn/rss/society.xml' + ), + JSON_OBJECT( + 'key', 'world_rss_url', + 'type', 'string', + 'label', '国际新闻 RSS 地址', + 'default', + 'https://www.chinanews.com.cn/rss/world.xml' + ), + JSON_OBJECT( + 'key', 'finance_rss_url', + 'type', 'string', + 'label', '财经新闻 RSS 地址', + 'default', + 'https://www.chinanews.com.cn/rss/finance.xml' + ) + ), + 30, 0, NOW(), 0, NOW()); + +-- 3. 新闻订阅 +INSERT INTO ai_model_provider (id, model_type, provider_code, name, fields, + sort, creator, create_date, updater, update_date) +VALUES ('SYSTEM_PLUGIN_NEWS_NEWSNOW', + 'Plugin', + 'get_news_from_newsnow', + 'newsnow新闻聚合', + JSON_ARRAY( + JSON_OBJECT( + 'key', 'url', + 'type', 'string', + 'label', '接口地址', + 'default', + 'https://newsnow.busiyi.world/api/s?id=' + ) + ), + 40, 0, NOW(), 0, NOW()); + + +-- 4. HomeAssistant 状态查询 +INSERT INTO ai_model_provider (id, model_type, provider_code, name, fields, + sort, creator, create_date, updater, update_date) +VALUES ('SYSTEM_PLUGIN_HA_GET_STATE', + 'Plugin', + 'hass_get_state', + 'HomeAssistant设备状态查询', + JSON_ARRAY( + JSON_OBJECT( + 'key', 'base_url', + 'type', 'string', + 'label', 'HA 服务器地址', + 'default', + (SELECT param_value FROM sys_params WHERE param_code = 'plugins.home_assistant.base_url') + ), + JSON_OBJECT( + 'key', 'api_key', + 'type', 'string', + 'label', 'HA API 访问令牌', + 'default', + (SELECT param_value FROM sys_params WHERE param_code = 'plugins.home_assistant.api_key') + ), + JSON_OBJECT( + 'key', 'devices', + 'type', 'array', + 'label', '设备列表(名称,实体ID;…)', + 'default', + (SELECT param_value FROM sys_params WHERE param_code = 'plugins.home_assistant.devices') + ) + ), + 50, 0, NOW(), 0, NOW()); + +-- 5. HomeAssistant 状态写入 +INSERT INTO ai_model_provider (id, model_type, provider_code, name, fields, + sort, creator, create_date, updater, update_date) +VALUES ('SYSTEM_PLUGIN_HA_SET_STATE', + 'Plugin', + 'hass_set_state', + 'HomeAssistant设备状态修改', + JSON_ARRAY(), + 60, 0, NOW(), 0, NOW()); + +-- 5. HomeAssistant 音乐播放 +INSERT INTO ai_model_provider (id, model_type, provider_code, name, fields, + sort, creator, create_date, updater, update_date) +VALUES ('SYSTEM_PLUGIN_HA_PLAY_MUSIC', + 'Plugin', + 'hass_play_music', + 'HomeAssistant音乐播放', + JSON_ARRAY(), + 70, 0, NOW(), 0, NOW()); + + +-- =============================== +-- 二、删除sys_params中旧的plugins.*参数 +-- =============================== +DELETE +FROM sys_params +WHERE param_code LIKE 'plugins.%'; + + +-- =============================== +-- 三、添加智能体插件id字段 +-- =============================== +CREATE TABLE IF NOT EXISTS ai_agent_plugin_mapping +( + id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键', + agent_id VARCHAR(32) NOT NULL COMMENT '智能体ID', + plugin_id VARCHAR(32) NOT NULL COMMENT '插件ID', + param_info JSON NOT NULL COMMENT '参数信息', + UNIQUE KEY uk_agent_provider (agent_id, plugin_id) +) COMMENT 'Agent与插件的唯一映射表'; + + +COMMIT; + diff --git a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml index f33d28a2..4d253f13 100755 --- a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml @@ -107,13 +107,6 @@ databaseChangeLog: - sqlFile: encoding: utf8 path: classpath:db/changelog/202505081146.sql - - changeSet: - id: 202505091409 - author: hrz - changes: - - sqlFile: - encoding: utf8 - path: classpath:db/changelog/202505091409.sql - changeSet: id: 202505091555 author: whosmyqueen @@ -170,6 +163,13 @@ databaseChangeLog: - sqlFile: encoding: utf8 path: classpath:db/changelog/202505271414.sql + - changeSet: + id: 202505292203 + author: CAIXYPROMISE + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202505292203.sql - changeSet: id: 202506010920 author: hrz @@ -204,4 +204,4 @@ databaseChangeLog: changes: - sqlFile: encoding: utf8 - path: classpath:db/changelog/202506080955.sql \ No newline at end of file + path: classpath:db/changelog/202506080955.sql diff --git a/main/manager-api/src/main/resources/mapper/agent/AgentDao.xml b/main/manager-api/src/main/resources/mapper/agent/AgentDao.xml index 322ca172..439be096 100644 --- a/main/manager-api/src/main/resources/mapper/agent/AgentDao.xml +++ b/main/manager-api/src/main/resources/mapper/agent/AgentDao.xml @@ -5,4 +5,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/main/manager-api/src/main/resources/mapper/agent/AgentPluginMappingMapper.xml b/main/manager-api/src/main/resources/mapper/agent/AgentPluginMappingMapper.xml new file mode 100644 index 00000000..659fcd6e --- /dev/null +++ b/main/manager-api/src/main/resources/mapper/agent/AgentPluginMappingMapper.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + id,agent_id,plugin_id + + + + + + + diff --git a/main/manager-web/src/apis/module/model.js b/main/manager-web/src/apis/module/model.js index badf4174..7db2b288 100644 --- a/main/manager-web/src/apis/module/model.js +++ b/main/manager-web/src/apis/module/model.js @@ -305,4 +305,20 @@ export default { }) }).send() }, + // 获取插件列表 + getPluginFunctionList(params, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/models/provider/plugin/names`) + .method('GET') + .success((res) => { + RequestService.clearRequestTime() + callback(res) + }) + .networkFail((err) => { + this.$message.error(err.msg || '获取插件列表失败') + RequestService.reAjaxFun(() => { + this.getPluginFunctionList(params, callback) + }) + }).send() + } } diff --git a/main/manager-web/src/components/FirmwareDialog.vue b/main/manager-web/src/components/FirmwareDialog.vue index 56e2f327..e4eb2bb7 100644 --- a/main/manager-web/src/components/FirmwareDialog.vue +++ b/main/manager-web/src/components/FirmwareDialog.vue @@ -1,5 +1,5 @@