پرش به محتوای اصلی
پرش به محتوای مقاله

پیاده‌سازی حافظه بلندمدت عامل‌های AI در .NET با Postgres و pgvector

·۱۲ مهر ۱۴۰۵۷ دقیقه مطالعه
راهنما
ساخت سیستم عامل‌محور در دات‌نت، بخش ۳: حافظه پایدار با Postgres و pgvector
ساخت سیستم عامل‌محور در دات‌نت، بخش ۳: حافظه پایدار با Postgres و pgvector
اشتراک‌گذاری
واقعاً چه چیز جدید است؟

معرفی یک فرمول ترکیبی (شباهت + تازگی + اهمیت) برای بازیابی حافظه در .NET که از تکیه صرف بر بردار معنایی فاصله می‌گیرد.

اگر امروز یک عامل هوش مصنوعی می‌سازید که با هر بار باز شدن برنامه همه چیز را فراموش می‌کند، در واقع یک تابع بدون وضعیت ساخته‌اید، نه یک دستیار هوشمند. برای اینکه یک عامل در محیط تولید (Production) کاربردی باشد، باید حافظه‌ای داشته باشد که پس از ریست شدن جلسه (Session) از بین نرود. بدون وجود یک وضعیت پایدار (Durable State)، ترجیحات کاربر و تصمیمات پیشین در لحظه‌ای که گفتگو به پایان می‌رسد، برای همیشه ناپدید می‌شوند.

به نقل از یک راهنمای پیاده‌سازی مفصل که در ۴ اکتبر ۲۰۲۶ منتشر شد، توسعه‌دهندگان .NET اکنون می‌توانند با ترکیب Postgres و افزونه pgvector، حافظه‌ای پایدار ایجاد کنند که ترجیحات کاربر و تصمیمات پیشین را در طول زمان حفظ کند. همان‌طور که در تحلیل‌های قبلی ما درباره‌ی معماری‌های RAG اشاره کردیم، تفاوت میان یک چت‌بات ساده و یک عامل هوشمند در توانایی مدیریت وضعیت (State) نهفته است. این رویکرد در اکوسیستم‌های دیگر نیز دنبال شده است؛ برای مثال، ۵ استراتژی برای پیاده‌سازی حافظهٔ پایدار در عامل‌های LangChain روش‌های متفاوتی را برای دستیابی به همین هدف در محیط‌های پایتونی بررسی کرده است.

بسیاری از برنامه‌نویسان به اشتباه کل متن گفتگوها (Raw Transcripts) را به عنوان حافظه ذخیره می‌کنند. این لاگ‌ها به طور نامحدود رشد می‌کنند و پر از نویز هستند؛ مواردی مثل گپ‌وگفت‌های کوتاه، درخواست‌های شفاف‌سازی یا زمانی که کاربر یک سوال واحد را به سه روش مختلف می‌پرسد. برای این نوع داده‌ها، یک ذخیره‌ساز جلسه مانند Redis با قابلیت تعیین زمان انقضا (TTL) کاملاً کافی است. اما حافظه بلندمدت واقعی باید شامل «حقایق تقطیرشده» باشد؛ یعنی اطلاعاتی که ماندگار هستند و ارزش بازیابی در جلسات آینده را دارند.

چه مواردی باید در حافظه بلندمدت قرار گیرند؟

برای جلوگیری از ورود نویز به سیستم، تنها انواع خاصی از داده‌ها باید به حافظه بلندمدت ارتقا یابند:

  • ترجیحات (Preferences): انتخاب‌های صریحی که کاربر بیان کرده است، مانند «همیشه از واحدهای متری استفاده کن».
  • تصمیمات (Decisions): نتایجی که عامل در تسهیل آن‌ها نقش داشته است، مانند «در اسپرینت ۱۴ به Dapr مهاجرت کردیم».
  • حقایق دامنه (Domain Facts): اطلاعات خاصی که توسط کاربر ارائه شده و مدل پایه (Base Model) به طور پیش‌فرض به آن‌ها دسترسی ندارد.
  • اصلاحات (Corrections): به‌روزرسانی‌هایی که کاربر روی داده‌های قبلی اعمال می‌کند، مانند «گفتم ضرب‌الاجل جمعه است، نه پنجشنبه».

هر مورد ذخیره‌شده در زمان نوشتن، یک امتیاز اهمیت بین ۰.۰ تا ۱.۰ و یک برچسب زمانی (Timestamp) دریافت می‌کند. هر دو مقدار برای فرآیند رتبه‌بندی در زمان بازیابی (Recall) حیاتی هستند.

