Files
MathApp/rag/services/config.py
Zareian 7307bfcadb Remove silent fallbacks found in the 2026-10-03 audit
- /api/ai/ask: the stub answer is reachable only under DEBUG or
  RAG_FORCE_MOCK. Without RAG keys on the server it used to return 200 with
  "this is a stub, set OPENAI_API_KEY" and spend the student's quota; now 503.
- /api/home: the `grade` alias is gone and sending it is a 400 instead of a
  silent fallback to the user's own grade. No client sent it.
- activate_subscription logs ERROR when the paid order's plan no longer exists
  (it silently became 30 days).
- resolve_grade_title logs instead of `except: pass`; curriculum_io no longer
  reads an `intro_text` key no file contains.
- CLAUDE.md: "No silent fallbacks" rules for future agents.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 10:38:42 +03:30

59 lines
2.6 KiB
Python

"""
تنظیماتِ RAG و هم‌خوانیِ نامِ collection با دادهٔ موجودِ تیمِ دیگر.
در Rag_backend نامِ collection این‌طور ساخته می‌شه:
f"{subject.name} {subject.grade.title}".lower()
قالبِ نام و نگاشتِ گرید از settings قابلِ تنظیم‌ان چون اسمِ دقیقِ گرید حساسه
(کاربر قبلاً با اسمِ نادرست به مشکل خورده بود). کلیدِ فیلترِ درس/فصل خودکار
از روی subject انتخاب می‌شه — نگاه کنید به rag/services/known_collections.py.
"""
import logging
from django.conf import settings
logger = logging.getLogger(__name__)
def rag_is_configured() -> bool:
return bool(
settings.OPENAI_API_KEY
and settings.QDRANT_API_KEY
and not settings.RAG_FORCE_MOCK
)
def resolve_grade_title(grade_id, fallback: str = '') -> str:
"""
gradeId → عنوانِ گرید همان‌طور که در نامِ collectionِ تیمِ دیگه اومده.
⚠️ عنوانِ Grade در دیتابیسِ ما «پایه نهم» است ولی نامِ collection «ریاضی
نهم»؛ بدونِ نرمال‌سازی، build_collection_name رشته‌ی «ریاضی پایه نهم» را
می‌ساخت که وجود ندارد و کلِ retrieval از مسیرِ gradeId بی‌سروصدا خالی
برمی‌گشت. مسیرِ lessonId خودش نرمال می‌کرد، این یکی نه.
"""
overrides = getattr(settings, 'RAG_GRADE_TITLE_OVERRIDES', {}) or {}
if grade_id in overrides:
return str(overrides[grade_id])
if grade_id is not None:
try:
from content.models import Grade
from .lesson_context import normalize_grade_title
grade = Grade.objects.filter(pk=grade_id).only('title').first()
if grade is not None:
return normalize_grade_title(grade.title)
except Exception: # noqa: BLE001
# پیش‌تر `pass` بود: خطای دیتابیس retrieval را بی‌صدا خالی می‌کرد.
logger.exception('resolve_grade_title: خواندنِ پایه‌ی %s شکست خورد', grade_id)
return fallback
def build_collection_name(subject: str, grade_title: str) -> str:
template = getattr(settings, 'RAG_COLLECTION_NAME_TEMPLATE', '{subject} {grade}')
return template.format(subject=(subject or '').strip(), grade=(grade_title or '').strip()).lower()
def retrieval_top_k() -> int:
return int(getattr(settings, 'RAG_RETRIEVAL_TOP_K', 4))