# .claude/tools/local_cache_client.py """ AI Cache Client for Local Development Integrates with n8n-based AI response caching system """ import requests import json import os import hashlib import time from typing import Optional, Dict, Any from datetime import datetime class AICacheClient: """Client for interacting with AI Cache MCP service.""" def __init__(self, base_url: str = None, enabled: bool = True): # type: ignore # Default to your n8n webhook URL self.base_url = base_url or os.getenv( "AI_CACHE_URL", "https://n8n.hotserv.cloud/webhook" ) self.enabled = ( enabled and os.getenv("AI_CACHE_ENABLED", "true").lower() == "true" ) self.timeout = int(os.getenv("AI_CACHE_TIMEOUT", "15")) # Stats tracking self.session_hits = 0 self.session_misses = 0 self.session_start = time.time() self.connection_failed = False if self.enabled: print(f"🧠 AI Cache enabled: {self.base_url}") self._test_connection() else: print("āš ļø AI Cache disabled") def _test_connection(self): """Test if the cache service is accessible.""" try: response = requests.get( f"{self.base_url}/ai-cache-stats", timeout=3 # Quick test ) if response.status_code == 200: print("āœ… Cache service is accessible") else: print(f"āš ļø Cache service returned HTTP {response.status_code}") self.connection_failed = True except Exception as e: print(f"āŒ Cache service unreachable: {str(e)[:50]}...") self.connection_failed = True def _normalize_prompt(self, prompt: str) -> str: """Normalize prompt for consistent matching.""" return prompt.strip().lower().replace("\n", " ").replace(" ", " ") def lookup_cache( self, prompt: str, agent_type: str, project: str = "job_forge" ) -> Optional[str]: """Look up a cached AI response.""" if not self.enabled or self.connection_failed: return None try: start_time = time.time() response = requests.post( f"{self.base_url}/ai-cache-lookup", json={"prompt": prompt, "agent_type": agent_type, "project": project}, timeout=self.timeout, ) lookup_time = (time.time() - start_time) * 1000 if response.status_code == 200: try: # Debug: print raw response raw_text = response.text print(f"šŸ” Debug - Raw response: '{raw_text[:100]}...'") if not raw_text.strip(): print(f"āŒ Cache MISS [{agent_type}] - Empty response | Lookup: {lookup_time:.0f}ms") self.session_misses += 1 return None data = response.json() if data.get("found"): similarity = data.get("similarity", 1.0) hit_count = data.get("hit_count", 1) print( f"āœ… Cache HIT! [{agent_type}] Similarity: {similarity:.2f} | Used: {hit_count}x | Lookup: {lookup_time:.0f}ms" ) self.session_hits += 1 return data.get("response") else: print(f"āŒ Cache MISS [{agent_type}] | Lookup: {lookup_time:.0f}ms") self.session_misses += 1 return None except json.JSONDecodeError as e: print(f"🚨 JSON decode error: {str(e)} | Response: '{response.text[:50]}'") self.session_misses += 1 return None else: print(f"āš ļø Cache lookup failed: HTTP {response.status_code}") return None except requests.exceptions.Timeout: print(f"ā±ļø Cache lookup timeout ({self.timeout}s)") return None except Exception as e: print(f"🚨 Cache error: {str(e)}") return None def store_cache( self, prompt: str, response: str, agent_type: str, ai_service: str = "claude", model: str = "claude-sonnet-4", project: str = "job_forge", ) -> bool: """Store an AI response in cache.""" if not self.enabled or not response or len(response.strip()) < 10: return False try: start_time = time.time() result = requests.post( f"{self.base_url}/ai-cache-store", json={ "prompt": prompt, "response": response, "ai_service": ai_service, "model": model, "agent_type": agent_type, "project": project, }, timeout=self.timeout, ) store_time = (time.time() - start_time) * 1000 if result.status_code == 200: data = result.json() if data.get("success"): print( f"šŸ’¾ Response cached [{agent_type}] | Store: {store_time:.0f}ms" ) return True else: print( f"šŸ“„ Already cached [{agent_type}] | Store: {store_time:.0f}ms" ) return False else: print(f"āš ļø Cache store failed: HTTP {result.status_code}") return False except requests.exceptions.Timeout: print(f"ā±ļø Cache store timeout ({self.timeout}s)") return False except Exception as e: print(f"🚨 Cache store error: {str(e)}") return False def get_stats(self) -> Dict[str, Any]: """Get cache statistics.""" try: response = requests.get( f"{self.base_url}/ai-cache-stats", timeout=self.timeout ) if response.status_code == 200: stats = response.json() # Add session stats session_time = time.time() - self.session_start session_total = self.session_hits + self.session_misses session_hit_rate = ( (self.session_hits / session_total * 100) if session_total > 0 else 0 ) stats["session_stats"] = { "hits": self.session_hits, "misses": self.session_misses, "total": session_total, "hit_rate_percentage": round(session_hit_rate, 1), "duration_minutes": round(session_time / 60, 1), } return stats else: return {"error": f"Failed to get stats: {response.status_code}"} except Exception as e: return {"error": f"Stats error: {str(e)}"} def print_session_summary(self): """Print session cache performance summary.""" total = self.session_hits + self.session_misses if total == 0: return hit_rate = (self.session_hits / total) * 100 session_time = (time.time() - self.session_start) / 60 print(f"\nšŸ“Š Cache Session Summary:") print( f" Hits: {self.session_hits} | Misses: {self.session_misses} | Hit Rate: {hit_rate:.1f}%" ) print(f" Session Time: {session_time:.1f} minutes") if hit_rate > 60: print(f" šŸŽ‰ Excellent cache performance!") elif hit_rate > 30: print(f" šŸ‘ Good cache performance") else: print(f" šŸ“ˆ Cache is learning your patterns...") # Global cache instance _cache_instance = None def get_cache() -> AICacheClient: """Get or create global cache instance.""" global _cache_instance if _cache_instance is None: _cache_instance = AICacheClient() return _cache_instance def cached_ai_query( prompt: str, agent_type: str, project: str = "job_forge" ) -> tuple[Optional[str], bool]: """ Helper function for cached AI queries. Returns: (cached_response, was_cache_hit) """ cache = get_cache() cached_response = cache.lookup_cache(prompt, agent_type, project) if cached_response: return cached_response, True else: return None, False def store_ai_response( prompt: str, response: str, agent_type: str, project: str = "job_forge" ): """Helper function to store AI responses.""" cache = get_cache() cache.store_cache(prompt, response, agent_type, project=project) def print_cache_stats(): """Print current cache statistics.""" cache = get_cache() stats = cache.get_stats() if "error" in stats: print(f"āŒ {stats['error']}") return summary = stats.get("summary", {}) session = stats.get("session_stats", {}) print(f"\nšŸ“ˆ AI Cache Statistics:") print(f" Overall Hit Rate: {summary.get('hit_rate_percentage', 0)}%") print(f" Total Saved: ${summary.get('total_cost_saved_usd', 0):.2f}") print(f" API Calls Saved: {summary.get('api_calls_saved', 0)}") if session: print( f" This Session: {session['hits']}/{session['total']} hits ({session['hit_rate_percentage']}%)" ) # Example usage for testing if __name__ == "__main__": # Test the cache cache = get_cache() # Test lookup result = cache.lookup_cache("What is the database schema?", "technical_lead") print(f"Lookup result: {result}") # Test store cache.store_cache( "What is the database schema?", "PostgreSQL with users and applications tables", "technical_lead", ) # Print stats print_cache_stats() cache.print_session_summary()