mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-27 01:23:55 +08:00
update:server连接api (#747)
* update:server连接manager-api * update:读取智能体模型配置 * update:添加默认模型的按钮 * update:优化配置读取方式 * update:server兼容manager接口改造 * update:优化私有配置加载 * update:加载私有模型配置
This commit is contained in:
@@ -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 importlib
|
||||
from config.logger import setup_logging
|
||||
from core.utils.util import read_config, get_project_dir
|
||||
|
||||
logger = setup_logging()
|
||||
|
||||
|
||||
def create_instance(class_name, *args, **kwargs):
|
||||
if os.path.exists(os.path.join('core', 'providers', 'memory', class_name, f'{class_name}.py')):
|
||||
lib_name = f'core.providers.memory.{class_name}.{class_name}'
|
||||
if os.path.exists(
|
||||
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:
|
||||
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)
|
||||
|
||||
raise ValueError(f"不支持的记忆服务类型: {class_name}")
|
||||
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
import os
|
||||
import json
|
||||
import yaml
|
||||
import socket
|
||||
import subprocess
|
||||
import logging
|
||||
import re
|
||||
import requests
|
||||
from typing import Dict, Any
|
||||
from core.utils import tts, llm, intent, memory, vad, asr
|
||||
|
||||
|
||||
def get_project_dir():
|
||||
"""获取项目根目录"""
|
||||
return (
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
+ "/"
|
||||
)
|
||||
TAG = __name__
|
||||
|
||||
|
||||
def get_local_ip():
|
||||
@@ -72,7 +65,7 @@ def is_private_ip(ip_addr):
|
||||
return False # IP address format error or insufficient segments
|
||||
|
||||
|
||||
def get_ip_info(ip_addr):
|
||||
def get_ip_info(ip_addr, logger):
|
||||
try:
|
||||
if is_private_ip(ip_addr):
|
||||
ip_addr = ""
|
||||
@@ -81,16 +74,10 @@ def get_ip_info(ip_addr):
|
||||
ip_info = {"city": resp.get("city")}
|
||||
return ip_info
|
||||
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 {}
|
||||
|
||||
|
||||
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):
|
||||
"""将数据写入 JSON 文件"""
|
||||
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):
|
||||
if "你" in modelKey:
|
||||
logging.error(
|
||||
"你还没配置"
|
||||
+ modelType
|
||||
+ "的密钥,请在配置文件中配置密钥,否则无法正常工作"
|
||||
raise ValueError(
|
||||
"你还没配置" + modelType + "的密钥,请检查一下所使用的LLM是否配置了密钥"
|
||||
)
|
||||
return False
|
||||
return True
|
||||
@@ -212,3 +197,105 @@ def extract_json_from_string(input_string):
|
||||
if match:
|
||||
return match.group(1) # 返回提取的 JSON 字符串
|
||||
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
|
||||
import opuslib_next
|
||||
import time
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
TAG = __name__
|
||||
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):
|
||||
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}")
|
||||
raise ValueError(f"不支持的VAD类型: {class_name},请检查该配置的type是否设置正确")
|
||||
|
||||
Reference in New Issue
Block a user