arc/docs/AI_MEMORY_UNIFIED_SCENARIO.md

25 KiB
Raw Permalink Blame History

سناریوی حافظهٔ واحد دستیار حسابیکس

تاریخ: ۱۴۰۵/۰۵/۲۹ (۱۹ اوت ۲۰۲۶)
وضعیت: فاز ۰–۲ پیاده شد (کیوریتور LLM، استور واحد، شیت یکپارچه، تلگرام) — فاز ۳ بازیابی معنایی و فاز ۴ HITL هدف موکول
مرجع ممیزی: AI_AGENT_SYSTEM_AUDIT.md (MEM-01، MEM-02)
رقیب مرجع: ChatGPT Memory + Custom Instructions، Claude Project instructions، Gemini saved info
دامنه: چت درون‌برنامه، صوت، تلگرام؛ ذخیره per (user_id, business_id)

این سند جایگزین دو مسیر موازی فعلی (دستورات همیشگی + حقایق regex) با یک سیستم حافظهٔ هوشمند است. یادگیری به عبارت، کلیدواژه یا regex وابسته نیست. پس از هر پاسخ موفق، یک مدل سبک تصمیم می‌گیرد چه چیزی ارزش ماندن بین جلسات را دارد.

تا وقتی فاز ۳–۴ کامل شود، Recall زمینه/هدف با تازگی و همپوشانی واژه است نه embedding.


۱. مسئله به زبان محصول

کاربر در جلسهٔ A می‌گوید نامش علی است، یا «گزارش‌ها را خلاصه و جدولی بده»، یا «پروژهٔ جاری نسیم‌کالاست». جلسهٔ B باید این‌ها را بداند بدون اینکه تاریخچهٔ جلسهٔ A در پرامپت ریخته شود.

امروز:

لایه هست؟ چرا برای سطح جهانی کافی نیست
تاریخچهٔ جلسه بله فقط همان session_id؛ جلسهٔ جدید صفر است
دستورات همیشگی بله فقط اگر کاربر دستی در شیت بنویسد
حقایق یادگرفته جدول و UI هست استخراج با regex و هر ۶ پیام؛ «اسم من علی است» جا نمی‌ماند
ابزار upsert_memory_item ثبت شده از فیلتر ابزار (~۴۸ تا) معمولاً حذف می‌شود؛ ایجنت حسابداری نباید ذهن حافظه باشد
دانشنامه / بینش / خلاصهٔ جلسه جدا هستند نباید با حافظهٔ شخص قاطی شوند

سطح جهانی یعنی سه کار همزمان: نوشتن هوشمند، خواندن مرتبط، کنترل شفاف کاربر — نه لیست عبارت‌های جادویی.


۲. اصول غیرقابل مذاکره

  1. معنا، نه عبارت. هیچ الگوی زبانی («یادت باشه»، «اسم من» و …) شرط یادگیری نیست. مدل کیوریتور از روی نوبت قضاوت می‌کند.
  2. یک محصول حافظه. کاربر یک «حافظهٔ دستیار» می‌بیند. دستورات همیشگی و حقایق یادگرفته دو منبع داخلی‌اند، دو قابلیت جدا نیستند.
  3. ایجنت حسابداری ≠ کیوریتور حافظه. حلقهٔ چت ابزار دامنه را می‌زند و به کاربر جواب می‌دهد. بعد از اتمام نوبت، یک عملیات سبک جدا (memory_curate) حافظه را به‌روز می‌کند. یادگیری را به tool-calling داخل حلقهٔ ۱۰۰ millابزاری نسپاریم.
  4. پایدار در برابر گذرا. حافظه برای هویت، ترجیح، قرارداد کاری و زمینهٔ پایدار است. عدد فاکتور امروز، ماندهٔ لحظه‌ای و خروجی tool در حافظه نمی‌ماند؛ آن‌ها ابزار و بینش‌اند.
  5. کاربر صاحب حافظه است. هر آیتم قابل دیدن، ویرایش، حذف و «دیگر یاد نگیر» است. کیوریتور حق بازنویسی دستورات دست‌نویس کاربر را ندارد مگر خود کاربر در گفتگو صریحاً سیاست را عوض کند.
  6. محدودهٔ tenant. کلید حافظه (user_id, business_id) است. بین کسب‌وکارها نشت نمی‌کند.
  7. کم‌حرفی در چت. پاسخ کاربر با «یادداشت شد» شلوغ نمی‌شود مگر کاربر صریحاً خواسته باشد چیزی را به خاطر بسپارد. یادگیری پیش‌فرض بی‌صداست؛ شفافیت در شیت حافظه و رویداد اختیاری UI است.
  8. هزینهٔ کنترل‌شده. کیوریتور مدل light است (مثل عنوان جلسه و خلاصهٔ تاریخچه)، بدون ابزار، خروجی ساخت‌یافته، ورودی کوتاه.

