import os import time import base64 from typing import Optional, Dict import httpx TAG = __name__ class DeviceNotFoundException(Exception): pass class DeviceBindException(Exception): def __init__(self, bind_code): self.bind_code = bind_code super().__init__(f"设备绑定异常,绑定码: {bind_code}") class ManageApiClient: _instance = None _client = None _secret = None def __new__(cls, config): """单例模式确保全局唯一实例,并支持传入配置参数""" if cls._instance is None: cls._instance = super().__new__(cls) cls._init_client(config) return cls._instance @classmethod def _init_client(cls, config): """初始化持久化连接池""" cls.config = config.get("manager-api") if not cls.config: raise Exception("manager-api配置错误") if not cls.config.get("url") or not cls.config.get("secret"): raise Exception("manager-api的url或secret配置错误") if "你" in cls.config.get("secret"): raise Exception("请先配置manager-api的secret") 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秒 ) @classmethod def _request(cls, method: str, endpoint: str, **kwargs) -> Dict: """发送单次HTTP请求并处理响应""" endpoint = endpoint.lstrip("/") response = cls._client.request(method, endpoint, **kwargs) response.raise_for_status() result = response.json() # 处理API返回的业务错误 if result.get("code") == 10041: raise DeviceNotFoundException(result.get("msg")) elif result.get("code") == 10042: raise DeviceBindException(result.get("msg")) elif result.get("code") != 0: raise Exception(f"API返回错误: {result.get('msg', '未知错误')}") # 返回成功数据 return result.get("data") if result.get("code") == 0 else None @classmethod def _should_retry(cls, exception: Exception) -> bool: """判断异常是否应该重试""" # 网络连接相关错误 if isinstance( exception, (httpx.ConnectError, httpx.TimeoutException, httpx.NetworkError) ): return True # HTTP状态码错误 if isinstance(exception, httpx.HTTPStatusError): status_code = exception.response.status_code return status_code in [408, 429, 500, 502, 503, 504] return False @classmethod def _execute_request(cls, method: str, endpoint: str, **kwargs) -> Dict: """带重试机制的请求执行器""" retry_count = 0 while retry_count <= cls.max_retries: try: # 执行请求 return cls._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} 次重试" ) time.sleep(cls.retry_delay) continue else: # 不重试,直接抛出异常 raise @classmethod def safe_close(cls): """安全关闭连接池""" if cls._client: cls._client.close() cls._instance = None def get_server_config() -> Optional[Dict]: """获取服务器基础配置""" return ManageApiClient._instance._execute_request("POST", "/config/server-base") def get_agent_models( mac_address: str, client_id: str, selected_module: Dict ) -> Optional[Dict]: """获取代理模型配置""" return ManageApiClient._instance._execute_request( "POST", "/config/agent-models", json={ "macAddress": mac_address, "clientId": client_id, "selectedModule": selected_module, }, ) def save_mem_local_short(mac_address: str, short_momery: str) -> Optional[Dict]: try: return ManageApiClient._instance._execute_request( "PUT", f"/agent/saveMemory/" + mac_address, json={ "summaryMemory": short_momery, }, ) except Exception as e: print(f"存储短期记忆到服务器失败: {e}") return None 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( "POST", f"/agent/chat-history/report", json={ "macAddress": mac_address, "sessionId": session_id, "chatType": chat_type, "content": content, "reportTime": report_time, "audioBase64": ( base64.b64encode(audio).decode("utf-8") if audio else None ), }, ) except Exception as e: print(f"TTS上报失败: {e}") return None def init_service(config): ManageApiClient(config) def manage_api_http_safe_close(): ManageApiClient.safe_close()