پشته فنی (Technical Stack)

در لایه فنی، این معماری در محیط .NET بر پایه EF Core و بسته‌های NuGet استوار است. توسعه‌دهندگان باید از Pgvector.EntityFrameworkCore (نسخه ۰.۳.۰ برای EF Core 9/10) و Npgsql.EntityFrameworkCore.PostgreSQL (نسخه ۱۰.۰.۳) استفاده کنند. برای کسانی که همچنان از EF Core 8 استفاده می‌کنند، راهنما توصیه می‌کند که نسخه را روی ۰.۲.۲ ثابت (Pin) کنند.

موجودیت اصلی یعنی MemoryItem شامل محتوا، بردار معنایی (Embedding) — مثل کارت معرفی عددی برای هر واژه که می‌گوید این کلمه «همسایه‌ی» چه کلمات دیگری است — امتیاز اهمیت (۰.۰ تا ۱.۰) و برچسب‌های زمانی برای زمان ایجاد و آخرین دسترسی است. کلاس AgentDbContext افزونه vector را پیکربندی کرده و ستون Embedding را به صورت vector(1536) تعریف می‌کند.

ثبت این سرویس در Program.cs از طریق متد زیر انجام می‌شود:
builder.Services.AddNpgsql<AgentDbContext>(connectionString, npgsqlOptions => npgsqlOptions.UseVector());

ساخت سیستم عامل‌محور در دات‌نت، بخش ۳: حافظه پایدار با Postgres و pgvector

ایندکس‌گذاری و مهاجرت‌ها (Migrations)

از آنجا که EF Core رابط Fluent API برای ایندکس‌های HNSW (Hierarchical Navigable Small World) ندارد، توسعه‌دهندگان باید کد DDL را به صورت دستی به Migration‌ها اضافه کنند. پس از اجرای دستور dotnet ef migrations add AddMemoryItem باید متد Up را تغییر داد تا افزونه و ایندکس مورد نظر را شامل شود.

یک ایندکس استاندارد برای محیط تولید از vector_cosine_ops با پارامترهای زیر استفاده می‌کند:

  • m = 16: حداکثر اتصالات پیش‌فرض در هر لایه.
  • ef_construction = 64: عرض جست‌وجو در زمان ساخت ایندکس.

مقادیر بالاتر باعث بهبود نرخ بازیابی (Recall) می‌شوند اما هزینه حافظه و زمان ساخت را افزایش می‌دهند. برای حافظه عامل‌ها که داده‌ها را به صورت تک‌تک دریافت می‌کنند (و نه به صورت بارگذاری انبوه)، HNSW بهترین انتخاب است. این در مقابل IVFFlat قرار می‌گیرد که پس از درج داده‌های انبوه، نیاز به یک مرحله آموزش ANALYZE دارد و در صورت حذف این مرحله، عملکردش افت می‌کند.

محدودیت‌های Embedding و تکه‌بندی (Chunking)

این سیستم از Microsoft.Extensions.AI بهره می‌برد تا سازگاری با Azure OpenAI و مدل‌های محلی از طریق Ollama را تضمین کند. این قابلیت از طریق یک Wrapper به نام IMemoryEmbeddingService روی IEmbeddingGenerator پیاده‌سازی شده است. تغییر Backendها تنها با یک تغییر در تزریق وابستگی (DI) امکان‌پذیر است. این معماری با Agent Framework 1.0 (منتشر شده در ۳ آوریل ۲۰۲۶) و Semantic Kernel فعلی سازگار است.

با این حال، یک محدودیت سخت‌افزاری جدی وجود دارد: HNSW در pgvector سقف ۲۰۰۰ بُعد دارد. مدل text-embedding-3-small با ۱۵۳۶ بُعد کاملاً سازگار است، اما مدل text-embedding-3-large با ۳۰۷۲ بُعد در زمان ایجاد ایندکس با خطای "column cannot have more than 2000 dimensions for hnsw index" مواجه می‌شود، مگر اینکه از نوع ستون halfvec استفاده شود.

برای جلوگیری از امتیازات مبهم (Mushy Scores)، استفاده از تکه‌بندی معنایی (Semantic Chunking) توصیه می‌شود. یک پاراگراف طولانی، مورد حافظه ضعیفی می‌سازد زیرا بردار معنایی، میانگین کل بلوک متنی را می‌گیرد. با استفاده از SemanticChunker.NET اثر Gregor Biswanger، توسعه‌دهندگان باید با آستانه صدک ۹۵ (95th percentile) شروع کنند. این مقدار را می‌توان در گام‌های پنج‌تایی (۹۰، ۹۵، ۹۸) تنظیم کرد تا تعادلی میان اندازه تکه‌ها و کاربرد آن‌ها ایجاد شود. همچنین باید فضای کافی در پنجره متنی (Context Window) — میزان متنی که مدل هم‌زمان «در ذهن» نگه می‌دارد — باقی گذاشت؛ برای مثال در یک کانتکست ۸,۱۹۲ توکنی، باید جایی برای پرامپت سیستمی، حافظه‌های بازیابی‌شده و نوبت جدید کاربر باشد.