۳. مدل مفهومی — یک حافظه، چند لایهٔ معنایی

کاربر یک صفحه می‌بیند: آنچه دستیار دربارهٔ شما و این کسب‌وکار می‌داند.

درون سیستم، هر رکورد یک آیتم حافظه با کلید پایدار است (نه slug متن آزاد).

kind چه کسی می‌نویسد اولویت در پرامپت مثال
instruction فقط کاربر (شیت یا گفتگوی صریح «از این به بعد همیشه…») بالاترین؛ الزام‌آور «مبالغ را تومان بگو؛ لحن رسمی»
identity کیوریتور یا بذر پروفایل حساب همیشه ارسال نام خطاب، نقش در کسب‌وکار
preference کیوریتور همیشه اگر فعال سبک گزارش، زبان، سطح جزئیات
context کیوریتور بازیابی مرتبط نام پروژه/برند داخلی، اصطلاح
goal کیوریتور؛ هدف عددی بهتر با تأیید مرتبط با سوال مالی هدف فروش ماهانه
constraint کیوریتور یا کاربر همیشه اگر فعال «این الگو را تکرار نکن» (از فیدبک منفی)

ai_business_memories.content در مهاجرت همان مجموعهٔ kind=instruction است.
ai_memory_items بقیهٔ kindها را نگه می‌دارد. API و UI یک payload واحد برمی‌گردانند.

۳.۱ آنچه حافظه نیست

منبع نقش داخل حافظه؟
پیام‌های جلسه کارِ کوتاه‌مدت خیر — مگر کیوریتور عصارهٔ پایدار بکشد
خلاصهٔ تاریخچهٔ همان جلسه بودجهٔ توکن خیر
بینش KPI (ai_insight_service) دادهٔ لحظه‌ای کسب‌وکار خیر
دانشنامه / RAG اسناد خیر
نتیجهٔ tool حقیقت دیتابیس همین حالا خیر

قانون کیوریتور: اگر با یک tool در نوبت بعد قابل بازیابی است و فردا عوض می‌شود، حافظه نیست.

۳.۲ بذر هویت از پروفایل

اگر کاربر در حسابیکس first_name/last_name دارد و هنوز identity.preferred_name خالی است، یک آیتم بذر با source=profile ساخته می‌شود. اگر در گفتگو نام دیگری برای خطاب بگوید، کیوریتور همان کلید را به‌روز می‌کند (source=curator)؛ پروفایل حساب عوض نمی‌شود.


۴. معماری هدف

                    ┌─────────────────────────────────────┐
                    │  Memory Store (واحد)                 │
                    │  instruction + items + keys پایدار  │
                    └──────────────┬──────────────────────┘
                                   │
              ┌────────────────────┼────────────────────┐
              ▼                    ▼                    ▼
     Recall Compiler        Curator (light LLM)    User UI / API
     قبل از هر نوبت چت      بعد از هر پاسخ موفق     یک شیت واحد
     انتخاب مرتبط           JSON patch             مشاهده/ویرایش/حذف

دو عملیات هوش مصنوعی جدا:

عملیات زمان مدل ابزار خروجی
Recall ساخت system prompt بدون LLM در فاز ۱؛ فاز ۲ embedding/رتبه — بلوک حافظه برای semi_static
Curate پس از persist پاسخ AI_OPERATION_MEMORY_CURATE ∈ light هیچ patch: upsert / update / delete / noop

