""" کلاینت‌های OpenAI و Qdrant به‌صورتِ lazy (نه در سطحِ ماژول). مخزنِ Rag_backend این‌ها رو موقعِ import می‌ساخت؛ نتیجه این بود که بدونِ کلید/شبکه، خودِ import هم crash می‌کرد. اینجا کلاینت فقط وقتی واقعاً لازم بشه ساخته می‌شه و بینِ فراخوانی‌ها cache می‌مونه. """ from functools import lru_cache from django.conf import settings class RagNotConfigured(RuntimeError): """کلید/کتابخانه‌ی لازم برای مسیرِ RAG موجود نیست.""" @lru_cache(maxsize=1) def get_openai_client(): if not settings.OPENAI_API_KEY: raise RagNotConfigured('OPENAI_API_KEY تنظیم نشده است.') try: from openai import OpenAI except ModuleNotFoundError as exc: raise RagNotConfigured('پکیجِ openai نصب نیست (pip install -r requirements.txt).') from exc return OpenAI( base_url=settings.OPENAI_BASE_URL, api_key=settings.OPENAI_API_KEY, timeout=settings.OPENAI_TIMEOUT_SECONDS, ) @lru_cache(maxsize=1) def get_qdrant_client(): if not settings.QDRANT_API_KEY: raise RagNotConfigured('QDRANT_API_KEY تنظیم نشده است.') try: from qdrant_client import QdrantClient except ModuleNotFoundError as exc: raise RagNotConfigured('پکیجِ qdrant-client نصب نیست (pip install -r requirements.txt).') from exc # check_compatibility=False: کلاینتِ نصب‌شده (1.19) با سرورِ تیمِ دیگه # (1.17) اختلافِ minor داره و کلاینت warning می‌ده؛ API‌هایی که استفاده # می‌کنیم (get_collections/upsert/query_points/scroll) پایدارن. return QdrantClient( url=settings.QDRANT_URL, api_key=settings.QDRANT_API_KEY, check_compatibility=False, timeout=settings.QDRANT_TIMEOUT_SECONDS, ) def is_account_limit(exc) -> bool: """ آیا این خطا یعنی «حسابِ سرویس دیگر جواب نمی‌دهد» یا فقط کندیِ لحظه‌ای؟ دو شکلِ واقعی که دیدیم و هر دو باید اجرا را متوقف کنند: 429 initial_usage_limit_exceeded — سقفِ حسابِ تازه، ساعت‌ها 403 insufficient user quota — اعتبار تمام شده نکته: متنِ دومی «insufficient user quota» است با فاصله، نه زیرخط. محافظِ اولِ ما دنبالِ `insufficient_quota` می‌گشت و نمی‌گرفتش، و اجرا ۷۷۰ فراخوانیِ محکوم‌به‌شکست زد به‌جای اینکه سرِ اولی بایستد. """ text = str(exc).lower() if 'usage_limit' in text or 'quota' in text: return True return '403' in text and 'insufficient' in text def reset_clients_cache(): """برای تست‌ها / بعد از تغییرِ settings در زمانِ اجرا.""" get_openai_client.cache_clear() get_qdrant_client.cache_clear()