مقیاس‌پذیری و عملکرد

عملکرد سیستم با رشد داده‌ها به شدت تغییر می‌کند. در چند صد ردیف، جست‌وجوی ترتیبی (Sequential Scan) ناچیز است. در ۱۰,۰۰۰ ردیف، کندی احساس می‌شود و در ۱۰۰,۰۰۰ ردیف، جست‌وجوی ترتیبی برای برنامه‌های کاربر-محور غیرقابل قبول است. HNSW این مشکل را با پیمایش یک گراف لایه‌ای از کلیات به جزئیات حل می‌کند، به این معنی که هزینه پرس‌وجو به جای خطی، به صورت لگاریتمی رشد می‌کند.

بر اساس بنچمارک مارس ۲۰۲۶ توسط DBI-services روی ۲۵ هزار مقاله ویکی‌پدیا که با text-embedding-3-large برداری شده بودند، تفاوت سرعت بین جست‌وجوی ترتیبی و HNSW بسیار چشمگیر است. در جداول کوچک، توسعه‌دهندگان حتی ممکن است نیاز داشته باشند enable_seqscan را غیرفعال کنند تا برنامه‌ریز (Planner) مجبور شود از ایندکس استفاده کند.

برای مقیاس‌های عظیم، راهنما اشاره می‌کند که pgvectorscale (بر پایه DiskANN) می‌تواند به ۴۷۱ پرس‌وجو در ثانیه (QPS) با صحت ۹۹٪ برای ۵۰ میلیون بردار برسد. اما برای اکثر سیستم‌های عاملی زیر ۱۰ میلیون بردار، pgvector به همراه HNSW به دلیل توانایی ترکیب پرس‌وجوهای رابطه‌ای و برداری، بهینه‌ترین انتخاب است.

یک نکته کلیدی در تنظیمات، مقدار hnsw.ef_search است که پیش‌فرض آن ۴۰ است و لیست کاندیداهای بازگشتی در زمان پرس‌وجو را محدود می‌کند. برای تنظیم این مقدار، باید از SET LOCAL درون یک تراکنش (مثلاً تنظیم روی ۱۰۰) استفاده کرد، نه SET در سطح جلسه (Session). تنظیمات سطح جلسه می‌توانند در Connection Pool باقی بمانند و به طور نامحسوس پرس‌وجوهای غیرمرتبط را مختل کنند.

مکانیسم بازیابی ترکیبی (Blended Recall)

استفاده از شباهت کسینوسی (Cosine Similarity) به تنهایی برای حافظه کافی نیست. یک خاطره بسیار مشابه از سه سال پیش، لزوماً مفیدتر از یک خاطره کمی مشابه اما مربوط به هفته گذشته نیست که کاربر آن را به عنوان مورد حیاتی علامت‌گذاری کرده است. مکانیسم بازیابی پیشنهادی، سه سیگنال متمایز را ترکیب می‌کند:

  • شباهت (۶۰٪): فاصله کسینوسی بین پرس‌وجو و بردار ذخیره شده که توسط EF Core به اپراتور <=> ترجمه می‌شود.
  • تازگی (۲۰٪): یک تابع زوال نمایی با نیمه‌عمر ۳۰ روزه. این مقدار با استفاده از EF.Functions.DateDiffDay بین تاریخ آخرین دسترسی و زمان فعلی محاسبه می‌شود. فرمول مورد استفاده -0.693f * DateDiffDay / 30.0 است.
  • اهمیت (۲۰٪): وزنی که به صورت دستی در زمان نوشتن به داده اختصاص داده شده است.

این امتیاز ترکیبی تضمین می‌کند که عامل مرتبط‌ترین و به‌روزترین اطلاعات را بازیابی کند. برای حفظ این وضعیت، سیستم هر بار که حافظه‌ای فراخوانی می‌شود، برچسب LastAccessedAt را با استفاده از ExecuteUpdateAsync به‌روز می‌کند تا ارتباط آن حافظه «تازه» بماند. این رویکرد برای حل مشکل فقدان بافتار (Context Loss) بسیار موثر است، مشابه آنچه در درون سازوکار MEMORA برای بازیابی تاریخچه تعاملات در عامل‌های هوشمند بررسی شده است که بر مدیریت روابط حافظه تمرکز دارد.

