mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-28 18:53:51 +08:00
后台初始化组件,gc全局化避免每次回收触发GIL锁,音频定时发送
This commit is contained in:
@@ -32,7 +32,16 @@ def load_config():
|
||||
custom_config = read_config(custom_config_path)
|
||||
|
||||
if custom_config.get("manager-api", {}).get("url"):
|
||||
config = get_config_from_api(custom_config)
|
||||
import asyncio
|
||||
try:
|
||||
loop = asyncio.get_running_loop()
|
||||
# 如果已经在事件循环中,使用异步版本
|
||||
config = asyncio.run_coroutine_threadsafe(
|
||||
get_config_from_api_async(custom_config), loop
|
||||
).result()
|
||||
except RuntimeError:
|
||||
# 如果不在事件循环中(启动时),创建新的事件循环
|
||||
config = asyncio.run(get_config_from_api_async(custom_config))
|
||||
else:
|
||||
# 合并配置
|
||||
config = merge_configs(default_config, custom_config)
|
||||
@@ -44,13 +53,13 @@ def load_config():
|
||||
return config
|
||||
|
||||
|
||||
def get_config_from_api(config):
|
||||
"""从Java API获取配置"""
|
||||
async def get_config_from_api_async(config):
|
||||
"""从Java API获取配置(异步版本)"""
|
||||
# 初始化API客户端
|
||||
init_service(config)
|
||||
|
||||
# 获取服务器配置
|
||||
config_data = get_server_config()
|
||||
config_data = await get_server_config()
|
||||
if config_data is None:
|
||||
raise Exception("Failed to fetch server config from API")
|
||||
|
||||
@@ -74,9 +83,9 @@ def get_config_from_api(config):
|
||||
return config_data
|
||||
|
||||
|
||||
def get_private_config_from_api(config, device_id, client_id):
|
||||
async def get_private_config_from_api(config, device_id, client_id):
|
||||
"""从Java API获取私有配置"""
|
||||
return get_agent_models(device_id, client_id, config["selected_module"])
|
||||
return await get_agent_models(device_id, client_id, config["selected_module"])
|
||||
|
||||
|
||||
def ensure_directories(config):
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import os
|
||||
import time
|
||||
import base64
|
||||
from typing import Optional, Dict
|
||||
|
||||
@@ -20,7 +19,7 @@ class DeviceBindException(Exception):
|
||||
|
||||
class ManageApiClient:
|
||||
_instance = None
|
||||
_client = None
|
||||
_async_clients = {} # 为每个事件循环存储独立的客户端
|
||||
_secret = None
|
||||
|
||||
def __new__(cls, config):
|
||||
@@ -32,7 +31,7 @@ class ManageApiClient:
|
||||
|
||||
@classmethod
|
||||
def _init_client(cls, config):
|
||||
"""初始化持久化连接池"""
|
||||
"""初始化配置(延迟创建客户端)"""
|
||||
cls.config = config.get("manager-api")
|
||||
|
||||
if not cls.config:
|
||||
@@ -47,23 +46,40 @@ class ManageApiClient:
|
||||
cls._secret = cls.config.get("secret")
|
||||
cls.max_retries = cls.config.get("max_retries", 6) # 最大重试次数
|
||||
cls.retry_delay = cls.config.get("retry_delay", 10) # 初始重试延迟(秒)
|
||||
# NOTE(goody): 2025/4/16 http相关资源统一管理,后续可以增加线程池或者超时
|
||||
# 后续也可以统一配置apiToken之类的走通用的Auth
|
||||
cls._client = httpx.Client(
|
||||
base_url=cls.config.get("url"),
|
||||
headers={
|
||||
"User-Agent": f"PythonClient/2.0 (PID:{os.getpid()})",
|
||||
"Accept": "application/json",
|
||||
"Authorization": "Bearer " + cls._secret,
|
||||
},
|
||||
timeout=cls.config.get("timeout", 30), # 默认超时时间30秒
|
||||
)
|
||||
# 不在这里创建 AsyncClient,延迟到实际使用时创建
|
||||
cls._async_clients = {}
|
||||
|
||||
@classmethod
|
||||
def _request(cls, method: str, endpoint: str, **kwargs) -> Dict:
|
||||
"""发送单次HTTP请求并处理响应"""
|
||||
async def _ensure_async_client(cls):
|
||||
"""确保异步客户端已创建(为每个事件循环创建独立的客户端)"""
|
||||
import asyncio
|
||||
try:
|
||||
loop = asyncio.get_running_loop()
|
||||
loop_id = id(loop)
|
||||
|
||||
# 为每个事件循环创建独立的客户端
|
||||
if loop_id not in cls._async_clients:
|
||||
cls._async_clients[loop_id] = httpx.AsyncClient(
|
||||
base_url=cls.config.get("url"),
|
||||
headers={
|
||||
"User-Agent": f"PythonClient/2.0 (PID:{os.getpid()})",
|
||||
"Accept": "application/json",
|
||||
"Authorization": "Bearer " + cls._secret,
|
||||
},
|
||||
timeout=cls.config.get("timeout", 30),
|
||||
)
|
||||
return cls._async_clients[loop_id]
|
||||
except RuntimeError:
|
||||
# 如果没有运行中的事件循环,创建一个临时的
|
||||
raise Exception("必须在异步上下文中调用")
|
||||
|
||||
@classmethod
|
||||
async def _async_request(cls, method: str, endpoint: str, **kwargs) -> Dict:
|
||||
"""发送单次异步HTTP请求并处理响应"""
|
||||
# 确保客户端已创建
|
||||
client = await cls._ensure_async_client()
|
||||
endpoint = endpoint.lstrip("/")
|
||||
response = cls._client.request(method, endpoint, **kwargs)
|
||||
response = await client.request(method, endpoint, **kwargs)
|
||||
response.raise_for_status()
|
||||
|
||||
result = response.json()
|
||||
@@ -96,22 +112,23 @@ class ManageApiClient:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def _execute_request(cls, method: str, endpoint: str, **kwargs) -> Dict:
|
||||
"""带重试机制的请求执行器"""
|
||||
async def _execute_async_request(cls, method: str, endpoint: str, **kwargs) -> Dict:
|
||||
"""带重试机制的异步请求执行器"""
|
||||
import asyncio
|
||||
retry_count = 0
|
||||
|
||||
while retry_count <= cls.max_retries:
|
||||
try:
|
||||
# 执行请求
|
||||
return cls._request(method, endpoint, **kwargs)
|
||||
# 执行异步请求
|
||||
return await cls._async_request(method, endpoint, **kwargs)
|
||||
except Exception as e:
|
||||
# 判断是否应该重试
|
||||
if retry_count < cls.max_retries and cls._should_retry(e):
|
||||
retry_count += 1
|
||||
print(
|
||||
f"{method} {endpoint} 请求失败,将在 {cls.retry_delay:.1f} 秒后进行第 {retry_count} 次重试"
|
||||
f"{method} {endpoint} 异步请求失败,将在 {cls.retry_delay:.1f} 秒后进行第 {retry_count} 次重试"
|
||||
)
|
||||
time.sleep(cls.retry_delay)
|
||||
await asyncio.sleep(cls.retry_delay)
|
||||
continue
|
||||
else:
|
||||
# 不重试,直接抛出异常
|
||||
@@ -119,22 +136,27 @@ class ManageApiClient:
|
||||
|
||||
@classmethod
|
||||
def safe_close(cls):
|
||||
"""安全关闭连接池"""
|
||||
if cls._client:
|
||||
cls._client.close()
|
||||
cls._instance = None
|
||||
"""安全关闭所有异步连接池"""
|
||||
import asyncio
|
||||
for client in list(cls._async_clients.values()):
|
||||
try:
|
||||
asyncio.run(client.aclose())
|
||||
except Exception:
|
||||
pass
|
||||
cls._async_clients.clear()
|
||||
cls._instance = None
|
||||
|
||||
|
||||
def get_server_config() -> Optional[Dict]:
|
||||
async def get_server_config() -> Optional[Dict]:
|
||||
"""获取服务器基础配置"""
|
||||
return ManageApiClient._instance._execute_request("POST", "/config/server-base")
|
||||
return await ManageApiClient._instance._execute_async_request("POST", "/config/server-base")
|
||||
|
||||
|
||||
def get_agent_models(
|
||||
async def get_agent_models(
|
||||
mac_address: str, client_id: str, selected_module: Dict
|
||||
) -> Optional[Dict]:
|
||||
"""获取代理模型配置"""
|
||||
return ManageApiClient._instance._execute_request(
|
||||
return await ManageApiClient._instance._execute_async_request(
|
||||
"POST",
|
||||
"/config/agent-models",
|
||||
json={
|
||||
@@ -145,9 +167,9 @@ def get_agent_models(
|
||||
)
|
||||
|
||||
|
||||
def save_mem_local_short(mac_address: str, short_momery: str) -> Optional[Dict]:
|
||||
async def save_mem_local_short(mac_address: str, short_momery: str) -> Optional[Dict]:
|
||||
try:
|
||||
return ManageApiClient._instance._execute_request(
|
||||
return await ManageApiClient._instance._execute_async_request(
|
||||
"PUT",
|
||||
f"/agent/saveMemory/" + mac_address,
|
||||
json={
|
||||
@@ -159,14 +181,14 @@ def save_mem_local_short(mac_address: str, short_momery: str) -> Optional[Dict]:
|
||||
return None
|
||||
|
||||
|
||||
def report(
|
||||
async def report(
|
||||
mac_address: str, session_id: str, chat_type: int, content: str, audio, report_time
|
||||
) -> Optional[Dict]:
|
||||
"""带熔断的业务方法示例"""
|
||||
"""异步聊天记录上报"""
|
||||
if not content or not ManageApiClient._instance:
|
||||
return None
|
||||
try:
|
||||
return ManageApiClient._instance._execute_request(
|
||||
return await ManageApiClient._instance._execute_async_request(
|
||||
"POST",
|
||||
f"/agent/chat-history/report",
|
||||
json={
|
||||
|
||||
Reference in New Issue
Block a user