- packs (20 / 60 / 150 questions for 45,000 / 119,000 / 259,000 toman) never expire and
give 7 / 30 / 60 days of unlimited hearts; old subscription plans stay for existing
subscribers but are no longer on sale
- credit ledger (subscription.QuestionCredit): +N per paid pack (once per order), -1 per
tutor question that reaches the API; the free daily questions are spent first; questions
stopped by the server filter, cached answers and failed answers cost nothing
- unlimited hearts from packs live in HeartsPass, so buying a pack never rewrites a
running subscription
- Message.charge records how each question was paid; GET /api/ai/quota and the ask
responses report free-left-today and credits
- HEARTS_ENFORCED now defaults to on: zero hearts stops answering until refill or a pack
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The model ends a shape question with <diagram>{json}</diagram>; rag/services/diagram.py
splits it off before mathtext, validates it against a whitelist and stores it on
Message.diagram (ai_tutor.0006). It reaches the app in the SSE done event and in
conversation history. Prompt tutor-v6-diagrams; a hint next to the question (shape
words only) raised gpt-4.1-mini from 3/12 to 12/12 diagrams, 0/8 on arithmetic.
progress.0005 adds tedi (600 xp) to the companion catalog.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
مهمترین خواستهی سینیور این بود که بسنجیم پاسخِ AI واقعاً یاد میدهد یا نه.
برای سنجیدن، اول باید پاسخها قابلِ ردیابی میشدند.
پرامپتها پایهآگاه شدند: تا پیش از این «معلمِ دبستانی» برای پایه نهم هاردکد
بود، هم در تولیدِ پاسخ و هم در vision — یعنی دانشآموزِ نهم لحنِ ابتدایی
میگرفت. PROMPT_VERSION حالا روی هر پیام ذخیره میشود.
هر پاسخ حاشیهنویسی میشود: مدل، نسخهی پرامپت، پایه، collection، مقدارِ
فیلتر، تعداد chunk، توکن، تأخیر و from_cache. فیدبک reason اختیاری گرفت تا
لایک/دیسلایک بعداً قابلِ تحلیل باشد («بشه چک کرد کدوم بهتر بوده»).
ai_feedback_report --by prompt_version نسبتِ رضایت را کنارِ مصرف/هزینه
میگذارد. rag_eval سوالهای واقعی را از مسیرِ واقعی میپرسد، با
--retrieval-only برای اجرای ارزان.
rag/services/lesson_context.py از lessonId تنها پایه و فصل را درمیآورد؛
normalize_grade_title هم اختلافِ «پایه نهم» در دیتابیس با «ریاضی نهم» در
نامِ collection را میبندد — بدونِ آن مسیرِ gradeId بیسروصدا خالی برمیگشت.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
When RAG is configured the endpoint retrieves lesson-scoped chunks and
generates a grounded answer; otherwise the previous deterministic stub
runs unchanged, so keyless runs and existing tests are unaffected.
Quota / idempotency / user-row lock / clean-503 behaviour is preserved on
every path.
models.py
- Conversation.subject (CharField) and Conversation.lesson_number
(IntegerField): retrieval metadata sent on the first ask and reused
for the rest of the conversation.
migrations/0004_conversation_lesson_number_conversation_subject.py
- migration for the two new fields.
serializers.py
- AskRequestSerializer: optional subject / grade / lessonNumber to pin
the Qdrant collection and lesson/chapter.
- AskResponseSerializer: usedChunks (retrieved [{title, summary}]) and
retrievalDebug (collection / filterKey / chapter-or-lesson / counts,
emitted only under DEBUG).
views.py
- generate_tutor_answer(): unchanged signature; RAG-configured branch
calls rag.services generation, stub branch otherwise.
- AskAIView.post(): resolve subject/grade/lessonNumber (falling back to
the Conversation), check the answer cache via resolve_target(), run
retrieve_context + generate_answer on a miss and populate the cache,
return usedChunks (+ retrievalDebug under DEBUG). Failures still
release the reserved quota slot and surface as 503.
- AskAIView._streaming_response(): opt-in "stream": true returns a
text/event-stream of chunk / done / error events; same retrieval,
phase-3 persist, idempotency and quota accounting as the JSON path,
and it consults the answer cache first. Non-stream JSON contract is
untouched.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>