make the tutor grade-aware and its answers measurable

مهم‌ترین خواسته‌ی سینیور این بود که بسنجیم پاسخِ 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>
This commit is contained in:
2026-09-13 19:00:40 +03:30
parent 78130e7194
commit fa56308a35
11 changed files with 731 additions and 32 deletions

View File

@@ -0,0 +1,78 @@
# Generated by Django 5.2.17 on 2026-09-12 14:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ai_tutor', '0004_conversation_lesson_number_conversation_subject'),
]
operations = [
migrations.AddField(
model_name='message',
name='chunk_count',
field=models.PositiveSmallIntegerField(default=0),
),
migrations.AddField(
model_name='message',
name='completion_tokens',
field=models.PositiveIntegerField(default=0),
),
migrations.AddField(
model_name='message',
name='from_cache',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='message',
name='grade_title',
field=models.CharField(blank=True, max_length=50),
),
migrations.AddField(
model_name='message',
name='latency_ms',
field=models.PositiveIntegerField(default=0),
),
migrations.AddField(
model_name='message',
name='model_name',
field=models.CharField(blank=True, max_length=100),
),
migrations.AddField(
model_name='message',
name='prompt_tokens',
field=models.PositiveIntegerField(default=0),
),
migrations.AddField(
model_name='message',
name='prompt_version',
field=models.CharField(blank=True, db_index=True, max_length=50),
),
migrations.AddField(
model_name='message',
name='retrieval_collection',
field=models.CharField(blank=True, max_length=200),
),
migrations.AddField(
model_name='message',
name='retrieval_filter_value',
field=models.CharField(blank=True, max_length=255),
),
migrations.AddField(
model_name='messagefeedback',
name='created_at',
field=models.DateTimeField(auto_now_add=True, null=True),
),
migrations.AddField(
model_name='messagefeedback',
name='reason',
field=models.CharField(blank=True, max_length=300),
),
migrations.AddField(
model_name='messagefeedback',
name='updated_at',
field=models.DateTimeField(auto_now=True, null=True),
),
]