حلقهٔ چت اصلی تغییر نمی‌کند جز اینکه بعد از commit پاسخ، curator زمان‌بندی شود و قبل از نوبت بعد recall از استور واحد بخواند.


۵. سناریوی نوبت — از پیام کاربر تا جلسهٔ بعد

۵.۱ قبل از فراخوانی مدل چت (Recall)

  1. استور حافظه برای (user, business) خوانده می‌شود.
  2. همیشه در پرامپت می‌آیند (سقف کاراکتر جدا):
    • همهٔ instruction
    • identity فعال
    • preference و constraint فعال
  3. انتخابی (سقف جدا، مثلاً ۱۲ آیتم):
    • context و goal مرتبط با سوال جاری
    • فاز ۱: تازگی + سقف؛ فاز ۲: شباهت معنایی با سوال
  4. خروجی همان لایهٔ semi_static.memory است تا prompt cache نشکند مگر حافظه واقعاً عوض شده باشد.
  5. به مدل چت گفته می‌شود: این بلوک زمینهٔ پایدار است؛ عدد لحظه‌ای را از tool بگیر؛ خودت حافظه را در این نوبت ننویس.

۵.۲ حین پاسخ (ایجنت)

ایجنت مثل امروز کار می‌کند. ابزارهای حافظه از حلقهٔ پیش‌فرض حذف می‌شوند تا با کیوریتور رقابت نکنند.

تنها استثنا: اگر کاربر در همین پیام بخواهد حافظه را مدیریت کند («چی دربارهٔ من می‌دانی؟»، «این را از حافظه پاک کن»، «از این به بعد همیشه جدول بده»)، intent معنایی — نه لیست کلمه — ابزارهای حافظه را به forced_names اضافه می‌کند. تشخیص این intent با خود مدل چت + یک دستهٔ ابزار memory در allowlist وقتی سوال از جنس حافظه است انجام می‌شود، یا با خروجی ساخت‌یافتهٔ کیوریتور در نوبت بعد. برای پاسخ فوری «چی می‌دانی؟» دستهٔ memory باید بتواند در همان نوبت در ابزارها باشد.

قاعدهٔ عملی:

  • سوال کاری (فاکتور، موجودی، گزارش) → ابزار حافظه در حلقه نیست؛ curator بعد از پاسخ می‌نویسد.
  • سوال دربارهٔ خود حافظه → get_user_memory / حذف / به‌روزرسانی instruction در همان نوبت مجاز است.

۵.۳ بعد از پاسخ موفق (Curate) — هستهٔ هوشمندی

وقتی پاسخ دستیار persist شد (استریم یا غیر استریم، متن یا صوت، در صورت امکان تلگرام):

  1. Job پس‌زمینه با queue معتبر (نه asyncio.create_task شکننده).
  2. Skip فقط ساختاری، نه زبانی:
    • پاسخ خالی / خطا / لغو
    • نوبت فقط تأیید نوشتن بدون محتوای جدید کاربر
    • سهمیهٔ روزانهٔ light تمام شده
  3. ورودی کیوریتور (کوتاه):
    • ۱ تا ۲ نوبت اخیر (متن کاربر + متن نهایی دستیار؛ بدون JSON ابزار)
    • فهرست فشردهٔ حافظهٔ موجود: key, kind, content تا ۱۲۰ کاراکتر
    • سیاست «چه چیزی حافظه است / نیست» از prompt سیِد aux.memory_curate
  4. مدل light با JSON schema، max_tokens پایین، temperature پایین، بدون tool.
  5. اعتبارسنجی قطعی روی خروجی (اینجا دیگر مدل نیست):
    • kind مجاز، طول محتوا، کلید پایدار با namespace
    • ممنوعیت راز (توکن، رمز، کلید API) با فیلتر قطعی روی محتوا نه روی گفتگوی کاربر
    • instruction فقط اگر actor=user_explicit
    • عدم ذخیرهٔ عدد تراکنشی / شناسهٔ سند به‌عنوان حقیقت پایدار
  6. اعمال patch: upsert با کلید پایدار (مثلاً identity.preferred_name نه slug متن).
  7. متریک: memory_curate_noop / applied / rejected / failed.
  8. رویداد اختیاری SSE یا فیلد در پاسخ بعدی: memory_updates[] برای UI («به خاطر سپرده شد: نام خطاب علی») — پیش‌فرض غیرمزاحم؛ در شیت مشخص.

