package xiaozhi.modules.config.service.impl; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import lombok.AllArgsConstructor; import xiaozhi.common.constant.Constant; import xiaozhi.common.exception.ErrorCode; import xiaozhi.common.exception.RenException; import xiaozhi.common.redis.RedisKeys; import xiaozhi.common.redis.RedisUtils; import xiaozhi.common.utils.ConvertUtils; import xiaozhi.common.utils.JsonUtils; import xiaozhi.modules.agent.dao.AgentVoicePrintDao; import xiaozhi.modules.agent.entity.AgentContextProviderEntity; import xiaozhi.modules.agent.entity.AgentEntity; import xiaozhi.modules.agent.entity.AgentPluginMapping; import xiaozhi.modules.agent.entity.AgentTemplateEntity; import xiaozhi.modules.agent.entity.AgentVoicePrintEntity; import xiaozhi.modules.agent.service.AgentContextProviderService; import xiaozhi.modules.agent.service.AgentMcpAccessPointService; import xiaozhi.modules.agent.service.AgentPluginMappingService; import xiaozhi.modules.agent.service.AgentService; import xiaozhi.modules.agent.service.AgentTemplateService; import xiaozhi.modules.correctword.service.CorrectWordFileService; import xiaozhi.modules.agent.vo.AgentVoicePrintVO; import xiaozhi.modules.correctword.vo.CorrectWordSimpleVO; import xiaozhi.modules.config.service.ConfigService; import xiaozhi.modules.device.entity.DeviceEntity; import xiaozhi.modules.device.service.DeviceService; import xiaozhi.modules.model.entity.ModelConfigEntity; import xiaozhi.modules.model.service.ModelConfigService; import xiaozhi.modules.sys.dto.SysParamsDTO; import xiaozhi.modules.sys.service.SysParamsService; import xiaozhi.modules.timbre.service.TimbreService; import xiaozhi.modules.timbre.vo.TimbreDetailsVO; import xiaozhi.modules.voiceclone.entity.VoiceCloneEntity; import xiaozhi.modules.voiceclone.service.VoiceCloneService; @Service @AllArgsConstructor public class ConfigServiceImpl implements ConfigService { private final SysParamsService sysParamsService; private final DeviceService deviceService; private final ModelConfigService modelConfigService; private final AgentService agentService; private final AgentTemplateService agentTemplateService; private final RedisUtils redisUtils; private final TimbreService timbreService; private final AgentPluginMappingService agentPluginMappingService; private final AgentMcpAccessPointService agentMcpAccessPointService; private final AgentContextProviderService agentContextProviderService; private final VoiceCloneService cloneVoiceService; private final AgentVoicePrintDao agentVoicePrintDao; private final CorrectWordFileService correctWordFileService; @Override public Map getConfig(Boolean isCache) { if (isCache) { // 先从Redis获取配置 Object cachedConfig = redisUtils.get(RedisKeys.getServerConfigKey()); if (cachedConfig != null) { return JsonUtils.toStringObjectMap(cachedConfig); } } // 构建配置信息 Map result = new HashMap<>(); buildConfig(result); // 查询默认智能体 AgentTemplateEntity agent = agentTemplateService.getDefaultTemplate(); if (agent == null) { throw new RenException(ErrorCode.AGENT_TEMPLATE_NOT_FOUND); } // 构建模块配置 buildModuleConfig( null, null, null, null, null, null, null, null, null, null, agent.getVadModelId(), agent.getAsrModelId(), null, null, null, null, null, null, null, result, isCache); // 将配置存入Redis redisUtils.set(RedisKeys.getServerConfigKey(), result); return result; } @Override public Map getAgentModels(String macAddress, Map selectedModule) { // 检查是否为管理控制台请求 String redisKey = RedisKeys.getTmpRegisterMacKey(macAddress); Object isAdminRequest = redisUtils.get(redisKey); if (isAdminRequest != null && "true".equals(isAdminRequest)) { // 管理控制台请求,返回getConfig的结果 redisUtils.delete(redisKey); // 使用后清理 return getConfig(true); } // 根据MAC地址查找设备 DeviceEntity device = deviceService.getDeviceByMacAddress(macAddress); if (device == null) { // 如果设备,去redis里看看有没有需要连接的设备 String cachedCode = deviceService.geCodeByDeviceId(macAddress); if (StringUtils.isNotBlank(cachedCode)) { throw new RenException(ErrorCode.OTA_DEVICE_NEED_BIND, cachedCode); } throw new RenException(ErrorCode.OTA_DEVICE_NOT_FOUND); } // 获取智能体信息 AgentEntity agent = agentService.getAgentById(device.getAgentId()); if (agent == null) { throw new RenException(ErrorCode.AGENT_NOT_FOUND); } // 获取音色信息 String voice = null; String referenceAudio = null; String referenceText = null; String language = null; TimbreDetailsVO timbre = timbreService.get(agent.getTtsVoiceId()); if (timbre != null) { voice = timbre.getTtsVoice(); referenceAudio = timbre.getReferenceAudio(); referenceText = timbre.getReferenceText(); // 优先使用用户选择的语言,如果没有则使用音色支持的第一个语言 if (StringUtils.isNotBlank(agent.getTtsLanguage())) { language = agent.getTtsLanguage(); } else if (StringUtils.isNotBlank(timbre.getLanguages())) { language = timbre.getLanguages().split("、")[0].trim(); } } else { VoiceCloneEntity voice_print = cloneVoiceService.selectById(agent.getTtsVoiceId()); if (voice_print != null) { voice = voice_print.getVoiceId(); // 优先使用用户选择的语言,如果没有则使用默认值 language = StringUtils.isNotBlank(agent.getTtsLanguage()) ? agent.getTtsLanguage() : "普通话"; } } // 构建返回数据 Map result = new HashMap<>(); // 获取单台设备每天最多输出字数 String deviceMaxOutputSize = sysParamsService.getValue("device_max_output_size", true); result.put("device_max_output_size", deviceMaxOutputSize); // 获取聊天记录配置 Integer chatHistoryConf = agent.getChatHistoryConf(); if (agent.getMemModelId() != null && agent.getMemModelId().equals(Constant.MEMORY_NO_MEM)) { chatHistoryConf = Constant.ChatHistoryConfEnum.IGNORE.getCode(); } else if (agent.getMemModelId() != null && !agent.getMemModelId().equals(Constant.MEMORY_NO_MEM) && agent.getChatHistoryConf() == null) { chatHistoryConf = Constant.ChatHistoryConfEnum.RECORD_TEXT_AUDIO.getCode(); } result.put("chat_history_conf", chatHistoryConf); // 如果客户端已实例化模型,则不返回 String alreadySelectedVadModelId = selectedModule.get("VAD"); if (alreadySelectedVadModelId != null && alreadySelectedVadModelId.equals(agent.getVadModelId())) { agent.setVadModelId(null); } String alreadySelectedAsrModelId = selectedModule.get("ASR"); if (alreadySelectedAsrModelId != null && alreadySelectedAsrModelId.equals(agent.getAsrModelId())) { 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); } } // 获取mcp接入点地址 String mcpEndpoint = agentMcpAccessPointService.getAgentMcpAccessAddress(agent.getId()); if (StringUtils.isNotBlank(mcpEndpoint) && mcpEndpoint.startsWith("ws")) { mcpEndpoint = mcpEndpoint.replace("/mcp/", "/call/"); result.put("mcp_endpoint", mcpEndpoint); } // 获取上下文源配置 AgentContextProviderEntity contextProviderEntity = agentContextProviderService.getByAgentId(agent.getId()); if (contextProviderEntity != null && contextProviderEntity.getContextProviders() != null && !contextProviderEntity.getContextProviders().isEmpty()) { result.put("context_providers", contextProviderEntity.getContextProviders()); } // 获取声纹信息 buildVoiceprintConfig(agent.getId(), result); // 构建模块配置 buildModuleConfig( agent.getAgentName(), agent.getSystemPrompt(), agent.getSummaryMemory(), voice, referenceAudio, referenceText, language, agent.getTtsVolume(), agent.getTtsRate(), agent.getTtsPitch(), agent.getVadModelId(), agent.getAsrModelId(), agent.getLlmModelId(), agent.getVllmModelId(), agent.getSlmModelId(), agent.getTtsModelId(), agent.getMemModelId(), agent.getIntentModelId(), null, result, true); return result; } @Override public List getCorrectWords(String macAddress) { DeviceEntity device = deviceService.getDeviceByMacAddress(macAddress); if (device == null) { return Collections.emptyList(); } List items = correctWordFileService.getAllItemsByAgentId(device.getAgentId()); return items.stream() .map(item -> item.getSourceWord() + "|" + item.getTargetWord()) .collect(Collectors.toList()); } /** * 构建配置信息 * * @param config 系统参数列表 * @return 配置信息 */ private Object buildConfig(Map config) { // 查询所有系统参数 List paramsList = sysParamsService.list(new HashMap<>()); for (SysParamsDTO param : paramsList) { String[] keys = param.getParamCode().split("\\."); Map current = config; // 遍历除最后一个key之外的所有key for (int i = 0; i < keys.length - 1; i++) { String key = keys[i]; Object nestedConfig = current.computeIfAbsent(key, ignored -> new HashMap()); Map nestedMap = JsonUtils.toStringObjectMap(nestedConfig); current.put(key, nestedMap); current = nestedMap; } // 处理最后一个key String lastKey = keys[keys.length - 1]; String value = param.getParamValue(); // 根据valueType转换值 switch (param.getValueType().toLowerCase()) { case "number": try { double doubleValue = Double.parseDouble(value); // 如果数值是整数形式,则转换为Integer if (doubleValue == (int) doubleValue) { current.put(lastKey, (int) doubleValue); } else { current.put(lastKey, doubleValue); } } catch (NumberFormatException e) { current.put(lastKey, value); } break; case "boolean": current.put(lastKey, Boolean.parseBoolean(value)); break; case "array": // 将分号分隔的字符串转换为数字数组 List list = new ArrayList<>(); for (String num : value.split(";")) { if (StringUtils.isNotBlank(num)) { list.add(num.trim()); } } current.put(lastKey, list); break; case "json": try { current.put(lastKey, JsonUtils.parseObject(value, Object.class)); } catch (Exception e) { current.put(lastKey, value); } break; default: current.put(lastKey, value); } } return config; } /** * 构建声纹配置信息 * * @param agentId 智能体ID * @param result 结果Map */ private void buildVoiceprintConfig(String agentId, Map result) { try { // 获取声纹接口地址 String voiceprintUrl = sysParamsService.getValue(Constant.SERVER_VOICE_PRINT, true); if (StringUtils.isBlank(voiceprintUrl) || "null".equals(voiceprintUrl)) { return; } // 获取智能体关联的声纹信息(不需要用户权限验证) List voiceprints = getVoiceprintsByAgentId(agentId); if (voiceprints == null || voiceprints.isEmpty()) { return; } // 构建speakers列表 List speakers = new ArrayList<>(); for (AgentVoicePrintVO voiceprint : voiceprints) { String speakerStr = String.format("%s,%s,%s", voiceprint.getId(), voiceprint.getSourceName(), voiceprint.getIntroduce() != null ? voiceprint.getIntroduce() : ""); speakers.add(speakerStr); } // 构建声纹配置 Map voiceprintConfig = new HashMap<>(); voiceprintConfig.put("url", voiceprintUrl); voiceprintConfig.put("speakers", speakers); // 获取声纹识别相似度阈值,默认0.4 String thresholdStr = sysParamsService.getValue("server.voiceprint_similarity_threshold", true); if (StringUtils.isNotBlank(thresholdStr) && !"null".equals(thresholdStr)) { try { double threshold = Double.parseDouble(thresholdStr); voiceprintConfig.put("similarity_threshold", threshold); } catch (NumberFormatException e) { // 如果解析失败,使用默认值0.4 voiceprintConfig.put("similarity_threshold", 0.4); } } else { voiceprintConfig.put("similarity_threshold", 0.4); } result.put("voiceprint", voiceprintConfig); } catch (Exception e) { // 声纹配置获取失败时不影响其他功能 System.err.println("获取声纹配置失败: " + e.getMessage()); } } /** * 获取智能体关联的声纹信息 * * @param agentId 智能体ID * @return 声纹信息列表 */ private List getVoiceprintsByAgentId(String agentId) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(AgentVoicePrintEntity::getAgentId, agentId); queryWrapper.orderByAsc(AgentVoicePrintEntity::getCreateDate); List entities = agentVoicePrintDao.selectList(queryWrapper); return ConvertUtils.sourceToTarget(entities, AgentVoicePrintVO.class); } /** * 构建模块配置 * * @param prompt 提示词 * @param voice 音色 * @param referenceAudio 参考音频路径 * @param referenceText 参考文本 * @param vadModelId VAD模型ID * @param asrModelId ASR模型ID * @param llmModelId LLM模型ID * @param ttsModelId TTS模型ID * @param memModelId 记忆模型ID * @param intentModelId 意图模型ID * @param result 结果Map */ private void buildModuleConfig( String assistantName, String prompt, String summaryMemory, String voice, String referenceAudio, String referenceText, String language, Integer ttsVolume, Integer ttsRate, Integer ttsPitch, String vadModelId, String asrModelId, String llmModelId, String vllmModelId, String slmModelId, String ttsModelId, String memModelId, String intentModelId, String ragModelId, Map result, boolean isCache) { Map selectedModule = new HashMap<>(); String[] modelTypes = { "VAD", "ASR", "TTS", "Memory", "Intent", "LLM", "VLLM", "SLM", "RAG" }; String[] modelIds = { vadModelId, asrModelId, ttsModelId, memModelId, intentModelId, llmModelId, vllmModelId, slmModelId, ragModelId }; String intentLLMModelId = null; String memLocalShortLLMModelId = null; for (int i = 0; i < modelIds.length; i++) { if (modelIds[i] == null) { continue; } // 关键:第三个参数传false,确保获取原始密钥 ModelConfigEntity model = modelConfigService.getModelByIdFromCache(modelIds[i]); if (model == null) { continue; } Map typeConfig = new HashMap<>(); if (model.getConfigJson() != null) { typeConfig.put(model.getId(), model.getConfigJson()); // 如果是TTS类型,添加private_voice属性 if ("TTS".equals(modelTypes[i])) { if (voice != null) ((Map) model.getConfigJson()).put("private_voice", voice); if (referenceAudio != null) ((Map) model.getConfigJson()).put("ref_audio", referenceAudio); if (referenceText != null) ((Map) model.getConfigJson()).put("ref_text", referenceText); if (language != null) ((Map) model.getConfigJson()).put("language", language); if (ttsVolume != null) ((Map) model.getConfigJson()).put("ttsVolume", ttsVolume); if (ttsRate != null) ((Map) model.getConfigJson()).put("ttsRate", ttsRate); if (ttsPitch != null) ((Map) model.getConfigJson()).put("ttsPitch", ttsPitch); // 火山引擎声音克隆需要替换resource_id Map map = (Map) model.getConfigJson(); if (Constant.VOICE_CLONE_HUOSHAN_DOUBLE_STREAM.equals(map.get("type"))) { // 如果voice是”S_”开头的,使用seed-icl-1.0 if (voice != null && voice.startsWith("S_")) { map.put("resource_id", "seed-icl-1.0"); } } } // 如果是Intent类型,且type=intent_llm,则给他添加附加模型 if ("Intent".equals(modelTypes[i])) { Map map = (Map) model.getConfigJson(); if ("intent_llm".equals(map.get("type"))) { intentLLMModelId = (String) map.get("llm"); if (StringUtils.isNotBlank(intentLLMModelId) && intentLLMModelId.equals(llmModelId)) { intentLLMModelId = null; } } if (map.get("functions") != null) { String functionStr = (String) map.get("functions"); if (StringUtils.isNotBlank(functionStr)) { String[] functions = functionStr.split(";"); map.put("functions", functions); } } System.out.println("map: " + map); } if ("Memory".equals(modelTypes[i])) { Map map = (Map) model.getConfigJson(); if ("mem_local_short".equals(map.get("type"))) { memLocalShortLLMModelId = (String) map.get("llm"); if (StringUtils.isNotBlank(memLocalShortLLMModelId) && memLocalShortLLMModelId.equals(llmModelId)) { memLocalShortLLMModelId = null; } } } // 如果是LLM类型,且intentLLMModelId不为空,则添加附加模型 if ("LLM".equals(modelTypes[i])) { if (StringUtils.isNotBlank(intentLLMModelId)) { if (!typeConfig.containsKey(intentLLMModelId)) { // 修改这里:添加isMaskSensitive=false参数 ModelConfigEntity intentLLM = modelConfigService.getModelByIdFromCache(intentLLMModelId); typeConfig.put(intentLLM.getId(), intentLLM.getConfigJson()); } } if (StringUtils.isNotBlank(memLocalShortLLMModelId)) { if (!typeConfig.containsKey(memLocalShortLLMModelId)) { // 修改这里:添加isMaskSensitive=false参数 ModelConfigEntity memLocalShortLLM = modelConfigService .getModelByIdFromCache(memLocalShortLLMModelId); typeConfig.put(memLocalShortLLM.getId(), memLocalShortLLM.getConfigJson()); } } // LLM也返回所选的SLM,如果同名id则不重复显示 if (StringUtils.isNotBlank(slmModelId) && !slmModelId.equals(llmModelId)) { if (!typeConfig.containsKey(slmModelId)) { ModelConfigEntity slmModel = modelConfigService.getModelByIdFromCache(slmModelId); if (slmModel != null && slmModel.getConfigJson() != null) { typeConfig.put(slmModel.getId(), slmModel.getConfigJson()); } } } } } result.put(modelTypes[i], typeConfig); selectedModule.put(modelTypes[i], model.getId()); } result.put("selected_module", selectedModule); if (StringUtils.isNotBlank(prompt)) { prompt = prompt.replace("{{assistant_name}}", StringUtils.isBlank(assistantName) ? "小智" : assistantName); } result.put("prompt", prompt); result.put("summaryMemory", summaryMemory); } }