register rag app, RAG provider settings, autoload .env
settings.py
- load .env via python-dotenv (override=False) so local runs and the RAG
keys work without `export`; production/docker env still wins.
- add 'rag' to INSTALLED_APPS.
- new RAG / AI provider block: OPENAI_API_KEY / OPENAI_BASE_URL (GapGPT)
/ OPENAI_CHAT_MODEL / OPENAI_VISION_MODEL / OPENAI_EMBEDDING_MODEL /
OPENAI_TIMEOUT_SECONDS, QDRANT_URL / QDRANT_API_KEY,
RAG_COLLECTION_NAME_TEMPLATE / RAG_GRADE_TITLE_OVERRIDES /
RAG_RETRIEVAL_TOP_K / RAG_FORCE_MOCK / RAG_ANSWER_CACHE_TTL.
- add 'rag_ingest' to DEFAULT_THROTTLE_RATES.
- under `manage.py test`: force RAG_FORCE_MOCK on and RAG_ANSWER_CACHE_TTL
to 0 so no test hits OpenAI/Qdrant or a warm cache.
urls.py
- mount rag.urls under /api/.
requirements.txt
- python-dotenv; openai, qdrant-client; pymupdf / pdf2image / pillow
(optional, only for PDF ingestion).
.env.example
- document the OPENAI_* / QDRANT_* / RAG_* keys, the discovered Qdrant
collection names, and the parked OPENAI_API_KEY_OPENAI_DIRECT slot
(no provider-switch logic by design).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,17 @@ from django.core.exceptions import ImproperlyConfigured
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
# مقادیرِ .env رو داخلِ os.environ میذاره تا os.environ.get های پایین کار کنن.
|
||||
# override=False: env واقعیِ سیستم و مقادیرِ docker-compose همچنان اولویت دارن،
|
||||
# پس رفتارِ پروداکشن/داکر عوض نمیشه؛ این فقط برای اجرای محلی (بدون export) و
|
||||
# کلیدهای RAG (accounts/... , OPENAI_API_KEY, QDRANT_API_KEY, ...) هست.
|
||||
try:
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv(BASE_DIR / '.env', override=False)
|
||||
except ModuleNotFoundError:
|
||||
pass
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
|
||||
@@ -83,6 +94,10 @@ INSTALLED_APPS = [
|
||||
|
||||
# Tadrisino apps (مدلِ مینیمالِ ذخیرهسازیِ کمپینهای پیامی، بدونِ منطقِ ارسال)
|
||||
'campaigns',
|
||||
|
||||
# Tadrisino apps (لایهی RAG: ذخیرهی chunkهای درسی + بازیابی از Qdrant،
|
||||
# مصرفکنندهش ai_tutor.generate_tutor_answer است)
|
||||
'rag',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
@@ -239,6 +254,7 @@ REST_FRAMEWORK = {
|
||||
'guest_session': '20/hour',
|
||||
'bazaar_verify': '10/hour',
|
||||
'ai_ask': '20/hour',
|
||||
'rag_ingest': '30/hour',
|
||||
'purchase': '10/hour',
|
||||
'token_refresh': '30/hour',
|
||||
'question_answer': '60/min',
|
||||
@@ -355,6 +371,52 @@ BAZAAR_CLIENT_SECRET = os.environ.get('BAZAAR_CLIENT_SECRET', '')
|
||||
BAZAAR_REFRESH_TOKEN = os.environ.get('BAZAAR_REFRESH_TOKEN', '')
|
||||
BAZAAR_API_BASE = os.environ.get('BAZAAR_API_BASE', 'https://pardakht.cafebazaar.ir/devapi/v2')
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# RAG / AI provider (اپ rag + ai_tutor.generate_tutor_answer)
|
||||
# -----------------------------------------------------------------------
|
||||
# provider همون OpenAI است ولی از طریقِ پروکسیِ ایرانیِ GapGPT (base_url).
|
||||
# بدونِ OPENAI_API_KEY و QDRANT_API_KEY، rag_is_configured() برابرِ False
|
||||
# میشه و generate_tutor_answer به همون پاسخِ stubِ قبلی برمیگرده (پس
|
||||
# تستهای موجود و اجرای بدونِ کلید دستنخورده کار میکنن).
|
||||
OPENAI_API_KEY = os.environ.get('OPENAI_API_KEY', '')
|
||||
OPENAI_BASE_URL = os.environ.get('OPENAI_BASE_URL', 'https://api.gapgpt.app/v1')
|
||||
OPENAI_CHAT_MODEL = os.environ.get('OPENAI_CHAT_MODEL', 'gpt-4o-mini')
|
||||
OPENAI_VISION_MODEL = os.environ.get('OPENAI_VISION_MODEL', 'gpt-4o')
|
||||
# نباید تغییر کنه مگر اینکه collectionهای Qdrant هم با همون مدلِ جدید بازساخته بشن.
|
||||
OPENAI_EMBEDDING_MODEL = os.environ.get('OPENAI_EMBEDDING_MODEL', 'text-embedding-3-small')
|
||||
OPENAI_EMBEDDING_DIM = int(os.environ.get('OPENAI_EMBEDDING_DIM', '1536'))
|
||||
OPENAI_TIMEOUT_SECONDS = int(os.environ.get('OPENAI_TIMEOUT_SECONDS', '60'))
|
||||
|
||||
QDRANT_URL = os.environ.get('QDRANT_URL', 'https://qdrant.tadrisino2.ir:443')
|
||||
QDRANT_API_KEY = os.environ.get('QDRANT_API_KEY', '')
|
||||
|
||||
# نامِ collection در دادهٔ تیمِ دیگه = f"{subject} {grade}".lower() . کلیدِ
|
||||
# فیلترِ درس خودکار انتخاب میشه (rag/services/known_collections.py): ریاضی →
|
||||
# `chapter` ، بقیه → `lesson_number`.
|
||||
RAG_COLLECTION_NAME_TEMPLATE = os.environ.get('RAG_COLLECTION_NAME_TEMPLATE', '{subject} {grade}')
|
||||
# نگاشتِ اختیاریِ gradeId → عنوانِ دقیقِ گرید همانطور که در نامِ collection
|
||||
# تیمِ دیگه اومده (مثلاً {6: 'ششم دبستان'})؛ اگه خالی باشه از content.Grade.title
|
||||
# استفاده میشه. کاربر قبلاً با نامِ نادرستِ گرید به مشکل خورده بود.
|
||||
RAG_GRADE_TITLE_OVERRIDES = {}
|
||||
RAG_RETRIEVAL_TOP_K = int(os.environ.get('RAG_RETRIEVAL_TOP_K', '4'))
|
||||
RAG_FORCE_MOCK = os.environ.get('RAG_FORCE_MOCK', '') == '1'
|
||||
|
||||
# کشِ پاسخِ نهایی برای سوالِ عیناً تکراری روی همون (collection + فصل + متنِ
|
||||
# سوال). فقط embedding/جستجو/تولید رو رد میکنه؛ کوتا/idempotency/ثبتِ پیام
|
||||
# مثلِ همیشه اجرا میشن. ۰ یعنی خاموش. TTL کوتاه چون دادهٔ Qdrant ممکنه
|
||||
# بهروز بشه.
|
||||
RAG_ANSWER_CACHE_TTL = int(os.environ.get('RAG_ANSWER_CACHE_TTL', '1800'))
|
||||
|
||||
# در اجرای تستها همیشه RAG خاموش است تا هیچ تستی به OpenAI/Qdrant واقعی
|
||||
# نزند (سرعت + پایداری). تستهایی که مسیرِ RAG را میآزمایند خودشان با
|
||||
# override_settings + mockِ کلاینتها این را برمیگردانند (rag/tests.py).
|
||||
import sys as _sys
|
||||
|
||||
if 'test' in _sys.argv:
|
||||
RAG_FORCE_MOCK = True
|
||||
RAG_ANSWER_CACHE_TTL = 0
|
||||
|
||||
|
||||
# Default primary key field type
|
||||
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ urlpatterns = [
|
||||
path('api/', include('content.urls')),
|
||||
path('api/', include('progress.urls')),
|
||||
path('api/', include('ai_tutor.urls')),
|
||||
path('api/', include('rag.urls')),
|
||||
|
||||
# اپهای فاز ۲ / بخش ۱ (subscription, payment, discount)
|
||||
path('api/', include('subscription.urls')),
|
||||
|
||||
Reference in New Issue
Block a user