import json, hashlib, os from pathlib import Path from typing import Optional import httpx class LLMClient: def __init__(self, model: str = "gpt-4o-mini", timeout: int = 15, cache_dir: str = ".cache/llm"): self.model = model self.timeout = timeout self.cache_dir = Path(cache_dir) self.cache_dir.mkdir(parents=True, exist_ok=True) def _cache_key(self, messages: list) -> str: return hashlib.sha256(json.dumps(messages, sort_keys=True).encode()).hexdigest() def _cache_get(self, key: str) -> Optional[str]: path = self.cache_dir / f"{key}.json" if path.exists(): return json.loads(path.read_text()).get("response") return None def _cache_set(self, key: str, response: str): (self.cache_dir / f"{key}.json").write_text(json.dumps({"response": response})) def call(self, messages: list, retries: int = 1) -> str: key = self._cache_key(messages) cached = self._cache_get(key) if cached: return cached api_key = os.environ.get("OPENAI_API_KEY", "") for attempt in range(retries + 1): try: resp = httpx.post( "https://api.openai.com/v1/chat/completions", json={"model": self.model, "messages": messages}, headers={"Authorization": f"Bearer {api_key}"}, timeout=self.timeout) resp.raise_for_status() result = resp.json()["choices"][0]["message"]["content"] self._cache_set(key, result) return result except Exception: if attempt == retries: raise return ""