یکپارچه‌سازی از طریق Middleware

برای عملیاتی کردن این سیستم، فرآیند بازیابی در یک Middleware یا قلاب SessionStart قرار می‌گیرد. قبل از رسیدن اولین پیام کاربر به مدل، سیستم ۵ مورد از مرتبط‌ترین حافظه‌ها را بر اساس پیام اولیه استخراج می‌کند. این موارد مستقیماً به عنوان یک بلوک «بستر بازیابی‌شده از جلسات پیشین» به پرامپت سیستمی (System Prompt) تزریق می‌شوند.

عامل هرگز مستقیماً با پایگاه داده تعامل نمی‌کند؛ او صرفاً پرامپتی را می‌بیند که با تاریخچه بلندمدت خودش غنی شده است. این معماری، عامل را از یک تابع بدون وضعیت به یک موجودیت پایدار تبدیل می‌کند. با تقطیر حقایق در زمان نوشتن و رتبه‌بندی آن‌ها با امتیاز وزنی در زمان خواندن، توسعه‌دهندگان می‌توانند عامل‌هایی بسازند که واقعاً در طول زمان از کاربران خود می‌آموزند.

برای کسانی که در حال پیاده‌سازی این سیستم هستند، چالش بعدی «بهداشت حافظه» است. به‌روزرسانی‌های آینده بر روی نیمه دیگر مشکل تمرکز خواهند داشت: حافظه‌هایی که ذخیره شده‌اند اما دیگر درست نیستند. این شامل تشخیص تضادها، حذف موارد تکراری (Deduplication)، تأیید در زمان خواندن و پاک‌سازی (Pruning) به عنوان یک سرویس میزبانی‌شده است تا تضمین شود پرس‌وجوی بازیابی، حقایقی را برمی‌گرداند که عامل می‌تواند به آن‌ها تکیه کند.

گام بعدی شما

  • اگر از EF Core 8 استفاده می‌کنید، حتماً نسخه Pgvector.EntityFrameworkCore را روی ۰.۲.۲ ثابت کنید.
  • برای مدل‌های بالای ۲۰۰۰ بُعد، از نوع داده halfvec در Postgres استفاده کنید تا با خطای ایندکس HNSW مواجه نشوید.
  • استراتژی تکه‌بندی داده‌ها را با SemanticChunker.NET تست کنید تا از «به‌هم‌ریختگی» بردارها جلوگیری کنید.
چرا این موضوع مهم است؟

این متدولوژی با تکیه بر اعتبار Postgres، هزینه و پیچیدگی مدیریت حافظه را برای توسعه‌دهندگان .NET به شدت کاهش می‌دهد. انتقال از حافظه موقت به حافظه ساختاریافته، پیش‌نیاز تبدیل چت‌بات‌ها به عامل‌های عملیاتی در مقیاس صنعتی است.

تأثیر برای ایران

توسعه‌دهندگان ایرانی که از اکوسیستم .NET استفاده می‌کنند، می‌توانند با این روش بدون نیاز به دیتابیس‌های برداری گران‌قیمت و ابری، حافظه بلندمدت را به‌صورت درون‌سازمانی (On-premises) پیاده کنند.

·نگاه ما
تحریریه دات‌هوش

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

منابع

این گزارش با خط‌لولهٔ خودکار دات‌هوش از منابع معتبر جهانی تدوین و زیر نظر تحریریه منتشر شده است. روش کار ما

گفتگو

پنج‌شنبه‌های هوش‌محور

بسته‌ی هفتگی دات‌هوش

۵ خبر، ۲ ابزار، ۱ پرامپت در هر شماره. به‌زودی راه‌اندازی می‌شود — هر پنج‌شنبه صبح.

خبر کلیدی
ابزار کاربردی
پرامپت حرفه‌ای
تحلیل پژوهش
به‌زودی
زاویه‌ی ایرانی
به‌زودی
تمرین این هفته
به‌زودی

راهنماهای دات‌هوش

راهنماهای کاربردیِ دات‌هوش برای کار با هوش مصنوعی — از همین‌جا شروع کنید:

دات‌هوش

راهنمای فارسی هوش مصنوعی — با نگاه به ایران

اخبار روزانه، معرفی ابزارها و مدل‌ها، و آموزشِ کار با هوش مصنوعی؛ همیشه با این پرسش که از ایران چه چیزی کار می‌کند و چه چیزی نه.