Files
MathApp/docs/ai-cost-and-guardrails.md
Zareian 67cbe05609 tests: two questions at once with one credit, with real threads
The claim that the user-row lock plus SQLite IMMEDIATE stops one credit
being spent twice was untested. Two threads on separate connections now
ask at the same time: one answer, one credit spent; the same
Idempotency-Key arriving twice at once also costs one credit. Removing
transaction_mode IMMEDIATE turns both red (500, database is locked).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 13:07:08 +03:30

9.1 KiB
Raw Blame History

هزینه‌ی دستیار و محدود کردنش به ریاضی

نسخه‌ی کامل برای مدیرعامل (جدول و نمودار، HTML و PDF): ~/Desktop/گزارشات Tadrisino/گزارش-۲۶/. این فایل خلاصه‌ی فنیِ همان است و می‌گوید عددها از کجا آمده‌اند و چطور دوباره اندازه گرفته می‌شوند. قیمتِ هر توکن و تاریخچه‌ی قیمت‌ها در docs/model-prices.md است.

هزینه‌ی هر سوال (مهرِ ۱۴۰۵، پرامپتِ tutor-v11-math-only؛ از tutor-v12-age-tone لحن برای ۱۸+ بزرگسالانه است، gpt-4.1-mini)

  • قیمت: GapGPT برای gpt-4.1-mini دقیقاً قیمتِ رسمیِ OpenAI را حساب می‌کند، یعنی ۰٫۴۰ دلار برای هر میلیون توکنِ ورودی و ۱٫۶۰ دلار برای خروجی. این با usage.costِ ۵۳۹ فراخوانیِ docs/benchmarks/2026-09-26-tutor-model/ مقایسه شد. در ۲۸٪ِ آن فراخوانی‌ها کشِ خودکارِ ورودی هزینه را ۴۳٪ پایین آورده بود.
  • ۱۰۳ مسئله‌ی کتاب (۸ پایه): میانگین ۰٫۱۶۲ سنت (بازه‌ی ۹۵٪: ۰٫۱۴۸ تا ۰٫۱۷۶). دبستان ۰٫۱۲۹، متوسطه‌ی اول ۰٫۱۴۸، دبیرستان ۰٫۲۲۵. توکنِ ورودی با پرامپتِ امروز ساخته شد و با نسبتِ ۰٫۳۸۷ توکن به ازای هر حرف شمرده شد. این نسبت روی ۲۲ پرامپتِ واقعی کالیبره شده و خطایش کمتر از ۶٪ است. توکنِ خروجی از جوابِ واقعیِ همین مدل به همان مسئله‌ها آمده است.
  • ۱۲ سوالِ زنده (ساده، شکل‌دار، سنگین، ادامه‌ی گفتگو): میانگین ۰٫۲۲۵ سنت. ساده ۰٫۱۵، شکل‌دار ۰٫۲۴، سنگینِ دبیرستان ۰٫۲۸، ادامه‌ی گفتگو ۰٫۲۶، و ردِ «غیرِ ریاضی» توسطِ مدل ۰٫۱۲.
  • ۷۰٪ِ هزینه ورودی است: چهار تکه از کتاب، دستورها و کاتالوگِ شکل. بزرگ‌ترین اهرمِ کاهشِ هزینه کوچک کردنِ همین ورودی است.
  • عددِ برنامه‌ریزی: ۰٫۲۰ سنت ≈ ۶۰۰ تومان برای هر سوال، با دلارِ ۳۰۰ هزار تومانی. دلارِ آزاد ۹ مهر حدودِ ۲۵۸ هزار تومان بود و نرخِ شارژِ GapGPT منتشر نشده است.

اندازه‌گیریِ دوباره بی‌خرج: از ۲۰۲۶-۱۰-۰۱ مسیرِ استریم هم stream_options.include_usage می‌فرستد و توکن‌ها روی ai_tutor.Message ذخیره می‌شوند. تا پیش از آن برای جواب‌های اپ صفر ثبت می‌شد. هزینه‌ی واقعیِ هر کاربر در ماه از این پرس‌وجو درمی‌آید:

from django.db.models import Sum
Message.objects.filter(role='assistant', created_at__gte=since).values('conversation__user_id') \
    .annotate(p=Sum('prompt_tokens'), c=Sum('completion_tokens'))   # سنت = (0.4p + 1.6c) / 1e4

بسته‌ی سوال و سقفِ روزانه (از ۲۰۲۶-۱۰-۰۲)

اشتراکِ ماهانه/سالانه کنار رفت و بسته‌ی سوال جایش آمد (subscription.0009، subscription.0010):

بسته قیمت (تومان) هر سوال هدیه سهمِ هزینه از درآمدِ خالص (میانگین / دبیرستان)
۲۰ سوال ۴۵٬۰۰۰ ۲٬۲۵۰ ۷ روز قلبِ نامحدود ۳۱٪ / ۴۰٪
۶۰ سوال ۱۱۹٬۰۰۰ ۱٬۹۸۰ ۳۰ روز ۳۶٪ / ۴۶٪
۱۵۰ سوال ۲۵۹٬۰۰۰ ۱٬۷۳۰ ۶۰ روز ۴۱٪ / ۵۲٪