کیوریتور منتظر نوبت ششم نمی‌ماند. هر پاسخ موفق یک شانس یادگیری است. اگر چیزی برای ماندن نباشد، JSON خالی/noop است و استور دست نمی‌خورد.

۵.۴ جلسهٔ بعد

Recall همان استور را می‌خواند. نام، ترجیح و دستورات بدون تاریخچهٔ جلسهٔ قبل در پرامپت هستند.


۶. قرارداد کیوریتور (خروجی ساخت‌یافته)

پرامپت کیوریتور در ai_default_prompts با کلید aux.memory_curate سیِد می‌شود تا ادمین بتواند سیاست را عوض کند، نه اینکه regex در کد سخت شود.

خروجی نمونه:

{
  "ops": [
    {
      "action": "upsert",
      "key": "identity.preferred_name",
      "kind": "identity",
      "content": "کاربر ترجیح می‌دهد علی خطاب شود.",
      "confidence": 0.86,
      "user_explicit": false
    }
  ]
}

action: upsert | update | delete | noop

قضاوت معنایی داخل پرامپت کیوریتور (خلاصهٔ سیاست، نه فهرست جمله):

  • ارزش ماندن بین جلسات دارد اگر فردا بدون این جمله دستیار اشتباه خطاب کند یا سبک/قرارداد را از دست بدهد.
  • ارزش ماندن ندارد اگر فقط برای همین سوال است، از دیتابیس با tool می‌آید، یا فردا کهنه می‌شود.
  • اگر با آیتم موجود در تضاد است: به‌روزرسانی همان کلید، نه ردیف جدید.
  • اگر کاربر خواست فراموش شود: delete روی همان کلید.
  • دستور همیشگی جدید فقط وقتی کاربر سیاست پایدار اعلام کرده، نه وقتی یک بار «این گزارش را جدولی بده».

کلیدهای پایدار پیشنهادی (قابل گسترش، نه بسته):

  • identity.preferred_name
  • identity.role_in_business
  • preference.report_style
  • preference.amount_unit
  • preference.language
  • context.current_project
  • context.internal_term.<slug>
  • goal.sales_monthly
  • constraint.avoid.<slug>
  • instruction → فقط از UI یا user_explicit

۷. ادغام محصولی دستورات و حقایق

وضعیت امروز دو سیستم موازی است. هدف یک سناریو است:

سطح قبل بعد
کاربر دو بلوک جدا در شیت یک حافظه: «سیاست‌های من» + «آنچه یاد گرفته‌ام» به‌عنوان بخش یک صفحه
API GET /memory با instructions و items همان endpoint، مدل واحد: entries[] با kind؛ فیلدهای قدیمی تا یک نسخه سازگار
ابزار مدل ۵ function جدا + توضیح متناقض ۳ عمل: read_memory, upsert_memory_entry, delete_memory_entry — فقط وقتی سوال از جنس حافظه است
پرامپت چت دو متن چسبیده یک بلوک compiled: سیاست‌ها، سپس هویت، سپس ترجیح، سپس زمینهٔ بازیابی‌شده
یادگیری regex + tool غیرقابل‌اتکا فقط curator (+ بذر پروفایل)
فیدبک thumbs-down آیتم hint خام همان استور با kind=constraint از مسیر curator یا فیدبک، با سقف روزانه در Redis/DB

ai_memory_structured.py (هدف فروش، واحد پول، …) دیگر JSON جدا روی AIBusinessMemory نیست؛ معادل کلیدهای پایدار است. فیلد structured در upsert حافظهٔ متنی deprecate می‌ماند.

متن آزاد ۴۰۰۰ کاراکتری فقط برای سیاست‌های کاربر می‌ماند. کیوریتور آن را با گلوله‌های پراکنده پر نمی‌کند.


