File size: 2,789 Bytes
28a08e7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
"""
backend/api/semantic_cache.py — S-CACHE-1: Cross-Node Semantic Cache
Ottimizzazione ZeroGPU: evita ricalcoli identici tra nodi A-E.
"""
import hashlib
import json
import logging
import time
from typing import Optional
from fastapi import APIRouter, Depends
from .auth_guard import require_role, AuthRole
from redis import redis_get, redis_set

_logger = logging.getLogger("api.semantic_cache")
router = APIRouter(prefix="/api/cache", tags=["cache"], dependencies=[Depends(require_role(AuthRole.MACHINE))])

# TTL: 24 ore per risposte semantiche
_CACHE_TTL = 86400 

def _gen_key(messages: list) -> str:
    """Genera una chiave hash basata sull'ultimo messaggio user (semplificato)."""
    try:
        # Prendiamo solo l'ultimo messaggio user per la cache semantica "veloce"
        user_msgs = [m for m in messages if m.get("role") == "user"]
        if not user_msgs:
            return ""
        last_content = user_msgs[-1].get("content", "").strip().lower()
        h = hashlib.sha256(last_content.encode()).hexdigest()
        return f"semcache:v1:{h[:16]}"
    except Exception:
        return ""

@router.post("/lookup")
async def cache_lookup(payload: dict):
    """Verifica se esiste una risposta cachata per il prompt corrente."""
    messages = payload.get("messages", [])
    key = _gen_key(messages)
    if not key:
        return {"hit": False}
    
    cached = await redis_get(key)
    if cached:
        _logger.info(f"[cache] Hit semantica per chiave {key}")
        try:
            return {"hit": True, "content": json.loads(cached)}
        except:
            return {"hit": True, "content": cached}
            
    return {"hit": False}

@router.post("/store")
async def cache_store(payload: dict):
    """Salva una risposta nella cache semantica condivisa."""
    messages = payload.get("messages", [])
    content = payload.get("content", "")
    if not content or len(content) < 50: # Non cachiamo risposte troppo brevi
        return {"ok": False}
        
    key = _gen_key(messages)
    if not key:
        return {"ok": False}
        
    # Salviamo come JSON per gestire metadati futuri
    ok = await redis_set(key, json.dumps(content), ttl=_CACHE_TTL)
    return {"ok": ok}

async def get_cached_response(messages: list) -> Optional[str]:
    """Helper interno per AIClient."""
    key = _gen_key(messages)
    if not key:
        return None
    cached = await redis_get(key)
    if cached:
        try:
            return json.loads(cached)
        except:
            return cached
    return None

async def set_cached_response(messages: list, content: str):
    """Helper interno per AIClient."""
    key = _gen_key(messages)
    if key and content and len(content) >= 50:
        await redis_set(key, json.dumps(content), ttl=_CACHE_TTL)