""" RMI AI Pipeline v3 — Full Production ===================================== Redis caching, FastAPI endpoints, usage tracking, retry logic. """ import contextlib import hashlib import json import logging import os import time import urllib.request from datetime import UTC, datetime logger = logging.getLogger("rmi.ai_v3") OLLAMA_KEY = os.getenv("OLLAMA_API_KEY", "") OLLAMA_URL = "https://ollama.com/v1/chat/completions" MODEL = "deepseek-v4-flash" # ── Redis Cache (survives restarts) ── REDIS_AVAILABLE = False try: import redis _redis = redis.Redis( host=os.getenv("REDIS_HOST", "rmi-redis"), port=int(os.getenv("REDIS_PORT", "6379")), password=os.getenv("REDIS_PASSWORD", ""), db=1, socket_connect_timeout=2, ) _redis.ping() REDIS_AVAILABLE = True except Exception: pass def _cache_get(key: str) -> str | None: if REDIS_AVAILABLE: try: return _redis.get(f"rmi:ai:{key}") except Exception: pass return None def _cache_set(key: str, value: str, ttl: int = 300): if REDIS_AVAILABLE: with contextlib.suppress(BaseException): _redis.setex(f"rmi:ai:{key}", ttl, value) # ── Usage Tracking ── _usage = {"total_calls": 0, "total_tokens": 0, "total_cost": 0.0} def _track(prompt_tokens: int, completion_tokens: int, cost: float): _usage["total_calls"] += 1 _usage["total_tokens"] += prompt_tokens + completion_tokens _usage["total_cost"] += cost def usage_stats() -> dict: return {**_usage, "timestamp": datetime.now(UTC).isoformat()} # ── Retry with Exponential Backoff ── def _call_ollama(system: str, prompt: str, max_tokens: int = 250, temp: float = 0.3, cache_ttl: int = 300) -> str: cache_key = hashlib.md5(f"{system[:60]}|{prompt[:120]}".encode()).hexdigest() cached = _cache_get(cache_key) if cached: val = cached.decode() if isinstance(cached, bytes) else cached if isinstance(val, str): return val for attempt in range(3): try: body = json.dumps( { "model": MODEL, "messages": [ {"role": "system", "content": system}, {"role": "user", "content": prompt}, ], "max_tokens": max_tokens, "temperature": temp, } ).encode() req = urllib.request.Request( OLLAMA_URL, data=body, headers={ "Authorization": f"Bearer {OLLAMA_KEY}", "Content-Type": "application/json", }, ) resp = urllib.request.urlopen(req, timeout=12) data = json.loads(resp.read()) result = data["choices"][0]["message"]["content"].strip() usage = data.get("usage", {}) _track(usage.get("prompt_tokens", 0), usage.get("completion_tokens", 0), 0.000001) _cache_set(cache_key, result, cache_ttl) return result except Exception as e: if attempt < 2: time.sleep(2**attempt) else: logger.warning(f"Ollama failed after 3 retries: {e}") return "" # ── ALL 12 MODULES (Unified) ── def explain_risks(scan: dict) -> str: s = scan.get("safety_score", 50) f = scan.get("risk_flags", []) g = scan.get("green_flags", []) n = scan.get("name", scan.get("symbol", "token")) r = _call_ollama( "Explain token risk to non-technical user. 3-4 sentences. Start with safety score. Use bold. End with DYOR.", f"Token:{n} Score:{s}/100 Risks:{', '.join(f[:5]) or 'none'} Green:{', '.join(g[:3]) or 'none'}", 150, 0.2, 600, ) return r or f"Safety: {s}/100. Risk flags: {', '.join(f[:3])}. Always DYOR." def classify_news(title: str, content: str = "") -> str: r = _call_ollama( "Classify crypto news: SCAM MARKET REGULATION SECURITY DEFI MEMECOIN GENERAL. Reply ONE word.", f"{title} {content[:200]}", 8, 0.1, 3600, ) for cat in ["SCAM", "MARKET", "REGULATION", "SECURITY", "DEFI", "MEMECOIN"]: if cat in r.upper(): return cat t = (title + content).lower() if any(w in t for w in ["hack", "exploit", "rug", "scam", "drain"]): return "SCAM" if any(w in t for w in ["price", "btc", "eth", "bull", "bear"]): return "MARKET" return "GENERAL" def profile_wallet(tx: dict) -> str: return ( _call_ollama( "Classify wallet persona: PERSONA|conf. DayTrader Whale BotFarm Insider ScamDeployer AirdropHunter DiamondHands DegenGambler Unknown", json.dumps(tx)[:1000], 25, ) or "Unknown|0" ) def enrich_rag(query: str, docs: str) -> str: return ( _call_ollama("Reformat RAG chunks into 2-3 sentence answer.", f"Q:{query}\nD:{docs[:2000]}", 200) or docs[:400] ) def rank_alerts(alerts: list) -> list: s = "\n".join(f"{a.get('id', '?')}|{a.get('severity', '?')}|{str(a.get('title', ''))[:80]}" for a in alerts[:10]) r = _call_ollama("Rank by urgency. Reply: id1,id2,id3...", s, 50) return [x.strip() for x in r.split(",") if x.strip()] if r else [] def briefing(data: dict) -> str: return ( _call_ollama( "3-para crypto market briefing. P1:volume P2:risks P3:watch. bold. 250 words.", json.dumps(data)[:2000], 350, 0.5, 1800, ) or "Briefing unavailable." ) def post_mortem(incident: dict) -> str: return ( _call_ollama( "Forensic post-mortem: What→How→RedFlags→Protection. bold. 200 words.", json.dumps(incident)[:1500], 300, 0.4, 3600, ) or "Autopsy unavailable." ) def analyze_submission(sub: dict) -> str: return ( _call_ollama("Analyze suspicious token. Verdict+2-3 concerns.", json.dumps(sub)[:1500], 200) or "Analysis unavailable." ) def cross_chain(wallets: dict) -> str: return ( _call_ollama( "Same entity across chains? MATCH|conf|reason or NO_MATCH|reason", json.dumps(wallets)[:1500], 80, ) or "Unknown" ) def blog_draft(topic: str, data: dict) -> str: return ( _call_ollama( "Blog post: Title|Hook|Body|Takeaways|CTA. Markdown.", f"Topic:{topic}\n{json.dumps(data)[:2000]}", 500, 0.6, 3600, ) or f"# {topic}\n\nDraft unavailable." ) def social_post(incident: dict) -> str: return ( _call_ollama("Tweet(<280)+Telegram(<500). Hook first.", json.dumps(incident)[:1000], 200, 0.7) or "Post unavailable." ) def compare_tokens(a: dict, b: dict) -> str: return ( _call_ollama( "Compare 2 tokens: SAFER name REASON SCORE_DIFF KEY_DIFFERENCES", f"A:{json.dumps(a)[:800]}\nB:{json.dumps(b)[:800]}", 200, ) or "Comparison unavailable." )