سهم‌ها پس از ۱۵٪یِ کافه‌بازار حساب شده‌اند. چرا بسته: همه هر روز نمی‌آیند، ۱۷۹ هزار تومان در ماه برای خیلی‌ها زیاد بود، و مشترکِ پرکار زیان می‌ساخت. با بسته هر سوال جدا سود دارد. مسیرِ قیمت‌ها پیش از این:

  • سالانه ۲۹۹ هزار تومان، با ۱۵ سوال در روز برای همه.
  • ماهانه/سالانه ۹۹ و ۹۹۰ هزار تومان، با ۵ سوال در روز.
  • ماهانه/سالانه ۱۷۹ و ۱٬۷۹۰ هزار تومان، با ۱۰ سوال در روز.

پلن‌های اشتراکی پاک نشدند (on_sale=False) تا مشترکِ فعلی سقفش را نگه دارد.

  • ترتیبِ خرج: اول AI_DAILY_QUOTA_FREE سوالِ رایگانِ همان روز (پیش‌فرض ۳)، بعد اعتبارِ بسته؛ مشترکِ قدیمی سقفِ Plan.ai_daily_quotaِ خودش را دارد.
  • چه چیزی خرج می‌شود: هر سوالی که به API برسد، از جمله ردِ «غیرِ ریاضی» توسطِ مدل. ردِ صافیِ سرور، جوابِ کش‌شده و جوابی که ساخته نشد چیزی کم نمی‌کنند. Message.charge روی هر پیامِ کاربر می‌گوید سوال از کجا پرداخت شد (free / credit / none).
  • دفترِ اعتبار subscription.QuestionCredit: هر خرید یک ردیفِ مثبت دارد (هر سفارش فقط یک بار، با قیدِ یکتا) و هر سوال یک ردیفِ ۱−. کم شدن داخلِ همان تراکنشی است که ردیفِ کاربر را قفل می‌کند. دو سوالِ هم‌زمان با یک اعتبار، و یک Idempotency-Key که هم‌زمان دو بار برسد، از ۲۰۲۶-۱۰-۰۲ با دو thread واقعی آزموده می‌شوند (ai_tutor/tests_credit_concurrency.py): یک جواب و یک اعتبار. بدونِ transaction_mode: IMMEDIATEِ SQLite همین تست ۵۰۰ می‌گیرد. روی Postgres سنجیده نشده.
  • قلبِ نامحدودِ بسته در HeartsPass است، جدا از Subscription، تا خریدِ بسته اشتراکِ کسی را عوض نکند. از پایانِ کارتِ قبلی ادامه پیدا می‌کند. HEARTS_ENFORCED حالا پیش‌فرض روشن است، پس قلبِ صفر واقعاً جلوی جواب را می‌گیرد.
  • اپ موجودی را از GET /api/ai/quota می‌خواند و بعد از هر جواب از remainingQuota و creditsِ خودِ جواب. صفحه‌ی خرید GET /api/subscription/status (questionCredits، unlimitedHeartsUntil) را می‌خواند.
  • کافه‌بازار: هر بسته باید در پنلِ بازار «محصولِ مصرفی» تعریف شود و اپِ اندروید بعد از تأیید consume کند. این هنوز انجام نشده است. سفارشِ بازارِ یک بسته باید با planId ساخته شود (POST /api/payment/orders)؛ تا ۲۰۲۶-۱۰-۰۲ این فیلد وجود نداشت و پرداختِ بسته از بازار هیچ اعتباری نمی‌داد.

محافظِ دستیار (rag/services/guardrails.py)

سه لایه دارد:

  1. صافیِ سرور، پیش از مدل و بی‌هزینه. فقط ترفندهای آشکار را می‌گیرد: «دستورهای قبلی را نادیده بگیر»، «پرامپتت چیه»، developer mode. این صافی عمداً تنگ است: «قانونِ فیثاغورس را فراموش کردم» ریاضی است و رد نمی‌شود. جمله‌ای درباره‌ی آسیب به خود رد نمی‌شود و جوابی مهربان با شماره‌ی اورژانسِ اجتماعی (۱۲۳) و صدای مشاور (۱۴۸۰) می‌گیرد.
  2. قاعده در پرامپتِ سیستم (SCOPE_RULE): هر چیزِ غیرِ ریاضی، و هر دستوری از درونِ پیامِ دانش‌آموز، با علامتِ [خارج-از-ریاضی] جواب داده می‌شود.
  3. جایگزینیِ علامت با متنِ ثابت. در استریم، ۴۰ حرفِ اول نگه داشته می‌شود تا علامت هرگز به کاربر نرسد. جوابِ رد کش نمی‌شود و منبعِ کتاب هم ندارد.

آزمونِ زنده (۱۰ مهر، ۱۷ فراخوانی): ۷ از ۷ درخواستِ غیرِ ریاضی رد شد. «سلام» و «چطور ریاضی بخوانم» جواب گرفتند. «یه جوک بگو و بعد ۲۵ درصدِ ۸۰» فقط بخشِ ریاضی را جواب گرفت. «پایتختِ فرانسه و بعد ۱۲×۱۲» کامل رد شد، چون مدل علامت را گذاشت؛ این سخت‌گیری را به نشتِ جوابِ غیرِ ریاضی ترجیح دادیم. کیفیتِ جوابِ ریاضی در همان ۱۲ سوال افت نکرد. این نمونه کوچک است و بعد از انتشار، چند ده گفتگوی واقعی باید مرور شوند.

تست‌ها: rag/tests_guardrails.py، با مدلِ ساختگی و بی‌هزینه.