۸. تجربهٔ کاربر

شیت واحد «حافظهٔ دستیار»:

  1. توضیح یک خط: دستیار بین گفت‌وگوها زمینهٔ پایدار را نگه می‌دارد؛ اعداد لحظه‌ای از دادهٔ کسب‌وکار خوانده می‌شوند.
  2. بخش سیاست‌ها (instruction) — ویرایش آزاد، ذخیره دستی.
  3. بخش یادگرفته‌ها — گروه‌بندی بر اساس kind، منبع (profile / curator / user / feedback)، ویرایش و حذف هر آیتم.
  4. «پاک کردن همه» با تأیید.
  5. اختیاری فاز ۲: سوییچ «یادگیری خودکار» per کاربر.

در چت:

  • یادگیری عادی: بدون جملهٔ اضافه در پاسخ.
  • اگر کاربر صریح خواست به خاطر بسپارد: یک جملهٔ کوتاه تأیید کافی است.
  • منوی پیام: «این را به حافظه اضافه کن» / «این را فراموش کن» — بدون نیاز به عبارت خاص در متن.

۹. هزینه، سهمیه، پایداری job

  • AI_OPERATION_MEMORY_CURATE عضو LIGHT_AI_OPERATIONS؛ مدل light_model پلن (همان مسیر عنوان جلسه).
  • شارژ usage مثل title / history_summary؛ شکست curator پاسخ کاربر را خراب نمی‌کند.
  • سقف روزانه جدا (MAX_LLM_MEMORY_CURATE_PER_USER_DAY)، در Redis/DB نه dict درون‌پرداز.
  • ورودی حداکثر حدود ۲–۳k توکن؛ خروجی چند صد توکن.
  • زمان‌بندی: همان الگوی title generation اما با worker/queue که بعد از برگشت از handler زنده بماند (رفع MEM-02).
  • اگر light در دسترس نبود: آن نوبت skip؛ fallback regex نداریم. بذر پروفایل همچنان بدون LLM اعمال می‌شود.

۱۰. کانال‌ها و مرزها

کانال Recall Curate
چت وب/اپ بله بله، بعد از persist
صوت روی همان session بله بله، روی متن نهایی
تلگرام باید به همان استور وصل شود بعد از پاسخ تلگرام، همان job
ایجنت ورک‌فلو / تیکت پشتیبانی فقط اگر همان کاربر+کسب‌وکار باشد خیر مگر کانال چت باشد

اپراتور پشتیبانی حافظهٔ tenant کاربر نهایی را با حافظهٔ خودش قاطی نمی‌کند.


۱۱. تست و معیار پذیرش (سطح جهانی)

بدون مدل زنده (اجباری):

  • JSON کیوریتور نامعتبر اعمال نمی‌شود.
  • instruction بدون user_explicit نوشته نمی‌شود.
  • کلید پایدار به‌جای ردیف تکراری به‌روز می‌شود.
  • راز و شناسهٔ سند در validator رد می‌شود.
  • Recall همیشه instruction+identity را می‌گذارد حتی اگر context خالی باشد.
  • جلسهٔ جدید بدون تاریخچهٔ قدیم، آیتم ذخیره‌شده را در بلوک پرامپت دارد (تست کامپایلر).
  • ابزار حافظه در سوال «گزارش فروش» در allowlist نیست؛ در سوال «چی دربارهٔ من می‌دانی؟» هست.

با فیکسچر مدل (طلایی):

نوبت انتظار
«اسم من را علی صدا کن» سپس جلسهٔ جدید «سلام» identity.preferred_name؛ پرامپت جلسهٔ بعد شامل علی
«این ماه فروش چقدر بود؟» noop حافظه؛ عدد در استور نیست
«از این به بعد گزارش‌ها را جدولی بده» preference.report_style یا instruction اگر سیاست کلی باشد
«اسم پروژه‌مان نسیم‌کالاست» سپس سوال نامرتبط و بعد «وضعیت پروژه چیست؟» context.current_project بازیابی می‌شود
«دیگر اسمم را به خاطر نیاور» delete کلید هویت
thumbs-down با توضیح سبک constraint بدون آلوده‌کردن instruction

