mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-28 10:03:54 +08:00
update:server连接api (#747)
* update:server连接manager-api * update:读取智能体模型配置 * update:添加默认模型的按钮 * update:优化配置读取方式 * update:server兼容manager接口改造 * update:优化私有配置加载 * update:加载私有模型配置
This commit is contained in:
@@ -71,6 +71,14 @@ public class SwaggerConfig {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public GroupedOpenApi configApi() {
|
||||||
|
return GroupedOpenApi.builder()
|
||||||
|
.group("config")
|
||||||
|
.pathsToMatch("/config/**")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public OpenAPI customOpenAPI() {
|
public OpenAPI customOpenAPI() {
|
||||||
return new OpenAPI().info(new Info()
|
return new OpenAPI().info(new Info()
|
||||||
|
|||||||
@@ -51,4 +51,5 @@ public interface ErrorCode {
|
|||||||
int PARAM_NUMBER_INVALID = 10037;
|
int PARAM_NUMBER_INVALID = 10037;
|
||||||
int PARAM_BOOLEAN_INVALID = 10038;
|
int PARAM_BOOLEAN_INVALID = 10038;
|
||||||
int PARAM_ARRAY_INVALID = 10039;
|
int PARAM_ARRAY_INVALID = 10039;
|
||||||
|
int PARAM_JSON_INVALID = 10040;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,14 @@ public class RedisKeys {
|
|||||||
* 模型名称的Key
|
* 模型名称的Key
|
||||||
*/
|
*/
|
||||||
public static String getModelNameById(String id) {
|
public static String getModelNameById(String id) {
|
||||||
return "sys:model:name:" + id;
|
return "model:name:" + id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型配置的Key
|
||||||
|
*/
|
||||||
|
public static String getModelConfigById(String id) {
|
||||||
|
return "model:data:" + id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -54,4 +61,18 @@ public class RedisKeys {
|
|||||||
public static String getAgentDeviceCountById(String id) {
|
public static String getAgentDeviceCountById(String id) {
|
||||||
return "agent:device:count:" + id;
|
return "agent:device:count:" + id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取系统配置缓存key
|
||||||
|
*/
|
||||||
|
public static String getServerConfigKey() {
|
||||||
|
return "server:config";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取音色详情缓存key
|
||||||
|
*/
|
||||||
|
public static String getTimbreDetailsKey(String id) {
|
||||||
|
return "timbre:details:" + id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -191,4 +191,5 @@ public class AgentController {
|
|||||||
.list(new QueryWrapper<AgentTemplateEntity>().orderByAsc("sort"));
|
.list(new QueryWrapper<AgentTemplateEntity>().orderByAsc("sort"));
|
||||||
return new Result<List<AgentTemplateEntity>>().ok(list);
|
return new Result<List<AgentTemplateEntity>>().ok(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -17,4 +17,12 @@ public interface AgentTemplateService extends IService<AgentTemplateEntity> {
|
|||||||
* @return 默认模板实体
|
* @return 默认模板实体
|
||||||
*/
|
*/
|
||||||
AgentTemplateEntity getDefaultTemplate();
|
AgentTemplateEntity getDefaultTemplate();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新默认模板中的模型ID
|
||||||
|
*
|
||||||
|
* @param modelType 模型类型
|
||||||
|
* @param modelId 模型ID
|
||||||
|
*/
|
||||||
|
void updateDefaultTemplateModelId(String modelType, String modelId);
|
||||||
}
|
}
|
||||||
|
|||||||
+35
@@ -29,4 +29,39 @@ public class AgentTemplateServiceImpl extends ServiceImpl<AgentTemplateDao, Agen
|
|||||||
.last("LIMIT 1");
|
.last("LIMIT 1");
|
||||||
return this.getOne(wrapper);
|
return this.getOne(wrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新默认模板中的模型ID
|
||||||
|
*
|
||||||
|
* @param modelType 模型类型
|
||||||
|
* @param modelId 模型ID
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void updateDefaultTemplateModelId(String modelType, String modelId) {
|
||||||
|
modelType = modelType.toUpperCase();
|
||||||
|
AgentTemplateEntity defaultTemplate = getDefaultTemplate();
|
||||||
|
if (defaultTemplate != null) {
|
||||||
|
switch (modelType) {
|
||||||
|
case "ASR":
|
||||||
|
defaultTemplate.setAsrModelId(modelId);
|
||||||
|
break;
|
||||||
|
case "VAD":
|
||||||
|
defaultTemplate.setVadModelId(modelId);
|
||||||
|
break;
|
||||||
|
case "LLM":
|
||||||
|
defaultTemplate.setLlmModelId(modelId);
|
||||||
|
break;
|
||||||
|
case "TTS":
|
||||||
|
defaultTemplate.setTtsModelId(modelId);
|
||||||
|
break;
|
||||||
|
case "Memory":
|
||||||
|
defaultTemplate.setMemModelId(modelId);
|
||||||
|
break;
|
||||||
|
case "Intent":
|
||||||
|
defaultTemplate.setIntentModelId(modelId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this.updateById(defaultTemplate);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+61
@@ -0,0 +1,61 @@
|
|||||||
|
package xiaozhi.modules.config.controller;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import xiaozhi.common.constant.Constant;
|
||||||
|
import xiaozhi.common.exception.RenException;
|
||||||
|
import xiaozhi.common.utils.Result;
|
||||||
|
import xiaozhi.common.validator.ValidatorUtils;
|
||||||
|
import xiaozhi.modules.config.dto.AgentModelsDTO;
|
||||||
|
import xiaozhi.modules.config.service.ConfigService;
|
||||||
|
import xiaozhi.modules.sys.dto.ConfigSecretDTO;
|
||||||
|
import xiaozhi.modules.sys.service.SysParamsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* xiaozhi-server 配置获取
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("config")
|
||||||
|
@Tag(name = "参数管理")
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ConfigController {
|
||||||
|
private final ConfigService configService;
|
||||||
|
private final SysParamsService sysParamsService;
|
||||||
|
|
||||||
|
@PostMapping("server-base")
|
||||||
|
@Operation(summary = "获取配置")
|
||||||
|
public Result<Object> getConfig(@RequestBody ConfigSecretDTO dto) {
|
||||||
|
// 效验数据
|
||||||
|
ValidatorUtils.validateEntity(dto);
|
||||||
|
checkSecret(dto.getSecret());
|
||||||
|
Object config = configService.getConfig(true);
|
||||||
|
return new Result<Object>().ok(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("agent-models")
|
||||||
|
@Operation(summary = "获取智能体模型")
|
||||||
|
public Result<Object> getAgentModels(@RequestBody AgentModelsDTO dto) {
|
||||||
|
// 效验数据
|
||||||
|
ValidatorUtils.validateEntity(dto);
|
||||||
|
checkSecret(dto.getSecret());
|
||||||
|
Object models = configService.getAgentModels(dto.getMacAddress(), dto.getSelectedModule());
|
||||||
|
return new Result<Object>().ok(models);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkSecret(String secret) {
|
||||||
|
String secretParam = sysParamsService.getValue(Constant.SERVER_SECRET, true);
|
||||||
|
// 验证密钥
|
||||||
|
if (StringUtils.isBlank(secret) || !secret.equals(secretParam)) {
|
||||||
|
throw new RenException("密钥错误");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package xiaozhi.modules.config.dto;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "获取智能体模型配置DTO")
|
||||||
|
public class AgentModelsDTO {
|
||||||
|
@NotBlank(message = "密钥不能为空")
|
||||||
|
@Schema(description = "密钥")
|
||||||
|
private String secret;
|
||||||
|
|
||||||
|
@NotBlank(message = "设备MAC地址不能为空")
|
||||||
|
@Schema(description = "设备MAC地址")
|
||||||
|
private String macAddress;
|
||||||
|
|
||||||
|
@NotBlank(message = "客户端ID不能为空")
|
||||||
|
@Schema(description = "客户端ID")
|
||||||
|
private String clientId;
|
||||||
|
|
||||||
|
@NotNull(message = "客户端已实例化的模型不能为空")
|
||||||
|
@Schema(description = "客户端已实例化的模型")
|
||||||
|
private Map<String, String> selectedModule;
|
||||||
|
}
|
||||||
+6
-1
@@ -1,10 +1,11 @@
|
|||||||
package xiaozhi.modules.sys.config;
|
package xiaozhi.modules.config.init;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.DependsOn;
|
import org.springframework.context.annotation.DependsOn;
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import xiaozhi.modules.config.service.ConfigService;
|
||||||
import xiaozhi.modules.sys.service.SysParamsService;
|
import xiaozhi.modules.sys.service.SysParamsService;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@@ -14,8 +15,12 @@ public class SystemInitConfig {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private SysParamsService sysParamsService;
|
private SysParamsService sysParamsService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ConfigService configService;
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
sysParamsService.initServerSecret();
|
sysParamsService.initServerSecret();
|
||||||
|
configService.getConfig(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package xiaozhi.modules.config.service;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public interface ConfigService {
|
||||||
|
/**
|
||||||
|
* 获取服务器配置
|
||||||
|
*
|
||||||
|
* @param isCache 是否缓存
|
||||||
|
* @return 配置信息
|
||||||
|
*/
|
||||||
|
Object getConfig(Boolean isCache);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取智能体模型配置
|
||||||
|
*
|
||||||
|
* @param macAddress MAC地址
|
||||||
|
* @param selectedModule 客户端已实例化的模型
|
||||||
|
* @return 模型配置信息
|
||||||
|
*/
|
||||||
|
Map<String, Object> getAgentModels(String macAddress, Map<String, String> selectedModule);
|
||||||
|
}
|
||||||
+253
@@ -0,0 +1,253 @@
|
|||||||
|
package xiaozhi.modules.config.service.impl;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import xiaozhi.common.exception.RenException;
|
||||||
|
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.AgentTemplateEntity;
|
||||||
|
import xiaozhi.modules.agent.service.AgentService;
|
||||||
|
import xiaozhi.modules.agent.service.AgentTemplateService;
|
||||||
|
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;
|
||||||
|
|
||||||
|
@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;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getConfig(Boolean isCache) {
|
||||||
|
if (isCache) {
|
||||||
|
// 先从Redis获取配置
|
||||||
|
Object cachedConfig = redisUtils.get(RedisKeys.getServerConfigKey());
|
||||||
|
if (cachedConfig != null) {
|
||||||
|
return cachedConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建配置信息
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
buildConfig(result);
|
||||||
|
|
||||||
|
// 查询默认智能体
|
||||||
|
AgentTemplateEntity agent = agentTemplateService.getDefaultTemplate();
|
||||||
|
if (agent == null) {
|
||||||
|
throw new RenException("默认智能体未找到");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建模块配置
|
||||||
|
buildModuleConfig(
|
||||||
|
agent.getSystemPrompt(),
|
||||||
|
null,
|
||||||
|
agent.getVadModelId(),
|
||||||
|
agent.getAsrModelId(),
|
||||||
|
agent.getLlmModelId(),
|
||||||
|
agent.getTtsModelId(),
|
||||||
|
agent.getMemModelId(),
|
||||||
|
agent.getIntentModelId(),
|
||||||
|
result,
|
||||||
|
isCache);
|
||||||
|
|
||||||
|
// 将配置存入Redis
|
||||||
|
redisUtils.set(RedisKeys.getServerConfigKey(), result);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> getAgentModels(String macAddress, Map<String, String> selectedModule) {
|
||||||
|
// 根据MAC地址查找设备
|
||||||
|
DeviceEntity device = deviceService.getDeviceByMacAddress(macAddress);
|
||||||
|
if (device == null) {
|
||||||
|
throw new RenException("设备未找到");
|
||||||
|
}
|
||||||
|
// 获取智能体信息
|
||||||
|
AgentEntity agent = agentService.getAgentById(device.getAgentId());
|
||||||
|
if (agent == null) {
|
||||||
|
throw new RenException("智能体未找到");
|
||||||
|
}
|
||||||
|
// 获取音色信息
|
||||||
|
String voice = null;
|
||||||
|
TimbreDetailsVO timbre = timbreService.get(agent.getTtsVoiceId());
|
||||||
|
if (timbre != null) {
|
||||||
|
voice = timbre.getTtsVoice();
|
||||||
|
}
|
||||||
|
// 构建返回数据
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
|
||||||
|
// 如果客户端已实例化模型,则不返回
|
||||||
|
String alreadySelectedVadModelId = (String) selectedModule.get("VAD");
|
||||||
|
if (alreadySelectedVadModelId != null && alreadySelectedVadModelId.equals(agent.getVadModelId())) {
|
||||||
|
agent.setVadModelId(null);
|
||||||
|
}
|
||||||
|
String alreadySelectedAsrModelId = (String) selectedModule.get("ASR");
|
||||||
|
if (alreadySelectedAsrModelId != null && alreadySelectedAsrModelId.equals(agent.getAsrModelId())) {
|
||||||
|
agent.setAsrModelId(null);
|
||||||
|
}
|
||||||
|
String alreadySelectedLlmModelId = (String) selectedModule.get("LLM");
|
||||||
|
if (alreadySelectedLlmModelId != null && alreadySelectedLlmModelId.equals(agent.getLlmModelId())) {
|
||||||
|
agent.setLlmModelId(null);
|
||||||
|
}
|
||||||
|
String alreadySelectedMemModelId = (String) selectedModule.get("Memory");
|
||||||
|
if (alreadySelectedMemModelId != null && alreadySelectedMemModelId.equals(agent.getMemModelId())) {
|
||||||
|
agent.setMemModelId(null);
|
||||||
|
}
|
||||||
|
String alreadySelectedIntentModelId = (String) selectedModule.get("Intent");
|
||||||
|
if (alreadySelectedIntentModelId != null && alreadySelectedIntentModelId.equals(agent.getIntentModelId())) {
|
||||||
|
agent.setIntentModelId(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建模块配置
|
||||||
|
buildModuleConfig(
|
||||||
|
agent.getSystemPrompt(),
|
||||||
|
voice,
|
||||||
|
agent.getVadModelId(),
|
||||||
|
agent.getAsrModelId(),
|
||||||
|
agent.getLlmModelId(),
|
||||||
|
agent.getTtsModelId(),
|
||||||
|
agent.getMemModelId(),
|
||||||
|
agent.getIntentModelId(),
|
||||||
|
result,
|
||||||
|
true);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建配置信息
|
||||||
|
*
|
||||||
|
* @param paramsList 系统参数列表
|
||||||
|
* @return 配置信息
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private Object buildConfig(Map<String, Object> config) {
|
||||||
|
|
||||||
|
// 查询所有系统参数
|
||||||
|
List<SysParamsDTO> paramsList = sysParamsService.list(new HashMap<>());
|
||||||
|
|
||||||
|
for (SysParamsDTO param : paramsList) {
|
||||||
|
String[] keys = param.getParamCode().split("\\.");
|
||||||
|
Map<String, Object> current = config;
|
||||||
|
|
||||||
|
// 遍历除最后一个key之外的所有key
|
||||||
|
for (int i = 0; i < keys.length - 1; i++) {
|
||||||
|
String key = keys[i];
|
||||||
|
if (!current.containsKey(key)) {
|
||||||
|
current.put(key, new HashMap<String, Object>());
|
||||||
|
}
|
||||||
|
current = (Map<String, Object>) current.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理最后一个key
|
||||||
|
String lastKey = keys[keys.length - 1];
|
||||||
|
String value = param.getParamValue();
|
||||||
|
|
||||||
|
// 根据valueType转换值
|
||||||
|
switch (param.getValueType().toLowerCase()) {
|
||||||
|
case "number":
|
||||||
|
try {
|
||||||
|
current.put(lastKey, Double.parseDouble(value));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
current.put(lastKey, value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "boolean":
|
||||||
|
current.put(lastKey, Boolean.parseBoolean(value));
|
||||||
|
break;
|
||||||
|
case "array":
|
||||||
|
// 将分号分隔的字符串转换为数字数组
|
||||||
|
List<String> 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 prompt 提示词
|
||||||
|
* @param voice 音色
|
||||||
|
* @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 prompt,
|
||||||
|
String voice,
|
||||||
|
String vadModelId,
|
||||||
|
String asrModelId,
|
||||||
|
String llmModelId,
|
||||||
|
String ttsModelId,
|
||||||
|
String memModelId,
|
||||||
|
String intentModelId,
|
||||||
|
Map<String, Object> result,
|
||||||
|
boolean isCache) {
|
||||||
|
Map<String, String> selectedModule = new HashMap<>();
|
||||||
|
|
||||||
|
String[] modelTypes = { "VAD", "ASR", "LLM", "TTS", "Memory", "Intent" };
|
||||||
|
String[] modelIds = { vadModelId, asrModelId, llmModelId, ttsModelId, memModelId, intentModelId };
|
||||||
|
|
||||||
|
for (int i = 0; i < modelIds.length; i++) {
|
||||||
|
if (modelIds[i] == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ModelConfigEntity model = modelConfigService.getModelById(modelIds[i], isCache);
|
||||||
|
Map<String, Object> typeConfig = new HashMap<>();
|
||||||
|
if (model.getConfigJson() != null) {
|
||||||
|
typeConfig.put(model.getId(), model.getConfigJson());
|
||||||
|
// 如果是TTS类型,添加private_voice属性
|
||||||
|
if ("TTS".equals(modelTypes[i]) && voice != null) {
|
||||||
|
((Map<String, Object>) model.getConfigJson()).put("private_voice", voice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.put(modelTypes[i], typeConfig);
|
||||||
|
selectedModule.put(modelTypes[i], model.getId());
|
||||||
|
}
|
||||||
|
result.put("selected_module", selectedModule);
|
||||||
|
result.put("prompt", prompt);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -66,4 +66,12 @@ public interface DeviceService {
|
|||||||
* @return 用户列表分页数据
|
* @return 用户列表分页数据
|
||||||
*/
|
*/
|
||||||
PageData<UserShowDeviceListVO> page(DevicePageUserDTO dto);
|
PageData<UserShowDeviceListVO> page(DevicePageUserDTO dto);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据MAC地址获取设备信息
|
||||||
|
*
|
||||||
|
* @param macAddress MAC地址
|
||||||
|
* @return 设备信息
|
||||||
|
*/
|
||||||
|
DeviceEntity getDeviceByMacAddress(String macAddress);
|
||||||
}
|
}
|
||||||
+10
@@ -240,6 +240,16 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
return new PageData<>(list, page.getTotal());
|
return new PageData<>(list, page.getTotal());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DeviceEntity getDeviceByMacAddress(String macAddress) {
|
||||||
|
if (StringUtils.isBlank(macAddress)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
QueryWrapper<DeviceEntity> wrapper = new QueryWrapper<>();
|
||||||
|
wrapper.eq("mac_address", macAddress);
|
||||||
|
return baseDao.selectOne(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
private DeviceReportRespDTO.ServerTime buildServerTime() {
|
private DeviceReportRespDTO.ServerTime buildServerTime() {
|
||||||
DeviceReportRespDTO.ServerTime serverTime = new DeviceReportRespDTO.ServerTime();
|
DeviceReportRespDTO.ServerTime serverTime = new DeviceReportRespDTO.ServerTime();
|
||||||
TimeZone tz = TimeZone.getDefault();
|
TimeZone tz = TimeZone.getDefault();
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ import lombok.AllArgsConstructor;
|
|||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
import xiaozhi.common.utils.ConvertUtils;
|
import xiaozhi.common.utils.ConvertUtils;
|
||||||
import xiaozhi.common.utils.Result;
|
import xiaozhi.common.utils.Result;
|
||||||
|
import xiaozhi.modules.agent.service.AgentTemplateService;
|
||||||
|
import xiaozhi.modules.config.service.ConfigService;
|
||||||
import xiaozhi.modules.model.dto.ModelBasicInfoDTO;
|
import xiaozhi.modules.model.dto.ModelBasicInfoDTO;
|
||||||
import xiaozhi.modules.model.dto.ModelConfigBodyDTO;
|
import xiaozhi.modules.model.dto.ModelConfigBodyDTO;
|
||||||
import xiaozhi.modules.model.dto.ModelConfigDTO;
|
import xiaozhi.modules.model.dto.ModelConfigDTO;
|
||||||
@@ -38,6 +40,8 @@ public class ModelController {
|
|||||||
private final ModelProviderService modelProviderService;
|
private final ModelProviderService modelProviderService;
|
||||||
private final TimbreService timbreService;
|
private final TimbreService timbreService;
|
||||||
private final ModelConfigService modelConfigService;
|
private final ModelConfigService modelConfigService;
|
||||||
|
private final ConfigService configService;
|
||||||
|
private final AgentTemplateService agentTemplateService;
|
||||||
|
|
||||||
@GetMapping("/names")
|
@GetMapping("/names")
|
||||||
@Operation(summary = "获取所有模型名称")
|
@Operation(summary = "获取所有模型名称")
|
||||||
@@ -75,6 +79,7 @@ public class ModelController {
|
|||||||
@PathVariable String provideCode,
|
@PathVariable String provideCode,
|
||||||
@RequestBody ModelConfigBodyDTO modelConfigBodyDTO) {
|
@RequestBody ModelConfigBodyDTO modelConfigBodyDTO) {
|
||||||
ModelConfigDTO modelConfigDTO = modelConfigService.add(modelType, provideCode, modelConfigBodyDTO);
|
ModelConfigDTO modelConfigDTO = modelConfigService.add(modelType, provideCode, modelConfigBodyDTO);
|
||||||
|
configService.getConfig(false);
|
||||||
return new Result<ModelConfigDTO>().ok(modelConfigDTO);
|
return new Result<ModelConfigDTO>().ok(modelConfigDTO);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,6 +91,7 @@ public class ModelController {
|
|||||||
@PathVariable String id,
|
@PathVariable String id,
|
||||||
@RequestBody ModelConfigBodyDTO modelConfigBodyDTO) {
|
@RequestBody ModelConfigBodyDTO modelConfigBodyDTO) {
|
||||||
ModelConfigDTO modelConfigDTO = modelConfigService.edit(modelType, provideCode, id, modelConfigBodyDTO);
|
ModelConfigDTO modelConfigDTO = modelConfigService.edit(modelType, provideCode, id, modelConfigBodyDTO);
|
||||||
|
configService.getConfig(false);
|
||||||
return new Result<ModelConfigDTO>().ok(modelConfigDTO);
|
return new Result<ModelConfigDTO>().ok(modelConfigDTO);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,6 +125,26 @@ public class ModelController {
|
|||||||
return new Result<Void>();
|
return new Result<Void>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PutMapping("/default/{id}")
|
||||||
|
@Operation(summary = "设置默认模型")
|
||||||
|
@RequiresPermissions("sys:role:superAdmin")
|
||||||
|
public Result<Void> setDefaultModel(@PathVariable String id) {
|
||||||
|
ModelConfigEntity entity = modelConfigService.selectById(id);
|
||||||
|
if (entity == null) {
|
||||||
|
return new Result<Void>().error("模型配置不存在");
|
||||||
|
}
|
||||||
|
// 将其他模型设置为非默认
|
||||||
|
modelConfigService.setDefaultModel(entity.getModelType(), 0);
|
||||||
|
entity.setIsDefault(1);
|
||||||
|
modelConfigService.updateById(entity);
|
||||||
|
|
||||||
|
// 更新模板表中对应的模型ID
|
||||||
|
agentTemplateService.updateDefaultTemplateModelId(entity.getModelType(), entity.getId());
|
||||||
|
|
||||||
|
configService.getConfig(false);
|
||||||
|
return new Result<Void>();
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/{modelId}/voices")
|
@GetMapping("/{modelId}/voices")
|
||||||
@Operation(summary = "获取模型音色")
|
@Operation(summary = "获取模型音色")
|
||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
|
|||||||
@@ -28,4 +28,21 @@ public interface ModelConfigService extends BaseService<ModelConfigEntity> {
|
|||||||
* @return 模型名称
|
* @return 模型名称
|
||||||
*/
|
*/
|
||||||
String getModelNameById(String id);
|
String getModelNameById(String id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据ID获取模型配置
|
||||||
|
*
|
||||||
|
* @param id 模型ID
|
||||||
|
* @param isCache 是否缓存
|
||||||
|
* @return 模型配置实体
|
||||||
|
*/
|
||||||
|
ModelConfigEntity getModelById(String id, boolean isCache);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置默认模型
|
||||||
|
*
|
||||||
|
* @param modelType 模型类型
|
||||||
|
* @param isDefault 是否默认
|
||||||
|
*/
|
||||||
|
void setDefaultModel(String modelType, int isDefault);
|
||||||
}
|
}
|
||||||
|
|||||||
+28
@@ -94,6 +94,8 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
|
|||||||
modelConfigEntity.setId(id);
|
modelConfigEntity.setId(id);
|
||||||
modelConfigEntity.setModelType(modelType);
|
modelConfigEntity.setModelType(modelType);
|
||||||
modelConfigDao.updateById(modelConfigEntity);
|
modelConfigDao.updateById(modelConfigEntity);
|
||||||
|
// 清除缓存
|
||||||
|
redisUtils.delete(RedisKeys.getModelConfigById(modelConfigEntity.getId()));
|
||||||
return ConvertUtils.sourceToTarget(modelConfigEntity, ModelConfigDTO.class);
|
return ConvertUtils.sourceToTarget(modelConfigEntity, ModelConfigDTO.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,4 +127,30 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ModelConfigEntity getModelById(String id, boolean isCache) {
|
||||||
|
if (StringUtils.isBlank(id)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (isCache) {
|
||||||
|
ModelConfigEntity cachedConfig = (ModelConfigEntity) redisUtils.get(RedisKeys.getModelConfigById(id));
|
||||||
|
if (cachedConfig != null) {
|
||||||
|
return ConvertUtils.sourceToTarget(cachedConfig, ModelConfigEntity.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ModelConfigEntity entity = modelConfigDao.selectById(id);
|
||||||
|
if (entity != null) {
|
||||||
|
redisUtils.set(RedisKeys.getModelConfigById(id), entity);
|
||||||
|
}
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDefaultModel(String modelType, int isDefault) {
|
||||||
|
ModelConfigEntity entity = new ModelConfigEntity();
|
||||||
|
entity.setIsDefault(isDefault);
|
||||||
|
modelConfigDao.update(entity, new QueryWrapper<ModelConfigEntity>()
|
||||||
|
.eq("model_type", modelType));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,7 +77,8 @@ public class ShiroConfig {
|
|||||||
filterMap.put("/user/captcha", "anon");
|
filterMap.put("/user/captcha", "anon");
|
||||||
filterMap.put("/user/login", "anon");
|
filterMap.put("/user/login", "anon");
|
||||||
filterMap.put("/user/register", "anon");
|
filterMap.put("/user/register", "anon");
|
||||||
filterMap.put("/device/register", "anon");
|
filterMap.put("/config/server-base", "anon");
|
||||||
|
filterMap.put("/config/agent-models", "anon");
|
||||||
filterMap.put("/**", "oauth2");
|
filterMap.put("/**", "oauth2");
|
||||||
shiroFilter.setFilterChainDefinitionMap(filterMap);
|
shiroFilter.setFilterChainDefinitionMap(filterMap);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package xiaozhi.modules.sys.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "配置密钥DTO")
|
||||||
|
public class ConfigSecretDTO {
|
||||||
|
@Schema(description = "密钥")
|
||||||
|
@NotBlank(message = "密钥不能为空")
|
||||||
|
private String secret;
|
||||||
|
}
|
||||||
+7
@@ -125,6 +125,13 @@ public class SysParamsServiceImpl extends BaseServiceImpl<SysParamsDao, SysParam
|
|||||||
throw new RenException(ErrorCode.PARAM_BOOLEAN_INVALID);
|
throw new RenException(ErrorCode.PARAM_BOOLEAN_INVALID);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "json":
|
||||||
|
try {
|
||||||
|
JsonUtils.parseObject(paramValue, Object.class);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RenException(ErrorCode.PARAM_JSON_INVALID);
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new RenException(ErrorCode.PARAM_TYPE_INVALID);
|
throw new RenException(ErrorCode.PARAM_TYPE_INVALID);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ public interface TimbreService extends BaseService<TimbreEntity> {
|
|||||||
* @param timbreId 音色表id
|
* @param timbreId 音色表id
|
||||||
* @return 音色信息
|
* @return 音色信息
|
||||||
*/
|
*/
|
||||||
TimbreDetailsVO get(Long timbreId);
|
TimbreDetailsVO get(String timbreId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存音色信息
|
* 保存音色信息
|
||||||
|
|||||||
+28
-2
@@ -59,9 +59,33 @@ public class TimbreServiceImpl extends BaseServiceImpl<TimbreDao, TimbreEntity>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TimbreDetailsVO get(Long timbreId) {
|
public TimbreDetailsVO get(String timbreId) {
|
||||||
|
if (StringUtils.isBlank(timbreId)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 先从Redis获取缓存
|
||||||
|
String key = RedisKeys.getTimbreDetailsKey(timbreId);
|
||||||
|
TimbreDetailsVO cachedDetails = (TimbreDetailsVO) redisUtils.get(key);
|
||||||
|
if (cachedDetails != null) {
|
||||||
|
return cachedDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果缓存中没有,则从数据库获取
|
||||||
TimbreEntity entity = baseDao.selectById(timbreId);
|
TimbreEntity entity = baseDao.selectById(timbreId);
|
||||||
return ConvertUtils.sourceToTarget(entity, TimbreDetailsVO.class);
|
if (entity == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为VO对象
|
||||||
|
TimbreDetailsVO details = ConvertUtils.sourceToTarget(entity, TimbreDetailsVO.class);
|
||||||
|
|
||||||
|
// 存入Redis缓存
|
||||||
|
if (details != null) {
|
||||||
|
redisUtils.set(key, details);
|
||||||
|
}
|
||||||
|
|
||||||
|
return details;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -79,6 +103,8 @@ public class TimbreServiceImpl extends BaseServiceImpl<TimbreDao, TimbreEntity>
|
|||||||
TimbreEntity timbreEntity = ConvertUtils.sourceToTarget(dto, TimbreEntity.class);
|
TimbreEntity timbreEntity = ConvertUtils.sourceToTarget(dto, TimbreEntity.class);
|
||||||
timbreEntity.setId(timbreId);
|
timbreEntity.setId(timbreId);
|
||||||
baseDao.updateById(timbreEntity);
|
baseDao.updateById(timbreEntity);
|
||||||
|
// 删除缓存
|
||||||
|
redisUtils.delete(RedisKeys.getTimbreDetailsKey(timbreId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+39
-37
@@ -4,50 +4,52 @@
|
|||||||
DELETE FROM `ai_model_config`;
|
DELETE FROM `ai_model_config`;
|
||||||
|
|
||||||
-- VAD模型配置
|
-- VAD模型配置
|
||||||
INSERT INTO `ai_model_config` VALUES ('VAD_SileroVAD', 'VAD', 'SileroVAD', '语音活动检测', 1, 1, '{\"type\": \"silero\", \"model_dir\": \"models/snakers4_silero-vad\", \"threshold\": 0.5, \"min_silence_duration_ms\": 700}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('VAD_SileroVAD', 'VAD', 'SileroVAD', '语音活动检测', 1, 1, '{\"type\": \"silero\", \"model_dir\": \"models/snakers4_silero-vad\", \"threshold\": 0.5, \"min_silence_duration_ms\": 700}', NULL, NULL, 1, NULL, NULL, NULL, NULL);
|
||||||
|
|
||||||
-- ASR模型配置
|
-- ASR模型配置
|
||||||
INSERT INTO `ai_model_config` VALUES ('ASR_FunASR', 'ASR', 'FunASR', 'FunASR语音识别', 1, 1, '{\"type\": \"fun_local\", \"model_dir\": \"models/SenseVoiceSmall\", \"output_dir\": \"tmp/\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('ASR_FunASR', 'ASR', 'FunASR', 'FunASR语音识别', 1, 1, '{\"type\": \"fun_local\", \"model_dir\": \"models/SenseVoiceSmall\", \"output_dir\": \"tmp/\"}', NULL, NULL, 1, NULL, NULL, NULL, NULL);
|
||||||
INSERT INTO `ai_model_config` VALUES ('ASR_SherpaASR', 'ASR', 'SherpaASR', 'Sherpa语音识别', 1, 0, '{\"type\": \"sherpa_onnx_local\", \"model_dir\": \"models/sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17\", \"output_dir\": \"tmp/\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('ASR_SherpaASR', 'ASR', 'SherpaASR', 'Sherpa语音识别', 0, 1, '{\"type\": \"sherpa_onnx_local\", \"model_dir\": \"models/sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17\", \"output_dir\": \"tmp/\"}', NULL, NULL, 2, NULL, NULL, NULL, NULL);
|
||||||
INSERT INTO `ai_model_config` VALUES ('ASR_DoubaoASR', 'ASR', 'DoubaoASR', '豆包语音识别', 1, 0, '{\"type\": \"doubao\", \"appid\": \"\", \"access_token\": \"\", \"cluster\": \"volcengine_input_common\", \"output_dir\": \"tmp/\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('ASR_DoubaoASR', 'ASR', 'DoubaoASR', '豆包语音识别', 0, 1, '{\"type\": \"doubao\", \"appid\": \"\", \"access_token\": \"\", \"cluster\": \"volcengine_input_common\", \"output_dir\": \"tmp/\"}', NULL, NULL, 3, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_model_config` VALUES ('ASR_TencentASR', 'ASR', 'TencentASR', '腾讯语音识别', 0, 1, '{\"type\": \"tencent\", \"appid\": \"\", \"secret_id\": \"\", \"secret_key\": \"你的secret_key\", \"output_dir\": \"tmp/\"}', NULL, NULL, 4, NULL, NULL, NULL, NULL);
|
||||||
|
|
||||||
-- LLM模型配置
|
-- LLM模型配置
|
||||||
INSERT INTO `ai_model_config` VALUES ('LLM_AliLLM', 'LLM', 'AliLLM', '通义千问', 1, 0, '{\"type\": \"openai\", \"base_url\": \"https://dashscope.aliyuncs.com/compatible-mode/v1\", \"model_name\": \"qwen-turbo\", \"api_key\": \"\", \"temperature\": 0.7, \"max_tokens\": 500, \"top_p\": 1, \"top_k\": 50, \"frequency_penalty\": 0}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('LLM_ChatGLMLLM', 'LLM', 'ChatGLMLLM', '智谱AI', 1, 1, '{\"type\": \"openai\", \"model_name\": \"glm-4-flash\", \"base_url\": \"https://open.bigmodel.cn/api/paas/v4/\", \"api_key\": \"你的api_key\"}', NULL, NULL, 1, NULL, NULL, NULL, NULL);
|
||||||
INSERT INTO `ai_model_config` VALUES ('LLM_AliAppLLM', 'LLM', 'AliAppLLM', '通义百炼', 1, 0, '{\"type\": \"AliBL\", \"base_url\": \"https://dashscope.aliyuncs.com/compatible-mode/v1\", \"app_id\": \"\", \"api_key\": \"\", \"is_no_prompt\": true, \"ali_memory_id\": false}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('LLM_OllamaLLM', 'LLM', 'OllamaLLM', 'Ollama本地模型', 0, 1, '{\"type\": \"ollama\", \"model_name\": \"qwen2.5\", \"base_url\": \"http://localhost:11434\"}', NULL, NULL, 2, NULL, NULL, NULL, NULL);
|
||||||
INSERT INTO `ai_model_config` VALUES ('LLM_DoubaoLLM', 'LLM', 'DoubaoLLM', '豆包大模型', 1, 0, '{\"type\": \"openai\", \"base_url\": \"https://ark.cn-beijing.volces.com/api/v3\", \"model_name\": \"doubao-pro-32k-functioncall-241028\", \"api_key\": \"\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('LLM_AliLLM', 'LLM', 'AliLLM', '通义千问', 0, 1, '{\"type\": \"openai\", \"base_url\": \"https://dashscope.aliyuncs.com/compatible-mode/v1\", \"model_name\": \"qwen-turbo\", \"api_key\": \"你的api_key\", \"temperature\": 0.7, \"max_tokens\": 500, \"top_p\": 1, \"top_k\": 50, \"frequency_penalty\": 0}', NULL, NULL, 3, NULL, NULL, NULL, NULL);
|
||||||
INSERT INTO `ai_model_config` VALUES ('LLM_DeepSeekLLM', 'LLM', 'DeepSeekLLM', 'DeepSeek', 1, 0, '{\"type\": \"openai\", \"model_name\": \"deepseek-chat\", \"url\": \"https://api.deepseek.com\", \"api_key\": \"\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('LLM_AliAppLLM', 'LLM', 'AliAppLLM', '通义百炼', 0, 1, '{\"type\": \"AliBL\", \"base_url\": \"https://dashscope.aliyuncs.com/compatible-mode/v1\", \"app_id\": \"你的app_id\", \"api_key\": \"你的api_key\", \"is_no_prompt\": true, \"ali_memory_id\": false}', NULL, NULL, 4, NULL, NULL, NULL, NULL);
|
||||||
INSERT INTO `ai_model_config` VALUES ('LLM_ChatGLMLLM', 'LLM', 'ChatGLMLLM', '智谱AI', 1, 1, '{\"type\": \"openai\", \"model_name\": \"glm-4-flash\", \"url\": \"https://open.bigmodel.cn/api/paas/v4/\", \"api_key\": \"\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('LLM_DoubaoLLM', 'LLM', 'DoubaoLLM', '豆包大模型', 0, 1, '{\"type\": \"openai\", \"base_url\": \"https://ark.cn-beijing.volces.com/api/v3\", \"model_name\": \"doubao-pro-32k-functioncall-241028\", \"api_key\": \"你的api_key\"}', NULL, NULL, 5, NULL, NULL, NULL, NULL);
|
||||||
INSERT INTO `ai_model_config` VALUES ('LLM_OllamaLLM', 'LLM', 'OllamaLLM', 'Ollama本地模型', 1, 0, '{\"type\": \"ollama\", \"model_name\": \"qwen2.5\", \"base_url\": \"http://localhost:11434\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('LLM_DeepSeekLLM', 'LLM', 'DeepSeekLLM', 'DeepSeek', 0, 1, '{\"type\": \"openai\", \"model_name\": \"deepseek-chat\", \"base_url\": \"https://api.deepseek.com\", \"api_key\": \"你的api_key\"}', NULL, NULL, 6, NULL, NULL, NULL, NULL);
|
||||||
INSERT INTO `ai_model_config` VALUES ('LLM_DifyLLM', 'LLM', 'DifyLLM', 'Dify', 1, 0, '{\"type\": \"dify\", \"base_url\": \"https://api.dify.ai/v1\", \"api_key\": \"\", \"mode\": \"chat-messages\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('LLM_DifyLLM', 'LLM', 'DifyLLM', 'Dify', 0, 1, '{\"type\": \"dify\", \"base_url\": \"https://api.dify.ai/v1\", \"api_key\": \"你的api_key\", \"mode\": \"chat-messages\"}', NULL, NULL, 7, NULL, NULL, NULL, NULL);
|
||||||
INSERT INTO `ai_model_config` VALUES ('LLM_GeminiLLM', 'LLM', 'GeminiLLM', '谷歌Gemini', 1, 0, '{\"type\": \"gemini\", \"api_key\": \"\", \"model_name\": \"gemini-2.0-flash\", \"http_proxy\": \"\", \"https_proxy\": \"\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('LLM_GeminiLLM', 'LLM', 'GeminiLLM', '谷歌Gemini', 0, 1, '{\"type\": \"gemini\", \"api_key\": \"你的api_key\", \"model_name\": \"gemini-2.0-flash\", \"http_proxy\": \"\", \"https_proxy\": \"\"}', NULL, NULL, 8, NULL, NULL, NULL, NULL);
|
||||||
INSERT INTO `ai_model_config` VALUES ('LLM_CozeLLM', 'LLM', 'CozeLLM', 'Coze', 1, 0, '{\"type\": \"coze\", \"bot_id\": \"\", \"user_id\": \"\", \"personal_access_token\": \"\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('LLM_CozeLLM', 'LLM', 'CozeLLM', 'Coze', 0, 1, '{\"type\": \"coze\", \"bot_id\": \"你的bot_id\", \"user_id\": \"你的user_id\", \"personal_access_token\": \"你的personal_access_token\"}', NULL, NULL, 9, NULL, NULL, NULL, NULL);
|
||||||
INSERT INTO `ai_model_config` VALUES ('LLM_LMStudioLLM', 'LLM', 'LMStudioLLM', 'LM Studio', 1, 0, '{\"type\": \"openai\", \"model_name\": \"deepseek-r1-distill-llama-8b@q4_k_m\", \"url\": \"http://localhost:1234/v1\", \"api_key\": \"lm-studio\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('LLM_LMStudioLLM', 'LLM', 'LMStudioLLM', 'LM Studio', 0, 1, '{\"type\": \"openai\", \"model_name\": \"deepseek-r1-distill-llama-8b@q4_k_m\", \"base_url\": \"http://localhost:1234/v1\", \"api_key\": \"lm-studio\"}', NULL, NULL, 10, NULL, NULL, NULL, NULL);
|
||||||
INSERT INTO `ai_model_config` VALUES ('LLM_FastgptLLM', 'LLM', 'FastgptLLM', 'FastGPT', 1, 0, '{\"type\": \"fastgpt\", \"base_url\": \"https://host/api/v1\", \"api_key\": \"fastgpt-xxx\", \"variables\": {\"k\": \"v\", \"k2\": \"v2\"}}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('LLM_FastgptLLM', 'LLM', 'FastgptLLM', 'FastGPT', 0, 1, '{\"type\": \"fastgpt\", \"base_url\": \"https://host/api/v1\", \"api_key\": \"fastgpt-xxx\", \"variables\": {\"k\": \"v\", \"k2\": \"v2\"}}', NULL, NULL, 11, NULL, NULL, NULL, NULL);
|
||||||
INSERT INTO `ai_model_config` VALUES ('LLM_XinferenceLLM', 'LLM', 'XinferenceLLM', 'Xinference大模型', 1, 0, '{\"type\": \"xinference\", \"model_name\": \"qwen2.5:72b-AWQ\", \"base_url\": \"http://localhost:9997\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('LLM_XinferenceLLM', 'LLM', 'XinferenceLLM', 'Xinference大模型', 0, 1, '{\"type\": \"xinference\", \"model_name\": \"qwen2.5:72b-AWQ\", \"base_url\": \"http://localhost:9997\"}', NULL, NULL, 12, NULL, NULL, NULL, NULL);
|
||||||
INSERT INTO `ai_model_config` VALUES ('LLM_XinferenceSmallLLM', 'LLM', 'XinferenceSmallLLM', 'Xinference小模型', 1, 0, '{\"type\": \"xinference\", \"model_name\": \"qwen2.5:3b-AWQ\", \"base_url\": \"http://localhost:9997\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('LLM_XinferenceSmallLLM', 'LLM', 'XinferenceSmallLLM', 'Xinference小模型', 0, 1, '{\"type\": \"xinference\", \"model_name\": \"qwen2.5:3b-AWQ\", \"base_url\": \"http://localhost:9997\"}', NULL, NULL, 13, NULL, NULL, NULL, NULL);
|
||||||
|
|
||||||
-- TTS模型配置
|
-- TTS模型配置
|
||||||
INSERT INTO `ai_model_config` VALUES ('TTS_EdgeTTS', 'TTS', 'EdgeTTS', 'Edge语音合成', 1, 1, '{\"type\": \"edge\", \"voice\": \"zh-CN-XiaoxiaoNeural\", \"output_dir\": \"tmp/\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('TTS_EdgeTTS', 'TTS', 'EdgeTTS', 'Edge语音合成', 1, 1, '{\"type\": \"edge\", \"voice\": \"zh-CN-XiaoxiaoNeural\", \"output_dir\": \"tmp/\"}', NULL, NULL, 1, NULL, NULL, NULL, NULL);
|
||||||
INSERT INTO `ai_model_config` VALUES ('TTS_DoubaoTTS', 'TTS', 'DoubaoTTS', '豆包语音合成', 1, 0, '{\"type\": \"doubao\", \"api_url\": \"https://openspeech.bytedance.com/api/v1/tts\", \"voice\": \"BV001_streaming\", \"output_dir\": \"tmp/\", \"authorization\": \"Bearer;\", \"appid\": \"\", \"access_token\": \"\", \"cluster\": \"volcano_tts\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('TTS_DoubaoTTS', 'TTS', 'DoubaoTTS', '豆包语音合成', 0, 1, '{\"type\": \"doubao\", \"api_url\": \"https://openspeech.bytedance.com/api/v1/tts\", \"voice\": \"BV001_streaming\", \"output_dir\": \"tmp/\", \"authorization\": \"Bearer;\", \"appid\": \"\", \"access_token\": \"\", \"cluster\": \"volcano_tts\"}', NULL, NULL, 2, NULL, NULL, NULL, NULL);
|
||||||
INSERT INTO `ai_model_config` VALUES ('TTS_CosyVoiceSiliconflow', 'TTS', 'CosyVoiceSiliconflow', '硅基流动语音合成', 1, 0, '{\"type\": \"siliconflow\", \"model\": \"FunAudioLLM/CosyVoice2-0.5B\", \"voice\": \"FunAudioLLM/CosyVoice2-0.5B:alex\", \"output_dir\": \"tmp/\", \"access_token\": \"\", \"response_format\": \"wav\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('TTS_CosyVoiceSiliconflow', 'TTS', 'CosyVoiceSiliconflow', '硅基流动语音合成', 0, 1, '{\"type\": \"siliconflow\", \"model\": \"FunAudioLLM/CosyVoice2-0.5B\", \"voice\": \"FunAudioLLM/CosyVoice2-0.5B:alex\", \"output_dir\": \"tmp/\", \"access_token\": \"\", \"response_format\": \"wav\"}', NULL, NULL, 3, NULL, NULL, NULL, NULL);
|
||||||
INSERT INTO `ai_model_config` VALUES ('TTS_CozeCnTTS', 'TTS', 'CozeCnTTS', 'Coze中文语音合成', 1, 0, '{\"type\": \"cozecn\", \"voice\": \"7426720361733046281\", \"output_dir\": \"tmp/\", \"access_token\": \"\", \"response_format\": \"wav\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('TTS_CozeCnTTS', 'TTS', 'CozeCnTTS', 'Coze中文语音合成', 0, 1, '{\"type\": \"cozecn\", \"voice\": \"7426720361733046281\", \"output_dir\": \"tmp/\", \"access_token\": \"\", \"response_format\": \"wav\"}', NULL, NULL, 4, NULL, NULL, NULL, NULL);
|
||||||
INSERT INTO `ai_model_config` VALUES ('TTS_FishSpeech', 'TTS', 'FishSpeech', 'FishSpeech语音合成', 1, 0, '{\"type\": \"fishspeech\", \"output_dir\": \"tmp/\", \"response_format\": \"wav\", \"reference_id\": null, \"reference_audio\": [\"/tmp/test.wav\"], \"reference_text\": [\"你弄来这些吟词宴曲来看,还是这些混话来欺负我。\"], \"normalize\": true, \"max_new_tokens\": 1024, \"chunk_length\": 200, \"top_p\": 0.7, \"repetition_penalty\": 1.2, \"temperature\": 0.7, \"streaming\": false, \"use_memory_cache\": \"on\", \"seed\": null, \"channels\": 1, \"rate\": 44100, \"api_key\": \"\", \"api_url\": \"http://127.0.0.1:8080/v1/tts\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('TTS_FishSpeech', 'TTS', 'FishSpeech', 'FishSpeech语音合成', 0, 1, '{\"type\": \"fishspeech\", \"output_dir\": \"tmp/\", \"response_format\": \"wav\", \"reference_id\": null, \"reference_audio\": [\"/tmp/test.wav\"], \"reference_text\": [\"你弄来这些吟词宴曲来看,还是这些混话来欺负我。\"], \"normalize\": true, \"max_new_tokens\": 1024, \"chunk_length\": 200, \"top_p\": 0.7, \"repetition_penalty\": 1.2, \"temperature\": 0.7, \"streaming\": false, \"use_memory_cache\": \"on\", \"seed\": null, \"channels\": 1, \"rate\": 44100, \"api_key\": \"\", \"api_url\": \"http://127.0.0.1:8080/v1/tts\"}', NULL, NULL, 5, NULL, NULL, NULL, NULL);
|
||||||
INSERT INTO `ai_model_config` VALUES ('TTS_GPT_SOVITS_V2', 'TTS', 'GPT_SOVITS_V2', 'GPT-SoVITS V2', 1, 0, '{\"type\": \"gpt_sovits_v2\", \"url\": \"http://127.0.0.1:9880/tts\", \"output_dir\": \"tmp/\", \"text_lang\": \"auto\", \"ref_audio_path\": \"caixukun.wav\", \"prompt_text\": \"\", \"prompt_lang\": \"zh\", \"top_k\": 5, \"top_p\": 1, \"temperature\": 1, \"text_split_method\": \"cut0\", \"batch_size\": 1, \"batch_threshold\": 0.75, \"split_bucket\": true, \"return_fragment\": false, \"speed_factor\": 1.0, \"streaming_mode\": false, \"seed\": -1, \"parallel_infer\": true, \"repetition_penalty\": 1.35, \"aux_ref_audio_paths\": []}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('TTS_GPT_SOVITS_V2', 'TTS', 'GPT_SOVITS_V2', 'GPT-SoVITS V2', 0, 1, '{\"type\": \"gpt_sovits_v2\", \"url\": \"http://127.0.0.1:9880/tts\", \"output_dir\": \"tmp/\", \"text_lang\": \"auto\", \"ref_audio_path\": \"caixukun.wav\", \"prompt_text\": \"\", \"prompt_lang\": \"zh\", \"top_k\": 5, \"top_p\": 1, \"temperature\": 1, \"text_split_method\": \"cut0\", \"batch_size\": 1, \"batch_threshold\": 0.75, \"split_bucket\": true, \"return_fragment\": false, \"speed_factor\": 1.0, \"streaming_mode\": false, \"seed\": -1, \"parallel_infer\": true, \"repetition_penalty\": 1.35, \"aux_ref_audio_paths\": []}', NULL, NULL, 6, NULL, NULL, NULL, NULL);
|
||||||
INSERT INTO `ai_model_config` VALUES ('TTS_GPT_SOVITS_V3', 'TTS', 'GPT_SOVITS_V3', 'GPT-SoVITS V3', 1, 0, '{\"type\": \"gpt_sovits_v3\", \"url\": \"http://127.0.0.1:9880\", \"output_dir\": \"tmp/\", \"text_language\": \"auto\", \"refer_wav_path\": \"caixukun.wav\", \"prompt_language\": \"zh\", \"prompt_text\": \"\", \"top_k\": 15, \"top_p\": 1.0, \"temperature\": 1.0, \"cut_punc\": \"\", \"speed\": 1.0, \"inp_refs\": [], \"sample_steps\": 32, \"if_sr\": false}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('TTS_GPT_SOVITS_V3', 'TTS', 'GPT_SOVITS_V3', 'GPT-SoVITS V3', 0, 1, '{\"type\": \"gpt_sovits_v3\", \"url\": \"http://127.0.0.1:9880\", \"output_dir\": \"tmp/\", \"text_language\": \"auto\", \"refer_wav_path\": \"caixukun.wav\", \"prompt_language\": \"zh\", \"prompt_text\": \"\", \"top_k\": 15, \"top_p\": 1.0, \"temperature\": 1.0, \"cut_punc\": \"\", \"speed\": 1.0, \"inp_refs\": [], \"sample_steps\": 32, \"if_sr\": false}', NULL, NULL, 7, NULL, NULL, NULL, NULL);
|
||||||
INSERT INTO `ai_model_config` VALUES ('TTS_MinimaxTTS', 'TTS', 'MinimaxTTS', 'MiniMax语音合成', 1, 0, '{\"type\": \"minimax\", \"output_dir\": \"tmp/\", \"group_id\": \"\", \"api_key\": \"\", \"model\": \"speech-01-turbo\", \"voice_id\": \"female-shaonv\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('TTS_MinimaxTTS', 'TTS', 'MinimaxTTS', 'MiniMax语音合成', 0, 1, '{\"type\": \"minimax\", \"output_dir\": \"tmp/\", \"group_id\": \"\", \"api_key\": \"你的api_key\", \"model\": \"speech-01-turbo\", \"voice_id\": \"female-shaonv\"}', NULL, NULL, 8, NULL, NULL, NULL, NULL);
|
||||||
INSERT INTO `ai_model_config` VALUES ('TTS_AliyunTTS', 'TTS', 'AliyunTTS', '阿里云语音合成', 1, 0, '{\"type\": \"aliyun\", \"output_dir\": \"tmp/\", \"appkey\": \"\", \"token\": \"\", \"voice\": \"xiaoyun\", \"access_key_id\": \"\", \"access_key_secret\": \"\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('TTS_AliyunTTS', 'TTS', 'AliyunTTS', '阿里云语音合成', 0, 1, '{\"type\": \"aliyun\", \"output_dir\": \"tmp/\", \"appkey\": \"\", \"token\": \"\", \"voice\": \"xiaoyun\", \"access_key_id\": \"\", \"access_key_secret\": \"\"}', NULL, NULL, 9, NULL, NULL, NULL, NULL);
|
||||||
INSERT INTO `ai_model_config` VALUES ('TTS_TTS302AI', 'TTS', 'TTS302AI', '302AI语音合成', 1, 0, '{\"type\": \"doubao\", \"api_url\": \"https://api.302ai.cn/doubao/tts_hd\", \"authorization\": \"Bearer \", \"voice\": \"zh_female_wanwanxiaohe_moon_bigtts\", \"output_dir\": \"tmp/\", \"access_token\": \"\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('TTS_TTS302AI', 'TTS', 'TTS302AI', '302AI语音合成', 0, 1, '{\"type\": \"doubao\", \"api_url\": \"https://api.302ai.cn/doubao/tts_hd\", \"authorization\": \"Bearer \", \"voice\": \"zh_female_wanwanxiaohe_moon_bigtts\", \"output_dir\": \"tmp/\", \"access_token\": \"\"}', NULL, NULL, 10, NULL, NULL, NULL, NULL);
|
||||||
INSERT INTO `ai_model_config` VALUES ('TTS_GizwitsTTS', 'TTS', 'GizwitsTTS', '机智云语音合成', 1, 0, '{\"type\": \"doubao\", \"api_url\": \"https://bytedance.gizwitsapi.com/api/v1/tts\", \"authorization\": \"Bearer \", \"voice\": \"zh_female_wanwanxiaohe_moon_bigtts\", \"output_dir\": \"tmp/\", \"access_token\": \"\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('TTS_GizwitsTTS', 'TTS', 'GizwitsTTS', '机智云语音合成', 0, 1, '{\"type\": \"doubao\", \"api_url\": \"https://bytedance.gizwitsapi.com/api/v1/tts\", \"authorization\": \"Bearer \", \"voice\": \"zh_female_wanwanxiaohe_moon_bigtts\", \"output_dir\": \"tmp/\", \"access_token\": \"\"}', NULL, NULL, 11, NULL, NULL, NULL, NULL);
|
||||||
INSERT INTO `ai_model_config` VALUES ('TTS_ACGNTTS', 'TTS', 'ACGNTTS', 'ACGN语音合成', 1, 0, '{\"type\": \"ttson\", \"token\": \"\", \"voice_id\": 1695, \"speed_factor\": 1, \"pitch_factor\": 0, \"volume_change_dB\": 0, \"to_lang\": \"ZH\", \"url\": \"https://u95167-bd74-2aef8085.westx.seetacloud.com:8443/flashsummary/tts?token=\", \"format\": \"mp3\", \"output_dir\": \"tmp/\", \"emotion\": 1}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('TTS_ACGNTTS', 'TTS', 'ACGNTTS', 'ACGN语音合成', 0, 1, '{\"type\": \"ttson\", \"token\": \"\", \"voice_id\": 1695, \"speed_factor\": 1, \"pitch_factor\": 0, \"volume_change_dB\": 0, \"to_lang\": \"ZH\", \"url\": \"https://u95167-bd74-2aef8085.westx.seetacloud.com:8443/flashsummary/tts?token=\", \"format\": \"mp3\", \"output_dir\": \"tmp/\", \"emotion\": 1}', NULL, NULL, 12, NULL, NULL, NULL, NULL);
|
||||||
INSERT INTO `ai_model_config` VALUES ('TTS_OpenAITTS', 'TTS', 'OpenAITTS', 'OpenAI语音合成', 1, 0, '{\"type\": \"openai\", \"api_key\": \"\", \"api_url\": \"https://api.openai.com/v1/audio/speech\", \"model\": \"tts-1\", \"voice\": \"onyx\", \"speed\": 1, \"output_dir\": \"tmp/\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('TTS_OpenAITTS', 'TTS', 'OpenAITTS', 'OpenAI语音合成', 0, 1, '{\"type\": \"openai\", \"api_key\": \"你的api_key\", \"api_url\": \"https://api.openai.com/v1/audio/speech\", \"model\": \"tts-1\", \"voice\": \"onyx\", \"speed\": 1, \"output_dir\": \"tmp/\"}', NULL, NULL, 13, NULL, NULL, NULL, NULL);
|
||||||
INSERT INTO `ai_model_config` VALUES ('TTS_CustomTTS', 'TTS', 'CustomTTS', '自定义语音合成', 1, 0, '{\"type\": \"custom\", \"url\": \"http://127.0.0.1:9880/tts\", \"params\": {}, \"headers\": {}, \"format\": \"wav\", \"output_dir\": \"tmp/\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('TTS_CustomTTS', 'TTS', 'CustomTTS', '自定义语音合成', 0, 1, '{\"type\": \"custom\", \"url\": \"http://127.0.0.1:9880/tts\", \"params\": {}, \"headers\": {}, \"format\": \"wav\", \"output_dir\": \"tmp/\"}', NULL, NULL, 14, NULL, NULL, NULL, NULL);
|
||||||
|
INSERT INTO `ai_model_config` VALUES ('TTS_TencentTTS', 'TTS', 'TencentTTS', '腾讯语音合成', 0, 1, '{\"type\": \"tencent\", \"appid\": \"\", \"secret_id\": \"\", \"secret_key\": \"\", \"region\": \"ap-guangzhou\", \"voice\": \"101001\", \"output_dir\": \"tmp/\"}', NULL, NULL, 3, NULL, NULL, NULL, NULL);
|
||||||
|
|
||||||
-- Memory模型配置
|
-- Memory模型配置
|
||||||
INSERT INTO `ai_model_config` VALUES ('Memory_mem0ai', 'Memory', 'mem0ai', 'Mem0AI记忆', 1, 0, '{\"type\": \"mem0ai\", \"api_key\": \"\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('Memory_nomem', 'Memory', 'nomem', '无记忆', 1, 1, '{\"type\": \"nomem\"}', NULL, NULL, 1, NULL, NULL, NULL, NULL);
|
||||||
INSERT INTO `ai_model_config` VALUES ('Memory_nomem', 'Memory', 'nomem', '无记忆', 1, 1, '{\"type\": \"nomem\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('Memory_mem_local_short', 'Memory', 'mem_local_short', '本地短期记忆', 0, 1, '{\"type\": \"mem_local_short\"}', NULL, NULL, 2, NULL, NULL, NULL, NULL);
|
||||||
INSERT INTO `ai_model_config` VALUES ('Memory_mem_local_short', 'Memory', 'mem_local_short', '本地短期记忆', 1, 0, '{\"type\": \"mem_local_short\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('Memory_mem0ai', 'Memory', 'mem0ai', 'Mem0AI记忆', 0, 1, '{\"type\": \"mem0ai\", \"api_key\": \"你的api_key\"}', NULL, NULL, 3, NULL, NULL, NULL, NULL);
|
||||||
|
|
||||||
-- Intent模型配置
|
-- Intent模型配置
|
||||||
INSERT INTO `ai_model_config` VALUES ('Intent_nointent', 'Intent', 'nointent', '无意图识别', 1, 0, '{\"type\": \"nointent\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('Intent_nointent', 'Intent', 'nointent', '无意图识别', 1, 0, '{\"type\": \"nointent\"}', NULL, NULL, 1, NULL, NULL, NULL, NULL);
|
||||||
INSERT INTO `ai_model_config` VALUES ('Intent_intent_llm', 'Intent', 'intent_llm', 'LLM意图识别', 1, 0, '{\"type\": \"intent_llm\", \"llm\": \"ChatGLMLLM\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('Intent_intent_llm', 'Intent', 'intent_llm', 'LLM意图识别', 0, 1, '{\"type\": \"intent_llm\", \"llm\": \"ChatGLMLLM\"}', NULL, NULL, 2, NULL, NULL, NULL, NULL);
|
||||||
INSERT INTO `ai_model_config` VALUES ('Intent_function_call', 'Intent', 'function_call', '函数调用意图识别', 1, 1, '{\"type\": \"function_call\", \"functions\": [\"change_role\", \"get_weather\", \"get_news\", \"play_music\"]}', NULL, NULL, 0, NULL, NULL, NULL, NULL);
|
INSERT INTO `ai_model_config` VALUES ('Intent_function_call', 'Intent', 'function_call', '函数调用意图识别', 0, 1, '{\"type\": \"function_call\", \"functions\": [\"change_role\", \"get_weather\", \"get_news\", \"play_music\"]}', NULL, NULL, 3, NULL, NULL, NULL, NULL);
|
||||||
+12
-12
@@ -23,7 +23,7 @@ create table sys_params
|
|||||||
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (100, 'server.ip', '0.0.0.0', 'string', 1, '服务器监听IP地址');
|
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (100, 'server.ip', '0.0.0.0', 'string', 1, '服务器监听IP地址');
|
||||||
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (101, 'server.port', '8000', 'number', 1, '服务器监听端口');
|
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (101, 'server.port', '8000', 'number', 1, '服务器监听端口');
|
||||||
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (102, 'server.secret', 'null', 'string', 1, '服务器密钥');
|
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (102, 'server.secret', 'null', 'string', 1, '服务器密钥');
|
||||||
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (201, 'log.log_format', '<green>{time:YYMMDD HH:mm:ss}</green>[{version}_{selected_module}][<light-blue>{extra[tag]}</light-blue>]-<level>{level}</level>-<light-green>{message}</light-green>', 'string', 1, '控制台日志格式');
|
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (201, 'log.log_format', '<green>{time:YYMMDD HH:mm:ss}</green>[<light-blue>{version}-{selected_module}</light-blue>][<light-blue>{extra[tag]}</light-blue>]-<level>{level}</level>-<light-green>{message}</light-green>', 'string', 1, '控制台日志格式');
|
||||||
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (202, 'log.log_format_file', '{time:YYYY-MM-DD HH:mm:ss} - {version}_{selected_module} - {name} - {level} - {extra[tag]} - {message}', 'string', 1, '文件日志格式');
|
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (202, 'log.log_format_file', '{time:YYYY-MM-DD HH:mm:ss} - {version}_{selected_module} - {name} - {level} - {extra[tag]} - {message}', 'string', 1, '文件日志格式');
|
||||||
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (203, 'log.log_level', 'INFO', 'string', 1, '日志级别');
|
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (203, 'log.log_level', 'INFO', 'string', 1, '日志级别');
|
||||||
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (204, 'log.log_dir', 'tmp', 'string', 1, '日志目录');
|
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (204, 'log.log_dir', 'tmp', 'string', 1, '日志目录');
|
||||||
@@ -47,15 +47,15 @@ INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, r
|
|||||||
"channels": 1,
|
"channels": 1,
|
||||||
"frame_duration": 60
|
"frame_duration": 60
|
||||||
}
|
}
|
||||||
}', 'string', 1, '小智类型');
|
}', 'json', 1, '小智类型');
|
||||||
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (310, 'wakeup_words', '你好小智;你好小志;小爱同学;你好小鑫;你好小新;小美同学;小龙小龙;喵喵同学;小滨小滨;小冰小冰', 'array', 1, '唤醒词列表,用于识别唤醒词');
|
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (310, 'wakeup_words', '你好小智;你好小志;小爱同学;你好小鑫;你好小新;小美同学;小龙小龙;喵喵同学;小滨小滨;小冰小冰', 'array', 1, '唤醒词列表,用于识别唤醒词');
|
||||||
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (400, 'plugin.weather.api_key', 'a861d0d5e7bf4ee1a83d9a9e4f96d4da', 'string', 1, '天气插件API密钥');
|
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (400, 'plugins.get_weather.api_key', 'a861d0d5e7bf4ee1a83d9a9e4f96d4da', 'string', 1, '天气插件API密钥');
|
||||||
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (401, 'plugin.weather.default_location', '广州', 'string', 1, '天气插件默认城市');
|
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (401, 'plugins.get_weather.default_location', '广州', 'string', 1, '天气插件默认城市');
|
||||||
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (410, 'plugin.news.default_rss_url', 'https://www.chinanews.com.cn/rss/society.xml', 'string', 1, '新闻插件默认RSS地址');
|
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (410, 'plugins.get_news.default_rss_url', 'https://www.chinanews.com.cn/rss/society.xml', 'string', 1, '新闻插件默认RSS地址');
|
||||||
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (411, 'plugin.news.category_urls', '{"society":"https://www.chinanews.com.cn/rss/society.xml","world":"https://www.chinanews.com.cn/rss/world.xml","finance":"https://www.chinanews.com.cn/rss/finance.xml"}', 'string', 1, '新闻插件分类RSS地址');
|
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (411, 'plugins.get_news.category_urls', '{"society":"https://www.chinanews.com.cn/rss/society.xml","world":"https://www.chinanews.com.cn/rss/world.xml","finance":"https://www.chinanews.com.cn/rss/finance.xml"}', 'json', 1, '新闻插件分类RSS地址');
|
||||||
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (421, 'plugin.home_assistant.devices', '客厅,玩具灯,switch.cuco_cn_460494544_cp1_on_p_2_1;卧室,台灯,switch.iot_cn_831898993_socn1_on_p_2_1', 'array', 1, 'Home Assistant设备列表');
|
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (421, 'plugins.home_assistant.devices', '客厅,玩具灯,switch.cuco_cn_460494544_cp1_on_p_2_1;卧室,台灯,switch.iot_cn_831898993_socn1_on_p_2_1', 'array', 1, 'Home Assistant设备列表');
|
||||||
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (422, 'plugin.home_assistant.base_url', 'http://homeassistant.local:8123', 'string', 1, 'Home Assistant服务器地址');
|
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (422, 'plugins.home_assistant.base_url', 'http://homeassistant.local:8123', 'string', 1, 'Home Assistant服务器地址');
|
||||||
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (423, 'plugin.home_assistant.api_key', '你的home assistant api访问令牌', 'string', 1, 'Home Assistant API密钥');
|
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (423, 'plugins.home_assistant.api_key', '你的home assistant api访问令牌', 'string', 1, 'Home Assistant API密钥');
|
||||||
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (430, 'plugin.music.music_dir', './music', 'string', 1, '音乐文件存放路径');
|
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (430, 'plugins.play_music.music_dir', './music', 'string', 1, '音乐文件存放路径');
|
||||||
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (431, 'plugin.music.music_ext', 'mp3;wav;p3', 'array', 1, '音乐文件类型');
|
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (431, 'plugins.play_music.music_ext', 'mp3;wav;p3', 'array', 1, '音乐文件类型');
|
||||||
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (432, 'plugin.music.refresh_time', '300', 'number', 1, '音乐列表刷新间隔(秒)');
|
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (432, 'plugins.play_music.refresh_time', '300', 'number', 1, '音乐列表刷新间隔(秒)');
|
||||||
@@ -23,13 +23,6 @@ databaseChangeLog:
|
|||||||
- sqlFile:
|
- sqlFile:
|
||||||
encoding: utf8
|
encoding: utf8
|
||||||
path: classpath:db/changelog/2025_tts_voive.sql
|
path: classpath:db/changelog/2025_tts_voive.sql
|
||||||
- changeSet:
|
|
||||||
id: 202504082210
|
|
||||||
author: John
|
|
||||||
changes:
|
|
||||||
- sqlFile:
|
|
||||||
encoding: utf8
|
|
||||||
path: classpath:db/changelog/202504082210.sql
|
|
||||||
- changeSet:
|
- changeSet:
|
||||||
id: 202504082211
|
id: 202504082211
|
||||||
author: John
|
author: John
|
||||||
@@ -37,13 +30,6 @@ databaseChangeLog:
|
|||||||
- sqlFile:
|
- sqlFile:
|
||||||
encoding: utf8
|
encoding: utf8
|
||||||
path: classpath:db/changelog/202504082211.sql
|
path: classpath:db/changelog/202504082211.sql
|
||||||
- changeSet:
|
|
||||||
id: 202504092313
|
|
||||||
author: John
|
|
||||||
changes:
|
|
||||||
- sqlFile:
|
|
||||||
encoding: utf8
|
|
||||||
path: classpath:db/changelog/202504092313.sql
|
|
||||||
- changeSet:
|
- changeSet:
|
||||||
id: 202504092335
|
id: 202504092335
|
||||||
author: John
|
author: John
|
||||||
@@ -51,3 +37,17 @@ databaseChangeLog:
|
|||||||
- sqlFile:
|
- sqlFile:
|
||||||
encoding: utf8
|
encoding: utf8
|
||||||
path: classpath:db/changelog/202504092335.sql
|
path: classpath:db/changelog/202504092335.sql
|
||||||
|
- changeSet:
|
||||||
|
id: 202504112044
|
||||||
|
author: John
|
||||||
|
changes:
|
||||||
|
- sqlFile:
|
||||||
|
encoding: utf8
|
||||||
|
path: classpath:db/changelog/202504112044.sql
|
||||||
|
- changeSet:
|
||||||
|
id: 202504112057
|
||||||
|
author: John
|
||||||
|
changes:
|
||||||
|
- sqlFile:
|
||||||
|
encoding: utf8
|
||||||
|
path: classpath:db/changelog/202504112057.sql
|
||||||
@@ -40,3 +40,4 @@
|
|||||||
10037=\u53C2\u6570\u503C\u5FC5\u987B\u662F\u6709\u6548\u7684\u6570\u5B57
|
10037=\u53C2\u6570\u503C\u5FC5\u987B\u662F\u6709\u6548\u7684\u6570\u5B57
|
||||||
10038=\u53C2\u6570\u503C\u5FC5\u987B\u662Ftrue\u6216false
|
10038=\u53C2\u6570\u503C\u5FC5\u987B\u662Ftrue\u6216false
|
||||||
10039=\u53C2\u6570\u503C\u5FC5\u987B\u662F\u6709\u6548\u7684JSON\u6570\u7EC4\u683C\u5F0F
|
10039=\u53C2\u6570\u503C\u5FC5\u987B\u662F\u6709\u6548\u7684JSON\u6570\u7EC4\u683C\u5F0F
|
||||||
|
10040=\u53C2\u6570\u503C\u5FC5\u987B\u662F\u6709\u6548\u7684JSON\u683C\u5F0F
|
||||||
|
|||||||
@@ -40,3 +40,4 @@
|
|||||||
10037=Parameter value must be a valid number
|
10037=Parameter value must be a valid number
|
||||||
10038=Parameter value must be true or false
|
10038=Parameter value must be true or false
|
||||||
10039=Parameter value must be a valid JSON array format
|
10039=Parameter value must be a valid JSON array format
|
||||||
|
10040=Parameter value must be a valid JSON format
|
||||||
@@ -40,3 +40,4 @@
|
|||||||
10037=\u53C2\u6570\u503C\u5FC5\u987B\u662F\u6709\u6548\u7684\u6570\u5B57
|
10037=\u53C2\u6570\u503C\u5FC5\u987B\u662F\u6709\u6548\u7684\u6570\u5B57
|
||||||
10038=\u53C2\u6570\u503C\u5FC5\u987B\u662Ftrue\u6216false
|
10038=\u53C2\u6570\u503C\u5FC5\u987B\u662Ftrue\u6216false
|
||||||
10039=\u53C2\u6570\u503C\u5FC5\u987B\u662F\u6709\u6548\u7684JSON\u6570\u7EC4\u683C\u5F0F
|
10039=\u53C2\u6570\u503C\u5FC5\u987B\u662F\u6709\u6548\u7684JSON\u6570\u7EC4\u683C\u5F0F
|
||||||
|
10040=\u53C2\u6570\u503C\u5FC5\u987B\u662F\u6709\u6548\u7684JSON\u683C\u5F0F
|
||||||
@@ -40,3 +40,4 @@
|
|||||||
10037=\u53C3\u6578\u503C\u5FC5\u9808\u662F\u6709\u6548\u7684\u6578\u5B57
|
10037=\u53C3\u6578\u503C\u5FC5\u9808\u662F\u6709\u6548\u7684\u6578\u5B57
|
||||||
10038=\u53C3\u6578\u503C\u5FC5\u9808\u662Ftrue\u6216false
|
10038=\u53C3\u6578\u503C\u5FC5\u9808\u662Ftrue\u6216false
|
||||||
10039=\u53C3\u6578\u503C\u5FC5\u9808\u662F\u6709\u6548\u7684JSON\u6578\u7D44\u683C\u5F0F
|
10039=\u53C3\u6578\u503C\u5FC5\u9808\u662F\u6709\u6548\u7684JSON\u6578\u7D44\u683C\u5F0F
|
||||||
|
10040=\u53C3\u6578\u503C\u5FC5\u9808\u662F\u6709\u6548\u7684JSON\u683C\u5F0F
|
||||||
@@ -142,40 +142,57 @@ export default {
|
|||||||
})
|
})
|
||||||
}).send()
|
}).send()
|
||||||
},
|
},
|
||||||
// 启用/禁用模型状态
|
// 启用/禁用模型状态
|
||||||
updateModelStatus(id, status, callback) {
|
updateModelStatus(id, status, callback) {
|
||||||
RequestService.sendRequest()
|
RequestService.sendRequest()
|
||||||
.url(`${getServiceUrl()}/models/enable/${id}/${status}`)
|
.url(`${getServiceUrl()}/models/enable/${id}/${status}`)
|
||||||
.method('PUT')
|
.method('PUT')
|
||||||
.success((res) => {
|
.success((res) => {
|
||||||
RequestService.clearRequestTime()
|
RequestService.clearRequestTime()
|
||||||
callback(res)
|
callback(res)
|
||||||
|
})
|
||||||
|
.fail((err) => {
|
||||||
|
console.error('更新模型状态失败:', err)
|
||||||
|
this.$message.error(err.msg || '更新模型状态失败')
|
||||||
|
RequestService.reAjaxFun(() => {
|
||||||
|
this.updateModelStatus(id, status, callback)
|
||||||
})
|
})
|
||||||
.fail((err) => {
|
}).send()
|
||||||
console.error('更新模型状态失败:', err)
|
},
|
||||||
this.$message.error(err.msg || '更新模型状态失败')
|
// 更新模型配置
|
||||||
RequestService.reAjaxFun(() => {
|
updateModel(params, callback) {
|
||||||
this.updateModelStatus(id, status, callback)
|
const { modelType, provideCode, id, formData } = params;
|
||||||
})
|
RequestService.sendRequest()
|
||||||
}).send()
|
.url(`${getServiceUrl()}/models/${modelType}/${provideCode}/${id}`)
|
||||||
},
|
.method('PUT')
|
||||||
// 更新模型配置
|
.data(formData)
|
||||||
updateModel(params, callback) {
|
.success((res) => {
|
||||||
const { modelType, provideCode, id, formData } = params;
|
RequestService.clearRequestTime();
|
||||||
RequestService.sendRequest()
|
callback(res);
|
||||||
.url(`${getServiceUrl()}/models/${modelType}/${provideCode}/${id}`)
|
})
|
||||||
.method('PUT')
|
.fail((err) => {
|
||||||
.data(formData)
|
console.error('更新模型失败:', err);
|
||||||
.success((res) => {
|
this.$message.error(err.msg || '更新模型失败');
|
||||||
RequestService.clearRequestTime();
|
RequestService.reAjaxFun(() => {
|
||||||
callback(res);
|
this.updateModel(params, callback);
|
||||||
|
});
|
||||||
|
}).send();
|
||||||
|
},
|
||||||
|
// 设置默认模型
|
||||||
|
setDefaultModel(id, callback) {
|
||||||
|
RequestService.sendRequest()
|
||||||
|
.url(`${getServiceUrl()}/models/default/${id}`)
|
||||||
|
.method('PUT')
|
||||||
|
.success((res) => {
|
||||||
|
RequestService.clearRequestTime()
|
||||||
|
callback(res)
|
||||||
|
})
|
||||||
|
.fail((err) => {
|
||||||
|
console.error('设置默认模型失败:', err)
|
||||||
|
this.$message.error(err.msg || '设置默认模型失败')
|
||||||
|
RequestService.reAjaxFun(() => {
|
||||||
|
this.setDefaultModel(id, callback)
|
||||||
})
|
})
|
||||||
.fail((err) => {
|
}).send()
|
||||||
console.error('更新模型失败:', err);
|
}
|
||||||
this.$message.error(err.msg || '更新模型失败');
|
|
||||||
RequestService.reAjaxFun(() => {
|
|
||||||
this.updateModel(params, callback);
|
|
||||||
});
|
|
||||||
}).send();
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,16 +4,6 @@
|
|||||||
|
|
||||||
<div class="operation-bar">
|
<div class="operation-bar">
|
||||||
<h2 class="page-title">{{ modelTypeText }}</h2>
|
<h2 class="page-title">{{ modelTypeText }}</h2>
|
||||||
<!-- <div class="right-operations">-->
|
|
||||||
<!-- <el-button plain size="small" @click="handleImport" style="background: #7b9de5; color: white;">-->
|
|
||||||
<!-- <img loading="lazy" alt="" src="@/assets/model/inner_conf.png">-->
|
|
||||||
<!-- 导入配置-->
|
|
||||||
<!-- </el-button>-->
|
|
||||||
<!-- <el-button plain size="small" @click="handleExport" style="background: #71c9d1; color: white;">-->
|
|
||||||
<!-- <img loading="lazy" alt="" src="@/assets/model/output_conf.png">-->
|
|
||||||
<!-- 导出配置-->
|
|
||||||
<!-- </el-button>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
<div class="action-group">
|
<div class="action-group">
|
||||||
<div class="search-group">
|
<div class="search-group">
|
||||||
<el-input
|
<el-input
|
||||||
@@ -76,13 +66,15 @@
|
|||||||
@change="handleStatusChange(scope.row)" />
|
@change="handleStatusChange(scope.row)" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column label="是否默认" align="center">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-switch v-model="scope.row.isDefault" class="custom-switch" :active-value="1" :inactive-value="0"
|
||||||
|
@change="handleDefaultChange(scope.row)" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column v-if="activeTab === 'tts'" label="音色管理" align="center">
|
<el-table-column v-if="activeTab === 'tts'" label="音色管理" align="center">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-button
|
<el-button type="text" size="mini" @click="openTtsDialog(scope.row)" class="voice-management-btn">
|
||||||
type="text"
|
|
||||||
size="mini"
|
|
||||||
@click="openTtsDialog(scope.row)"
|
|
||||||
class="voice-management-btn">
|
|
||||||
音色管理
|
音色管理
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
@@ -130,7 +122,7 @@
|
|||||||
|
|
||||||
<ModelEditDialog :modelType="activeTab" :visible.sync="editDialogVisible" :modelData="editModelData"
|
<ModelEditDialog :modelType="activeTab" :visible.sync="editDialogVisible" :modelData="editModelData"
|
||||||
@save="handleModelSave" />
|
@save="handleModelSave" />
|
||||||
<TtsModel :visible.sync="ttsDialogVisible" :ttsModelId="selectedTtsModelId"/>
|
<TtsModel :visible.sync="ttsDialogVisible" :ttsModelId="selectedTtsModelId" />
|
||||||
<AddModelDialog :modelType="activeTab" :visible.sync="addDialogVisible" @confirm="handleAddConfirm" />
|
<AddModelDialog :modelType="activeTab" :visible.sync="addDialogVisible" @confirm="handleAddConfirm" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -304,14 +296,6 @@ export default {
|
|||||||
this.currentPage = page;
|
this.currentPage = page;
|
||||||
this.$refs.modelTable.clearSelection();
|
this.$refs.modelTable.clearSelection();
|
||||||
},
|
},
|
||||||
handleImport() {
|
|
||||||
// TODO: 导入配置
|
|
||||||
console.log('导入配置');
|
|
||||||
},
|
|
||||||
handleExport() {
|
|
||||||
// TODO: 导出配置
|
|
||||||
console.log('导出配置');
|
|
||||||
},
|
|
||||||
handleModelSave({ provideCode, formData }) {
|
handleModelSave({ provideCode, formData }) {
|
||||||
const modelType = this.activeTab;
|
const modelType = this.activeTab;
|
||||||
const id = formData.id;
|
const id = formData.id;
|
||||||
@@ -412,7 +396,7 @@ export default {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// 处理启用/禁用状态变更
|
// 处理启用/禁用状态变更
|
||||||
handleStatusChange(model) {
|
handleStatusChange(model) {
|
||||||
const newStatus = model.isEnabled ? 1 : 0
|
const newStatus = model.isEnabled ? 1 : 0
|
||||||
const originalStatus = model.isEnabled
|
const originalStatus = model.isEnabled
|
||||||
@@ -434,12 +418,24 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
},
|
||||||
|
handleDefaultChange(model) {
|
||||||
|
Api.model.setDefaultModel(model.id, ({ data }) => {
|
||||||
|
if (data.code === 0) {
|
||||||
|
this.$message.success('设置默认模型成功')
|
||||||
|
this.loadData()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.el-switch {
|
||||||
|
height: 23px;
|
||||||
|
}
|
||||||
|
|
||||||
::v-deep .el-table tr {
|
::v-deep .el-table tr {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -564,6 +564,7 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-table .el-button--text) {
|
:deep(.el-table .el-button--text) {
|
||||||
color: #7079aa !important;
|
color: #7079aa !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from core.utils.util import check_ffmpeg_installed
|
|||||||
|
|
||||||
TAG = __name__
|
TAG = __name__
|
||||||
|
|
||||||
|
|
||||||
async def wait_for_exit():
|
async def wait_for_exit():
|
||||||
"""Windows 和 Linux 兼容的退出监听"""
|
"""Windows 和 Linux 兼容的退出监听"""
|
||||||
loop = asyncio.get_running_loop()
|
loop = asyncio.get_running_loop()
|
||||||
@@ -19,10 +20,12 @@ async def wait_for_exit():
|
|||||||
# Linux/macOS: 用 signal 监听 Ctrl + C
|
# Linux/macOS: 用 signal 监听 Ctrl + C
|
||||||
def stop():
|
def stop():
|
||||||
stop_event.set()
|
stop_event.set()
|
||||||
|
|
||||||
loop.add_signal_handler(signal.SIGINT, stop)
|
loop.add_signal_handler(signal.SIGINT, stop)
|
||||||
loop.add_signal_handler(signal.SIGTERM, stop) # 支持 kill 进程
|
loop.add_signal_handler(signal.SIGTERM, stop) # 支持 kill 进程
|
||||||
await stop_event.wait()
|
await stop_event.wait()
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
check_config_file()
|
check_config_file()
|
||||||
check_ffmpeg_installed()
|
check_ffmpeg_installed()
|
||||||
@@ -44,6 +47,7 @@ async def main():
|
|||||||
pass
|
pass
|
||||||
print("服务器已关闭,程序退出。")
|
print("服务器已关闭,程序退出。")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
asyncio.run(main())
|
asyncio.run(main())
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ enable_stop_tts_notify: false
|
|||||||
# 说完话是否开启提示音,音效地址
|
# 说完话是否开启提示音,音效地址
|
||||||
stop_tts_notify_voice: "config/assets/tts_notify.mp3"
|
stop_tts_notify_voice: "config/assets/tts_notify.mp3"
|
||||||
|
|
||||||
CMD_exit:
|
exit_commands:
|
||||||
- "退出"
|
- "退出"
|
||||||
- "关闭"
|
- "关闭"
|
||||||
|
|
||||||
@@ -222,6 +222,7 @@ ASR:
|
|||||||
output_dir: tmp/
|
output_dir: tmp/
|
||||||
VAD:
|
VAD:
|
||||||
SileroVAD:
|
SileroVAD:
|
||||||
|
type: silero
|
||||||
threshold: 0.5
|
threshold: 0.5
|
||||||
model_dir: models/snakers4_silero-vad
|
model_dir: models/snakers4_silero-vad
|
||||||
min_silence_duration_ms: 700 # 如果说话停顿比较长,可以把这个值设置大一些
|
min_silence_duration_ms: 700 # 如果说话停顿比较长,可以把这个值设置大一些
|
||||||
|
|||||||
@@ -0,0 +1,178 @@
|
|||||||
|
import os
|
||||||
|
import argparse
|
||||||
|
import requests
|
||||||
|
import yaml
|
||||||
|
import time
|
||||||
|
|
||||||
|
# 添加全局配置缓存
|
||||||
|
_config_cache = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_project_dir():
|
||||||
|
"""获取项目根目录"""
|
||||||
|
return os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + "/"
|
||||||
|
|
||||||
|
|
||||||
|
def read_config(config_path):
|
||||||
|
with open(config_path, "r", encoding="utf-8") as file:
|
||||||
|
config = yaml.safe_load(file)
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def load_config():
|
||||||
|
"""加载配置文件"""
|
||||||
|
global _config_cache
|
||||||
|
if _config_cache is not None:
|
||||||
|
return _config_cache
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description="Server configuration")
|
||||||
|
config_file = get_config_file()
|
||||||
|
|
||||||
|
parser.add_argument("--config_path", type=str, default=config_file)
|
||||||
|
args = parser.parse_args()
|
||||||
|
config = read_config(args.config_path)
|
||||||
|
|
||||||
|
if config.get("manager-api", {}).get("url"):
|
||||||
|
config = get_config_from_api(config)
|
||||||
|
|
||||||
|
# 初始化目录
|
||||||
|
ensure_directories(config)
|
||||||
|
_config_cache = config
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def get_config_file():
|
||||||
|
"""获取配置文件路径,优先使用私有配置文件(若存在)。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 配置文件路径(相对路径或默认路径)
|
||||||
|
"""
|
||||||
|
default_config_file = "config.yaml"
|
||||||
|
config_file = default_config_file
|
||||||
|
if os.path.exists(get_project_dir() + "data/." + default_config_file):
|
||||||
|
config_file = "data/." + default_config_file
|
||||||
|
return config_file
|
||||||
|
|
||||||
|
|
||||||
|
def _make_api_request(api_url, secret, endpoint, json_data=None):
|
||||||
|
"""执行API请求的通用函数
|
||||||
|
|
||||||
|
Args:
|
||||||
|
api_url: API的基础URL
|
||||||
|
secret: API密钥
|
||||||
|
endpoint: API端点
|
||||||
|
json_data: 请求的JSON数据
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: API返回的数据
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
Exception: 当请求失败时抛出异常
|
||||||
|
"""
|
||||||
|
if not api_url or not secret:
|
||||||
|
raise Exception("manager-api的url或secret配置错误")
|
||||||
|
|
||||||
|
if "你" in secret:
|
||||||
|
raise Exception("请先配置manager-api的secret")
|
||||||
|
|
||||||
|
max_retries = 10
|
||||||
|
retry_delay = 2 # 秒
|
||||||
|
|
||||||
|
for attempt in range(max_retries):
|
||||||
|
try:
|
||||||
|
response = requests.post(f"{api_url}{endpoint}", json=json_data)
|
||||||
|
if response.status_code == 200:
|
||||||
|
result = response.json()
|
||||||
|
if result.get("code") != 0:
|
||||||
|
raise Exception(f"API返回错误: {result.get('msg', '未知错误')}")
|
||||||
|
return result.get("data")
|
||||||
|
|
||||||
|
error_msg = f"manager-api请求失败,状态码: {response.status_code}"
|
||||||
|
try:
|
||||||
|
error_data = response.json()
|
||||||
|
if "msg" in error_data:
|
||||||
|
error_msg = f"{error_msg}, 错误信息: {error_data['msg']}"
|
||||||
|
except:
|
||||||
|
error_msg = f"{error_msg}, 响应内容: {response.text}"
|
||||||
|
|
||||||
|
if attempt < max_retries - 1:
|
||||||
|
print(f"请求manager-api失败,正在重试 ({attempt + 1}/{max_retries})...")
|
||||||
|
time.sleep(retry_delay)
|
||||||
|
else:
|
||||||
|
raise Exception(error_msg)
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
if attempt < max_retries - 1:
|
||||||
|
print(f"请求manager-api异常,正在重试 ({attempt + 1}/{max_retries})...")
|
||||||
|
time.sleep(retry_delay)
|
||||||
|
else:
|
||||||
|
raise Exception(f"manager-api请求异常: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
def get_config_from_api(config):
|
||||||
|
"""从Java API获取配置"""
|
||||||
|
api_url = config["manager-api"].get("url", "")
|
||||||
|
secret = config["manager-api"].get("secret", "")
|
||||||
|
|
||||||
|
config_data = _make_api_request(
|
||||||
|
api_url, secret, "/config/server-base", {"secret": secret}
|
||||||
|
)
|
||||||
|
config_data["read_config_from_api"] = True
|
||||||
|
config_data["manager-api"] = {
|
||||||
|
"url": api_url,
|
||||||
|
"secret": secret,
|
||||||
|
}
|
||||||
|
return config_data
|
||||||
|
|
||||||
|
|
||||||
|
def get_private_config_from_api(config, device_id, client_id):
|
||||||
|
"""从Java API获取私有配置"""
|
||||||
|
api_url = config["manager-api"].get("url", "")
|
||||||
|
secret = config["manager-api"].get("secret", "")
|
||||||
|
|
||||||
|
return _make_api_request(
|
||||||
|
api_url,
|
||||||
|
secret,
|
||||||
|
"/config/agent-models",
|
||||||
|
{
|
||||||
|
"secret": secret,
|
||||||
|
"macAddress": device_id,
|
||||||
|
"clientId": client_id,
|
||||||
|
"selectedModule": config["selected_module"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_directories(config):
|
||||||
|
"""确保所有配置路径存在"""
|
||||||
|
dirs_to_create = set()
|
||||||
|
project_dir = get_project_dir() # 获取项目根目录
|
||||||
|
# 日志文件目录
|
||||||
|
log_dir = config.get("log", {}).get("log_dir", "tmp")
|
||||||
|
dirs_to_create.add(os.path.join(project_dir, log_dir))
|
||||||
|
|
||||||
|
# ASR/TTS模块输出目录
|
||||||
|
for module in ["ASR", "TTS"]:
|
||||||
|
for provider in config.get(module, {}).values():
|
||||||
|
output_dir = provider.get("output_dir", "")
|
||||||
|
if output_dir:
|
||||||
|
dirs_to_create.add(output_dir)
|
||||||
|
|
||||||
|
# 根据selected_module创建模型目录
|
||||||
|
selected_modules = config.get("selected_module", {})
|
||||||
|
for module_type in ["ASR", "LLM", "TTS"]:
|
||||||
|
selected_provider = selected_modules.get(module_type)
|
||||||
|
if not selected_provider:
|
||||||
|
continue
|
||||||
|
provider_config = config.get(module_type, {}).get(selected_provider, {})
|
||||||
|
output_dir = provider_config.get("output_dir")
|
||||||
|
if output_dir:
|
||||||
|
full_model_dir = os.path.join(project_dir, output_dir)
|
||||||
|
dirs_to_create.add(full_model_dir)
|
||||||
|
|
||||||
|
# 统一创建目录(保留原data目录创建)
|
||||||
|
for dir_path in dirs_to_create:
|
||||||
|
try:
|
||||||
|
os.makedirs(dir_path, exist_ok=True)
|
||||||
|
except PermissionError:
|
||||||
|
print(f"警告:无法创建目录 {dir_path},请检查写入权限")
|
||||||
@@ -1,11 +1,30 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from config.settings import load_config
|
from config.config_loader import load_config
|
||||||
|
|
||||||
SERVER_VERSION = "0.2.1"
|
SERVER_VERSION = "0.2.1"
|
||||||
|
|
||||||
|
|
||||||
|
def get_module_abbreviation(module_name, module_dict):
|
||||||
|
"""获取模块名称的缩写,如果为空则返回00"""
|
||||||
|
return (
|
||||||
|
module_dict.get(module_name, "")[:2] if module_dict.get(module_name) else "00"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_module_string(selected_module):
|
||||||
|
"""构建模块字符串"""
|
||||||
|
return (
|
||||||
|
get_module_abbreviation("VAD", selected_module)
|
||||||
|
+ get_module_abbreviation("ASR", selected_module)
|
||||||
|
+ get_module_abbreviation("LLM", selected_module)
|
||||||
|
+ get_module_abbreviation("TTS", selected_module)
|
||||||
|
+ get_module_abbreviation("Memory", selected_module)
|
||||||
|
+ get_module_abbreviation("Intent", selected_module)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def setup_logging():
|
def setup_logging():
|
||||||
"""从配置文件中读取日志配置,并设置日志输出格式和级别"""
|
"""从配置文件中读取日志配置,并设置日志输出格式和级别"""
|
||||||
config = load_config()
|
config = load_config()
|
||||||
@@ -18,11 +37,7 @@ def setup_logging():
|
|||||||
"log_format_file",
|
"log_format_file",
|
||||||
"{time:YYYY-MM-DD HH:mm:ss} - {version_{selected_module}} - {name} - {level} - {extra[tag]} - {message}",
|
"{time:YYYY-MM-DD HH:mm:ss} - {version_{selected_module}} - {name} - {level} - {extra[tag]} - {message}",
|
||||||
)
|
)
|
||||||
|
selected_module_str = build_module_string(config.get("selected_module", {}))
|
||||||
selected_module = config.get("selected_module")
|
|
||||||
selected_module_str = "".join(
|
|
||||||
[value[0] + value[1] for key, value in selected_module.items()]
|
|
||||||
)
|
|
||||||
|
|
||||||
log_format = log_format.replace("{version}", SERVER_VERSION)
|
log_format = log_format.replace("{version}", SERVER_VERSION)
|
||||||
log_format = log_format.replace("{selected_module}", selected_module_str)
|
log_format = log_format.replace("{selected_module}", selected_module_str)
|
||||||
|
|||||||
@@ -1,241 +0,0 @@
|
|||||||
import os
|
|
||||||
import time
|
|
||||||
import yaml
|
|
||||||
from config.logger import setup_logging
|
|
||||||
from typing import Dict, Any, Optional
|
|
||||||
from copy import deepcopy
|
|
||||||
from core.utils.util import get_project_dir
|
|
||||||
from core.utils import llm, tts
|
|
||||||
from core.utils.lock_manager import FileLockManager
|
|
||||||
|
|
||||||
TAG = __name__
|
|
||||||
|
|
||||||
class PrivateConfig:
|
|
||||||
def __init__(self, device_id: str, default_config: Dict[str, Any], auth_code_gen=None):
|
|
||||||
self.device_id = device_id
|
|
||||||
self.default_config = default_config
|
|
||||||
self.config_path = get_project_dir() + 'data/.private_config.yaml'
|
|
||||||
self.logger = setup_logging()
|
|
||||||
self.private_config = {}
|
|
||||||
self.auth_code_gen = auth_code_gen
|
|
||||||
self.lock_manager = FileLockManager()
|
|
||||||
|
|
||||||
async def load_or_create(self):
|
|
||||||
try:
|
|
||||||
await self.lock_manager.acquire_lock(self.config_path)
|
|
||||||
try:
|
|
||||||
if os.path.exists(self.config_path):
|
|
||||||
with open(self.config_path, 'r', encoding='utf-8') as f:
|
|
||||||
all_configs = yaml.safe_load(f) or {}
|
|
||||||
else:
|
|
||||||
all_configs = {}
|
|
||||||
|
|
||||||
if self.device_id not in all_configs:
|
|
||||||
# Get selected module names
|
|
||||||
selected_modules = self.default_config['selected_module']
|
|
||||||
selected_tts = selected_modules['TTS']
|
|
||||||
selected_llm = selected_modules['LLM']
|
|
||||||
selected_asr = selected_modules['ASR']
|
|
||||||
selected_vad = selected_modules['VAD']
|
|
||||||
|
|
||||||
# 生成认证码
|
|
||||||
auth_code = None
|
|
||||||
if self.auth_code_gen:
|
|
||||||
auth_code = self.auth_code_gen.generate_code()
|
|
||||||
|
|
||||||
# Initialize device config with only necessary configurations
|
|
||||||
device_config = {
|
|
||||||
'selected_module': deepcopy(selected_modules),
|
|
||||||
'prompt': self.default_config['prompt'],
|
|
||||||
'LLM': {
|
|
||||||
selected_llm: deepcopy(self.default_config['LLM'][selected_llm])
|
|
||||||
},
|
|
||||||
'TTS': {
|
|
||||||
selected_tts: deepcopy(self.default_config['TTS'][selected_tts])
|
|
||||||
},
|
|
||||||
'ASR': {
|
|
||||||
selected_asr: deepcopy(self.default_config['ASR'][selected_asr])
|
|
||||||
},
|
|
||||||
'VAD': {
|
|
||||||
selected_vad: deepcopy(self.default_config['VAD'][selected_vad])
|
|
||||||
},
|
|
||||||
'auth_code': auth_code # 添加认证码字段
|
|
||||||
}
|
|
||||||
|
|
||||||
all_configs[self.device_id] = device_config
|
|
||||||
|
|
||||||
# Save updated configs
|
|
||||||
with open(self.config_path, 'w', encoding='utf-8') as f:
|
|
||||||
yaml.dump(all_configs, f, allow_unicode=True)
|
|
||||||
|
|
||||||
self.private_config = all_configs[self.device_id]
|
|
||||||
|
|
||||||
finally:
|
|
||||||
self.lock_manager.release_lock(self.config_path)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.bind(tag=TAG).error(f"Error handling private config: {e}")
|
|
||||||
self.private_config = {}
|
|
||||||
|
|
||||||
async def update_config(self, selected_modules: Dict[str, str], prompt: str, nickname: str) -> bool:
|
|
||||||
"""更新设备配置
|
|
||||||
Args:
|
|
||||||
selected_modules: 选择的模块配置,格式如 {'LLM': 'AliLLM', 'TTS': 'EdgeTTS',...}
|
|
||||||
prompt: 提示词配置
|
|
||||||
Returns:
|
|
||||||
bool: 更新是否成功
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
await self.lock_manager.acquire_lock(self.config_path)
|
|
||||||
try:
|
|
||||||
# Read main config to get full module configurations
|
|
||||||
main_config = self.default_config
|
|
||||||
|
|
||||||
# Create new device config
|
|
||||||
device_config = {
|
|
||||||
'selected_module': selected_modules,
|
|
||||||
'prompt': prompt,
|
|
||||||
'nickname': nickname,
|
|
||||||
}
|
|
||||||
if self.private_config.get('last_chat_time'):
|
|
||||||
device_config['last_chat_time'] = self.private_config['last_chat_time']
|
|
||||||
if self.private_config.get('owner'):
|
|
||||||
device_config['owner'] = self.private_config['owner']
|
|
||||||
|
|
||||||
# Copy full module configurations from main config
|
|
||||||
for module_type, selected_name in selected_modules.items():
|
|
||||||
if selected_name and selected_name in main_config.get(module_type, {}):
|
|
||||||
device_config[module_type] = {
|
|
||||||
selected_name: main_config[module_type][selected_name]
|
|
||||||
}
|
|
||||||
|
|
||||||
# Read all configs
|
|
||||||
if os.path.exists(self.config_path):
|
|
||||||
with open(self.config_path, 'r', encoding='utf-8') as f:
|
|
||||||
all_configs = yaml.safe_load(f) or {}
|
|
||||||
else:
|
|
||||||
all_configs = {}
|
|
||||||
|
|
||||||
# Update device config
|
|
||||||
all_configs[self.device_id] = device_config
|
|
||||||
self.private_config = device_config
|
|
||||||
|
|
||||||
# Save back to file
|
|
||||||
with open(self.config_path, 'w', encoding='utf-8') as f:
|
|
||||||
yaml.dump(all_configs, f, allow_unicode=True)
|
|
||||||
|
|
||||||
return True
|
|
||||||
finally:
|
|
||||||
self.lock_manager.release_lock(self.config_path)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.bind(tag=TAG).error(f"Error updating config: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def delete_config(self) -> bool:
|
|
||||||
"""删除设备配置
|
|
||||||
Returns:
|
|
||||||
bool: 删除是否成功
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
await self.lock_manager.acquire_lock(self.config_path)
|
|
||||||
try:
|
|
||||||
# 读取所有配置
|
|
||||||
if os.path.exists(self.config_path):
|
|
||||||
with open(self.config_path, 'r', encoding='utf-8') as f:
|
|
||||||
all_configs = yaml.safe_load(f) or {}
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# 删除设备配置
|
|
||||||
if self.device_id in all_configs:
|
|
||||||
del all_configs[self.device_id]
|
|
||||||
|
|
||||||
# 保存更新后的配置
|
|
||||||
with open(self.config_path, 'w', encoding='utf-8') as f:
|
|
||||||
yaml.dump(all_configs, f, allow_unicode=True)
|
|
||||||
|
|
||||||
self.private_config = {}
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
finally:
|
|
||||||
self.lock_manager.release_lock(self.config_path)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.bind(tag=TAG).error(f"Error deleting config: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def create_private_instances(self):
|
|
||||||
# 判断存在私有配置,并且self.device_id在私有配置中
|
|
||||||
if not self.private_config:
|
|
||||||
self.logger.bind(tag=TAG).error("Private config not found for device_id: {}", self.device_id)
|
|
||||||
return None, None
|
|
||||||
|
|
||||||
"""创建私有处理模块实例"""
|
|
||||||
config = self.private_config
|
|
||||||
selected_modules = config['selected_module']
|
|
||||||
return (
|
|
||||||
llm.create_instance(
|
|
||||||
selected_modules["LLM"]
|
|
||||||
if not 'type' in config["LLM"][selected_modules["LLM"]]
|
|
||||||
else
|
|
||||||
config["LLM"][selected_modules["LLM"]]['type'],
|
|
||||||
config["LLM"][selected_modules["LLM"]],
|
|
||||||
),
|
|
||||||
tts.create_instance(
|
|
||||||
selected_modules["TTS"]
|
|
||||||
if not 'type' in config["TTS"][selected_modules["TTS"]]
|
|
||||||
else
|
|
||||||
config["TTS"][selected_modules["TTS"]]["type"],
|
|
||||||
config["TTS"][selected_modules["TTS"]],
|
|
||||||
self.default_config.get("delete_audio", True) # Using default_config for global settings
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
async def update_last_chat_time(self, timestamp=None):
|
|
||||||
"""更新设备最近一次的聊天时间
|
|
||||||
Args:
|
|
||||||
timestamp: 指定的时间戳,不传则使用当前时间
|
|
||||||
"""
|
|
||||||
if not self.private_config:
|
|
||||||
self.logger.bind(tag=TAG).error("Private config not found")
|
|
||||||
return False
|
|
||||||
|
|
||||||
try:
|
|
||||||
await self.lock_manager.acquire_lock(self.config_path)
|
|
||||||
try:
|
|
||||||
if timestamp is None:
|
|
||||||
timestamp = int(time.time())
|
|
||||||
|
|
||||||
self.private_config['last_chat_time'] = timestamp
|
|
||||||
|
|
||||||
# 读取所有配置
|
|
||||||
with open(self.config_path, 'r', encoding='utf-8') as f:
|
|
||||||
all_configs = yaml.safe_load(f) or {}
|
|
||||||
|
|
||||||
# 更新当前设备配置
|
|
||||||
all_configs[self.device_id] = self.private_config
|
|
||||||
|
|
||||||
# 保存回文件
|
|
||||||
with open(self.config_path, 'w', encoding='utf-8') as f:
|
|
||||||
yaml.dump(all_configs, f, allow_unicode=True)
|
|
||||||
|
|
||||||
return True
|
|
||||||
finally:
|
|
||||||
self.lock_manager.release_lock(self.config_path)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.bind(tag=TAG).error(f"Error updating last chat time: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_auth_code(self) -> str:
|
|
||||||
"""获取设备的认证码
|
|
||||||
Returns:
|
|
||||||
str: 认证码,如果没有返回空字符串
|
|
||||||
"""
|
|
||||||
return self.private_config.get('auth_code', '')
|
|
||||||
|
|
||||||
def get_owner(self) -> Optional[str]:
|
|
||||||
"""获取设备当前所有者"""
|
|
||||||
return self.private_config.get('owner')
|
|
||||||
@@ -1,82 +1,11 @@
|
|||||||
import os
|
import os
|
||||||
import argparse
|
|
||||||
from ruamel.yaml import YAML
|
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
from core.utils.util import read_config, get_project_dir
|
from config.config_loader import read_config, get_project_dir, load_config
|
||||||
|
|
||||||
default_config_file = "config.yaml"
|
default_config_file = "config.yaml"
|
||||||
|
|
||||||
|
|
||||||
def ensure_directories(config):
|
def find_missing_keys(new_config, old_config, parent_key=""):
|
||||||
"""确保所有配置路径存在"""
|
|
||||||
dirs_to_create = set()
|
|
||||||
project_dir = get_project_dir() # 获取项目根目录
|
|
||||||
# 日志文件目录
|
|
||||||
log_dir = config.get('log', {}).get('log_dir', 'tmp')
|
|
||||||
dirs_to_create.add(os.path.join(project_dir, log_dir))
|
|
||||||
|
|
||||||
# ASR/TTS模块输出目录
|
|
||||||
for module in ['ASR', 'TTS']:
|
|
||||||
for provider in config.get(module, {}).values():
|
|
||||||
output_dir = provider.get('output_dir', '')
|
|
||||||
if output_dir:
|
|
||||||
dirs_to_create.add(output_dir)
|
|
||||||
|
|
||||||
# 根据selected_module创建模型目录
|
|
||||||
selected_modules = config.get('selected_module', {})
|
|
||||||
for module_type in ['ASR', 'LLM', 'TTS']:
|
|
||||||
selected_provider = selected_modules.get(module_type)
|
|
||||||
if not selected_provider:
|
|
||||||
continue
|
|
||||||
provider_config = config.get(module_type, {}).get(selected_provider, {})
|
|
||||||
output_dir = provider_config.get('output_dir')
|
|
||||||
if output_dir:
|
|
||||||
full_model_dir = os.path.join(project_dir, output_dir)
|
|
||||||
dirs_to_create.add(full_model_dir)
|
|
||||||
|
|
||||||
# 统一创建目录(保留原data目录创建)
|
|
||||||
for dir_path in dirs_to_create:
|
|
||||||
try:
|
|
||||||
os.makedirs(dir_path, exist_ok=True)
|
|
||||||
except PermissionError:
|
|
||||||
print(f"警告:无法创建目录 {dir_path},请检查写入权限")
|
|
||||||
|
|
||||||
|
|
||||||
def get_config_file():
|
|
||||||
global default_config_file
|
|
||||||
"""获取配置文件路径,优先使用私有配置文件(若存在)。
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: 配置文件路径(相对路径或默认路径)
|
|
||||||
"""
|
|
||||||
config_file = default_config_file
|
|
||||||
if os.path.exists(get_project_dir() + "data/." + default_config_file):
|
|
||||||
config_file = "data/." + default_config_file
|
|
||||||
return config_file
|
|
||||||
|
|
||||||
|
|
||||||
def load_config():
|
|
||||||
"""加载配置文件"""
|
|
||||||
parser = argparse.ArgumentParser(description="Server configuration")
|
|
||||||
config_file = get_config_file()
|
|
||||||
|
|
||||||
parser.add_argument("--config_path", type=str, default=config_file)
|
|
||||||
args = parser.parse_args()
|
|
||||||
config = read_config(args.config_path)
|
|
||||||
# 初始化目录
|
|
||||||
ensure_directories(config)
|
|
||||||
return config
|
|
||||||
|
|
||||||
|
|
||||||
def update_config(config):
|
|
||||||
yaml = YAML()
|
|
||||||
yaml.preserve_quotes = True
|
|
||||||
"""将配置保存到YAML文件"""
|
|
||||||
with open(get_config_file(), 'w') as f:
|
|
||||||
yaml.dump(config, f)
|
|
||||||
|
|
||||||
|
|
||||||
def find_missing_keys(new_config, old_config, parent_key=''):
|
|
||||||
"""
|
"""
|
||||||
递归查找缺失的配置项
|
递归查找缺失的配置项
|
||||||
返回格式:[缺失配置路径]
|
返回格式:[缺失配置路径]
|
||||||
@@ -98,28 +27,28 @@ def find_missing_keys(new_config, old_config, parent_key=''):
|
|||||||
# 递归检查嵌套字典
|
# 递归检查嵌套字典
|
||||||
if isinstance(value, Mapping):
|
if isinstance(value, Mapping):
|
||||||
sub_missing = find_missing_keys(
|
sub_missing = find_missing_keys(
|
||||||
value,
|
value, old_config[key], parent_key=full_path
|
||||||
old_config[key],
|
|
||||||
parent_key=full_path
|
|
||||||
)
|
)
|
||||||
missing_keys.extend(sub_missing)
|
missing_keys.extend(sub_missing)
|
||||||
|
|
||||||
return missing_keys
|
return missing_keys
|
||||||
|
|
||||||
|
|
||||||
def check_config_file():
|
def check_config_file():
|
||||||
old_config_file = get_config_file()
|
old_config_file = get_project_dir() + "data/." + default_config_file
|
||||||
global default_config_file
|
if not os.path.exists(old_config_file):
|
||||||
if not 'data' in old_config_file:
|
|
||||||
return
|
return
|
||||||
old_config = read_config(get_project_dir() + old_config_file)
|
old_config = load_config()
|
||||||
new_config = read_config(get_project_dir() + default_config_file)
|
new_config = read_config(get_project_dir() + default_config_file)
|
||||||
# 查找缺失的配置项
|
# 查找缺失的配置项
|
||||||
missing_keys = find_missing_keys(new_config, old_config)
|
missing_keys = find_missing_keys(new_config, old_config)
|
||||||
|
read_config_from_api = old_config.get("read_config_from_api", False)
|
||||||
|
if read_config_from_api:
|
||||||
|
return
|
||||||
|
|
||||||
if missing_keys:
|
if missing_keys:
|
||||||
|
missing_keys_str = "\n".join(f"- {key}" for key in missing_keys)
|
||||||
error_msg = "您的配置文件太旧了,缺少了:\n"
|
error_msg = "您的配置文件太旧了,缺少了:\n"
|
||||||
error_msg += "\n".join(f"- {key}" for key in missing_keys)
|
error_msg += missing_keys_str
|
||||||
error_msg += "\n建议您:\n"
|
error_msg += "\n建议您:\n"
|
||||||
error_msg += "1、备份data/.config.yaml文件\n"
|
error_msg += "1、备份data/.config.yaml文件\n"
|
||||||
error_msg += "2、将根目录的config.yaml文件复制到data下,重命名为.config.yaml\n"
|
error_msg += "2、将根目录的config.yaml文件复制到data下,重命名为.config.yaml\n"
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# 如果你只想轻量化安装xiaozhi-server,只使用本地的配置文件,不需要理会这个文件,不需要改动本文件任何东西
|
||||||
|
# 如果你想从manager-api获取配置,请往下看:
|
||||||
|
# 请将本文件复制到xiaozhi-server/data目录下,没有data目录,请创建一个,并将复制过去的文件命名为.config.yaml
|
||||||
|
# 注意如果data目录有.config.yaml文件,请先删除它
|
||||||
|
# 先启动manager-api和manager-web,注册一个账号,第一个注册的账号为管理员
|
||||||
|
# 使用管理员,进入【参数管理】页面,找到【server.secret】,复制它到参数值,注意每次从零部署,server.secret都会变化
|
||||||
|
# 打开本data目录下的.config.yaml文件,修改manager-api.secret为刚才复制出来的server.secret
|
||||||
|
manager-api:
|
||||||
|
# 你的manager-api的地址,最好使用局域网ip
|
||||||
|
url: http://127.0.0.1:8002/xiaozhi
|
||||||
|
# 你的manager-api的token,就是刚才复制出来的server.secret
|
||||||
|
secret: 你的server.secret值
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
|
import copy
|
||||||
import json
|
import json
|
||||||
import uuid
|
import uuid
|
||||||
import time
|
import time
|
||||||
@@ -17,16 +18,16 @@ from core.utils.util import (
|
|||||||
get_string_no_punctuation_or_emoji,
|
get_string_no_punctuation_or_emoji,
|
||||||
extract_json_from_string,
|
extract_json_from_string,
|
||||||
get_ip_info,
|
get_ip_info,
|
||||||
|
initialize_modules,
|
||||||
)
|
)
|
||||||
from concurrent.futures import ThreadPoolExecutor, TimeoutError
|
from concurrent.futures import ThreadPoolExecutor, TimeoutError
|
||||||
from core.handle.sendAudioHandle import sendAudioMessage
|
from core.handle.sendAudioHandle import sendAudioMessage
|
||||||
from core.handle.receiveAudioHandle import handleAudioMessage
|
from core.handle.receiveAudioHandle import handleAudioMessage
|
||||||
from core.handle.functionHandler import FunctionHandler
|
from core.handle.functionHandler import FunctionHandler
|
||||||
from plugins_func.register import Action, ActionResponse
|
from plugins_func.register import Action, ActionResponse
|
||||||
from config.private_config import PrivateConfig
|
|
||||||
from core.auth import AuthMiddleware, AuthenticationError
|
from core.auth import AuthMiddleware, AuthenticationError
|
||||||
from core.utils.auth_code_gen import AuthCodeGenerator
|
|
||||||
from core.mcp.manager import MCPManager
|
from core.mcp.manager import MCPManager
|
||||||
|
from config.config_loader import get_private_config_from_api
|
||||||
|
|
||||||
TAG = __name__
|
TAG = __name__
|
||||||
|
|
||||||
@@ -41,7 +42,7 @@ class ConnectionHandler:
|
|||||||
def __init__(
|
def __init__(
|
||||||
self, config: Dict[str, Any], _vad, _asr, _llm, _tts, _memory, _intent
|
self, config: Dict[str, Any], _vad, _asr, _llm, _tts, _memory, _intent
|
||||||
):
|
):
|
||||||
self.config = config
|
self.config = copy.deepcopy(config)
|
||||||
self.logger = setup_logging()
|
self.logger = setup_logging()
|
||||||
self.auth = AuthMiddleware(config)
|
self.auth = AuthMiddleware(config)
|
||||||
|
|
||||||
@@ -95,15 +96,12 @@ class ConnectionHandler:
|
|||||||
self.iot_descriptors = {}
|
self.iot_descriptors = {}
|
||||||
self.func_handler = None
|
self.func_handler = None
|
||||||
|
|
||||||
self.cmd_exit = self.config["CMD_exit"]
|
self.cmd_exit = self.config["exit_commands"]
|
||||||
self.max_cmd_length = 0
|
self.max_cmd_length = 0
|
||||||
for cmd in self.cmd_exit:
|
for cmd in self.cmd_exit:
|
||||||
if len(cmd) > self.max_cmd_length:
|
if len(cmd) > self.max_cmd_length:
|
||||||
self.max_cmd_length = len(cmd)
|
self.max_cmd_length = len(cmd)
|
||||||
|
|
||||||
self.private_config = None
|
|
||||||
self.auth_code_gen = AuthCodeGenerator.get_instance()
|
|
||||||
self.is_device_verified = False # 添加设备验证状态标志
|
|
||||||
self.close_after_chat = False # 是否在聊天结束后关闭连接
|
self.close_after_chat = False # 是否在聊天结束后关闭连接
|
||||||
self.use_function_call_mode = False
|
self.use_function_call_mode = False
|
||||||
if self.config["selected_module"]["Intent"] == "function_call":
|
if self.config["selected_module"]["Intent"] == "function_call":
|
||||||
@@ -121,7 +119,6 @@ class ConnectionHandler:
|
|||||||
|
|
||||||
# 进行认证
|
# 进行认证
|
||||||
await self.auth.authenticate(self.headers)
|
await self.auth.authenticate(self.headers)
|
||||||
device_id = self.headers.get("device-id", None)
|
|
||||||
|
|
||||||
# 认证通过,继续处理
|
# 认证通过,继续处理
|
||||||
self.websocket = ws
|
self.websocket = ws
|
||||||
@@ -130,39 +127,6 @@ class ConnectionHandler:
|
|||||||
self.welcome_msg = self.config["xiaozhi"]
|
self.welcome_msg = self.config["xiaozhi"]
|
||||||
self.welcome_msg["session_id"] = self.session_id
|
self.welcome_msg["session_id"] = self.session_id
|
||||||
await self.websocket.send(json.dumps(self.welcome_msg))
|
await self.websocket.send(json.dumps(self.welcome_msg))
|
||||||
# Load private configuration if device_id is provided
|
|
||||||
bUsePrivateConfig = self.config.get("use_private_config", False)
|
|
||||||
if bUsePrivateConfig and device_id:
|
|
||||||
try:
|
|
||||||
self.private_config = PrivateConfig(
|
|
||||||
device_id, self.config, self.auth_code_gen
|
|
||||||
)
|
|
||||||
await self.private_config.load_or_create()
|
|
||||||
# 判断是否已经绑定
|
|
||||||
owner = self.private_config.get_owner()
|
|
||||||
self.is_device_verified = owner is not None
|
|
||||||
|
|
||||||
if self.is_device_verified:
|
|
||||||
await self.private_config.update_last_chat_time()
|
|
||||||
|
|
||||||
llm, tts = self.private_config.create_private_instances()
|
|
||||||
if all([llm, tts]):
|
|
||||||
self.llm = llm
|
|
||||||
self.tts = tts
|
|
||||||
self.logger.bind(tag=TAG).info(
|
|
||||||
f"Loaded private config and instances for device {device_id}"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.logger.bind(tag=TAG).error(
|
|
||||||
f"Failed to create instances for device {device_id}"
|
|
||||||
)
|
|
||||||
self.private_config = None
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.bind(tag=TAG).error(
|
|
||||||
f"Error initializing private config: {e}"
|
|
||||||
)
|
|
||||||
self.private_config = None
|
|
||||||
raise
|
|
||||||
|
|
||||||
# 异步初始化
|
# 异步初始化
|
||||||
self.executor.submit(self._initialize_components)
|
self.executor.submit(self._initialize_components)
|
||||||
@@ -211,10 +175,11 @@ class ConnectionHandler:
|
|||||||
await handleAudioMessage(self, message)
|
await handleAudioMessage(self, message)
|
||||||
|
|
||||||
def _initialize_components(self):
|
def _initialize_components(self):
|
||||||
|
"""初始化组件"""
|
||||||
|
self._initialize_models()
|
||||||
|
|
||||||
"""加载提示词"""
|
"""加载提示词"""
|
||||||
self.prompt = self.config["prompt"]
|
self.prompt = self.config["prompt"]
|
||||||
if self.private_config:
|
|
||||||
self.prompt = self.private_config.private_config.get("prompt", self.prompt)
|
|
||||||
self.dialogue.put(Message(role="system", content=self.prompt))
|
self.dialogue.put(Message(role="system", content=self.prompt))
|
||||||
|
|
||||||
"""加载记忆"""
|
"""加载记忆"""
|
||||||
@@ -222,13 +187,100 @@ class ConnectionHandler:
|
|||||||
"""加载意图识别"""
|
"""加载意图识别"""
|
||||||
self._initialize_intent()
|
self._initialize_intent()
|
||||||
"""加载位置信息"""
|
"""加载位置信息"""
|
||||||
self.client_ip_info = get_ip_info(self.client_ip)
|
self.client_ip_info = get_ip_info(self.client_ip, self.logger)
|
||||||
if self.client_ip_info is not None and "city" in self.client_ip_info:
|
if self.client_ip_info is not None and "city" in self.client_ip_info:
|
||||||
self.logger.bind(tag=TAG).info(f"Client ip info: {self.client_ip_info}")
|
self.logger.bind(tag=TAG).info(f"Client ip info: {self.client_ip_info}")
|
||||||
self.prompt = self.prompt + f"\nuser location:{self.client_ip_info}"
|
self.prompt = self.prompt + f"\nuser location:{self.client_ip_info}"
|
||||||
|
|
||||||
self.dialogue.update_system_message(self.prompt)
|
self.dialogue.update_system_message(self.prompt)
|
||||||
|
|
||||||
|
def _initialize_models(self):
|
||||||
|
read_config_from_api = self.config.get("read_config_from_api", False)
|
||||||
|
"""如果是从配置文件获取,则进行二次实例化"""
|
||||||
|
if not read_config_from_api:
|
||||||
|
return
|
||||||
|
"""从接口获取差异化的配置进行二次实例化,非全量重新实例化"""
|
||||||
|
try:
|
||||||
|
private_config = get_private_config_from_api(
|
||||||
|
self.config,
|
||||||
|
self.headers.get("device-id", None),
|
||||||
|
self.headers.get("client-id", None),
|
||||||
|
)
|
||||||
|
private_config["delete_audio"] = self.config["delete_audio"]
|
||||||
|
self.logger.bind(tag=TAG).info(f"获取差异化配置成功: {private_config}")
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.bind(tag=TAG).error(f"获取差异化配置失败: {e}")
|
||||||
|
private_config = {}
|
||||||
|
|
||||||
|
init_vad, init_asr, init_llm, init_tts, init_memory, init_intent = (
|
||||||
|
False,
|
||||||
|
False,
|
||||||
|
False,
|
||||||
|
False,
|
||||||
|
False,
|
||||||
|
False,
|
||||||
|
)
|
||||||
|
if private_config.get("VAD", None) is not None:
|
||||||
|
init_vad = True
|
||||||
|
self.config["vad"] = private_config["VAD"]
|
||||||
|
self.config["selected_module"]["VAD"] = private_config["selected_module"][
|
||||||
|
"VAD"
|
||||||
|
]
|
||||||
|
if private_config.get("ASR", None) is not None:
|
||||||
|
init_asr = True
|
||||||
|
self.config["asr"] = private_config["ASR"]
|
||||||
|
self.config["selected_module"]["ASR"] = private_config["selected_module"][
|
||||||
|
"ASR"
|
||||||
|
]
|
||||||
|
if private_config.get("LLM", None) is not None:
|
||||||
|
init_llm = True
|
||||||
|
self.config["llm"] = private_config["LLM"]
|
||||||
|
self.config["selected_module"]["LLM"] = private_config["selected_module"][
|
||||||
|
"LLM"
|
||||||
|
]
|
||||||
|
if private_config.get("TTS", None) is not None:
|
||||||
|
init_tts = True
|
||||||
|
self.config["tts"] = private_config["TTS"]
|
||||||
|
self.config["selected_module"]["TTS"] = private_config["selected_module"][
|
||||||
|
"TTS"
|
||||||
|
]
|
||||||
|
if private_config.get("Memory", None) is not None:
|
||||||
|
init_memory = True
|
||||||
|
self.config["memory"] = private_config["Memory"]
|
||||||
|
self.config["selected_module"]["Memory"] = private_config[
|
||||||
|
"selected_module"
|
||||||
|
]["Memory"]
|
||||||
|
if private_config.get("Intent", None) is not None:
|
||||||
|
init_intent = True
|
||||||
|
self.config["intent"] = private_config["Intent"]
|
||||||
|
self.config["selected_module"]["Intent"] = private_config[
|
||||||
|
"selected_module"
|
||||||
|
]["Intent"]
|
||||||
|
if private_config.get("prompt", None) is not None:
|
||||||
|
self.config["prompt"] = private_config["prompt"]
|
||||||
|
try:
|
||||||
|
modules = initialize_modules(
|
||||||
|
self.logger,
|
||||||
|
private_config,
|
||||||
|
init_vad,
|
||||||
|
init_asr,
|
||||||
|
init_llm,
|
||||||
|
init_tts,
|
||||||
|
init_memory,
|
||||||
|
init_intent,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.bind(tag=TAG).error(f"初始化组件失败: {e}")
|
||||||
|
modules = {}
|
||||||
|
if modules.get("tts", None) is not None:
|
||||||
|
self.tts = modules["tts"]
|
||||||
|
if modules.get("llm", None) is not None:
|
||||||
|
self.llm = modules["llm"]
|
||||||
|
if modules.get("intent", None) is not None:
|
||||||
|
self.intent = modules["intent"]
|
||||||
|
if modules.get("memory", None) is not None:
|
||||||
|
self.memory = modules["memory"]
|
||||||
|
|
||||||
def _initialize_memory(self):
|
def _initialize_memory(self):
|
||||||
"""初始化记忆模块"""
|
"""初始化记忆模块"""
|
||||||
device_id = self.headers.get("device-id", None)
|
device_id = self.headers.get("device-id", None)
|
||||||
@@ -281,34 +333,7 @@ class ConnectionHandler:
|
|||||||
if m.role == "system":
|
if m.role == "system":
|
||||||
m.content = prompt
|
m.content = prompt
|
||||||
|
|
||||||
async def _check_and_broadcast_auth_code(self):
|
|
||||||
"""检查设备绑定状态并广播认证码"""
|
|
||||||
if not self.private_config.get_owner():
|
|
||||||
auth_code = self.private_config.get_auth_code()
|
|
||||||
if auth_code:
|
|
||||||
# 发送验证码语音提示
|
|
||||||
text = f"请在后台输入验证码:{' '.join(auth_code)}"
|
|
||||||
self.recode_first_last_text(text)
|
|
||||||
future = self.executor.submit(self.speak_and_play, text)
|
|
||||||
self.tts_queue.put(future)
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def isNeedAuth(self):
|
|
||||||
bUsePrivateConfig = self.config.get("use_private_config", False)
|
|
||||||
if not bUsePrivateConfig:
|
|
||||||
# 如果不使用私有配置,就不需要验证
|
|
||||||
return False
|
|
||||||
return not self.is_device_verified
|
|
||||||
|
|
||||||
def chat(self, query):
|
def chat(self, query):
|
||||||
if self.isNeedAuth():
|
|
||||||
self.llm_finish_task = True
|
|
||||||
future = asyncio.run_coroutine_threadsafe(
|
|
||||||
self._check_and_broadcast_auth_code(), self.loop
|
|
||||||
)
|
|
||||||
future.result()
|
|
||||||
return True
|
|
||||||
|
|
||||||
self.dialogue.put(Message(role="user", content=query))
|
self.dialogue.put(Message(role="user", content=query))
|
||||||
|
|
||||||
@@ -391,13 +416,6 @@ class ConnectionHandler:
|
|||||||
def chat_with_function_calling(self, query, tool_call=False):
|
def chat_with_function_calling(self, query, tool_call=False):
|
||||||
self.logger.bind(tag=TAG).debug(f"Chat with function calling start: {query}")
|
self.logger.bind(tag=TAG).debug(f"Chat with function calling start: {query}")
|
||||||
"""Chat with function calling for intent detection using streaming"""
|
"""Chat with function calling for intent detection using streaming"""
|
||||||
if self.isNeedAuth():
|
|
||||||
self.llm_finish_task = True
|
|
||||||
future = asyncio.run_coroutine_threadsafe(
|
|
||||||
self._check_and_broadcast_auth_code(), self.loop
|
|
||||||
)
|
|
||||||
future.result()
|
|
||||||
return True
|
|
||||||
|
|
||||||
if not tool_call:
|
if not tool_call:
|
||||||
self.dialogue.put(Message(role="user", content=query))
|
self.dialogue.put(Message(role="user", content=query))
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import os
|
|||||||
import random
|
import random
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
TAG = __name__
|
||||||
logger = setup_logging()
|
logger = setup_logging()
|
||||||
|
|
||||||
WAKEUP_CONFIG = {
|
WAKEUP_CONFIG = {
|
||||||
@@ -69,6 +70,14 @@ def getWakeupWordFile(file_name):
|
|||||||
|
|
||||||
|
|
||||||
async def wakeupWordsResponse(conn):
|
async def wakeupWordsResponse(conn):
|
||||||
|
wait_max_time = 5
|
||||||
|
while conn.llm is None or not conn.llm.response_no_stream:
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
wait_max_time -= 1
|
||||||
|
if wait_max_time <= 0:
|
||||||
|
logger.bind(tag=TAG).error("连接对象没有llm")
|
||||||
|
return
|
||||||
|
|
||||||
"""唤醒词响应"""
|
"""唤醒词响应"""
|
||||||
wakeup_word = random.choice(WAKEUP_CONFIG["words"])
|
wakeup_word = random.choice(WAKEUP_CONFIG["words"])
|
||||||
result = conn.llm.response_no_stream(conn.config["prompt"], wakeup_word)
|
result = conn.llm.response_no_stream(conn.config["prompt"], wakeup_word)
|
||||||
|
|||||||
@@ -1,26 +1,30 @@
|
|||||||
"""MCP服务管理器"""
|
"""MCP服务管理器"""
|
||||||
|
|
||||||
import os, json
|
import os, json
|
||||||
from typing import Dict, Any, List
|
from typing import Dict, Any, List
|
||||||
from .MCPClient import MCPClient
|
from .MCPClient import MCPClient
|
||||||
from config.logger import setup_logging
|
from config.logger import setup_logging
|
||||||
from core.utils.util import get_project_dir
|
from plugins_func.register import register_function, ToolType
|
||||||
from plugins_func.register import register_function, ActionResponse, Action, ToolType
|
from config.config_loader import get_project_dir
|
||||||
|
|
||||||
TAG = __name__
|
TAG = __name__
|
||||||
|
|
||||||
|
|
||||||
class MCPManager:
|
class MCPManager:
|
||||||
"""管理多个MCP服务的集中管理器"""
|
"""管理多个MCP服务的集中管理器"""
|
||||||
|
|
||||||
def __init__(self,conn) -> None:
|
def __init__(self, conn) -> None:
|
||||||
"""
|
"""
|
||||||
初始化MCP管理器
|
初始化MCP管理器
|
||||||
"""
|
"""
|
||||||
self.conn = conn
|
self.conn = conn
|
||||||
self.logger = setup_logging()
|
self.logger = setup_logging()
|
||||||
self.config_path = get_project_dir() + 'data/.mcp_server_settings.json'
|
self.config_path = get_project_dir() + "data/.mcp_server_settings.json"
|
||||||
if os.path.exists(self.config_path) == False:
|
if os.path.exists(self.config_path) == False:
|
||||||
self.config_path = ""
|
self.config_path = ""
|
||||||
self.logger.bind(tag=TAG).warning(f"请检查mcp服务配置文件:data/.mcp_server_settings.json")
|
self.logger.bind(tag=TAG).warning(
|
||||||
|
f"请检查mcp服务配置文件:data/.mcp_server_settings.json"
|
||||||
|
)
|
||||||
self.client: Dict[str, MCPClient] = {}
|
self.client: Dict[str, MCPClient] = {}
|
||||||
self.tools = []
|
self.tools = []
|
||||||
|
|
||||||
@@ -33,11 +37,13 @@ class MCPManager:
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(self.config_path, 'r', encoding='utf-8') as f:
|
with open(self.config_path, "r", encoding="utf-8") as f:
|
||||||
config = json.load(f)
|
config = json.load(f)
|
||||||
return config.get('mcpServers', {})
|
return config.get("mcpServers", {})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.bind(tag=TAG).error(f"Error loading MCP config from {self.config_path}: {e}")
|
self.logger.bind(tag=TAG).error(
|
||||||
|
f"Error loading MCP config from {self.config_path}: {e}"
|
||||||
|
)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
async def initialize_servers(self) -> None:
|
async def initialize_servers(self) -> None:
|
||||||
@@ -45,7 +51,9 @@ class MCPManager:
|
|||||||
config = self.load_config()
|
config = self.load_config()
|
||||||
for name, srv_config in config.items():
|
for name, srv_config in config.items():
|
||||||
if not srv_config.get("command"):
|
if not srv_config.get("command"):
|
||||||
self.logger.bind(tag=TAG).warning(f"Skipping server {name}: command not specified")
|
self.logger.bind(tag=TAG).warning(
|
||||||
|
f"Skipping server {name}: command not specified"
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -56,12 +64,18 @@ class MCPManager:
|
|||||||
client_tools = client.get_available_tools()
|
client_tools = client.get_available_tools()
|
||||||
self.tools.extend(client_tools)
|
self.tools.extend(client_tools)
|
||||||
for tool in client_tools:
|
for tool in client_tools:
|
||||||
func_name = "mcp_"+tool["function"]["name"]
|
func_name = "mcp_" + tool["function"]["name"]
|
||||||
register_function(func_name, tool, ToolType.MCP_CLIENT)(self.execute_tool)
|
register_function(func_name, tool, ToolType.MCP_CLIENT)(
|
||||||
self.conn.func_handler.function_registry.register_function(func_name)
|
self.execute_tool
|
||||||
|
)
|
||||||
|
self.conn.func_handler.function_registry.register_function(
|
||||||
|
func_name
|
||||||
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.bind(tag=TAG).error(f"Failed to initialize MCP server {name}: {e}")
|
self.logger.bind(tag=TAG).error(
|
||||||
|
f"Failed to initialize MCP server {name}: {e}"
|
||||||
|
)
|
||||||
self.conn.func_handler.upload_functions_desc()
|
self.conn.func_handler.upload_functions_desc()
|
||||||
|
|
||||||
def get_all_tools(self) -> List[Dict[str, Any]]:
|
def get_all_tools(self) -> List[Dict[str, Any]]:
|
||||||
@@ -79,7 +93,10 @@ class MCPManager:
|
|||||||
bool: 是否是MCP工具
|
bool: 是否是MCP工具
|
||||||
"""
|
"""
|
||||||
for tool in self.tools:
|
for tool in self.tools:
|
||||||
if tool.get("function") != None and tool["function"].get("name") == tool_name:
|
if (
|
||||||
|
tool.get("function") != None
|
||||||
|
and tool["function"].get("name") == tool_name
|
||||||
|
):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -93,7 +110,9 @@ class MCPManager:
|
|||||||
Raises:
|
Raises:
|
||||||
ValueError: 工具未找到时抛出
|
ValueError: 工具未找到时抛出
|
||||||
"""
|
"""
|
||||||
self.logger.bind(tag=TAG).info(f"Executing tool {tool_name} with arguments: {arguments}")
|
self.logger.bind(tag=TAG).info(
|
||||||
|
f"Executing tool {tool_name} with arguments: {arguments}"
|
||||||
|
)
|
||||||
for client in self.client.values():
|
for client in self.client.values():
|
||||||
if client.has_tool(tool_name):
|
if client.has_tool(tool_name):
|
||||||
return await client.call_tool(tool_name, arguments)
|
return await client.call_tool(tool_name, arguments)
|
||||||
@@ -106,5 +125,7 @@ class MCPManager:
|
|||||||
await client.cleanup()
|
await client.cleanup()
|
||||||
self.logger.bind(tag=TAG).info(f"Cleaned up MCP client: {name}")
|
self.logger.bind(tag=TAG).info(f"Cleaned up MCP client: {name}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.bind(tag=TAG).error(f"Error cleaning up MCP client {name}: {e}")
|
self.logger.bind(tag=TAG).error(
|
||||||
|
f"Error cleaning up MCP client {name}: {e}"
|
||||||
|
)
|
||||||
self.client.clear()
|
self.client.clear()
|
||||||
|
|||||||
@@ -4,15 +4,17 @@ from core.providers.llm.base import LLMProviderBase
|
|||||||
from config.logger import setup_logging
|
from config.logger import setup_logging
|
||||||
import requests
|
import requests
|
||||||
import json
|
import json
|
||||||
|
|
||||||
TAG = __name__
|
TAG = __name__
|
||||||
logger = setup_logging()
|
logger = setup_logging()
|
||||||
|
|
||||||
|
|
||||||
class LLMProvider(LLMProviderBase):
|
class LLMProvider(LLMProviderBase):
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
"""初始化Gemini LLM Provider"""
|
"""初始化Gemini LLM Provider"""
|
||||||
self.model_name = config.get("model_name", "gemini-1.5-pro")
|
self.model_name = config.get("model_name", "gemini-1.5-pro")
|
||||||
self.api_key = config.get("api_key")
|
self.api_key = config.get("api_key")
|
||||||
self.http_proxy=config.get("http_proxy")
|
self.http_proxy = config.get("http_proxy")
|
||||||
self.https_proxy = config.get("https_proxy")
|
self.https_proxy = config.get("https_proxy")
|
||||||
have_key = check_model_key("LLM", self.api_key)
|
have_key = check_model_key("LLM", self.api_key)
|
||||||
|
|
||||||
@@ -22,7 +24,7 @@ class LLMProvider(LLMProviderBase):
|
|||||||
try:
|
try:
|
||||||
# 初始化Gemini客户端
|
# 初始化Gemini客户端
|
||||||
# 配置代理(如果提供了代理配置)
|
# 配置代理(如果提供了代理配置)
|
||||||
self.proxies=None
|
self.proxies = None
|
||||||
if self.http_proxy is not "" or self.https_proxy is not "":
|
if self.http_proxy is not "" or self.https_proxy is not "":
|
||||||
|
|
||||||
self.proxies = {
|
self.proxies = {
|
||||||
@@ -62,19 +64,16 @@ class LLMProvider(LLMProviderBase):
|
|||||||
role = "model" if msg["role"] == "assistant" else "user"
|
role = "model" if msg["role"] == "assistant" else "user"
|
||||||
content = msg["content"].strip()
|
content = msg["content"].strip()
|
||||||
if content:
|
if content:
|
||||||
chat_history.append({
|
chat_history.append({"role": role, "parts": [{"text": content}]})
|
||||||
"role": role,
|
|
||||||
"parts": [{"text":content}]
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
# 获取当前消息
|
# 获取当前消息
|
||||||
current_msg = dialogue[-1]["content"]
|
current_msg = dialogue[-1]["content"]
|
||||||
|
|
||||||
# 构建请求体
|
# 构建请求体
|
||||||
request_body = {
|
request_body = {
|
||||||
"contents": chat_history + [{"role": "user", "parts": [{"text":current_msg}]}],
|
"contents": chat_history
|
||||||
"generationConfig": self.generation_config
|
+ [{"role": "user", "parts": [{"text": current_msg}]}],
|
||||||
|
"generationConfig": self.generation_config,
|
||||||
}
|
}
|
||||||
|
|
||||||
# 构建请求URL
|
# 构建请求URL
|
||||||
@@ -87,11 +86,17 @@ class LLMProvider(LLMProviderBase):
|
|||||||
|
|
||||||
# 发送POST请求,经测试手动 request 无法使用 stream 模式
|
# 发送POST请求,经测试手动 request 无法使用 stream 模式
|
||||||
if self.proxies:
|
if self.proxies:
|
||||||
response = requests.post(url, headers=headers, json=request_body, stream=False, proxies=self.proxies)
|
response = requests.post(
|
||||||
|
url,
|
||||||
|
headers=headers,
|
||||||
|
json=request_body,
|
||||||
|
stream=False,
|
||||||
|
proxies=self.proxies,
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
data = response.json() # 直接解析JSON
|
data = response.json() # 直接解析JSON
|
||||||
if 'candidates' in data and data['candidates']:
|
if "candidates" in data and data["candidates"]:
|
||||||
yield data['candidates'][0]['content']['parts'][0]['text']
|
yield data["candidates"][0]["content"]["parts"][0]["text"]
|
||||||
else:
|
else:
|
||||||
yield "未找到候选回复。"
|
yield "未找到候选回复。"
|
||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
@@ -104,13 +109,11 @@ class LLMProvider(LLMProviderBase):
|
|||||||
|
|
||||||
# 发送消息并获取流式响应
|
# 发送消息并获取流式响应
|
||||||
response = chat.send_message(
|
response = chat.send_message(
|
||||||
current_msg,
|
current_msg, stream=True, generation_config=self.generation_config
|
||||||
stream=True,
|
|
||||||
generation_config=self.generation_config
|
|
||||||
)
|
)
|
||||||
# 处理流式响应
|
# 处理流式响应
|
||||||
for chunk in response:
|
for chunk in response:
|
||||||
if hasattr(chunk, 'text') and chunk.text:
|
if hasattr(chunk, "text") and chunk.text:
|
||||||
yield chunk.text
|
yield chunk.text
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -125,9 +128,6 @@ class LLMProvider(LLMProviderBase):
|
|||||||
else:
|
else:
|
||||||
yield f"【Gemini服务响应异常: {error_msg}】"
|
yield f"【Gemini服务响应异常: {error_msg}】"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
yield f"请求失败:{e}"
|
yield f"请求失败:{e}"
|
||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class LLMProvider(LLMProviderBase):
|
|||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
self.model_name = config.get("model_name")
|
self.model_name = config.get("model_name")
|
||||||
self.api_key = config.get("api_key")
|
self.api_key = config.get("api_key")
|
||||||
if 'base_url' in config:
|
if "base_url" in config:
|
||||||
self.base_url = config.get("base_url")
|
self.base_url = config.get("base_url")
|
||||||
else:
|
else:
|
||||||
self.base_url = config.get("url")
|
self.base_url = config.get("url")
|
||||||
@@ -33,18 +33,22 @@ class LLMProvider(LLMProviderBase):
|
|||||||
for chunk in responses:
|
for chunk in responses:
|
||||||
try:
|
try:
|
||||||
# 检查是否存在有效的choice且content不为空
|
# 检查是否存在有效的choice且content不为空
|
||||||
delta = chunk.choices[0].delta if getattr(chunk, 'choices', None) else None
|
delta = (
|
||||||
content = delta.content if hasattr(delta, 'content') else ''
|
chunk.choices[0].delta
|
||||||
|
if getattr(chunk, "choices", None)
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
content = delta.content if hasattr(delta, "content") else ""
|
||||||
except IndexError:
|
except IndexError:
|
||||||
content = ''
|
content = ""
|
||||||
if content:
|
if content:
|
||||||
# 处理标签跨多个chunk的情况
|
# 处理标签跨多个chunk的情况
|
||||||
if '<think>' in content:
|
if "<think>" in content:
|
||||||
is_active = False
|
is_active = False
|
||||||
content = content.split('<think>')[0]
|
content = content.split("<think>")[0]
|
||||||
if '</think>' in content:
|
if "</think>" in content:
|
||||||
is_active = True
|
is_active = True
|
||||||
content = content.split('</think>')[-1]
|
content = content.split("</think>")[-1]
|
||||||
if is_active:
|
if is_active:
|
||||||
yield content
|
yield content
|
||||||
|
|
||||||
@@ -54,10 +58,7 @@ class LLMProvider(LLMProviderBase):
|
|||||||
def response_with_functions(self, session_id, dialogue, functions=None):
|
def response_with_functions(self, session_id, dialogue, functions=None):
|
||||||
try:
|
try:
|
||||||
stream = self.client.chat.completions.create(
|
stream = self.client.chat.completions.create(
|
||||||
model=self.model_name,
|
model=self.model_name, messages=dialogue, stream=True, tools=functions
|
||||||
messages=dialogue,
|
|
||||||
stream=True,
|
|
||||||
tools=functions
|
|
||||||
)
|
)
|
||||||
|
|
||||||
for chunk in stream:
|
for chunk in stream:
|
||||||
|
|||||||
@@ -6,13 +6,14 @@ from core.utils.util import check_model_key
|
|||||||
|
|
||||||
TAG = __name__
|
TAG = __name__
|
||||||
|
|
||||||
|
|
||||||
class MemoryProvider(MemoryProviderBase):
|
class MemoryProvider(MemoryProviderBase):
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
super().__init__(config)
|
super().__init__(config)
|
||||||
self.api_key = config.get("api_key", "")
|
self.api_key = config.get("api_key", "")
|
||||||
self.api_version = config.get("api_version", "v1.1")
|
self.api_version = config.get("api_version", "v1.1")
|
||||||
have_key = check_model_key("Mem0ai", self.api_key)
|
have_key = check_model_key("Mem0ai", self.api_key)
|
||||||
if not have_key :
|
if not have_key:
|
||||||
self.use_mem0 = False
|
self.use_mem0 = False
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
@@ -35,38 +36,39 @@ class MemoryProvider(MemoryProviderBase):
|
|||||||
# Format the content as a message list for mem0
|
# Format the content as a message list for mem0
|
||||||
messages = [
|
messages = [
|
||||||
{"role": message.role, "content": message.content}
|
{"role": message.role, "content": message.content}
|
||||||
for message in msgs if message.role != "system"
|
for message in msgs
|
||||||
|
if message.role != "system"
|
||||||
]
|
]
|
||||||
result = self.client.add(messages, user_id=self.role_id, output_format=self.api_version)
|
result = self.client.add(
|
||||||
|
messages, user_id=self.role_id, output_format=self.api_version
|
||||||
|
)
|
||||||
logger.bind(tag=TAG).debug(f"Save memory result: {result}")
|
logger.bind(tag=TAG).debug(f"Save memory result: {result}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.bind(tag=TAG).error(f"保存记忆失败: {str(e)}")
|
logger.bind(tag=TAG).error(f"保存记忆失败: {str(e)}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def query_memory(self, query: str)-> str:
|
async def query_memory(self, query: str) -> str:
|
||||||
if not self.use_mem0:
|
if not self.use_mem0:
|
||||||
return ""
|
return ""
|
||||||
try:
|
try:
|
||||||
results = self.client.search(
|
results = self.client.search(
|
||||||
query,
|
query, user_id=self.role_id, output_format=self.api_version
|
||||||
user_id=self.role_id,
|
|
||||||
output_format=self.api_version
|
|
||||||
)
|
)
|
||||||
if not results or 'results' not in results:
|
if not results or "results" not in results:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
# Format each memory entry with its update time up to minutes
|
# Format each memory entry with its update time up to minutes
|
||||||
memories = []
|
memories = []
|
||||||
for entry in results['results']:
|
for entry in results["results"]:
|
||||||
timestamp = entry.get('updated_at', '')
|
timestamp = entry.get("updated_at", "")
|
||||||
if timestamp:
|
if timestamp:
|
||||||
try:
|
try:
|
||||||
# Parse and reformat the timestamp
|
# Parse and reformat the timestamp
|
||||||
dt = timestamp.split('.')[0] # Remove milliseconds
|
dt = timestamp.split(".")[0] # Remove milliseconds
|
||||||
formatted_time = dt.replace('T', ' ')
|
formatted_time = dt.replace("T", " ")
|
||||||
except:
|
except:
|
||||||
formatted_time = timestamp
|
formatted_time = timestamp
|
||||||
memory = entry.get('memory', '')
|
memory = entry.get("memory", "")
|
||||||
if timestamp and memory:
|
if timestamp and memory:
|
||||||
# Store tuple of (timestamp, formatted_string) for sorting
|
# Store tuple of (timestamp, formatted_string) for sorting
|
||||||
memories.append((timestamp, f"[{formatted_time}] {memory}"))
|
memories.append((timestamp, f"[{formatted_time}] {memory}"))
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import time
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import yaml
|
import yaml
|
||||||
from core.utils.util import get_project_dir
|
from config.config_loader import get_project_dir
|
||||||
|
|
||||||
|
|
||||||
short_term_memory_prompt = """
|
short_term_memory_prompt = """
|
||||||
# 时空记忆编织者
|
# 时空记忆编织者
|
||||||
@@ -71,11 +72,12 @@ short_term_memory_prompt = """
|
|||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def extract_json_data(json_code):
|
def extract_json_data(json_code):
|
||||||
start = json_code.find("```json")
|
start = json_code.find("```json")
|
||||||
# 从start开始找到下一个```结束
|
# 从start开始找到下一个```结束
|
||||||
end = json_code.find("```", start+1)
|
end = json_code.find("```", start + 1)
|
||||||
#print("start:", start, "end:", end)
|
# print("start:", start, "end:", end)
|
||||||
if start == -1 or end == -1:
|
if start == -1 or end == -1:
|
||||||
try:
|
try:
|
||||||
jsonData = json.loads(json_code)
|
jsonData = json.loads(json_code)
|
||||||
@@ -83,16 +85,18 @@ def extract_json_data(json_code):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Error:", e)
|
print("Error:", e)
|
||||||
return ""
|
return ""
|
||||||
jsonData = json_code[start+7:end]
|
jsonData = json_code[start + 7 : end]
|
||||||
return jsonData
|
return jsonData
|
||||||
|
|
||||||
|
|
||||||
TAG = __name__
|
TAG = __name__
|
||||||
|
|
||||||
|
|
||||||
class MemoryProvider(MemoryProviderBase):
|
class MemoryProvider(MemoryProviderBase):
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
super().__init__(config)
|
super().__init__(config)
|
||||||
self.short_momery = ""
|
self.short_momery = ""
|
||||||
self.memory_path = get_project_dir() + 'data/.memory.yaml'
|
self.memory_path = get_project_dir() + "data/.memory.yaml"
|
||||||
self.load_memory()
|
self.load_memory()
|
||||||
|
|
||||||
def init_memory(self, role_id, llm):
|
def init_memory(self, role_id, llm):
|
||||||
@@ -102,7 +106,7 @@ class MemoryProvider(MemoryProviderBase):
|
|||||||
def load_memory(self):
|
def load_memory(self):
|
||||||
all_memory = {}
|
all_memory = {}
|
||||||
if os.path.exists(self.memory_path):
|
if os.path.exists(self.memory_path):
|
||||||
with open(self.memory_path, 'r', encoding='utf-8') as f:
|
with open(self.memory_path, "r", encoding="utf-8") as f:
|
||||||
all_memory = yaml.safe_load(f) or {}
|
all_memory = yaml.safe_load(f) or {}
|
||||||
if self.role_id in all_memory:
|
if self.role_id in all_memory:
|
||||||
self.short_momery = all_memory[self.role_id]
|
self.short_momery = all_memory[self.role_id]
|
||||||
@@ -110,10 +114,10 @@ class MemoryProvider(MemoryProviderBase):
|
|||||||
def save_memory_to_file(self):
|
def save_memory_to_file(self):
|
||||||
all_memory = {}
|
all_memory = {}
|
||||||
if os.path.exists(self.memory_path):
|
if os.path.exists(self.memory_path):
|
||||||
with open(self.memory_path, 'r', encoding='utf-8') as f:
|
with open(self.memory_path, "r", encoding="utf-8") as f:
|
||||||
all_memory = yaml.safe_load(f) or {}
|
all_memory = yaml.safe_load(f) or {}
|
||||||
all_memory[self.role_id] = self.short_momery
|
all_memory[self.role_id] = self.short_momery
|
||||||
with open(self.memory_path, 'w', encoding='utf-8') as f:
|
with open(self.memory_path, "w", encoding="utf-8") as f:
|
||||||
yaml.dump(all_memory, f, allow_unicode=True)
|
yaml.dump(all_memory, f, allow_unicode=True)
|
||||||
|
|
||||||
async def save_memory(self, msgs):
|
async def save_memory(self, msgs):
|
||||||
@@ -128,13 +132,13 @@ class MemoryProvider(MemoryProviderBase):
|
|||||||
for msg in msgs:
|
for msg in msgs:
|
||||||
if msg.role == "user":
|
if msg.role == "user":
|
||||||
msgStr += f"User: {msg.content}\n"
|
msgStr += f"User: {msg.content}\n"
|
||||||
elif msg.role== "assistant":
|
elif msg.role == "assistant":
|
||||||
msgStr += f"Assistant: {msg.content}\n"
|
msgStr += f"Assistant: {msg.content}\n"
|
||||||
if len(self.short_momery) > 0:
|
if len(self.short_momery) > 0:
|
||||||
msgStr+="历史记忆:\n"
|
msgStr += "历史记忆:\n"
|
||||||
msgStr+=self.short_momery
|
msgStr += self.short_momery
|
||||||
|
|
||||||
#当前时间
|
# 当前时间
|
||||||
time_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
|
time_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
|
||||||
msgStr += f"当前时间:{time_str}"
|
msgStr += f"当前时间:{time_str}"
|
||||||
|
|
||||||
@@ -142,7 +146,7 @@ class MemoryProvider(MemoryProviderBase):
|
|||||||
|
|
||||||
json_str = extract_json_data(result)
|
json_str = extract_json_data(result)
|
||||||
try:
|
try:
|
||||||
json_data = json.loads(json_str) # 检查json格式是否正确
|
json_data = json.loads(json_str) # 检查json格式是否正确
|
||||||
self.short_momery = json_str
|
self.short_momery = json_str
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Error:", e)
|
print("Error:", e)
|
||||||
@@ -152,5 +156,5 @@ class MemoryProvider(MemoryProviderBase):
|
|||||||
|
|
||||||
return self.short_momery
|
return self.short_momery
|
||||||
|
|
||||||
async def query_memory(self, query: str)-> str:
|
async def query_memory(self, query: str) -> str:
|
||||||
return self.short_momery
|
return self.short_momery
|
||||||
@@ -6,6 +6,10 @@ import requests
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from core.utils.util import check_model_key
|
from core.utils.util import check_model_key
|
||||||
from core.providers.tts.base import TTSProviderBase
|
from core.providers.tts.base import TTSProviderBase
|
||||||
|
from config.logger import setup_logging
|
||||||
|
|
||||||
|
TAG = __name__
|
||||||
|
logger = setup_logging()
|
||||||
|
|
||||||
|
|
||||||
class TTSProvider(TTSProviderBase):
|
class TTSProvider(TTSProviderBase):
|
||||||
@@ -21,18 +25,19 @@ class TTSProvider(TTSProviderBase):
|
|||||||
check_model_key("TTS", self.access_token)
|
check_model_key("TTS", self.access_token)
|
||||||
|
|
||||||
def generate_filename(self, extension=".wav"):
|
def generate_filename(self, extension=".wav"):
|
||||||
return os.path.join(self.output_file, f"tts-{datetime.now().date()}@{uuid.uuid4().hex}{extension}")
|
return os.path.join(
|
||||||
|
self.output_file,
|
||||||
|
f"tts-{datetime.now().date()}@{uuid.uuid4().hex}{extension}",
|
||||||
|
)
|
||||||
|
|
||||||
async def text_to_speak(self, text, output_file):
|
async def text_to_speak(self, text, output_file):
|
||||||
request_json = {
|
request_json = {
|
||||||
"app": {
|
"app": {
|
||||||
"appid": f"{self.appid}",
|
"appid": f"{self.appid}",
|
||||||
"token": "access_token",
|
"token": "access_token",
|
||||||
"cluster": self.cluster
|
"cluster": self.cluster,
|
||||||
},
|
|
||||||
"user": {
|
|
||||||
"uid": "1"
|
|
||||||
},
|
},
|
||||||
|
"user": {"uid": "1"},
|
||||||
"audio": {
|
"audio": {
|
||||||
"voice_type": self.voice,
|
"voice_type": self.voice,
|
||||||
"encoding": "wav",
|
"encoding": "wav",
|
||||||
@@ -46,17 +51,21 @@ class TTSProvider(TTSProviderBase):
|
|||||||
"text_type": "plain",
|
"text_type": "plain",
|
||||||
"operation": "query",
|
"operation": "query",
|
||||||
"with_frontend": 1,
|
"with_frontend": 1,
|
||||||
"frontend_type": "unitTson"
|
"frontend_type": "unitTson",
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
resp = requests.post(self.api_url, json.dumps(request_json), headers=self.header)
|
resp = requests.post(
|
||||||
|
self.api_url, json.dumps(request_json), headers=self.header
|
||||||
|
)
|
||||||
if "data" in resp.json():
|
if "data" in resp.json():
|
||||||
data = resp.json()["data"]
|
data = resp.json()["data"]
|
||||||
file_to_save = open(output_file, "wb")
|
file_to_save = open(output_file, "wb")
|
||||||
file_to_save.write(base64.b64decode(data))
|
file_to_save.write(base64.b64decode(data))
|
||||||
else:
|
else:
|
||||||
raise Exception(f"{__name__} status_code: {resp.status_code} response: {resp.content}")
|
raise Exception(
|
||||||
|
f"{__name__} status_code: {resp.status_code} response: {resp.content}"
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception(f"{__name__} error: {e}")
|
raise Exception(f"{__name__} error: {e}")
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class ServeReferenceAudio(BaseModel):
|
|||||||
def decode_audio(cls, values):
|
def decode_audio(cls, values):
|
||||||
audio = values.get("audio")
|
audio = values.get("audio")
|
||||||
if (
|
if (
|
||||||
isinstance(audio, str) and len(audio) > 255
|
isinstance(audio, str) and len(audio) > 255
|
||||||
): # Check if audio is a string (Base64)
|
): # Check if audio is a string (Base64)
|
||||||
try:
|
try:
|
||||||
values["audio"] = base64.b64decode(audio)
|
values["audio"] = base64.b64decode(audio)
|
||||||
@@ -107,7 +107,10 @@ class TTSProvider(TTSProviderBase):
|
|||||||
self.api_url = config.get("api_url", "http://127.0.0.1:8080/v1/tts")
|
self.api_url = config.get("api_url", "http://127.0.0.1:8080/v1/tts")
|
||||||
|
|
||||||
def generate_filename(self, extension=".wav"):
|
def generate_filename(self, extension=".wav"):
|
||||||
return os.path.join(self.output_file, f"tts-{datetime.now().date()}@{uuid.uuid4().hex}{extension}")
|
return os.path.join(
|
||||||
|
self.output_file,
|
||||||
|
f"tts-{datetime.now().date()}@{uuid.uuid4().hex}{extension}",
|
||||||
|
)
|
||||||
|
|
||||||
async def text_to_speak(self, text, output_file):
|
async def text_to_speak(self, text, output_file):
|
||||||
# Prepare reference data
|
# Prepare reference data
|
||||||
@@ -117,9 +120,7 @@ class TTSProvider(TTSProviderBase):
|
|||||||
data = {
|
data = {
|
||||||
"text": text,
|
"text": text,
|
||||||
"references": [
|
"references": [
|
||||||
ServeReferenceAudio(
|
ServeReferenceAudio(audio=audio if audio else b"", text=text)
|
||||||
audio=audio if audio else b"", text=text
|
|
||||||
)
|
|
||||||
for text, audio in zip(ref_texts, byte_audios)
|
for text, audio in zip(ref_texts, byte_audios)
|
||||||
],
|
],
|
||||||
"reference_id": self.reference_id,
|
"reference_id": self.reference_id,
|
||||||
@@ -139,7 +140,9 @@ class TTSProvider(TTSProviderBase):
|
|||||||
|
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
self.api_url,
|
self.api_url,
|
||||||
data=ormsgpack.packb(pydantic_data, option=ormsgpack.OPT_SERIALIZE_PYDANTIC),
|
data=ormsgpack.packb(
|
||||||
|
pydantic_data, option=ormsgpack.OPT_SERIALIZE_PYDANTIC
|
||||||
|
),
|
||||||
headers={
|
headers={
|
||||||
"Authorization": f"Bearer {self.api_key}",
|
"Authorization": f"Bearer {self.api_key}",
|
||||||
"Content-Type": "application/msgpack",
|
"Content-Type": "application/msgpack",
|
||||||
@@ -152,8 +155,6 @@ class TTSProvider(TTSProviderBase):
|
|||||||
with open(output_file, "wb") as audio_file:
|
with open(output_file, "wb") as audio_file:
|
||||||
audio_file.write(audio_content)
|
audio_file.write(audio_content)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print(f"Request failed with status code {response.status_code}")
|
print(f"Request failed with status code {response.status_code}")
|
||||||
print(response.json())
|
print(response.json())
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ import requests
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from core.utils.util import check_model_key
|
from core.utils.util import check_model_key
|
||||||
from core.providers.tts.base import TTSProviderBase
|
from core.providers.tts.base import TTSProviderBase
|
||||||
|
from config.logger import setup_logging
|
||||||
|
|
||||||
|
TAG = __name__
|
||||||
|
logger = setup_logging()
|
||||||
|
|
||||||
|
|
||||||
class TTSProvider(TTSProviderBase):
|
class TTSProvider(TTSProviderBase):
|
||||||
def __init__(self, config, delete_audio_file):
|
def __init__(self, config, delete_audio_file):
|
||||||
@@ -18,23 +23,28 @@ class TTSProvider(TTSProviderBase):
|
|||||||
check_model_key("TTS", self.api_key)
|
check_model_key("TTS", self.api_key)
|
||||||
|
|
||||||
def generate_filename(self, extension=".wav"):
|
def generate_filename(self, extension=".wav"):
|
||||||
return os.path.join(self.output_file, f"tts-{datetime.now().date()}@{uuid.uuid4().hex}{extension}")
|
return os.path.join(
|
||||||
|
self.output_file,
|
||||||
|
f"tts-{datetime.now().date()}@{uuid.uuid4().hex}{extension}",
|
||||||
|
)
|
||||||
|
|
||||||
async def text_to_speak(self, text, output_file):
|
async def text_to_speak(self, text, output_file):
|
||||||
headers = {
|
headers = {
|
||||||
"Authorization": f"Bearer {self.api_key}",
|
"Authorization": f"Bearer {self.api_key}",
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json",
|
||||||
}
|
}
|
||||||
data = {
|
data = {
|
||||||
"model": self.model,
|
"model": self.model,
|
||||||
"input": text,
|
"input": text,
|
||||||
"voice": self.voice,
|
"voice": self.voice,
|
||||||
"response_format": "wav",
|
"response_format": "wav",
|
||||||
"speed": self.speed
|
"speed": self.speed,
|
||||||
}
|
}
|
||||||
response = requests.post(self.api_url, json=data, headers=headers)
|
response = requests.post(self.api_url, json=data, headers=headers)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
with open(output_file, "wb") as audio_file:
|
with open(output_file, "wb") as audio_file:
|
||||||
audio_file.write(response.content)
|
audio_file.write(response.content)
|
||||||
else:
|
else:
|
||||||
raise Exception(f"OpenAI TTS请求失败: {response.status_code} - {response.text}")
|
raise Exception(
|
||||||
|
f"OpenAI TTS请求失败: {response.status_code} - {response.text}"
|
||||||
|
)
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
class VADProviderBase(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def is_vad(self, conn, data) -> bool:
|
||||||
|
"""检测音频数据中的语音活动"""
|
||||||
|
pass
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import time
|
||||||
|
import numpy as np
|
||||||
|
import torch
|
||||||
|
import opuslib_next
|
||||||
|
from config.logger import setup_logging
|
||||||
|
from core.providers.vad.base import VADProviderBase
|
||||||
|
|
||||||
|
TAG = __name__
|
||||||
|
logger = setup_logging()
|
||||||
|
|
||||||
|
|
||||||
|
class VADProvider(VADProviderBase):
|
||||||
|
def __init__(self, config):
|
||||||
|
logger.bind(tag=TAG).info("SileroVAD", config)
|
||||||
|
self.model, self.utils = torch.hub.load(
|
||||||
|
repo_or_dir=config["model_dir"],
|
||||||
|
source="local",
|
||||||
|
model="silero_vad",
|
||||||
|
force_reload=False,
|
||||||
|
)
|
||||||
|
(get_speech_timestamps, _, _, _, _) = self.utils
|
||||||
|
|
||||||
|
self.decoder = opuslib_next.Decoder(16000, 1)
|
||||||
|
self.vad_threshold = config.get("threshold")
|
||||||
|
self.silence_threshold_ms = config.get("min_silence_duration_ms")
|
||||||
|
|
||||||
|
def is_vad(self, conn, opus_packet):
|
||||||
|
try:
|
||||||
|
pcm_frame = self.decoder.decode(opus_packet, 960)
|
||||||
|
conn.client_audio_buffer.extend(pcm_frame) # 将新数据加入缓冲区
|
||||||
|
|
||||||
|
# 处理缓冲区中的完整帧(每次处理512采样点)
|
||||||
|
client_have_voice = False
|
||||||
|
while len(conn.client_audio_buffer) >= 512 * 2:
|
||||||
|
# 提取前512个采样点(1024字节)
|
||||||
|
chunk = conn.client_audio_buffer[: 512 * 2]
|
||||||
|
conn.client_audio_buffer = conn.client_audio_buffer[512 * 2 :]
|
||||||
|
|
||||||
|
# 转换为模型需要的张量格式
|
||||||
|
audio_int16 = np.frombuffer(chunk, dtype=np.int16)
|
||||||
|
audio_float32 = audio_int16.astype(np.float32) / 32768.0
|
||||||
|
audio_tensor = torch.from_numpy(audio_float32)
|
||||||
|
|
||||||
|
# 检测语音活动
|
||||||
|
speech_prob = self.model(audio_tensor, 16000).item()
|
||||||
|
client_have_voice = speech_prob >= self.vad_threshold
|
||||||
|
|
||||||
|
# 如果之前有声音,但本次没有声音,且与上次有声音的时间查已经超过了静默阈值,则认为已经说完一句话
|
||||||
|
if conn.client_have_voice and not client_have_voice:
|
||||||
|
stop_duration = (
|
||||||
|
time.time() * 1000 - conn.client_have_voice_last_time
|
||||||
|
)
|
||||||
|
if stop_duration >= self.silence_threshold_ms:
|
||||||
|
conn.client_voice_stop = True
|
||||||
|
if client_have_voice:
|
||||||
|
conn.client_have_voice = True
|
||||||
|
conn.client_have_voice_last_time = time.time() * 1000
|
||||||
|
|
||||||
|
return client_have_voice
|
||||||
|
except opuslib_next.OpusError as e:
|
||||||
|
logger.bind(tag=TAG).info(f"解码错误: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.bind(tag=TAG).error(f"Error processing audio packet: {e}")
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
import random
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
from typing import Set
|
|
||||||
|
|
||||||
class AuthCodeGenerator:
|
|
||||||
_instance = None
|
|
||||||
_instance_lock = threading.Lock()
|
|
||||||
|
|
||||||
def __new__(cls):
|
|
||||||
if not cls._instance:
|
|
||||||
with cls._instance_lock:
|
|
||||||
if not cls._instance:
|
|
||||||
cls._instance = super(AuthCodeGenerator, cls).__new__(cls)
|
|
||||||
# 初始化随机种子
|
|
||||||
random.seed(time.time())
|
|
||||||
return cls._instance
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
# 确保 __init__ 只被调用一次
|
|
||||||
if not hasattr(self, '_initialized'):
|
|
||||||
self._used_codes: Set[str] = set()
|
|
||||||
self._code_timestamps = {}
|
|
||||||
self._lock = threading.Lock()
|
|
||||||
self._code_timeout = 3 * 24 * 60 * 60
|
|
||||||
self._initialized = True
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_instance(cls):
|
|
||||||
"""获取AuthCodeGenerator的单例实例"""
|
|
||||||
return cls()
|
|
||||||
|
|
||||||
def generate_code(self) -> str:
|
|
||||||
"""
|
|
||||||
生成6位数字认证码,确保不重复
|
|
||||||
返回: 6位数字字符串
|
|
||||||
"""
|
|
||||||
with self._lock:
|
|
||||||
self._clean_expired_codes() # 清理过期code
|
|
||||||
while True:
|
|
||||||
# 使用时间戳和已用码数量作为种子,确保每次生成不同的随机数
|
|
||||||
seed = int(time.time() * 1000) + len(self._used_codes)
|
|
||||||
random.seed(seed)
|
|
||||||
|
|
||||||
# 生成6位随机数字
|
|
||||||
code = ''.join(str(random.randint(0, 9)) for _ in range(6))
|
|
||||||
|
|
||||||
# 检查是否已存在
|
|
||||||
if code not in self._used_codes:
|
|
||||||
self._used_codes.add(code)
|
|
||||||
self._code_timestamps[code] = time.time()
|
|
||||||
return code
|
|
||||||
|
|
||||||
def remove_code(self, code: str) -> bool:
|
|
||||||
"""
|
|
||||||
删除已使用的认证码
|
|
||||||
参数:
|
|
||||||
code: 要删除的认证码
|
|
||||||
返回:
|
|
||||||
bool: 删除成功返回True,码不存在返回False
|
|
||||||
"""
|
|
||||||
print('remove_code', code)
|
|
||||||
with self._lock:
|
|
||||||
if code in self._used_codes:
|
|
||||||
self._used_codes.remove(code)
|
|
||||||
if code in self._code_timestamps:
|
|
||||||
del self._code_timestamps[code]
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def is_code_used(self, code: str) -> bool:
|
|
||||||
"""
|
|
||||||
检查认证码是否已被使用
|
|
||||||
参数:
|
|
||||||
code: 要检查的认证码
|
|
||||||
返回:
|
|
||||||
bool: 如果码存在返回True,否则返回False
|
|
||||||
"""
|
|
||||||
with self._lock:
|
|
||||||
return code in self._used_codes
|
|
||||||
|
|
||||||
def clear_codes(self):
|
|
||||||
"""清空所有已使用的认证码"""
|
|
||||||
with self._lock:
|
|
||||||
self._used_codes.clear()
|
|
||||||
self._code_timestamps.clear()
|
|
||||||
|
|
||||||
def _clean_expired_codes(self):
|
|
||||||
"""清理过期的认证码"""
|
|
||||||
current_time = time.time()
|
|
||||||
expired_codes = [
|
|
||||||
code for code, timestamp in self._code_timestamps.items()
|
|
||||||
if (current_time - timestamp) > self._code_timeout
|
|
||||||
]
|
|
||||||
for code in expired_codes:
|
|
||||||
self._used_codes.remove(code)
|
|
||||||
del self._code_timestamps[code]
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
import asyncio
|
|
||||||
from typing import Dict
|
|
||||||
from config.logger import setup_logging
|
|
||||||
|
|
||||||
TAG = __name__
|
|
||||||
logger = setup_logging()
|
|
||||||
|
|
||||||
class FileLockManager:
|
|
||||||
_instance = None
|
|
||||||
_locks: Dict[str, asyncio.Lock] = {}
|
|
||||||
|
|
||||||
def __new__(cls):
|
|
||||||
if cls._instance is None:
|
|
||||||
cls._instance = super(FileLockManager, cls).__new__(cls)
|
|
||||||
return cls._instance
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_lock(cls, file_path: str) -> asyncio.Lock:
|
|
||||||
"""获取指定文件的锁"""
|
|
||||||
if file_path not in cls._locks:
|
|
||||||
cls._locks[file_path] = asyncio.Lock()
|
|
||||||
return cls._locks[file_path]
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def acquire_lock(cls, file_path: str):
|
|
||||||
"""获取锁"""
|
|
||||||
lock = cls.get_lock(file_path)
|
|
||||||
await lock.acquire()
|
|
||||||
logger.bind(tag=TAG).debug(f"Acquired lock for {file_path}")
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def release_lock(cls, file_path: str):
|
|
||||||
"""释放锁"""
|
|
||||||
if file_path in cls._locks:
|
|
||||||
try:
|
|
||||||
cls._locks[file_path].release()
|
|
||||||
logger.bind(tag=TAG).debug(f"Released lock for {file_path}")
|
|
||||||
except RuntimeError as e:
|
|
||||||
logger.bind(tag=TAG).warning(f"Failed to release lock for {file_path}: {e}")
|
|
||||||
@@ -2,16 +2,17 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import importlib
|
import importlib
|
||||||
from config.logger import setup_logging
|
from config.logger import setup_logging
|
||||||
from core.utils.util import read_config, get_project_dir
|
|
||||||
|
|
||||||
logger = setup_logging()
|
logger = setup_logging()
|
||||||
|
|
||||||
|
|
||||||
def create_instance(class_name, *args, **kwargs):
|
def create_instance(class_name, *args, **kwargs):
|
||||||
if os.path.exists(os.path.join('core', 'providers', 'memory', class_name, f'{class_name}.py')):
|
if os.path.exists(
|
||||||
lib_name = f'core.providers.memory.{class_name}.{class_name}'
|
os.path.join("core", "providers", "memory", class_name, f"{class_name}.py")
|
||||||
|
):
|
||||||
|
lib_name = f"core.providers.memory.{class_name}.{class_name}"
|
||||||
if lib_name not in sys.modules:
|
if lib_name not in sys.modules:
|
||||||
sys.modules[lib_name] = importlib.import_module(f'{lib_name}')
|
sys.modules[lib_name] = importlib.import_module(f"{lib_name}")
|
||||||
return sys.modules[lib_name].MemoryProvider(*args, **kwargs)
|
return sys.modules[lib_name].MemoryProvider(*args, **kwargs)
|
||||||
|
|
||||||
raise ValueError(f"不支持的记忆服务类型: {class_name}")
|
raise ValueError(f"不支持的记忆服务类型: {class_name}")
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
import os
|
|
||||||
import json
|
import json
|
||||||
import yaml
|
|
||||||
import socket
|
import socket
|
||||||
import subprocess
|
import subprocess
|
||||||
import logging
|
|
||||||
import re
|
import re
|
||||||
import requests
|
import requests
|
||||||
|
from typing import Dict, Any
|
||||||
|
from core.utils import tts, llm, intent, memory, vad, asr
|
||||||
|
|
||||||
|
TAG = __name__
|
||||||
def get_project_dir():
|
|
||||||
"""获取项目根目录"""
|
|
||||||
return (
|
|
||||||
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
||||||
+ "/"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_local_ip():
|
def get_local_ip():
|
||||||
@@ -72,7 +65,7 @@ def is_private_ip(ip_addr):
|
|||||||
return False # IP address format error or insufficient segments
|
return False # IP address format error or insufficient segments
|
||||||
|
|
||||||
|
|
||||||
def get_ip_info(ip_addr):
|
def get_ip_info(ip_addr, logger):
|
||||||
try:
|
try:
|
||||||
if is_private_ip(ip_addr):
|
if is_private_ip(ip_addr):
|
||||||
ip_addr = ""
|
ip_addr = ""
|
||||||
@@ -81,16 +74,10 @@ def get_ip_info(ip_addr):
|
|||||||
ip_info = {"city": resp.get("city")}
|
ip_info = {"city": resp.get("city")}
|
||||||
return ip_info
|
return ip_info
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error getting client ip info: {e}")
|
logger.bind(tag=TAG).error(f"Error getting client ip info: {e}")
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
def read_config(config_path):
|
|
||||||
with open(config_path, "r", encoding="utf-8") as file:
|
|
||||||
config = yaml.safe_load(file)
|
|
||||||
return config
|
|
||||||
|
|
||||||
|
|
||||||
def write_json_file(file_path, data):
|
def write_json_file(file_path, data):
|
||||||
"""将数据写入 JSON 文件"""
|
"""将数据写入 JSON 文件"""
|
||||||
with open(file_path, "w", encoding="utf-8") as file:
|
with open(file_path, "w", encoding="utf-8") as file:
|
||||||
@@ -169,10 +156,8 @@ def remove_punctuation_and_length(text):
|
|||||||
|
|
||||||
def check_model_key(modelType, modelKey):
|
def check_model_key(modelType, modelKey):
|
||||||
if "你" in modelKey:
|
if "你" in modelKey:
|
||||||
logging.error(
|
raise ValueError(
|
||||||
"你还没配置"
|
"你还没配置" + modelType + "的密钥,请检查一下所使用的LLM是否配置了密钥"
|
||||||
+ modelType
|
|
||||||
+ "的密钥,请在配置文件中配置密钥,否则无法正常工作"
|
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
@@ -212,3 +197,105 @@ def extract_json_from_string(input_string):
|
|||||||
if match:
|
if match:
|
||||||
return match.group(1) # 返回提取的 JSON 字符串
|
return match.group(1) # 返回提取的 JSON 字符串
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_modules(
|
||||||
|
logger,
|
||||||
|
config: Dict[str, Any],
|
||||||
|
init_vad=False,
|
||||||
|
init_asr=False,
|
||||||
|
init_llm=False,
|
||||||
|
init_tts=False,
|
||||||
|
init_memory=False,
|
||||||
|
init_intent=False,
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
初始化所有模块组件
|
||||||
|
|
||||||
|
Args:
|
||||||
|
config: 配置字典
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict[str, Any]: 包含所有初始化后的模块的字典
|
||||||
|
"""
|
||||||
|
modules = {}
|
||||||
|
|
||||||
|
# 初始化TTS模块
|
||||||
|
if init_tts:
|
||||||
|
tts_type = (
|
||||||
|
config["selected_module"]["TTS"]
|
||||||
|
if "type" not in config["TTS"][config["selected_module"]["TTS"]]
|
||||||
|
else config["TTS"][config["selected_module"]["TTS"]]["type"]
|
||||||
|
)
|
||||||
|
modules["tts"] = tts.create_instance(
|
||||||
|
tts_type,
|
||||||
|
config["TTS"][config["selected_module"]["TTS"]],
|
||||||
|
config["delete_audio"],
|
||||||
|
)
|
||||||
|
logger.bind(tag=TAG).info(f"初始化组件: tts成功")
|
||||||
|
|
||||||
|
# 初始化LLM模块
|
||||||
|
if init_llm:
|
||||||
|
llm_type = (
|
||||||
|
config["selected_module"]["LLM"]
|
||||||
|
if "type" not in config["LLM"][config["selected_module"]["LLM"]]
|
||||||
|
else config["LLM"][config["selected_module"]["LLM"]]["type"]
|
||||||
|
)
|
||||||
|
modules["llm"] = llm.create_instance(
|
||||||
|
llm_type,
|
||||||
|
config["LLM"][config["selected_module"]["LLM"]],
|
||||||
|
)
|
||||||
|
logger.bind(tag=TAG).info(f"初始化组件: llm成功")
|
||||||
|
|
||||||
|
# 初始化Intent模块
|
||||||
|
if init_intent:
|
||||||
|
intent_type = (
|
||||||
|
config["selected_module"]["Intent"]
|
||||||
|
if "type" not in config["Intent"][config["selected_module"]["Intent"]]
|
||||||
|
else config["Intent"][config["selected_module"]["Intent"]]["type"]
|
||||||
|
)
|
||||||
|
modules["intent"] = intent.create_instance(
|
||||||
|
intent_type,
|
||||||
|
config["Intent"][config["selected_module"]["Intent"]],
|
||||||
|
)
|
||||||
|
logger.bind(tag=TAG).info(f"初始化组件: intent成功")
|
||||||
|
# 初始化Memory模块
|
||||||
|
if init_memory:
|
||||||
|
memory_type = (
|
||||||
|
config["selected_module"]["Memory"]
|
||||||
|
if "type" not in config["Memory"][config["selected_module"]["Memory"]]
|
||||||
|
else config["Memory"][config["selected_module"]["Memory"]]["type"]
|
||||||
|
)
|
||||||
|
modules["memory"] = memory.create_instance(
|
||||||
|
memory_type,
|
||||||
|
config["Memory"][config["selected_module"]["Memory"]],
|
||||||
|
)
|
||||||
|
logger.bind(tag=TAG).info(f"初始化组件: memory成功")
|
||||||
|
|
||||||
|
# 初始化VAD模块
|
||||||
|
if init_vad:
|
||||||
|
vad_type = (
|
||||||
|
config["selected_module"]["VAD"]
|
||||||
|
if "type" not in config["VAD"][config["selected_module"]["VAD"]]
|
||||||
|
else config["VAD"][config["selected_module"]["VAD"]]["type"]
|
||||||
|
)
|
||||||
|
modules["vad"] = vad.create_instance(
|
||||||
|
vad_type,
|
||||||
|
config["VAD"][config["selected_module"]["VAD"]],
|
||||||
|
)
|
||||||
|
logger.bind(tag=TAG).info(f"初始化组件: vad成功")
|
||||||
|
# 初始化ASR模块
|
||||||
|
if init_asr:
|
||||||
|
asr_type = (
|
||||||
|
config["selected_module"]["ASR"]
|
||||||
|
if "type" not in config["ASR"][config["selected_module"]["ASR"]]
|
||||||
|
else config["ASR"][config["selected_module"]["ASR"]]["type"]
|
||||||
|
)
|
||||||
|
modules["asr"] = asr.create_instance(
|
||||||
|
asr_type,
|
||||||
|
config["ASR"][config["selected_module"]["ASR"]],
|
||||||
|
config["delete_audio"],
|
||||||
|
)
|
||||||
|
logger.bind(tag=TAG).info(f"初始化组件: asr成功")
|
||||||
|
|
||||||
|
return modules
|
||||||
|
|||||||
@@ -1,77 +1,19 @@
|
|||||||
from abc import ABC, abstractmethod
|
import importlib
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from core.providers.vad.base import VADProviderBase
|
||||||
from config.logger import setup_logging
|
from config.logger import setup_logging
|
||||||
import opuslib_next
|
|
||||||
import time
|
|
||||||
import numpy as np
|
|
||||||
import torch
|
|
||||||
|
|
||||||
TAG = __name__
|
TAG = __name__
|
||||||
logger = setup_logging()
|
logger = setup_logging()
|
||||||
|
|
||||||
class VAD(ABC):
|
|
||||||
@abstractmethod
|
|
||||||
def is_vad(self, conn, data):
|
|
||||||
"""检测音频数据中的语音活动"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
def create_instance(class_name: str, *args, **kwargs) -> VADProviderBase:
|
||||||
|
"""工厂方法创建VAD实例"""
|
||||||
|
if os.path.exists(os.path.join("core", "providers", "vad", f"{class_name}.py")):
|
||||||
|
lib_name = f"core.providers.vad.{class_name}"
|
||||||
|
if lib_name not in sys.modules:
|
||||||
|
sys.modules[lib_name] = importlib.import_module(f"{lib_name}")
|
||||||
|
return sys.modules[lib_name].VADProvider(*args, **kwargs)
|
||||||
|
|
||||||
class SileroVAD(VAD):
|
raise ValueError(f"不支持的VAD类型: {class_name},请检查该配置的type是否设置正确")
|
||||||
def __init__(self, config):
|
|
||||||
logger.bind(tag=TAG).info("SileroVAD", config)
|
|
||||||
self.model, self.utils = torch.hub.load(repo_or_dir=config["model_dir"],
|
|
||||||
source='local',
|
|
||||||
model='silero_vad',
|
|
||||||
force_reload=False)
|
|
||||||
(get_speech_timestamps, _, _, _, _) = self.utils
|
|
||||||
|
|
||||||
self.decoder = opuslib_next.Decoder(16000, 1)
|
|
||||||
self.vad_threshold = config.get("threshold")
|
|
||||||
self.silence_threshold_ms = config.get("min_silence_duration_ms")
|
|
||||||
|
|
||||||
def is_vad(self, conn, opus_packet):
|
|
||||||
try:
|
|
||||||
pcm_frame = self.decoder.decode(opus_packet, 960)
|
|
||||||
conn.client_audio_buffer.extend(pcm_frame) # 将新数据加入缓冲区
|
|
||||||
|
|
||||||
# 处理缓冲区中的完整帧(每次处理512采样点)
|
|
||||||
client_have_voice = False
|
|
||||||
while len(conn.client_audio_buffer) >= 512 * 2:
|
|
||||||
# 提取前512个采样点(1024字节)
|
|
||||||
chunk = conn.client_audio_buffer[:512 * 2]
|
|
||||||
conn.client_audio_buffer = conn.client_audio_buffer[512 * 2:]
|
|
||||||
|
|
||||||
# 转换为模型需要的张量格式
|
|
||||||
audio_int16 = np.frombuffer(chunk, dtype=np.int16)
|
|
||||||
audio_float32 = audio_int16.astype(np.float32) / 32768.0
|
|
||||||
audio_tensor = torch.from_numpy(audio_float32)
|
|
||||||
|
|
||||||
# 检测语音活动
|
|
||||||
speech_prob = self.model(audio_tensor, 16000).item()
|
|
||||||
client_have_voice = speech_prob >= self.vad_threshold
|
|
||||||
|
|
||||||
# 如果之前有声音,但本次没有声音,且与上次有声音的时间查已经超过了静默阈值,则认为已经说完一句话
|
|
||||||
if conn.client_have_voice and not client_have_voice:
|
|
||||||
stop_duration = time.time() * 1000 - conn.client_have_voice_last_time
|
|
||||||
if stop_duration >= self.silence_threshold_ms:
|
|
||||||
conn.client_voice_stop = True
|
|
||||||
if client_have_voice:
|
|
||||||
conn.client_have_voice = True
|
|
||||||
conn.client_have_voice_last_time = time.time() * 1000
|
|
||||||
|
|
||||||
return client_have_voice
|
|
||||||
except opuslib_next.OpusError as e:
|
|
||||||
logger.bind(tag=TAG).info(f"解码错误: {e}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.bind(tag=TAG).error(f"Error processing audio packet: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
def create_instance(class_name, *args, **kwargs) -> VAD:
|
|
||||||
# 获取类对象
|
|
||||||
cls_map = {
|
|
||||||
"SileroVAD": SileroVAD,
|
|
||||||
# 可扩展其他SileroVAD实现
|
|
||||||
}
|
|
||||||
|
|
||||||
if cls := cls_map.get(class_name):
|
|
||||||
return cls(*args, **kwargs)
|
|
||||||
raise ValueError(f"不支持的SileroVAD类型: {class_name}")
|
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ import asyncio
|
|||||||
import websockets
|
import websockets
|
||||||
from config.logger import setup_logging
|
from config.logger import setup_logging
|
||||||
from core.connection import ConnectionHandler
|
from core.connection import ConnectionHandler
|
||||||
from core.utils.util import get_local_ip
|
from core.utils.util import get_local_ip, initialize_modules
|
||||||
from core.utils import asr, vad, llm, tts, memory, intent
|
|
||||||
|
|
||||||
TAG = __name__
|
TAG = __name__
|
||||||
|
|
||||||
@@ -12,74 +11,16 @@ class WebSocketServer:
|
|||||||
def __init__(self, config: dict):
|
def __init__(self, config: dict):
|
||||||
self.config = config
|
self.config = config
|
||||||
self.logger = setup_logging()
|
self.logger = setup_logging()
|
||||||
self._vad, self._asr, self._llm, self._tts, self._memory, self.intent = (
|
modules = initialize_modules(
|
||||||
self._create_processing_instances()
|
self.logger, self.config, True, True, True, True, True, True
|
||||||
)
|
|
||||||
self.active_connections = set() # 添加全局连接记录
|
|
||||||
|
|
||||||
def _create_processing_instances(self):
|
|
||||||
memory_cls_name = self.config["selected_module"].get(
|
|
||||||
"Memory", "nomem"
|
|
||||||
) # 默认使用nomem
|
|
||||||
has_memory_cfg = (
|
|
||||||
self.config.get("Memory") and memory_cls_name in self.config["Memory"]
|
|
||||||
)
|
|
||||||
memory_cfg = self.config["Memory"][memory_cls_name] if has_memory_cfg else {}
|
|
||||||
|
|
||||||
"""创建处理模块实例"""
|
|
||||||
return (
|
|
||||||
vad.create_instance(
|
|
||||||
self.config["selected_module"]["VAD"],
|
|
||||||
self.config["VAD"][self.config["selected_module"]["VAD"]],
|
|
||||||
),
|
|
||||||
asr.create_instance(
|
|
||||||
(
|
|
||||||
self.config["selected_module"]["ASR"]
|
|
||||||
if not "type"
|
|
||||||
in self.config["ASR"][self.config["selected_module"]["ASR"]]
|
|
||||||
else self.config["ASR"][self.config["selected_module"]["ASR"]][
|
|
||||||
"type"
|
|
||||||
]
|
|
||||||
),
|
|
||||||
self.config["ASR"][self.config["selected_module"]["ASR"]],
|
|
||||||
self.config["delete_audio"],
|
|
||||||
),
|
|
||||||
llm.create_instance(
|
|
||||||
(
|
|
||||||
self.config["selected_module"]["LLM"]
|
|
||||||
if not "type"
|
|
||||||
in self.config["LLM"][self.config["selected_module"]["LLM"]]
|
|
||||||
else self.config["LLM"][self.config["selected_module"]["LLM"]][
|
|
||||||
"type"
|
|
||||||
]
|
|
||||||
),
|
|
||||||
self.config["LLM"][self.config["selected_module"]["LLM"]],
|
|
||||||
),
|
|
||||||
tts.create_instance(
|
|
||||||
(
|
|
||||||
self.config["selected_module"]["TTS"]
|
|
||||||
if not "type"
|
|
||||||
in self.config["TTS"][self.config["selected_module"]["TTS"]]
|
|
||||||
else self.config["TTS"][self.config["selected_module"]["TTS"]][
|
|
||||||
"type"
|
|
||||||
]
|
|
||||||
),
|
|
||||||
self.config["TTS"][self.config["selected_module"]["TTS"]],
|
|
||||||
self.config["delete_audio"],
|
|
||||||
),
|
|
||||||
memory.create_instance(memory_cls_name, memory_cfg),
|
|
||||||
intent.create_instance(
|
|
||||||
(
|
|
||||||
self.config["selected_module"]["Intent"]
|
|
||||||
if not "type"
|
|
||||||
in self.config["Intent"][self.config["selected_module"]["Intent"]]
|
|
||||||
else self.config["Intent"][
|
|
||||||
self.config["selected_module"]["Intent"]
|
|
||||||
]["type"]
|
|
||||||
),
|
|
||||||
self.config["Intent"][self.config["selected_module"]["Intent"]],
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
self._vad = modules["vad"]
|
||||||
|
self._asr = modules["asr"]
|
||||||
|
self._tts = modules["tts"]
|
||||||
|
self._llm = modules["llm"]
|
||||||
|
self._intent = modules["intent"]
|
||||||
|
self._memory = modules["memory"]
|
||||||
|
self.active_connections = set()
|
||||||
|
|
||||||
async def start(self):
|
async def start(self):
|
||||||
server_config = self.config["server"]
|
server_config = self.config["server"]
|
||||||
@@ -111,7 +52,7 @@ class WebSocketServer:
|
|||||||
self._llm,
|
self._llm,
|
||||||
self._tts,
|
self._tts,
|
||||||
self._memory,
|
self._memory,
|
||||||
self.intent,
|
self._intent,
|
||||||
)
|
)
|
||||||
self.active_connections.add(handler)
|
self.active_connections.add(handler)
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -5,9 +5,8 @@ from tabulate import tabulate
|
|||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
from core.utils.llm import create_instance as create_llm_instance
|
from core.utils.llm import create_instance as create_llm_instance
|
||||||
from core.utils.tts import create_instance as create_tts_instance
|
from core.utils.tts import create_instance as create_tts_instance
|
||||||
from core.utils.util import read_config
|
|
||||||
import statistics
|
import statistics
|
||||||
from config.settings import get_config_file
|
from config.settings import load_config
|
||||||
import inspect
|
import inspect
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
@@ -18,17 +17,16 @@ logging.basicConfig(level=logging.WARNING)
|
|||||||
|
|
||||||
class AsyncPerformanceTester:
|
class AsyncPerformanceTester:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.config = read_config(get_config_file())
|
self.config = load_config()
|
||||||
self.test_sentences = self.config.get("module_test", {}).get(
|
self.test_sentences = self.config.get("module_test", {}).get(
|
||||||
"test_sentences",
|
"test_sentences",
|
||||||
["你好,请介绍一下你自己", "What's the weather like today?",
|
[
|
||||||
"请用100字概括量子计算的基本原理和应用前景"]
|
"你好,请介绍一下你自己",
|
||||||
|
"What's the weather like today?",
|
||||||
|
"请用100字概括量子计算的基本原理和应用前景",
|
||||||
|
],
|
||||||
)
|
)
|
||||||
self.results = {
|
self.results = {"llm": {}, "tts": {}, "combinations": []}
|
||||||
"llm": {},
|
|
||||||
"tts": {},
|
|
||||||
"combinations": []
|
|
||||||
}
|
|
||||||
|
|
||||||
async def _check_ollama_service(self, base_url: str, model_name: str) -> bool:
|
async def _check_ollama_service(self, base_url: str, model_name: str) -> bool:
|
||||||
"""异步检查Ollama服务状态"""
|
"""异步检查Ollama服务状态"""
|
||||||
@@ -46,7 +44,9 @@ class AsyncPerformanceTester:
|
|||||||
data = await response.json()
|
data = await response.json()
|
||||||
models = data.get("models", [])
|
models = data.get("models", [])
|
||||||
if not any(model["name"] == model_name for model in models):
|
if not any(model["name"] == model_name for model in models):
|
||||||
print(f"🚫 Ollama模型 {model_name} 未找到,请先使用 ollama pull {model_name} 下载")
|
print(
|
||||||
|
f"🚫 Ollama模型 {model_name} 未找到,请先使用 ollama pull {model_name} 下载"
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
print(f"🚫 无法获取Ollama模型列表")
|
print(f"🚫 无法获取Ollama模型列表")
|
||||||
@@ -62,17 +62,16 @@ class AsyncPerformanceTester:
|
|||||||
logging.getLogger("core.providers.tts.base").setLevel(logging.WARNING)
|
logging.getLogger("core.providers.tts.base").setLevel(logging.WARNING)
|
||||||
|
|
||||||
token_fields = ["access_token", "api_key", "token"]
|
token_fields = ["access_token", "api_key", "token"]
|
||||||
if any(field in config and any(x in config[field] for x in ["你的", "placeholder"]) for field in
|
if any(
|
||||||
token_fields):
|
field in config
|
||||||
|
and any(x in config[field] for x in ["你的", "placeholder"])
|
||||||
|
for field in token_fields
|
||||||
|
):
|
||||||
print(f"⏭️ TTS {tts_name} 未配置access_token/api_key,已跳过")
|
print(f"⏭️ TTS {tts_name} 未配置access_token/api_key,已跳过")
|
||||||
return {"name": tts_name, "type": "tts", "errors": 1}
|
return {"name": tts_name, "type": "tts", "errors": 1}
|
||||||
|
|
||||||
module_type = config.get('type', tts_name)
|
module_type = config.get("type", tts_name)
|
||||||
tts = create_tts_instance(
|
tts = create_tts_instance(module_type, config, delete_audio_file=True)
|
||||||
module_type,
|
|
||||||
config,
|
|
||||||
delete_audio_file=True
|
|
||||||
)
|
|
||||||
|
|
||||||
print(f"🎵 测试 TTS: {tts_name}")
|
print(f"🎵 测试 TTS: {tts_name}")
|
||||||
|
|
||||||
@@ -103,7 +102,7 @@ class AsyncPerformanceTester:
|
|||||||
"name": tts_name,
|
"name": tts_name,
|
||||||
"type": "tts",
|
"type": "tts",
|
||||||
"avg_time": total_time / test_count,
|
"avg_time": total_time / test_count,
|
||||||
"errors": 0
|
"errors": 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -115,8 +114,8 @@ class AsyncPerformanceTester:
|
|||||||
try:
|
try:
|
||||||
# 对于Ollama,跳过api_key检查并进行特殊处理
|
# 对于Ollama,跳过api_key检查并进行特殊处理
|
||||||
if llm_name == "Ollama":
|
if llm_name == "Ollama":
|
||||||
base_url = config.get('base_url', 'http://localhost:11434')
|
base_url = config.get("base_url", "http://localhost:11434")
|
||||||
model_name = config.get('model_name')
|
model_name = config.get("model_name")
|
||||||
if not model_name:
|
if not model_name:
|
||||||
print(f"🚫 Ollama未配置model_name")
|
print(f"🚫 Ollama未配置model_name")
|
||||||
return {"name": llm_name, "type": "llm", "errors": 1}
|
return {"name": llm_name, "type": "llm", "errors": 1}
|
||||||
@@ -124,21 +123,27 @@ class AsyncPerformanceTester:
|
|||||||
if not await self._check_ollama_service(base_url, model_name):
|
if not await self._check_ollama_service(base_url, model_name):
|
||||||
return {"name": llm_name, "type": "llm", "errors": 1}
|
return {"name": llm_name, "type": "llm", "errors": 1}
|
||||||
else:
|
else:
|
||||||
if "api_key" in config and any(x in config["api_key"] for x in ["你的", "placeholder", "sk-xxx"]):
|
if "api_key" in config and any(
|
||||||
|
x in config["api_key"] for x in ["你的", "placeholder", "sk-xxx"]
|
||||||
|
):
|
||||||
print(f"🚫 跳过未配置的LLM: {llm_name}")
|
print(f"🚫 跳过未配置的LLM: {llm_name}")
|
||||||
return {"name": llm_name, "type": "llm", "errors": 1}
|
return {"name": llm_name, "type": "llm", "errors": 1}
|
||||||
|
|
||||||
# 获取实际类型(兼容旧配置)
|
# 获取实际类型(兼容旧配置)
|
||||||
module_type = config.get('type', llm_name)
|
module_type = config.get("type", llm_name)
|
||||||
llm = create_llm_instance(module_type, config)
|
llm = create_llm_instance(module_type, config)
|
||||||
|
|
||||||
# 统一使用UTF-8编码
|
# 统一使用UTF-8编码
|
||||||
test_sentences = [s.encode('utf-8').decode('utf-8') for s in self.test_sentences]
|
test_sentences = [
|
||||||
|
s.encode("utf-8").decode("utf-8") for s in self.test_sentences
|
||||||
|
]
|
||||||
|
|
||||||
# 创建所有句子的测试任务
|
# 创建所有句子的测试任务
|
||||||
sentence_tasks = []
|
sentence_tasks = []
|
||||||
for sentence in test_sentences:
|
for sentence in test_sentences:
|
||||||
sentence_tasks.append(self._test_single_sentence(llm_name, llm, sentence))
|
sentence_tasks.append(
|
||||||
|
self._test_single_sentence(llm_name, llm, sentence)
|
||||||
|
)
|
||||||
|
|
||||||
# 并发执行所有句子测试
|
# 并发执行所有句子测试
|
||||||
sentence_results = await asyncio.gather(*sentence_tasks)
|
sentence_results = await asyncio.gather(*sentence_tasks)
|
||||||
@@ -166,9 +171,15 @@ class AsyncPerformanceTester:
|
|||||||
"type": "llm",
|
"type": "llm",
|
||||||
"avg_response": sum(response_times) / len(response_times),
|
"avg_response": sum(response_times) / len(response_times),
|
||||||
"avg_first_token": sum(first_token_times) / len(first_token_times),
|
"avg_first_token": sum(first_token_times) / len(first_token_times),
|
||||||
"std_first_token": statistics.stdev(first_token_times) if len(first_token_times) > 1 else 0,
|
"std_first_token": (
|
||||||
"std_response": statistics.stdev(response_times) if len(response_times) > 1 else 0,
|
statistics.stdev(first_token_times)
|
||||||
"errors": 0
|
if len(first_token_times) > 1
|
||||||
|
else 0
|
||||||
|
),
|
||||||
|
"std_response": (
|
||||||
|
statistics.stdev(response_times) if len(response_times) > 1 else 0
|
||||||
|
),
|
||||||
|
"errors": 0,
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"LLM {llm_name} 测试失败: {str(e)}")
|
print(f"LLM {llm_name} 测试失败: {str(e)}")
|
||||||
@@ -184,8 +195,10 @@ class AsyncPerformanceTester:
|
|||||||
|
|
||||||
async def process_response():
|
async def process_response():
|
||||||
nonlocal first_token_received, first_token_time
|
nonlocal first_token_received, first_token_time
|
||||||
for chunk in llm.response("perf_test", [{"role": "user", "content": sentence}]):
|
for chunk in llm.response(
|
||||||
if not first_token_received and chunk.strip() != '':
|
"perf_test", [{"role": "user", "content": sentence}]
|
||||||
|
):
|
||||||
|
if not first_token_received and chunk.strip() != "":
|
||||||
first_token_time = time.time() - sentence_start
|
first_token_time = time.time() - sentence_start
|
||||||
first_token_received = True
|
first_token_received = True
|
||||||
print(f"✓ {llm_name} 首个Token: {first_token_time:.3f}s")
|
print(f"✓ {llm_name} 首个Token: {first_token_time:.3f}s")
|
||||||
@@ -199,13 +212,15 @@ class AsyncPerformanceTester:
|
|||||||
print(f"✓ {llm_name} 完成响应: {response_time:.3f}s")
|
print(f"✓ {llm_name} 完成响应: {response_time:.3f}s")
|
||||||
|
|
||||||
if first_token_time is None:
|
if first_token_time is None:
|
||||||
first_token_time = response_time # 如果没有检测到first token,使用总响应时间
|
first_token_time = (
|
||||||
|
response_time # 如果没有检测到first token,使用总响应时间
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"name": llm_name,
|
"name": llm_name,
|
||||||
"type": "llm",
|
"type": "llm",
|
||||||
"first_token_time": first_token_time,
|
"first_token_time": first_token_time,
|
||||||
"response_time": response_time
|
"response_time": response_time,
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"⚠️ {llm_name} 句子测试失败: {str(e)}")
|
print(f"⚠️ {llm_name} 句子测试失败: {str(e)}")
|
||||||
@@ -214,24 +229,37 @@ class AsyncPerformanceTester:
|
|||||||
def _generate_combinations(self):
|
def _generate_combinations(self):
|
||||||
"""生成最佳组合建议"""
|
"""生成最佳组合建议"""
|
||||||
valid_llms = [
|
valid_llms = [
|
||||||
k for k, v in self.results["llm"].items()
|
k
|
||||||
|
for k, v in self.results["llm"].items()
|
||||||
if v["errors"] == 0 and v["avg_first_token"] >= 0.05
|
if v["errors"] == 0 and v["avg_first_token"] >= 0.05
|
||||||
]
|
]
|
||||||
valid_tts = [k for k, v in self.results["tts"].items() if v["errors"] == 0]
|
valid_tts = [k for k, v in self.results["tts"].items() if v["errors"] == 0]
|
||||||
|
|
||||||
# 找出基准值
|
# 找出基准值
|
||||||
min_first_token = min([self.results["llm"][llm]["avg_first_token"] for llm in valid_llms]) if valid_llms else 1
|
min_first_token = (
|
||||||
min_tts_time = min([self.results["tts"][tts]["avg_time"] for tts in valid_tts]) if valid_tts else 1
|
min([self.results["llm"][llm]["avg_first_token"] for llm in valid_llms])
|
||||||
|
if valid_llms
|
||||||
|
else 1
|
||||||
|
)
|
||||||
|
min_tts_time = (
|
||||||
|
min([self.results["tts"][tts]["avg_time"] for tts in valid_tts])
|
||||||
|
if valid_tts
|
||||||
|
else 1
|
||||||
|
)
|
||||||
|
|
||||||
for llm in valid_llms:
|
for llm in valid_llms:
|
||||||
for tts in valid_tts:
|
for tts in valid_tts:
|
||||||
# 计算相对性能分数(越小越好)
|
# 计算相对性能分数(越小越好)
|
||||||
llm_score = self.results["llm"][llm]["avg_first_token"] / min_first_token
|
llm_score = (
|
||||||
|
self.results["llm"][llm]["avg_first_token"] / min_first_token
|
||||||
|
)
|
||||||
tts_score = self.results["tts"][tts]["avg_time"] / min_tts_time
|
tts_score = self.results["tts"][tts]["avg_time"] / min_tts_time
|
||||||
|
|
||||||
# 计算稳定性分数(标准差/平均值,越小越稳定)
|
# 计算稳定性分数(标准差/平均值,越小越稳定)
|
||||||
llm_stability = self.results["llm"][llm]["std_first_token"] / self.results["llm"][llm][
|
llm_stability = (
|
||||||
"avg_first_token"]
|
self.results["llm"][llm]["std_first_token"]
|
||||||
|
/ self.results["llm"][llm]["avg_first_token"]
|
||||||
|
)
|
||||||
|
|
||||||
# 综合得分(考虑性能和稳定性)
|
# 综合得分(考虑性能和稳定性)
|
||||||
# 性能权重0.7,稳定性权重0.3
|
# 性能权重0.7,稳定性权重0.3
|
||||||
@@ -240,16 +268,20 @@ class AsyncPerformanceTester:
|
|||||||
# 总分 = LLM得分(70%) + TTS得分(30%)
|
# 总分 = LLM得分(70%) + TTS得分(30%)
|
||||||
total_score = llm_final_score * 0.7 + tts_score * 0.3
|
total_score = llm_final_score * 0.7 + tts_score * 0.3
|
||||||
|
|
||||||
self.results["combinations"].append({
|
self.results["combinations"].append(
|
||||||
"llm": llm,
|
{
|
||||||
"tts": tts,
|
"llm": llm,
|
||||||
"score": total_score,
|
"tts": tts,
|
||||||
"details": {
|
"score": total_score,
|
||||||
"llm_first_token": self.results["llm"][llm]["avg_first_token"],
|
"details": {
|
||||||
"llm_stability": llm_stability,
|
"llm_first_token": self.results["llm"][llm][
|
||||||
"tts_time": self.results["tts"][tts]["avg_time"]
|
"avg_first_token"
|
||||||
|
],
|
||||||
|
"llm_stability": llm_stability,
|
||||||
|
"tts_time": self.results["tts"][tts]["avg_time"],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
# 分数越小越好
|
# 分数越小越好
|
||||||
self.results["combinations"].sort(key=lambda x: x["score"])
|
self.results["combinations"].sort(key=lambda x: x["score"])
|
||||||
@@ -260,42 +292,45 @@ class AsyncPerformanceTester:
|
|||||||
for name, data in self.results["llm"].items():
|
for name, data in self.results["llm"].items():
|
||||||
if data["errors"] == 0:
|
if data["errors"] == 0:
|
||||||
stability = data["std_first_token"] / data["avg_first_token"]
|
stability = data["std_first_token"] / data["avg_first_token"]
|
||||||
llm_table.append([
|
llm_table.append(
|
||||||
name, # 不需要固定宽度,让tabulate自己处理对齐
|
[
|
||||||
f"{data['avg_first_token']:.3f}秒",
|
name, # 不需要固定宽度,让tabulate自己处理对齐
|
||||||
f"{data['avg_response']:.3f}秒",
|
f"{data['avg_first_token']:.3f}秒",
|
||||||
f"{stability:.3f}"
|
f"{data['avg_response']:.3f}秒",
|
||||||
])
|
f"{stability:.3f}",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
if llm_table:
|
if llm_table:
|
||||||
print("\nLLM 性能排行:")
|
print("\nLLM 性能排行:")
|
||||||
print(tabulate(
|
print(
|
||||||
llm_table,
|
tabulate(
|
||||||
headers=["模型名称", "首字耗时", "总耗时", "稳定性"],
|
llm_table,
|
||||||
tablefmt="github",
|
headers=["模型名称", "首字耗时", "总耗时", "稳定性"],
|
||||||
colalign=("left", "right", "right", "right"),
|
tablefmt="github",
|
||||||
disable_numparse=True
|
colalign=("left", "right", "right", "right"),
|
||||||
))
|
disable_numparse=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
print("\n⚠️ 没有可用的LLM模块进行测试。")
|
print("\n⚠️ 没有可用的LLM模块进行测试。")
|
||||||
|
|
||||||
tts_table = []
|
tts_table = []
|
||||||
for name, data in self.results["tts"].items():
|
for name, data in self.results["tts"].items():
|
||||||
if data["errors"] == 0:
|
if data["errors"] == 0:
|
||||||
tts_table.append([
|
tts_table.append([name, f"{data['avg_time']:.3f}秒"]) # 不需要固定宽度
|
||||||
name, # 不需要固定宽度
|
|
||||||
f"{data['avg_time']:.3f}秒"
|
|
||||||
])
|
|
||||||
|
|
||||||
if tts_table:
|
if tts_table:
|
||||||
print("\nTTS 性能排行:")
|
print("\nTTS 性能排行:")
|
||||||
print(tabulate(
|
print(
|
||||||
tts_table,
|
tabulate(
|
||||||
headers=["模型名称", "合成耗时"],
|
tts_table,
|
||||||
tablefmt="github",
|
headers=["模型名称", "合成耗时"],
|
||||||
colalign=("left", "right"),
|
tablefmt="github",
|
||||||
disable_numparse=True
|
colalign=("left", "right"),
|
||||||
))
|
disable_numparse=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
print("\n⚠️ 没有可用的TTS模块进行测试。")
|
print("\n⚠️ 没有可用的TTS模块进行测试。")
|
||||||
|
|
||||||
@@ -303,21 +338,31 @@ class AsyncPerformanceTester:
|
|||||||
print("\n推荐配置组合 (得分越小越好):")
|
print("\n推荐配置组合 (得分越小越好):")
|
||||||
combo_table = []
|
combo_table = []
|
||||||
for combo in self.results["combinations"][:5]:
|
for combo in self.results["combinations"][:5]:
|
||||||
combo_table.append([
|
combo_table.append(
|
||||||
f"{combo['llm']} + {combo['tts']}", # 不需要固定宽度
|
[
|
||||||
f"{combo['score']:.3f}",
|
f"{combo['llm']} + {combo['tts']}", # 不需要固定宽度
|
||||||
f"{combo['details']['llm_first_token']:.3f}秒",
|
f"{combo['score']:.3f}",
|
||||||
f"{combo['details']['llm_stability']:.3f}",
|
f"{combo['details']['llm_first_token']:.3f}秒",
|
||||||
f"{combo['details']['tts_time']:.3f}秒"
|
f"{combo['details']['llm_stability']:.3f}",
|
||||||
])
|
f"{combo['details']['tts_time']:.3f}秒",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
print(tabulate(
|
print(
|
||||||
combo_table,
|
tabulate(
|
||||||
headers=["组合方案", "综合得分", "LLM首字耗时", "稳定性", "TTS合成耗时"],
|
combo_table,
|
||||||
tablefmt="github",
|
headers=[
|
||||||
colalign=("left", "right", "right", "right", "right"),
|
"组合方案",
|
||||||
disable_numparse=True
|
"综合得分",
|
||||||
))
|
"LLM首字耗时",
|
||||||
|
"稳定性",
|
||||||
|
"TTS合成耗时",
|
||||||
|
],
|
||||||
|
tablefmt="github",
|
||||||
|
colalign=("left", "right", "right", "right", "right"),
|
||||||
|
disable_numparse=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
print("\n⚠️ 没有可用的模块组合建议。")
|
print("\n⚠️ 没有可用的模块组合建议。")
|
||||||
|
|
||||||
@@ -341,18 +386,21 @@ class AsyncPerformanceTester:
|
|||||||
for llm_name, config in self.config.get("LLM", {}).items():
|
for llm_name, config in self.config.get("LLM", {}).items():
|
||||||
# 检查配置有效性
|
# 检查配置有效性
|
||||||
if llm_name == "CozeLLM":
|
if llm_name == "CozeLLM":
|
||||||
if any(x in config.get("bot_id", "") for x in ["你的"]) \
|
if any(x in config.get("bot_id", "") for x in ["你的"]) or any(
|
||||||
or any(x in config.get("user_id", "") for x in ["你的"]):
|
x in config.get("user_id", "") for x in ["你的"]
|
||||||
|
):
|
||||||
print(f"⏭️ LLM {llm_name} 未配置bot_id/user_id,已跳过")
|
print(f"⏭️ LLM {llm_name} 未配置bot_id/user_id,已跳过")
|
||||||
continue
|
continue
|
||||||
elif "api_key" in config and any(x in config["api_key"] for x in ["你的", "placeholder", "sk-xxx"]):
|
elif "api_key" in config and any(
|
||||||
|
x in config["api_key"] for x in ["你的", "placeholder", "sk-xxx"]
|
||||||
|
):
|
||||||
print(f"⏭️ LLM {llm_name} 未配置api_key,已跳过")
|
print(f"⏭️ LLM {llm_name} 未配置api_key,已跳过")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 对于Ollama,先检查服务状态
|
# 对于Ollama,先检查服务状态
|
||||||
if llm_name == "Ollama":
|
if llm_name == "Ollama":
|
||||||
base_url = config.get('base_url', 'http://localhost:11434')
|
base_url = config.get("base_url", "http://localhost:11434")
|
||||||
model_name = config.get('model_name')
|
model_name = config.get("model_name")
|
||||||
if not model_name:
|
if not model_name:
|
||||||
print(f"🚫 Ollama未配置model_name")
|
print(f"🚫 Ollama未配置model_name")
|
||||||
continue
|
continue
|
||||||
@@ -361,27 +409,33 @@ class AsyncPerformanceTester:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
print(f"📋 添加LLM测试任务: {llm_name}")
|
print(f"📋 添加LLM测试任务: {llm_name}")
|
||||||
module_type = config.get('type', llm_name)
|
module_type = config.get("type", llm_name)
|
||||||
llm = create_llm_instance(module_type, config)
|
llm = create_llm_instance(module_type, config)
|
||||||
|
|
||||||
# 为每个句子创建独立任务
|
# 为每个句子创建独立任务
|
||||||
for sentence in self.test_sentences:
|
for sentence in self.test_sentences:
|
||||||
sentence = sentence.encode('utf-8').decode('utf-8')
|
sentence = sentence.encode("utf-8").decode("utf-8")
|
||||||
all_tasks.append(self._test_single_sentence(llm_name, llm, sentence))
|
all_tasks.append(self._test_single_sentence(llm_name, llm, sentence))
|
||||||
|
|
||||||
# TTS测试任务
|
# TTS测试任务
|
||||||
for tts_name, config in self.config.get("TTS", {}).items():
|
for tts_name, config in self.config.get("TTS", {}).items():
|
||||||
token_fields = ["access_token", "api_key", "token"]
|
token_fields = ["access_token", "api_key", "token"]
|
||||||
if any(field in config and any(x in config[field] for x in ["你的", "placeholder"]) for field in
|
if any(
|
||||||
token_fields):
|
field in config
|
||||||
|
and any(x in config[field] for x in ["你的", "placeholder"])
|
||||||
|
for field in token_fields
|
||||||
|
):
|
||||||
print(f"⏭️ TTS {tts_name} 未配置access_token/api_key,已跳过")
|
print(f"⏭️ TTS {tts_name} 未配置access_token/api_key,已跳过")
|
||||||
continue
|
continue
|
||||||
print(f"🎵 添加TTS测试任务: {tts_name}")
|
print(f"🎵 添加TTS测试任务: {tts_name}")
|
||||||
all_tasks.append(self._test_tts(tts_name, config))
|
all_tasks.append(self._test_tts(tts_name, config))
|
||||||
|
|
||||||
print(
|
print(
|
||||||
f"\n✅ 找到 {len([t for t in all_tasks if 'test_single_sentence' in str(t)]) / len(self.test_sentences):.0f} 个可用LLM模块")
|
f"\n✅ 找到 {len([t for t in all_tasks if 'test_single_sentence' in str(t)]) / len(self.test_sentences):.0f} 个可用LLM模块"
|
||||||
print(f"✅ 找到 {len([t for t in all_tasks if '_test_tts' in str(t)])} 个可用TTS模块")
|
)
|
||||||
|
print(
|
||||||
|
f"✅ 找到 {len([t for t in all_tasks if '_test_tts' in str(t)])} 个可用TTS模块"
|
||||||
|
)
|
||||||
print("\n⏳ 开始并发测试所有模块...\n")
|
print("\n⏳ 开始并发测试所有模块...\n")
|
||||||
|
|
||||||
# 并发执行所有测试任务
|
# 并发执行所有测试任务
|
||||||
@@ -389,7 +443,11 @@ class AsyncPerformanceTester:
|
|||||||
|
|
||||||
# 处理LLM结果
|
# 处理LLM结果
|
||||||
llm_results = {}
|
llm_results = {}
|
||||||
for result in [r for r in all_results if r and isinstance(r, dict) and r.get("type") == "llm"]:
|
for result in [
|
||||||
|
r
|
||||||
|
for r in all_results
|
||||||
|
if r and isinstance(r, dict) and r.get("type") == "llm"
|
||||||
|
]:
|
||||||
llm_name = result["name"]
|
llm_name = result["name"]
|
||||||
if llm_name not in llm_results:
|
if llm_name not in llm_results:
|
||||||
llm_results[llm_name] = {
|
llm_results[llm_name] = {
|
||||||
@@ -397,9 +455,11 @@ class AsyncPerformanceTester:
|
|||||||
"type": "llm",
|
"type": "llm",
|
||||||
"first_token_times": [],
|
"first_token_times": [],
|
||||||
"response_times": [],
|
"response_times": [],
|
||||||
"errors": 0
|
"errors": 0,
|
||||||
}
|
}
|
||||||
llm_results[llm_name]["first_token_times"].append(result["first_token_time"])
|
llm_results[llm_name]["first_token_times"].append(
|
||||||
|
result["first_token_time"]
|
||||||
|
)
|
||||||
llm_results[llm_name]["response_times"].append(result["response_time"])
|
llm_results[llm_name]["response_times"].append(result["response_time"])
|
||||||
|
|
||||||
# 计算LLM平均值和标准差
|
# 计算LLM平均值和标准差
|
||||||
@@ -408,16 +468,29 @@ class AsyncPerformanceTester:
|
|||||||
self.results["llm"][llm_name] = {
|
self.results["llm"][llm_name] = {
|
||||||
"name": llm_name,
|
"name": llm_name,
|
||||||
"type": "llm",
|
"type": "llm",
|
||||||
"avg_response": sum(data["response_times"]) / len(data["response_times"]),
|
"avg_response": sum(data["response_times"])
|
||||||
"avg_first_token": sum(data["first_token_times"]) / len(data["first_token_times"]),
|
/ len(data["response_times"]),
|
||||||
"std_first_token": statistics.stdev(data["first_token_times"]) if len(
|
"avg_first_token": sum(data["first_token_times"])
|
||||||
data["first_token_times"]) > 1 else 0,
|
/ len(data["first_token_times"]),
|
||||||
"std_response": statistics.stdev(data["response_times"]) if len(data["response_times"]) > 1 else 0,
|
"std_first_token": (
|
||||||
"errors": 0
|
statistics.stdev(data["first_token_times"])
|
||||||
|
if len(data["first_token_times"]) > 1
|
||||||
|
else 0
|
||||||
|
),
|
||||||
|
"std_response": (
|
||||||
|
statistics.stdev(data["response_times"])
|
||||||
|
if len(data["response_times"]) > 1
|
||||||
|
else 0
|
||||||
|
),
|
||||||
|
"errors": 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
# 处理TTS结果
|
# 处理TTS结果
|
||||||
for result in [r for r in all_results if r and isinstance(r, dict) and r.get("type") == "tts"]:
|
for result in [
|
||||||
|
r
|
||||||
|
for r in all_results
|
||||||
|
if r and isinstance(r, dict) and r.get("type") == "tts"
|
||||||
|
]:
|
||||||
if result["errors"] == 0:
|
if result["errors"] == 0:
|
||||||
self.results["tts"][result["name"]] = result
|
self.results["tts"][result["name"]] = result
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user