أنواع ذاكرة الوكلاء وتكلفة تخزينها وإعادة تضمينها
قارن الذاكرة الدلالية والعرضية والإجرائية في جدول واحد، واكتشف تكلفة تخزين كل نوع وإعادة تضمينه عملياً على خادم VPS قبل اختيار المخزن المناسب.
ما هي أنواع ذاكرة الوكلاء الثلاثة
تنقسم ذاكرة الوكيل إلى ثلاثة أنواع، ويؤثر كل نوع منها بشكل مختلف في الموارد التي تدفع تكلفتها: تحتفظ الذاكرة الدلالية بالحقائق، وتحتفظ الذاكرة العرضية بما حدث، وتحتفظ الذاكرة الإجرائية بكيفية تنفيذ مهمة. يعرّف الجدول أدناه كل نوع مع مثال من الخادم. أما بقية هذا القسم فتتناول الجانب الذي لا يُكتب عنه عادةً، وهو تكلفة تخزين كل نوع وتكلفة إعادة بنائه.
The data behind this chart
[
{
"label": "Semantic",
"what_it_holds": "Facts the agent should treat as currently true",
"server_example": "The database listens on 10.8.0.4:5432 and the nightly dump runs at 03:15 UTC"
},
{
"label": "Episodic",
"what_it_holds": "A record of one past event or session",
"server_example": "On 2026-08-11 the deploy failed because the disk was full, and rotating logs fixed it"
},
{
"label": "Procedural",
"what_it_holds": "How to carry out a task, as steps the agent can run",
"server_example": "The restore runbook: stop the service, load the dump, run migrations, start the service"
}
]الأسماء مستعارة من علم النفس البشري، لكن هذا الاستعارة تقريبية. ويبرَّر هذا التقسيم لسبب عملي: تختلف الأنواع الثلاثة في أحجامها ومسارات إصلاحها، لذلك يؤدي وضعها كلها في مخزن متجهات واحد إلى جعل كل نوع منها أسوأ.
الذاكرة الدلالية صغيرة، وستحتاج إلى تحريرها يدوياً
لا تتجاوز بضع مئات من الحقائق عن خوادمك بضعة عشرات من الكيلوبايتات من النص. التخزين ليس المشكلة هنا، بل التصحيح. تكون الحقيقة الخاطئة في الذاكرة الدلالية خاطئة في كل إجابة يقدمها الوكيل بعدها، لذلك يجب أن يتيح لك مخزن البيانات العثور على حقيقة واحدة باسمها، وتغييرها، والتأكد من زوال القيمة القديمة.
يشير ذلك إلى استخدام مخزن ذي مفاتيح: جدول Postgres يضم مفتاحاً أساسياً، أو دليلاً يحتوي على ملفات Markdown صغيرة ضمن git. يتيح لك كلا الخيارين تنفيذ استعلام واحد، وعرض القيمة، وتحريرها في موضعها. لا يوفّر البحث بالتشابه ذلك، لأنك تسترجع البيانات وفق التشابه بدلاً من المفتاح. تتحول عبارة «غيّر منفذ قاعدة البيانات» إلى «اعثر على كل جزء يذكر منفذ قاعدة البيانات»، ولا يمكنك إثبات أنك عثرت عليها كلها. اجعل الحقائق مرتبطة بمفاتيح. ويمكنك أيضاً إنشاء embeddings لها إذا أردت استرجاعاً مرناً، لكن تعامل مع النسخة المرتبطة بالمفتاح على أنها مصدر الحقيقة.
لا تعلن الحقائق القديمة عن نفسها. قد يتغير المنفذ ويبقى الصف كما هو، فيواصل الوكيل تقديم رقم كان صحيحاً في يونيو. سياسة تقادم الذاكرة وحذفها للوكيل هي النصف الآخر من هذه الصفحة، ويكون تصميمها أقل تكلفة بكثير عندما يظل الجدول صغيراً.
لماذا تنمو الذاكرة العرضية بلا حدود
الذاكرة العرضية سجل، والسجلات تنمو. كل جلسة، وكل استدعاء لأداة، وكل أمر فاشل مرشح لأن يصبح حلقة. الوكيل الذي يكتب صفاً واحداً لكل دورة سيكتب خلال شهر صفوفاً أكثر بكثير مما سيقرأه أي شخص، والقرص ليس التكلفة الوحيدة: فكل حلقة مضمّنة تنضم أيضاً إلى الفهرس الذي يجب على البحث اجتيازه.
حدّد قاعدة الاحتفاظ في اليوم الذي تنشئ فيه الجدول، ما دام الحذف لا يزال مجانياً. يجيب سؤالان عن معظم الحالات. أولاً، ما الذي يستحق الكتابة أصلاً: ملخص الجلسة يستحق ذلك عادةً، أما الناتج الكامل لعملية ls -la واحدة فلا يستحقه عادةً. ثانياً، ما مدة احتفاظك بكل فئة من الحلقات: مثلاً، الاحتفاظ بالحلَق الخام لمدة 30 يوماً، وملخصات الجلسات لمدة عام.
أضف إلى كل صف حلقة طابعاً زمنياً created_at وعموداً source. من دون created_at لا يمكنك الحذف حسب العمر. ومن دون source لا يمكنك حذف كل ما وصل من مصدر سيئ واحد، وهذا ما تحتاج إليه تحديداً في اليوم الذي يتضح فيه أن صفحة ويب أو تذكرة ما كانت تكتب تعليمات داخل الذاكرة.
DELETE FROM episodes WHERE created_at < now() - interval '30 days';شغّل ذلك من مؤقت systemd، ثم راقب تغيّر عدد الصفوف وحجم الجدول فعلياً بعد ذلك. سياسة الاحتفاظ التي لا ينفذها أحد ليست سوى تعليق.
psql -d agentmem -c "SELECT count(*) FROM episodes;"
psql -d agentmem -c "SELECT pg_size_pretty(pg_total_relation_size('episodes'));"تُحفَظ الذاكرة الإجرائية في مستودع
الذاكرة الإجرائية هي طريقة تنفيذ الوكيل لمهمة ما: shell script، أو skill file، أو runbook يتضمن خطوات مرقمة. هذا محتوى برمجي، والمحتوى البرمجي مكانه حيث يُحفَظ الكود: في مستودع git يتضمن المراجعة والإصدارات وdiff يمكنك قراءته.
عند تخزين runbook على شكل مقاطع مضمّنة، تحصل على نسخة تقريبية منه. يعرض الاسترجاع المقاطع التي حققت أعلى نتيجة، لذلك قد ينفذ الوكيل الخطوة 2 والخطوة 5 بينما لا تظهر الخطوة 3 إطلاقاً، ولا يُسجَّل أي إصدار من الإجراء نُفِّذ. في git، يجيب git log عن السؤالين معاً. وتكلفة التخزين تقترب من الصفر، وهذا سبب آخر لعدم دفع تكلفة vector من أجله.
ما التكلفة الفعلية لـ embedding على القرص
The data behind this chart
[
{
"label": "bge-small-en-v1.5 (384 dims)",
"bytes_per_vector": 1544,
"mib_per_100k_rows": 147.2
},
{
"label": "bge-base-en-v1.5 (768 dims)",
"bytes_per_vector": 3080,
"mib_per_100k_rows": 293.7
},
{
"label": "bge-large-en-v1.5 (1024 dims)",
"bytes_per_vector": 4104,
"mib_per_100k_rows": 391.4
},
{
"label": "text-embedding-3-small (1536 dims)",
"bytes_per_vector": 6152,
"mib_per_100k_rows": 586.7
},
{
"label": "text-embedding-3-large (3072 dims)",
"bytes_per_vector": 12296,
"mib_per_100k_rows": 1172.6
}
]يخزّن pgvector قيمة vector بمقدار 4 bytes لكل بُعد، إضافة إلى ترويسة بحجم 8 bytes. لذلك تكون هذه العملية الحسابية ثابتة، ويمكنك التخطيط لها قبل تحميل أي شيء. يبلغ حجم متجه واحد ذي 384 بُعداً 1544 bytes، ولذلك يشغل 100,000 مقطع 147.2 MiB من المتجهات. أما corpus نفسه عند تضمينه باستخدام 3,072 بُعداً، فيشغل 1172.6 MiB، بمقدار 12296 bytes لكل صف. النص نفسه، لكن مساحة التخزين تقترب من ثمانية أضعاف.
هذا يخص عمود المتجهات وحده. يضاف إليه نص المقطع، والمفتاح الأساسي، والحمولة الزائدة للصف، والفهرس. والفهرس هو الجزء الذي ينساه كثيرون. يحتفظ HNSW (وهو اختصار لـ hierarchical navigable small world، وفهرس الرسم البياني الذي ينشئه pgvector) بنسخته الخاصة من المتجهات التي يربط بينها، ولذلك يتجاوز مخزن مفهرس الأرقام السابقة بأكثر من الضعف بهامش مريح. قِس استهلاكك الفعلي بدلاً من التخمين.
SELECT pg_size_pretty(pg_total_relation_size('memories')) AS total,
pg_size_pretty(pg_relation_size('memories_embedding_idx')) AS idx;تحدد RAM ما إذا كان البحث سيبدو سريعاً، لأن اجتياز الرسم البياني يكون سريعاً فقط ما دام موجوداً في الذاكرة. عندما يكون الفهرس أكبر من سعة الذاكرة التي يستطيع Postgres الاحتفاظ بها، تبدأ عمليات البحث بقراءة البيانات من القرص، ويرتفع زمن الاستجابة. ولعملية الإنشاء حد خاص بها، وهو maintenance_work_mem. وعندما يتجاوزه الرسم البياني، تعرض عملية الإنشاء ذلك وتتباطأ:
NOTICE: hnsw graph no longer fits into maintenance_work_mem after 61440 tuples
HINT: Increase maintenance_work_mem to speed up builds.هناك آليتان لتقليص حجم corpus نفسه. اختر model أصغر، لأن 384 بُعداً تكلّف ربع تكلفة 1,536 بُعداً، ولأن فرق الدقة غالباً ما يكون صغيراً بما يكفي لقبوله عند البحث عن ملاحظاتك مجدداً. أو خزّن البيانات بدقة نصفية: يأخذ النوع halfvec مقدار 2 bytes لكل بُعد، إضافة إلى الترويسة نفسها بحجم 8 bytes، ما يقارب خفض حجم العمود وفهرسه معاً إلى النصف.
هناك حد يجدر بك معرفته قبل اختيار model. اعتباراً من August 2026، يمكن فهرسة عمود vector حتى 2,000 بُعد، ولذلك يقبل العمود embedding ذي 3,072 بُعداً، بينما يرفضه الفهرس:
ERROR: column cannot have more than 2000 dimensions for hnsw indexيفهرس halfvec حتى 4,000 بُعد، ولذلك يكون الإصلاح المعتاد هو فهرسة القيمة بعد تحويل نوعها:
CREATE INDEX ON memories USING hnsw ((embedding::halfvec(3072)) halfvec_cosine_ops);عندما يتفوق pgvector على خدمة ذاكرة منفصلة
إذا كان الخادم يشغّل Postgres بالفعل، فستكون المتجهات ضمن حزمة واحدة وباستخدام عبارة واحدة.
psql -V
sudo apt install postgresql-16-pgvectorCREATE EXTENSION vector;يمثل الرقم في اسم الحزمة الإصدار الرئيسي من Postgres. وهو 16 في Ubuntu 24.04، لذلك اقرأ psql -V قبل كتابته.
يتيح الاحتفاظ بالذاكرة في قاعدة البيانات نفسها إجراء نسخة احتياطية واحدة تشمل الذاكرة وبيانات التطبيق في اللحظة نفسها، واستخدام تجمع اتصالات واحد، والاستفادة من المعاملات: إما أن تُثبَّت المعلومة والصف الذي يصفها معاً، أو يفشلا معاً. ولا تستطيع خدمة منفصلة ضمان ذلك.
انتقل إلى خدمة ذاكرة مخصصة عندما ينطبق أحد الشروط التالية. يتنافس حمل البحث مع تطبيقك ويحتاج إلى جهاز خاص به. أو تتشارك عدة وكلاء على عدة مضيفين ذاكرة واحدة. أو تريد منطق الاستخراج وإزالة التكرارات الذي يأتي مع منتج مكتمل، وهذا هو سبب اختيار خادم Mem0 مستضاف ذاتياً للذاكرة. بالنسبة إلى وكيل واحد ومجموعة نصوص تضم بضعة ملايين منخفضة من المقاطع، يكون pgvector على الخادم الذي تشغّله بالفعل أسهل في التشغيل وأقل عرضة للتعطل. يغطي تشغيل قاعدة بيانات متجهات على VPS اختيار المحرك نفسه ومتطلبات كل محرك من RAM.
تكلفة إعادة التضمين عند تغيير النموذج
لا يمكن مقارنة المتجهات الناتجة عن نموذجين مختلفين، لذلك لا يمكنك تضمين الذكريات الجديدة باستخدام نموذج جديد وترك الصفوف القديمة كما هي. يعيد الجدول المختلط نتائج بلا معنى، لأن المسافة المحسوبة بين نظامي إحداثيات مختلفين لا تحمل معنى. يعني تغيير النموذج إعادة تضمين كامل مجموعة البيانات.
تتكون هذه التكلفة من أربعة أجزاء: الرموز، وهي تكلفة API أو وقت المعالج CPU ووحدة معالجة الرسومات GPU على جهازك؛ والوقت الفعلي اللازم للتنفيذ؛ ومساحة القرص اللازمة لتخزين العمودين معاً أثناء الملء الخلفي؛ وإعادة بناء الفهرس في النهاية. الترتيب الآمن هو إضافة عمود جديد، وملؤه على دفعات، وتبديل الاستعلامات لاستخدامه، ثم حذف العمود القديم وفهرسه.
قِس معدل التنفيذ على أجهزتك بدلاً من الاعتماد على رقم منشور، لأن تنفيذ التضمين باستخدام CPU فقط على VPS صغير أبطأ بكثير من تنفيذ النموذج نفسه على GPU. احسب زمن معالجة جزء ممثل، ثم اضربه في حجم مجموعة البيانات.
ollama pull nomic-embed-text
time curl -s http://localhost:11434/api/embed \
-d '{"model": "nomic-embed-text", "input": "one representative chunk of your corpus"}' > /dev/nullيضع نموذج التضمين المحلي أوزانه أيضاً على القرص نفسه الذي يخزن مستودع الذاكرة، وتشرح أماكن تخزين Ollama للنماذج التي تم تنزيلها أين تذهب هذه المساحة.
هناك متطلب واحد يجعل كل ذلك ممكناً: احتفظ بالنص المصدر بجوار كل متجه. لا يمكن إعادة تضمين مستودع لا يحتوي إلا على المتجهات، لأنه لم يعد هناك نص لإرساله إلى النموذج الجديد. إذا لم تتمكن من الإجابة عن السؤال "ما النص الذي أنتج هذا الصف؟"، فمسار الترحيل لديك هو إعادة بناء كاملة انطلاقاً من المصدر الذي أتى منه النص أولاً.
ما يجب مراقبته بعد تشغيل الذاكرة
لا تتغير التكلفة وحدها مع امتلاء مخزن الذاكرة. تصبح الحقائق القديمة غير محدثة، وتزاحم الأحداث القديمة النتائج المفيدة. وهذه هي مشكلة التشذيب مجدداً. مخزن الذاكرة أيضاً مدخل قابل للكتابة إلى سلوك الوكيل مستقبلاً، لذلك يمكن لأي شيء يُسمح له بالكتابة فيه أن يوجّه الوكيل لاحقاً. إذا وصلت نصوص من صفحات الويب أو التذاكر إلى الذاكرة، فاقرأ كيفية عمل تسميم ذاكرة الوكيل قبل توسيع نطاق المصادر المسموح لها بالكتابة. كما يؤثر حجم الاسترجاع في استهلاك الرموز المميزة مع كل طلب، ومن هنا تبدأ الحفاظ على إمكانية التنبؤ بالتكلفة التشغيلية للوكيل.
FAQ
هل أحتاج إلى قاعدة بيانات متجهات لذاكرة الوكيل؟
ليس لتخزين الحقائق. الذاكرة الدلالية صغيرة، وتحتاج إلى تصحيحها بالاسم، لذلك يخدمها جدول مفهرس بالمفاتيح أو مجلد لملفات markdown في git بشكل أفضل، لأنك تستطيع رؤية قيمة واحدة وتعديلها. تصبح embeddings مجدية عندما تحتاج إلى الاسترجاع حسب المعنى من مجموعة نصوص أكبر من أن تُدرج عناصرها، وهذا ينطبق عادةً على الذاكرة العرضية والمستندات. إذا كنت تشغّل Postgres مسبقاً، فإن CREATE EXTENSION vector يغطي هذه الحاجة من دون إضافة خدمة أخرى لإدارتها.
ما مقدار مساحة القرص التي سيستخدمها مخزن ذاكرة الوكيل؟
حجم المتجهات متوقع، إذ يبلغ 4 بايت لكل بُعد، إضافة إلى ترويسة بحجم 8 بايت في pgvector. عند استخدام 768 بُعداً، يبلغ ذلك 293.7 MiB لكل 100,000 صف، وعند استخدام 384 بُعداً يبلغ 147.2 MiB. أضف إلى ذلك نص المقطع، والنفقات الإضافية للصف، وفهرس HNSW الذي يحتفظ بنسخته الخاصة من المتجهات. لذلك خصص على الأقل ضعف حجم المتجهات، وقِس الحجم الفعلي باستخدام pg_total_relation_size.
أين يجب أن توجد الذاكرة الإجرائية؟
في مستودع git، على شكل scripts أو ملفات مهارات يشغّلها الوكيل مباشرة. تحتاج الإجراءات إلى استرجاع دقيق وإلى سجل للإصدارات، ولا يوفّر لك البحث بالتشابه أياً منهما. يعود runbook مقسّماً إلى مقاطع مع الأجزاء الأعلى تقييماً، وقد يعني ذلك وصول الخطوتين 2 و5 بينما تكون الخطوة 3 مفقودة، من دون تسجيل للإصدار الذي جرى تشغيله.
ما تكلفة تغيير نموذج embedding؟
إعادة embedding كاملة لمجموعة النصوص، لأن المتجهات الناتجة عن نماذج مختلفة لا يمكن مقارنتها بعضها ببعض. احسب تكلفة الرموز أو وقت GPU، ومساحة القرص اللازمة للعمودين القديم والجديد في الوقت نفسه، وإعادة بناء الفهرس. أضف العمود الجديد، واملأه على دفعات، وبدّل الاستعلام لاستخدامه، ثم احذف العمود القديم. يعتمد كل ذلك على الاحتفاظ بالنص المصدر بجانب كل متجه.