import logging import os from typing import Optional import httpx from app.main import settings logger = logging.getLogger(__name__) HERMES_PROFILES_DIR = os.path.expanduser("~/.hermes/profiles") HERMES_TIMEOUT = 30 DEGRADED_RESPONSE = "(系统提示:AI引擎暂时不可用,请稍后再试)" DEGRADED_PROFILE_LIST: list[str] = [] class HermesClient: def __init__(self): self.api_url = settings.hermes_api_url.rstrip("/") self.model = settings.hermes_model self.profiles_dir = HERMES_PROFILES_DIR self.timeout = HERMES_TIMEOUT async def chat(self, profile_name: str, message: str) -> str: soul = await self.read_soul(profile_name) messages = [] if soul: messages.append({"role": "system", "content": soul}) messages.append({"role": "user", "content": message}) try: async with httpx.AsyncClient(timeout=self.timeout) as client: response = await client.post( f"{self.api_url}/chat/completions", json={ "model": self.model, "messages": messages, "temperature": 0.85, "max_tokens": 1024, }, ) response.raise_for_status() data = response.json() choice = data.get("choices", [{}])[0] return choice.get("message", {}).get("content", "").strip() or DEGRADED_RESPONSE except httpx.TimeoutException: logger.warning(f"Hermes API timeout after {self.timeout}s for profile '{profile_name}'") return DEGRADED_RESPONSE except httpx.ConnectError: logger.error(f"Hermes API unreachable at '{self.api_url}'") return DEGRADED_RESPONSE except Exception as e: logger.error(f"Hermes API error for '{profile_name}': {e}") return DEGRADED_RESPONSE async def create_profile(self, name: str, soul_content: str) -> bool: profile_dir = os.path.join(self.profiles_dir, name) os.makedirs(profile_dir, exist_ok=True) soul_path = os.path.join(profile_dir, "SOUL.md") with open(soul_path, "w", encoding="utf-8") as f: f.write(soul_content) logger.info(f"Profile created: {name} -> {profile_dir}") return True async def delete_profile(self, name: str) -> bool: import shutil profile_dir = os.path.join(self.profiles_dir, name) if os.path.exists(profile_dir): shutil.rmtree(profile_dir) logger.info(f"Profile deleted: {name}") return True logger.warning(f"Profile not found for deletion: {name}") return False async def get_profile_list(self) -> list[str]: global DEGRADED_PROFILE_LIST if not os.path.exists(self.profiles_dir): return [] try: dirs = [ d for d in os.listdir(self.profiles_dir) if os.path.isdir(os.path.join(self.profiles_dir, d)) and os.path.exists(os.path.join(self.profiles_dir, d, "SOUL.md")) ] DEGRADED_PROFILE_LIST = dirs return dirs except Exception as e: logger.error(f"Failed to list profiles: {e}") return DEGRADED_PROFILE_LIST async def read_soul(self, profile_name: str) -> str: soul_path = os.path.join(self.profiles_dir, profile_name, "SOUL.md") if os.path.exists(soul_path): with open(soul_path, "r", encoding="utf-8") as f: return f.read() return "" hermes_client = HermesClient()