پذیرش محصول:

  • کاربر در شیت یک فهرست واحد می‌بیند.
  • پس از معرفی نام در جلسهٔ اول، جلسهٔ دوم بدون تکرار نام، خطاب درست است.
  • هیچ وابستگی به وجود واژهٔ «یادت باشه» در تست طلایی نیست.

۱۲. فاز اجرا

فاز محتوا خروج
۰ — قرارداد مدل entries، کلید پایدار، compiler پرامپت واحد، API سازگار عقب‌رو، حذف regex از مسیر اصلی استور واحد؛ رفتار یادگیری هنوز کامل نیست
۱ — Curator aux.memory_curate، operation light، job بعد از هر پاسخ، validator، متریک، بذر پروفایل سناریوی نام و ترجیح بدون عبارت جادویی
۲ — محصول واحد شیت یکپارچه، رویداد «به خاطر سپرده شد»، ابزارهای سه‌تایی فقط برای intent حافظه، تلگرام UX سطح ChatGPT
۳ — Recall هوشمند embedding یا رتبه برای context/goal وقتی تعداد آیتم زیاد است پرامپت شلوغ نمی‌شود
۴ — HITL هدف goal حساس با پیش‌نمایش تأیید؛ سوییچ یادگیری خودکار ممیزی MEM-01/۲ بسته شود

فاز ۰–۱ باید با هم به production بروند؛ در غیر این صورت UI واحد با یادگیری کر می‌ماند یا کیوریتور روی دو مدل ذهنی قدیمی می‌نویسد.


۱۳. نقشهٔ فایل‌ها (پیاده‌سازی بعدی)

کار فایل‌های اصلی
استور و compiler ai_memory_service.py, ai_memory_item_service.py
کیوریتور سرویس جدید ai_memory_curator.py؛ جایگزینی منطق regex در extract_learned_candidates
job ai_memory_hooks.py — queue به‌جای create_task شکننده
routing ai_constants.py, ai_model_service.py
پرامپت ai_default_prompts.py → aux.memory_curate
ابزار ai_function_extensions_memory.py, ai_tool_intent.py (دستهٔ memory)
چت adapters/api/v1/ai/chat.py بعد از persist
تزریق پرامپت ai_service.py / format_memory_for_prompt
UI ai_chat_memory_sheet.dart
تست test_ai_memory_service.py + تست curator/compiler جدید

Regex فعلی فقط تا شروع فاز ۱ به‌عنوان کد مرده/تست سازگاری می‌ماند؛ مسیر production آن را صدا نمی‌زند.


۱۴. ضدالگوها (عمداً انجام نمی‌دهیم)

  • ریختن N جلسهٔ قبلی در context به‌جای حافظه.
  • وادار کردن ایجنت حسابداری به upsert_memory_item در هر نوبت فروش.
  • فهرست جملات فارسی برای «تشخیص نام».
  • یکی کردن دانشنامه با حافظهٔ شخص.
  • ذخیرهٔ KPI بینش داخل memory items.
  • پر کردن دستورات همیشگی توسط مدل.
  • Fallback regex وقتی مدل light قطع است (سکوت بهتر از حقیقت غلط است).
  • دو UI یا دو API برای «دستور» و «حقیقت» به‌عنوان محصول جدا.

۱۵. تصمیم‌های قفل‌شده

  1. یادگیری = کیوریتور بعد از پاسخ، نه regex و نه ابزار داخل حلقهٔ پیش‌فرض.
  2. محصول = یک حافظه با چند kind؛ دستورات همیشگی kind=instruction با اولویت الزام‌آور.
  3. Recall فاز ۱ قاعده‌ای (اولویت kind + سقف)؛ فاز ۳ معنایی.
  4. کلید پایدار اجباری است تا ادغام و فراموشی ممکن باشد.
  5. این سند مرجع اجراست؛ MEM-01 و MEM-02 پس از فاز ۲–۴ در ممیزی انجام‌شده می‌شوند.