""" کشِ پاسخِ RAG برای سوالِ عیناً تکراری. کلید = هشِ (collection + مقدارِ فیلترِ درس/فصل + پایه + متنِ نرمال‌شده‌ی سوال). فقط مرحله‌ی گران (embedding + جستجوی Qdrant + تولیدِ GPT) رو رد می‌کنه؛ کوتای روزانه، idempotency و ثبتِ Message همچنان در view اجرا می‌شن. """ import hashlib from django.conf import settings from django.core.cache import cache # v2: پایه هم واردِ کلید شد (پایین را ببینید). نسخه‌ی prefix عمداً جلو رفت تا # کلیدهای v1 که پایه در آن‌ها نبود، خودبه‌خود بی‌اثر شوند. _PREFIX = 'rag:ans:v2:' def ttl() -> int: return int(getattr(settings, 'RAG_ANSWER_CACHE_TTL', 0)) def enabled() -> bool: return ttl() > 0 def _norm(text: str) -> str: return ' '.join((text or '').split()).strip().lower() def make_key(collection, filter_value, question: str, grade_title: str = '') -> str: """ ⚠️ `grade_title` جزءِ کلید است چون تولیدِ پاسخ از این به بعد پایه‌آگاه است (لحن و سطحِ توضیح از پایه می‌آید). بدونِ آن، وقتی subject/فصل مشخص نیست collection و filter هر دو None می‌شوند و کلید فقط به متنِ سوال می‌ماند — یعنی پاسخی که برای دانش‌آموزِ نهم ساخته شده عیناً به دانش‌آموزِ سوم برمی‌گشت. """ raw = f'{collection}|{filter_value}|{(grade_title or "").strip()}|{_norm(question)}' return _PREFIX + hashlib.sha1(raw.encode('utf-8')).hexdigest() def get(key: str) -> dict | None: if not enabled(): return None return cache.get(key) def set(key: str, payload: dict) -> None: # noqa: A003 if not enabled(): return cache.set(key, payload, ttl())