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:
35
.env.example
35
.env.example
@@ -18,3 +18,38 @@ KAVENEGAR_API_KEY=
|
||||
|
||||
# رمزِ ادمینِ Grafana (کاربرِ admin) — فقط برای دسترسیِ محلی
|
||||
GRAFANA_ADMIN_PASSWORD=admin
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# RAG / AI tutor (اپ rag) — موقعِ تست پر کنید
|
||||
# -----------------------------------------------------------------------
|
||||
# بدونِ OPENAI_API_KEY و QDRANT_API_KEY، مسیرِ RAG غیرفعاله و
|
||||
# POST /api/ai/ask همون پاسخِ stubِ قبلی رو میده (سازگاریِ عقبرو).
|
||||
# فعلاً از GapGPT (پروکسیِ OpenAI برای ایران) استفاده میکنیم.
|
||||
OPENAI_API_KEY=
|
||||
OPENAI_BASE_URL=https://api.gapgpt.app/v1
|
||||
# اگه بعداً خواستی مستقیم OpenAI واقعی رو تست کنی: کلیدِ واقعی رو این پایین
|
||||
# پارک کن، بعد فقط دو خطِ بالا رو باهاش عوض کن. هیچ منطقِ سوییچی در کد نیست
|
||||
# تا ساده و بیاشتباه بمونه.
|
||||
# OPENAI_BASE_URL=https://api.openai.com/v1
|
||||
OPENAI_API_KEY_OPENAI_DIRECT=
|
||||
OPENAI_CHAT_MODEL=gpt-4o-mini
|
||||
OPENAI_VISION_MODEL=gpt-4o
|
||||
# این رو تغییر نده؛ باید با مدلی که تیمِ دیگه باهاش collectionها رو ساخته یکی باشه.
|
||||
OPENAI_EMBEDDING_MODEL=text-embedding-3-small
|
||||
|
||||
QDRANT_URL=https://qdrant.tadrisino2.ir:443
|
||||
QDRANT_API_KEY=
|
||||
|
||||
# نامِ collection = f"{subject} {grade}".lower() . collectionهای موجود روی
|
||||
# سرورِ تیمِ دیگه (بررسیشده): «علوم تجربی سوم» ، «مطالعات اجتماعی چهارم» ،
|
||||
# «مطالعات اجتماعی ششم» ، «ریاضی سوم» ، «ریاضی ششم» ، «ریاضی هفتم».
|
||||
# یعنی جزءِ گرید فقط «سوم/چهارم/ششم/هفتم» است (بدونِ «دبستان»).
|
||||
#
|
||||
# کلیدِ فیلترِ درس دیگر دستی تنظیم نمیشه — rag/services/known_collections.py
|
||||
# خودش تشخیص میده: ریاضی → `chapter` (و شمارهی فصل رو به رشتهی دقیق تبدیل
|
||||
# میکنه)، بقیه → `lesson_number`. RAG_QDRANT_LESSON_FILTER_KEY فقط برای
|
||||
# سازگاریِ عقبرو مونده و استفاده نمیشه.
|
||||
RAG_COLLECTION_NAME_TEMPLATE={subject} {grade}
|
||||
RAG_RETRIEVAL_TOP_K=4
|
||||
# RAG_FORCE_MOCK=1 یعنی حتی با وجودِ کلیدها هم مسیرِ RAG خاموش بمونه.
|
||||
RAG_FORCE_MOCK=
|
||||
|
||||
@@ -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')),
|
||||
|
||||
@@ -3,3 +3,16 @@ djangorestframework==3.18.0
|
||||
drf-spectacular==0.30.0
|
||||
requests==2.33.1
|
||||
redis==5.2.1
|
||||
python-dotenv==1.0.1
|
||||
|
||||
# RAG / AI tutor (اپ rag). کلاینتها lazy import میشن؛ نبودِ اینها فقط
|
||||
# مسیرِ RAG رو از کار میندازه، نه بقیهی پروژه رو.
|
||||
openai>=1.40,<3
|
||||
qdrant-client>=1.12
|
||||
|
||||
# فقط برای ingestِ PDF (اختیاری). pdf2image به poppler سیستمی نیاز داره
|
||||
# (macOS: brew install poppler). نبودشون فقط endpointِ /api/rag/ingest/pdf
|
||||
# رو با خطای صریح از کار میندازه.
|
||||
pymupdf>=1.24
|
||||
pdf2image>=1.17
|
||||
pillow>=10
|
||||
|
||||
Reference in New Issue
Block a user