* feat(docs): mirror every docs/ page in all 65 locales Extends the documentation mirrors from the 22-page core set (#13940) to every Markdown page under docs/: 152 sources x 65 locales = 9,880 mirrors (6,208 new), language bars rewritten for the full locale list, state adopted so the blocking drift gate now covers all 152 pages. run-translation.mjs: an oversized block made only of table rows or list items (PROVIDER_REFERENCE.md 244-row table, FREE_TIERS.md 71-item list) is cut at item boundaries and rejoined without a blank line — the single 16-40 KB request outlived the backend socket for verbose scripts. 48 older mirrors whose tables had lost rows were retranslated with --force. * docs(i18n): refresh mirrors for the sources the base changed since the branch cut Section-level retranslation of the 29 docs (and README.md) whose source or mirrors moved on release/v3.8.51 during the run, then state adoption; the drift gate is green again on the merged tree.
88 KiB
Memory System (فارسی)
🌐 Languages: 🇺🇸 English · 🇪🇹 am · 🇸🇦 ar · 🇦🇿 az · 🇧🇬 bg · 🇧🇩 bn · 🇨🇿 cs · 🇩🇰 da · 🇩🇪 de · 🇬🇷 el · 🇪🇸 es · 🇪🇪 et · 🇫🇮 fi · 🇫🇷 fr · 🇮🇪 ga · 🇮🇳 gu · 🇳🇬 ha · 🇮🇱 he · 🇮🇳 hi · 🇭🇷 hr · 🇭🇺 hu · 🇦🇲 hy · 🇮🇩 id · 🇳🇬 ig · 🇮🇹 it · 🇯🇵 ja · 🇬🇪 ka · 🇰🇭 km · 🇮🇳 kn · 🇰🇷 ko · 🇱🇹 lt · 🇱🇻 lv · 🇮🇳 ml · 🇮🇳 mr · 🇲🇾 ms · 🇲🇹 mt · 🇲🇲 my · 🇳🇵 ne · 🇳🇱 nl · 🇳🇴 no · 🇮🇳 or · 🇮🇳 pa · 🇵🇭 phi · 🇵🇱 pl · 🇵🇹 pt · 🇧🇷 pt-BR · 🇷🇴 ro · 🇷🇺 ru · 🇱🇰 si · 🇸🇰 sk · 🇸🇮 sl · 🇷🇸 sr · 🇸🇪 sv · 🇰🇪 sw · 🇮🇳 ta · 🇮🇳 te · 🇹🇭 th · 🇹🇷 tr · 🇺🇦 uk-UA · 🇵🇰 ur · 🇺🇿 uz · 🇻🇳 vi · 🇳🇬 yo · 🇨🇳 zh-CN · 🇹🇼 zh-TW
منبع حقیقت:
src/lib/memory/وsrc/app/api/memory/آخرین بهروزرسانی: 2026-06-28 — v3.8.40 (غیرفعال بهصورت پیشفرض + تکمیل کوانتیزهسازی int8)
OmniRoute حافظهٔ مکالمهای پایداری را ارائه میدهد که بر اساس کلید API (و در صورت تمایل، شناسهٔ نشست) کلیدگذاری میشود. حافظهها بهطور خودکار از پاسخهای LLM از طریق تطبیق سبکوزن الگوهای regex استخراج شده و بهعنوان یک پیام system ابتدایی (یا برای ارائهدهندگانی که نقش system را رد میکنند، بهعنوان نخستین پیام user) دوباره به درخواستهای بعدی تزریق میشوند.
حافظه بهصورت پیشفرض خاموش است (v3.8.30+). مقدار
DEFAULT_MEMORY_SETTINGS.enabledاکنونfalseاست (src/lib/memory/settings.ts). فعالکردن حافظه تا سقفmaxTokens(~2k) از زمینهٔ بازیابیشده را به هر درخواست چت تزریق میکند که هزینهٔ آن محاسبه میشود — هزینهای غیرمنتظره برای نصبهای جدید و کلاینتهایی که زمینهٔ خود را مدیریت میکنند. آن را بهطور صریح از مسیر Settings → Memory فعال کنید (MemorySkillsTabهنگام فعالبودن حافظه، هشدار هزینهٔ توکن را نمایش میدهد). یک کلاینت میتواند با هدر درخواستx-omniroute-no-memory(true/1/yes) حافظه را برای یک درخواست منفرد غیرفعال کند — جدول هدرهای درخواست را در API_REFERENCE.md ببینید. یک درخواست بدون حافظه،memoryOwnerId = nullرا تنظیم میکند که هم تزریق حافظه و هم تزریق مهارت را برای آن درخواست غیرفعال میکند (open-sse/handlers/chatCore/headers.ts::isNoMemoryRequested).
دامنهٔ حافظه بهازای هر کلید API است، نه بهازای هر کاربر — هر درخواستی که
با همان کلید API احراز هویت شود، از مخزن حافظهٔ یکسانی استفاده میکند و در صورت
تمایل میتوان دامنه را بیشتر با sessionId محدود کرد.
معماری
کلاینت → /v1/chat/completions (apiKeyInfo در بالادست تعیین میشود)
→ handleChatCore() [open-sse/handlers/chatCore.ts]
→ resolveMemoryOwnerId(apiKeyInfo) # شناسه را استخراج میکند
→ getMemorySettings() # تنظیمات ذخیرهشده در کش
→ shouldInjectMemory(body, {enabled}) # دروازه
→ retrieveMemories(apiKeyId, config) # SQL + FTS5 + بردار اختیاری
→ injectMemory(body, memories, provider) # پیام system یا user
→ فراخوانی ارائهدهندهٔ بالادست
→ هنگام پاسخ: extractFacts(text, apiKeyId, sessionId) # غیرمسدودکننده
→ setImmediate → createMemory(fact) برای هر تطابق
→ embed(content) + upsertVector(id, vec)
نقاط فراخوانی تزریق و استخراج در
open-sse/handlers/chatCore.ts متصل شدهاند (بهدنبال retrieveMemories، injectMemory
و extractFacts بگردید).
معماری موتور (تفکیک سهلایهای)
موتور حافظه، مسیر بازیابی را هنگام اجرا بر اساس زیرساخت و تنظیمات موجود تعیین میکند. سه لایه وجود دارد که بهترتیب اولویت اعمال میشوند:
┌─────────────────────────────────────────────────────────────┐
│ لایهٔ ۰ — کلیدواژه (FTS5) │
│ دسترسپذیری مبتنی بر کاوش: هنگامی که بیلد SQLite از آن │
│ پشتیبانی کند، FTS5 در دسترس است │
│ (better-sqlite3 / node:sqlite / bun:sqlite)؛ در بیلدهای فاقد │
│ FTS5 در دسترس نیست (برای مثال sql.js/WASM — │
│ "no such module: fts5"). وقتی strategy = "exact" باشد یا │
│ بهعنوان مسیر جایگزین استفاده میشود؛ وضعیت keyword در │
│ engine-status نتیجهٔ کاوش را منعکس میکند. │
└──────────────────────────────────┬──────────────────────────┘
│ strategy = semantic|hybrid؟
▼
┌─────────────────────────────────────────────────────────────┐
│ لایهٔ ۱ — بردار تعبیهشده (sqlite-vec) │
│ sqlite-vec v0.1.9 از طریق db.loadExtension() بارگذاری میشود.│
│ جستوجوی KNN به روش نیروی کامل روی بردارهای Float32. فعال │
│ است هنگامی که: │
│ • بارگذاری sqlite-vec با loadExtension موفق شود │
│ • یک منبع embedding در دسترس باشد (remote | static | │
│ transformers) که بتواند یک Float32Array تولید کند │
│ • جدول vec_memories وجود داشته باشد (در نخستین ready() │
│ ایجاد میشود) │
└──────────────────────────────────┬──────────────────────────┘
│ qdrant.enabled؟
▼
┌─────────────────────────────────────────────────────────────┐
│ لایهٔ ۲ — Qdrant (پایگاه دادهٔ برداری خارجی با فعالسازی │
│ اختیاری) │
│ در صورت فعالبودن، برای semantic/hybrid جایگزین sqlite-vec │
│ میشود. به نمونهٔ در حال اجرای Qdrant و host/port │
│ پیکربندیشده نیاز دارد. │
└─────────────────────────────────────────────────────────────┘
تنزل سطح بهصورت خودکار و شفاف انجام میشود:
- اگر sqlite-vec بارگذاری نشود، لایهٔ ۱ در دسترس نخواهد بود ← به لایهٔ ۰ بازمیگردد.
- اگر منبع embedding خطایی برگرداند، لایهٔ ۱ به لایهٔ ۰ بازمیگردد.
- اگر Qdrant سالم نباشد، لایهٔ ۲ به لایهٔ ۱ بازمیگردد (یا اگر لایهٔ ۱ نیز در دسترس نباشد، به لایهٔ ۰).
منابع تعبیهسازی
لایهٔ تعبیهسازی (src/lib/memory/embedding/) بر اساس
MemorySettingsExtended.embeddingSource مشخص میکند که از کدام منبع استفاده شود:
| منبع | توضیحات | نیازمند کلید | شروع سرد |
|---|---|---|---|
remote |
از API تعبیهسازی ارائهدهندهٔ پیکربندیشده استفاده میکند (OpenAI، Cohere و غیره) | بله | ندارد |
static |
تعبیهسازی محلی مبتنی بر جدول جستوجو از طریق potion-base-8M (WordPiece + میانگینگیری تجمیعی) |
خیر | ~200ms |
transformers |
استنتاج محلی ONNX از طریق @huggingface/transformers v4، all-MiniLM-L6-v2 |
خیر | ~3s + ~400MB RAM |
auto |
انتخاب در زمان اجرا: remote (اگر کلید وجود داشته باشد) → static → transformers → null | بستگی دارد | بستگی دارد |
ترتیب انتخاب برای auto:
- نخستین ارائهدهنده را در
listEmbeddingProviders()کهhasKey === trueدارد پیدا کن →remote. - اگر
settings.staticEnabled === trueباشد →static. - اگر
settings.transformersEnabled === trueباشد →transformers. - در غیر این صورت →
null(به جستوجوی کلیدواژهای FTS5 تنزل مییابد).
کش تعبیهسازی (src/lib/memory/embedding/cache.ts) از یک نگاشت LRU درونحافظهای
با کلید ${source}:${model}:${dim}:${sha256(text)} استفاده میکند که حداکثر
MEMORY_EMBEDDING_CACHE_MAX ورودی (مقدار پیشفرض 1000) و TTL برابر با
MEMORY_EMBEDDING_CACHE_TTL_MS (مقدار پیشفرض 5 دقیقه) دارد. این کش در طول
چرخهٔ حیات هر فرایند میان همهٔ فراخوانها مشترک است.
RRF ترکیبی (k=60)
هنگامی که strategy = "hybrid" باشد و مخزن برداری در دسترس باشد، بازیابی از
ترکیب رتبهٔ متقابل برای ادغام نتایج FTS5 و برداری استفاده میکند:
RRF(d) = Σ 1 / (k + rank_i(d)) که در آن k = 60 است (قابل پیکربندی از طریق MEMORY_RRF_K)
i
بهطور مشخص:
- جستوجوی FTS5 را اجرا کن → فهرست رتبهبندیشدهٔ
R_fts(موقعیت 1..N). - جستوجوی برداری KNN را اجرا کن → فهرست رتبهبندیشدهٔ
R_vec(موقعیت 1..M). - برای هر
memoryIdیکتا:
rrf_score = 1/(60 + fts_rank)+1/(60 + vec_rank)(اگر در فهرست نباشد، 0). - بر اساس
rrf_scoreبهصورت نزولی مرتب کن و پیمایش بودجهٔ توکن را اعمال کن.
RRF بهعنوان روشی مؤثر شناخته شده است که به نرمالسازی امتیاز میان سامانههای
بازیابی ناهمگون نیازی ندارد. مقدار پیشفرض k=60 از مقالهٔ اصلی Cormack و
همکاران گرفته شده و برای مجموعههای کوچک (<10k حافظه) عملکرد خوبی دارد.
بازپُرکنی (تنبل + نمایهسازی مجدد)
هنگامی که مدل تعبیهسازی تغییر کند (که از طریق embedding_signature تشخیص
داده میشود)، مخزن برداری از نو ساخته شده و همهٔ حافظههای موجود در جدول
memories با needs_reindex = 1 علامتگذاری میشوند.
بازپُرکنی تنبل: در بازیابی بعدی، هر حافظهای که ورودی برداری نداشته باشد،
پیش از اجرای جستوجو تعبیه شده و در vec_memories درج میشود. این کار هزینهٔ
بازپُرکنی را بدون مسدود کردن راهاندازی، میان درخواستهای واقعی مستهلک میکند.
نمایهسازی مجدد صریح: زبانهٔ Engine در /dashboard/memory دکمهای با عنوان
"اکنون دوباره نمایهسازی کن" ارائه میدهد که POST /api/memory/reindex را فراخوانی میکند. گرداننده،
runReindexBatch() را از src/lib/memory/reindex.ts فراخوانی میکند که در هر
درخواست حداکثر limit ورودی معلق را پردازش میکند. پیشرفت را میتوان از طریق
GET /api/memory/engine-status (vectorStore.needsReindex) بهصورت دورهای بررسی کرد.
جدول memory_vec_meta (مهاجرت 083_memory_vec.sql) موارد زیر را ذخیره میکند:
active_dim— بُعد برداری فعلی (null = هنوز کالیبره نشده است).embedding_signature—${source}:${model}:${dim}که برای تشخیص تغییرات استفاده میشود.last_reset_at— مُهر زمانی آخرین بازنشانی کامل.vec_loaded— پرچم 0/1 که نشان میدهد آیا sqlite-vec با موفقیت بارگذاری شده است یا خیر.
افزونهٔ تنظیمات
نه فیلد مربوط به تعبیهسازی و بردار در MemorySettingsExtended واقع در
src/shared/schemas/memory.ts موجود است و از طریق src/lib/db/settings.ts ماندگار میشوند:
| فیلد | نوع | پیشفرض | توضیحات |
|---|---|---|---|
embeddingSource |
"remote" | "static" | "transformers" | "auto" |
"auto" |
منبع تعبیهسازی مورد استفاده |
embeddingProviderModel |
string | null |
null |
ارائهدهنده/مدل با قالب provider/model |
customBaseUrl |
string | null |
null |
URL پایهٔ نقطهٔ پایانی سازگار با OpenAI فقط برای حافظه |
customModelId |
string | null |
null |
شناسهٔ مدل ارسالشده به نقطهٔ پایانی سفارشی |
transformersEnabled |
boolean |
false |
فعالسازی اختیاری Transformers.js (MiniLM، حدود 400MB) |
staticEnabled |
boolean |
false |
فعالسازی اختیاری مدل محلی ایستای potion-base-8M |
rerankEnabled |
boolean |
false |
فعالسازی مرحلهٔ رتبهبندی مجدد (افزودن +200-500ms/req) |
rerankProviderModel |
string | null |
null |
ارائهدهنده/مدل رتبهبندی مجدد با قالب provider/model |
vectorStore |
"sqlite-vec" | "qdrant" | "auto" |
"auto" |
بکاند برداری مورد استفاده |
این موارد از طریق GET /PUT /api/settings/memory (شِمای MemorySettingsExtendedSchema) در دسترس قرار میگیرند.
برای منبع remote، حافظه همچنین تنظیمات اختیاری customBaseUrl و
customModelId را میپذیرد. این دو در کنار هم، یک نقطهٔ پایانی /embeddings
سازگار با OpenAI و مدل مربوط به آن را بدون تغییر رجیستری سراسری تعبیهسازی انتخاب
میکنند. نقطهٔ پایانی پیش از استفاده نرمالسازی شده و توسط سیاست URL خروجی
ارائهدهنده بررسی میشود: استفاده از HTTP(S) الزامی است، اعتبارنامههای تعبیهشده
و رشتههای پرسوجو رد میشوند و آدرسهای فرادادهٔ ابری همچنان مسدود میمانند.
مقادیر خالی، ارائهدهندهٔ انتخابشده از رجیستری را حفظ میکنند. خطاهای بازگرداندهشده
به داشبورد پاکسازی میشوند و اعتبارنامههای نقطهٔ پایانی هرگز ثبت نمیشوند.
TODO (D20): دامنهٔ
global(اشتراکگذاری حافظهها میان همهٔ کلیدهای API) در این نسخه پیادهسازی نشده است. این قابلیت به تغییرات شِما و یک مسیر بازیابی سراسری نیاز دارد. آن را جداگانه پیگیری کنید.
لایههای ذخیرهسازی
اصلی: SQLite (جدول memories)
توسط مهاجرت 015_create_memories.sql ایجاد میشود:
| ستون | نوع | نکات |
|---|---|---|
id |
TEXT PRIMARY KEY |
UUID تولیدشده از طریق crypto.randomUUID() |
api_key_id |
TEXT NOT NULL |
کلید API مالک |
session_id |
TEXT |
دامنهٔ اختیاری برای هر مکالمه |
type |
TEXT NOT NULL |
یکی از factual، episodic، procedural، semantic |
key |
TEXT |
کلید پایدار درج یا بهروزرسانی، برای مثال preference:i_prefer_python |
content |
TEXT NOT NULL |
متن واقعی واقعیت |
metadata |
TEXT |
دادهٔ JSON (category، extractedAt، source، ...) |
created_at / updated_at |
TEXT |
رشتههای ISO 8601 |
expires_at |
TEXT |
انقضای اختیاری؛ NULL بهمعنای دائمی است |
memory_id |
INTEGER UNIQUE |
افزودهشده توسط 023_fix_memory_fts_uuid.sql برای اتصال UUIDها ↔ rowidهای FTS5 |
ایندکسها: api_key_id، session_id، type، expires_at، بهعلاوهٔ ایندکس
یکتای memory_id.
معنای درج یا بهروزرسانی: createMemory() بهدنبال ردیفی موجود با
(api_key_id, key) یکسان میگردد و در صورت یافتن، آن را درجا بهروزرسانی
میکند (metadata با استفاده از انتشار سطحی ادغام میشود). این کار مانع از رشد
نامحدود جدول در اثر تکرار عبارتهای مربوط به ترجیحات میشود.
جستوجوی تماممتن (جدول مجازی memory_fts)
022_add_memory_fts5.sql یک جدول مجازی FTS5 روی content و
key ایجاد میکند. 023_fix_memory_fts_uuid.sql یک باگ واقعی را برطرف میکند
که در آن کلید اصلی UUID به rowid عدد صحیح FTS5 متصل نمیشد — این مهاجرت ستون
memory_id را اضافه میکند، جدول FTS را دوباره میسازد و تریگرهای
(memory_fts_ai، memory_fts_ad، memory_fts_au) را متصل میکند تا FTS را
هنگام INSERT، DELETE و UPDATE همگام نگه دارند.
این قابلیت توسط retrieval.ts برای راهبردهای semantic و hybrid استفاده
میشود (پایین را ببینید). کد بازیابی با hasTable("memory_fts") محافظت میشود
و اگر جدول FTS موجود نباشد یا پرسوجوی FTS خطا ایجاد کند، به ترتیب زمانی
بازمیگردد.
اختیاری: Qdrant (سطح ۲ ذخیرهگاه برداری)
src/lib/memory/qdrant.ts یک یکپارچهسازی اختیاری Qdrant را بهعنوان سطح ۲
ذخیرهگاه برداری پیادهسازی میکند. بازیابی فقط زمانی به Qdrant هدایت میشود که
انتخابگر موتور memoryVectorStore === "qdrant" باشد — مقدار پیشفرض "auto"
(و "sqlite-vec") هرگز Qdrant را انتخاب نمیکند. کلید تغییر وضعیت زبانهٔ
Engine، هر دو گزینهٔ qdrantEnabled و memoryVectorStore را با هم تنظیم
میکند: فعالسازی، Qdrant را به ذخیرهگاه اصلی تبدیل میکند و غیرفعالسازی آن را
به "auto" بازنشانی میکند (#5597 — پیش از این اصلاح، فعالسازی بیاثر بود،
زیرا هیچچیز انتخابگر موتور را مقداردهی نمیکرد). اگر Qdrant قابل دسترسی نباشد
یا چیزی برنگرداند، بازیابی به sqlite-vec → FTS5 بازمیگردد.
upsertSemanticMemoryPoint()— با استفاده از مدل embedding پیکربندیشده،key + contentرا embed میکند، از وجود collection اطمینان حاصل میکند (در نخستین استفاده، بردارهایی با فاصلهٔ کسینوسی ایجاد میکند) و یک point را با payload برابر با{memoryId, apiKeyId, sessionId, key, content, metadata, createdAtUnix, expiresAtUnix}درج یا بهروزرسانی میکند.searchSemanticMemory(query, topK, scope)— query را embed میکند و collection را با فیلترkind = "omniroute_memory"و در صورت نیاز بر اساسapiKeyId/sessionIdجستوجو میکند. مقدارtopKرا به بازهٔ[1, 20]محدود میکند.deleteSemanticMemoryPoint(id)— یک point را حذف میکند. پس از حذف ردیف SQLite توسطdeleteMemory()فراخوانی میشود (D15).cleanupSemanticMemoryPoints({retentionDays})— pointهایی را کهexpiresAtUnixآنها در گذشته است یاcreatedAtUnixآنها از حد آستانهٔ نگهداری قدیمیتر است، بهصورت گروهی حذف میکند. ابتدا آنها را میشمارد تا داشبورد بتواند اعداد واقعی را نمایش دهد.checkQdrantHealth()— بررسی سلامت باGET /readyzهمراه با سنجش تأخیر.
رابط کاربری تنظیمات، پیکربندی Qdrant، بررسی سلامت، آزمایش جستوجوی معنایی و پاکسازی را در زبانهٔ Engine در /dashboard/memory ارائه میکند. از v3.8.6، تمام routeهای متناظر در src/app/api/settings/qdrant/ متصل و فعال هستند:
| Route | متد | توضیحات |
|---|---|---|
/api/settings/qdrant |
GET / PUT |
خواندن / بهروزرسانی تنظیمات Qdrant |
/api/settings/qdrant/health |
GET |
بررسی زندهبودن + تأخیر |
/api/settings/qdrant/search |
POST |
آزمایش جستوجوی معنایی |
/api/settings/qdrant/cleanup |
POST |
حذف pointهای منقضی / قدیمی |
/api/settings/qdrant/embedding-models |
GET |
فهرست مدلهای embedding موجود |
نکات رفتاری (آنچه باید انتظار داشته باشید):
- انتخاب Engine — فعالکردن Qdrant در زبانهٔ Engine، آن را به store اصلی تبدیل میکند (
memoryVectorStore="qdrant"را تنظیم میکند)؛ غیرفعالکردن آن، مقدار را به"auto"بازنشانی میکند (#5597). - بدون back-fill — فقط memoryهایی که پس از فعالشدن Qdrant ایجاد/بهروزرسانی میشوند، در آن نوشته خواهند شد (dual-write از نوع fire-and-forget). memoryهای ازپیشموجود SQLite مهاجرت نمیشوند؛ «Reindex Now» فقط index مربوط به sqlite-vec را بازسازی میکند، نه Qdrant را.
- بُعد بردار بهصورت خودکار تشخیص داده میشود و در نخستین استفاده از embedding واقعی به دست میآید — هیچ فیلدی برای واردکردن بُعد وجود ندارد. تغییر مدل embedding پس از ایجاد یک collection بهطور خودکار مدیریت نمیشود: collection موجود بدون تغییر باقی میماند، عملیات نوشتن/جستوجو با بُعد ناسازگار شکست میخورند و سامانه به sqlite-vec بازمیگردد. برای تغییر embedder، collection را دوباره ایجاد کنید (با نامی جدید، یا با حذف آن در Qdrant).
- معیار فاصله — همیشه Cosine است (هنگام ایجاد collection بهصورت hardcoded تنظیم میشود و قابل پیکربندی نیست).
- احراز هویت — فقط API key (بهصورت header با نام
api-keyارسال میشود؛ برای Docker محلی بدون احراز هویت اختیاری است). از JWT/RBAC استفاده نمیشود. - فیلدهای پیکربندی — رابط کاربری
host،port،collection،embeddingModelوapiKeyرا ارائه میکند.vectorSize/hnswEfConstructفقط از طریق env/DB در دسترساند وvectorSizeبرای ایجاد collection استفاده نمیشود (بُعد از embedding به دست میآید).
کوانتیزهسازی بردار (int8 — اختیاری، در هر دو backend)
هر دو backend برداری از کوانتیزهسازی اختیاری int8 پشتیبانی میکنند تا حجم حافظهٔ بردارهای ذخیرهشده را با هزینهٔ کاهش اندک recall کم کنند (حدود ۴ برابر کوچکتر از Float32). مقدار پیشفرض در هر دو غیرفعال است — مگر اینکه صریحاً فعال شود، بردارها با دقت کامل باقی میمانند.
| Backend | تنظیم | نوع | پیشفرض | محل خواندن |
|---|---|---|---|---|
| Qdrant | qdrantQuantization (کلید DB) |
"none" | "int8" | "binary" |
"none" |
src/lib/memory/qdrant.ts::normalizeQdrantConfig() |
| sqlite-vec | MEMORY_VEC_QUANTIZATION (env) |
"none" | "int8" |
"none" |
src/lib/memory/vectorStore.ts::requestedVecQuantization() |
- Qdrant برای هر instance از طریق کلید تنظیمات
qdrantQuantizationپیکربندی میشود (بهصورت فیلدquantizationدرPUT /api/settings/qdrantارائه شده است). هنگام استفاده از"int8"، تابعbuildQuantizationConfig()کوانتیزهسازی scalar را درخواست میکند (always_ram، با quantile برابر با0.99) و جستوجوهاrescore: trueرا فعال میکنند تا بردارهای با دقت کامل، مجموعهٔ کاندیداهای int8 را پالایش کنند. - کوانتیزهسازی sqlite-vec فقط از طریق environment انجام میشود (نه یک تنظیم DB): مقدار
MEMORY_VEC_QUANTIZATION=int8را تنظیم کنید تا بردارهای محلی از طریقvec_quantize_int8(?, 'unit')بهصورت یک ستونint8[dim]ذخیره شوند. حالت انتخابشده درembedding_signatureگنجانده میشود (با پسوند:int8)، بنابراین تغییر حالت باعث reindex کامل جدولvec_memoriesمیشود — همان مسیر lazy-backfill که هنگام تغییر مدل embedding استفاده میشود.
انواع حافظه
MemoryType (src/lib/memory/types.ts):
| نوع | کاربرد |
|---|---|
factual |
ترجیحات، واقعیتهای پایدار دربارهٔ کاربر، الگوهای رفتاری |
episodic |
تصمیمهای مرتبط با یک لحظهٔ خاص («Postgres را انتخاب کردم») |
procedural |
حافظهٔ گردشکار / چگونگی انجام کار (رزروشده؛ در حال حاضر استخراجگر خودکاری ندارد) |
semantic |
برای ورودیهای مخزن برداری رزرو شده است |
راهبرد بازیابی MemoryConfig یکی از exact، semantic یا hybrid است،
و دامنه یکی از session، apiKey یا global است. دامنهٔ پیشفرض حاصل از
getMemorySettings() برابر با apiKey است.
استخراج واقعیت (extraction.ts)
استخراج مبتنی بر عبارت منظم است، نه مبتنی بر LLM — این فرایند درونپردازشی و با
setImmediate() اجرا میشود؛ بنابراین هرگز جریان پاسخ را مسدود نمیکند:
- الگوهای ترجیح ←
MemoryType.FACTUAL(برای مثالI prefer …،I really like …،my favorite is …،I hate …) - الگوهای تصمیم ←
MemoryType.EPISODIC(برای مثالI'll use …،I chose …،I went with …،I'm going to adopt …) - الگوهای رفتاری ←
MemoryType.FACTUAL(برای مثالI usually …،I always …،I tend to …)
هر تطبیق پاکسازی میشود (trim، فشردهسازی فاصلههای سفید، محدودسازی به 500 نویسه)،
درون دسته با استفاده از factKey(category, content) پایدار رفع تکرار میشود و
از طریق createMemory() با فرادادهٔ
{category, extractedAt, source: "llm_response"} ذخیره میشود. متن ورودی به
64 KiB (MAX_EXTRACTION_TEXT_LENGTH) محدود است — اگر طولانیتر باشد، از انتهای متن
استفاده میشود تا جدیدترین محتوای دستیار همیشه در پردازش مشارکت داشته باشد.
extractFactsFromText(text) برای آزمونها export شده است و واقعیتهای ساختاریافته را
بدون ذخیرهسازی آنها بازمیگرداند.
بازیابی (retrieval.ts)
retrieveMemories(apiKeyId, config) نقطهٔ ورود اصلی است. این تابع:
- پیکربندی را از طریق
MemoryConfigSchemaنرمالسازی و اعتبارسنجی میکند. - هنگامی که
enabledبرابر با false باشد یاmaxTokens <= 0باشد، بلافاصله[]را بازمیگرداند. - مقدار
maxTokensرا به بازهٔ[1, 8000]محدود میکند. - تشخیص میدهد که آیا جدول مدرن
memoriesوجود دارد یا جدول قدیمیmemory، تا پایگاههای دادهٔ قدیمی همچنان کار کنند. - کوئری پایه را با محافظ انقضا
(
expires_at IS NULL OR datetime(expires_at) > datetime('now'))، دامنهٔ اختیاری نشست و آستانهٔ اختیاریretentionDaysمیسازد. - بر اساس راهبرد منشعب میشود:
exact(پیشفرض): ترتیب زمانیORDER BY created_at DESC LIMIT 100.semantic: اگرconfig.queryوmemory_ftsوجود داشته باشند، ازmemory_fts MATCH ?با JOIN استفاده میکند و بر اساس رتبهٔ FTS مرتب میکند؛ اگر FTS تعداد 0 ردیف بازگرداند، به ترتیب زمانی بازمیگردد.hybrid: اجتماع نتایج FTS (با ارتباط بیشتر) و مجموعهٔ زمانی که بر اساس id رفع تکرار شدهاند.
- در صورت ارائهٔ کوئری، امتیاز ارتباط کلیدواژه (
getRelevanceScore) را بر روی JSON مربوط بهcontent،keyوmetadataمحاسبه میکند. ردیفهای دارای امتیاز صفر فیلتر میشوند. - ابتدا بر اساس امتیاز بهصورت نزولی و سپس بر اساس
createdAtبهصورت نزولی مرتب میکند. - فهرست رتبهبندیشده را پیمایش میکند و تا زمانی ورودیها را میپذیرد که مجموع جاری
estimateTokens(content)(تقریباًlength / 4) از بودجه فراتر نرود. در صورت وجود هرگونه تطبیق، همیشه حداقل یک ورودی بازمیگرداند.
estimateTokens export شده است و در بازیابی، خلاصهسازی و ابزار MCP با نام
omniroute_memory_search استفاده میشود.
تزریق (injection.ts)
injectMemory(request, memories, provider):
- تمام محتوای حافظهها را در یک رشتهٔ واحد بهشکل
Memory context: …به هم متصل میکند. - راهبردی را بر اساس نام ارائهدهنده انتخاب میکند:
- پیام سیستمی (پیشفرض برای OpenAI، Anthropic، Gemini و غیره) — یک
{role: "system", content: memoryText}را پیش از تمام پیامهای سیستمی موجود قرار میدهد تا اعلانهای سیستمی کاربر همچنان اولویت داشته باشند. - پیام کاربر (راهکار جایگزین) — برای ارائهدهندگان موجود در
PROVIDERS_WITHOUT_SYSTEM_MESSAGE:o1،o1-mini،o1-preview،glm،glmt،glm-cn،zai،qianfan. این ارائهدهندگان نقش سیستمی را رد میکنند و در غیر این صورت خطای 400 برمیگردانند (رجوع کنید به مسئلهٔ #1701 برای GLM/Zhipu).
- پیام سیستمی (پیشفرض برای OpenAI، Anthropic، Gemini و غیره) — یک
- تعداد، راهبرد و مدل را تحت
memory.injection.injectedثبت میکند.
providerSupportsSystemMessage(provider) برای فراخوانهایی که لازم است
تصمیمهای مسیریابی خود را بگیرند، صادر شده است. ارائهدهندگان ناشناخته برای
اطمینان، بهطور پیشفرض مقدار true دارند (نقش سیستمی مجاز است).
تنظیمات (settings.ts)
پیکربندی حافظه در جدول تنظیمات DB ذخیره میشود، نه در متغیرهای محیطی.
getMemorySettings() دادهها را از getSettings() میخواند و نتیجه را
درون پردازه کش میکند؛ invalidateMemorySettingsCache() پس از عملیات نوشتن،
توسط مسیر PUT تنظیمات فراخوانی میشود.
فیلدهای قدیمی (همهٔ نسخهها)
| کلید DB | نوع | پیشفرض | کنترل UI |
|---|---|---|---|
memoryEnabled |
بولی | false (از v3.8.30 بهطور پیشفرض غیرفعال است) |
روشن/خاموش کردن حافظه |
memoryMaxTokens |
عدد صحیح | 2000 (محدودهٔ 0–16000) |
بودجهٔ توکن برای تزریق |
memoryRetentionDays |
عدد صحیح | 30 (محدودهٔ 1–365) |
بازهٔ نگهداری |
memoryStrategy |
شمارشی | "hybrid" (یکی از recent، semantic، hybrid) |
راهبرد بازیابی |
skillsEnabled |
بولی | false |
تزریق مهارت بهازای هر کلید را تغییر میدهد (رجوع کنید به SKILLS.md) |
نکته: راهبرد "recent" در UI از طریق toMemoryRetrievalConfig() به
راهبرد بازیابی داخلی "exact" نگاشت میشود (ترتیب زمانی).
فیلدهای جدید (v3.8.6، طرح 21 D9)
برای توضیحات فیلدها، بخش «گسترش تنظیمات» در بالا را نیز ببینید.
| کلید DB | فیلد API | پیشفرض |
|---|---|---|
memoryEmbeddingSource |
embeddingSource |
"auto" |
memoryEmbeddingModel |
embeddingProviderModel |
null |
memoryTransformersEnabled |
transformersEnabled |
false |
memoryStaticEnabled |
staticEnabled |
false |
memoryRerankEnabled |
rerankEnabled |
false |
memoryRerankModel |
rerankProviderModel |
null |
memoryVectorStore |
vectorStore |
"auto" |
کلیدهای DB مرتبط با Qdrant (qdrantEnabled، qdrantHost، qdrantPort،
qdrantApiKey، qdrantCollection با مقدار پیشفرض "omniroute_memory"،
qdrantEmbeddingModel با مقدار پیشفرض "openai/text-embedding-3-small") توسط
normalizeQdrantConfig() در qdrant.ts خوانده میشوند.
متغیرهای محیطی (v3.8.6)
شش متغیر محیطی اختیاری، رفتار زمان اجرای موتور را تنظیم میکنند (مستندشده در .env.example):
| متغیر | پیشفرض | توضیحات |
|---|---|---|
MEMORY_EMBEDDING_CACHE_TTL_MS |
300000 |
TTL کش تعبیهسازی (۵ دقیقه) |
MEMORY_EMBEDDING_CACHE_MAX |
1000 |
حداکثر تعداد ورودیها در کش LRU تعبیهسازی |
MEMORY_TRANSFORMERS_MODEL |
Xenova/all-MiniLM-L6-v2 |
مخزن HF برای مدل Transformers.js |
MEMORY_STATIC_MODEL |
minishlab/potion-base-8M |
مخزن HF برای مدل ثابت potion |
MEMORY_STATIC_CACHE_DIR |
<DATA_DIR>/embeddings |
محل ذخیرهٔ مدلهای دانلودشده |
MEMORY_VEC_TOP_K |
20 |
مقدار پیشفرض top-K برای جستوجوی برداری |
MEMORY_RRF_K |
60 |
ثابت k در RRF برای جستوجوی ترکیبی |
MEMORY_VEC_QUANTIZATION |
none |
برای ذخیرهٔ بردارهای محلی sqlite-vec بهصورت کوانتیزه، آن را روی int8 تنظیم کنید (حدود ۴ برابر کوچکتر؛ اختیاری). تغییر حالت، نمایهسازی مجدد را اجباری میکند. |
خلاصهسازی (summarization.ts)
summarizeMemories(apiKeyId, sessionId?, maxTokens = 4000) زمانی محتوای قدیمیتر را فشرده میکند که مجموع جاری توکنهای حافظههای یک کلید از بودجه فراتر رود. این تابع ردیفها را بهترتیب نزولی created_at پیمایش میکند، ردیفهایی را که در بودجه جا میشوند نگه میدارد و برای بقیه، content را در همان محل با سه جملهٔ نخست محتوای اصلی جایگزین میکند. tokensSaved اختلاف مقدار estimateTokens بین محتوای قدیمی و جدید است.
این روال در پایپلاین فعلی چت در دسترس است، اما بهصورت خودکار فراخوانی نمیشود — اگر به فشردهسازی مداوم نیاز دارید، آن را از طریق یک cron، یک اقدام مدیریتی یا کد اتصال MemoryConfig.autoSummarize فراخوانی کنید. از دست رفتن داده یکطرفه است: متن اصلی بازنویسی میشود.
API REST
همهٔ endpointها به احراز هویت مدیریتی (requireManagementAuth) نیاز دارند.
endpointهای اصلی حافظه (موجود + بهروزشده)
| متد | مسیر | توضیحات |
|---|---|---|
GET |
/api/memory |
فهرست صفحهبندیشده با فیلترهای apiKeyId، type، sessionId، q، limit، page و offset. پاسخ شامل stats.total، stats.tokensUsed، stats.hitRate و cacheStats است |
POST |
/api/memory |
ایجاد ورودی (اعتبارسنجیشده با Zod: content، key، و مقادیر اختیاری type، sessionId، apiKeyId، metadata و expiresAt). تابع createMemory() را فراخوانی میکند که بر اساس (apiKeyId, key) عملیات upsert را انجام میدهد |
GET |
/api/memory/[id] |
دریافت یک ورودی منفرد بر اساس UUID |
PUT |
/api/memory/[id] |
بهروزرسانی فیلدهای ورودی (type، key، content و metadata). بدنه: MemoryUpdatePutSchema. در صورت موجود بودن منبع embedding، بردار را نیز همگام میکند. |
DELETE |
/api/memory/[id] |
حذف یک ورودی؛ همچنین آن را از vec_memories (D15) حذف میکند و بهصورت best-effort از Qdrant نیز پاک میکند. در صورت موجود نبودن، 404 برمیگرداند. |
GET |
/api/memory/health |
تابع verifyExtractionPipeline("health-check") را اجرا میکند — چرخهٔ کامل ایجاد←فهرستکردن←حذف. مقدار {working, latencyMs, error?} را برمیگرداند |
endpointهای جدید موتور حافظه (برنامهٔ 21)
| متد | مسیر | توضیحات |
|---|---|---|
POST |
/api/memory/retrieve-preview |
اجرای آزمایشی retrieveMemories — نتایج رتبهبندیشده را همراه با امتیاز، سطح و توکنها برمیگرداند. بدنه: RetrievePreviewSchema. حافظهها را تزریق یا تغییر نمیدهد. |
GET |
/api/memory/embedding-providers |
ارائهدهندگان دارای مدلهای embedding را فهرست میکند و نشان میدهد کدامیک API key پیکربندیشده دارند. |
GET |
/api/memory/engine-status |
وضعیت کامل موتور را برمیگرداند: سطح کلیدواژه، تفکیک embedding، آمار ذخیرهساز برداری، سلامت Qdrant و پیکربندی رتبهبندی مجدد. ساختار: MemoryEngineStatusSchema. |
POST |
/api/memory/summarize |
فشردهسازی حافظه را بهصورت دستی فعال میکند. بدنه: MemorySummarizeSchema (olderThanDays، apiKeyId?، dryRun). مقدار {candidates, tokensSaved} را برمیگرداند. |
POST |
/api/memory/reindex |
بازایندکسگذاری برداری را برای حافظههایی با needs_reindex=1 فعال میکند. بدنه: MemoryReindexSchema (force). مقدار {started, pending} را برمیگرداند. |
endpointهای تنظیمات
| متد | مسیر | توضیحات |
|---|---|---|
GET |
/api/settings/memory |
مقدار نرمالسازیشدهٔ فعلی MemorySettingsExtended (7 فیلد جدید + فیلدهای قدیمی) |
PUT |
/api/settings/memory |
بهروزرسانی هر فیلدی از MemorySettingsExtendedSchema (در مجموع 12 فیلد) |
GET |
/api/settings/qdrant |
تنظیمات فعلی Qdrant (QdrantSettingsSchema) |
PUT |
/api/settings/qdrant |
بهروزرسانی تنظیمات Qdrant. بدنه: QdrantSettingsUpdateSchema. رشتهٔ خالی برای apiKey کلید را حذف میکند. |
GET |
/api/settings/qdrant/health |
اجرای پروب زندهبودن روی نمونهٔ پیکربندیشدهٔ Qdrant. مقدار QdrantHealthResultSchema را برمیگرداند. |
POST |
/api/settings/qdrant/search |
آزمایش جستوجوی معنایی در Qdrant. بدنه: QdrantSearchSchema (query، topK). |
POST |
/api/settings/qdrant/cleanup |
حذف نقاط Qdrant مربوط به حافظههای منقضیشده / قدیمی. |
GET |
/api/settings/qdrant/embedding-models |
فهرستکردن مدلهای embedding موجود برای Qdrant. |
کوئری فهرست /api/memory هم از صفحهبندی مبتنی بر page (parsePaginationParams) و هم از offset خام پشتیبانی میکند — در صورت وجود offset، این مقدار اولویت دارد و یک page مشتقشده برای ساختار پاسخ محاسبه میشود.
ابزارهای MCP (open-sse/mcp-server/tools/memoryTools.ts)
هنگامی که سرور MCP فعال است، سه ابزار حافظه ثبت میشوند:
omniroute_memory_search—{apiKeyId, query?, type?, maxTokens?, limit?}→ تابعretrieveMemories()را پوشش میدهد. از نسخهٔ v3.8.6 (D16)، مقدارstrategyبهجای اینکه بهصورت ثابت روی"exact"تنظیم شود، ازgetMemorySettings()خوانده میشود. اگرqueryارائه شده باشد وstrategyبرابر باsemanticیاhybridباشد، در صورت موجود بودن، از مخزن برداری استفاده میشود.omniroute_memory_add—{apiKeyId, sessionId?, type, key, content, metadata?}→ تابعcreateMemory()را پوشش میدهد. فقط ۴ نوع استاندارد را میپذیرد:factual،episodic،procedural،semantic(D17).omniroute_memory_clear—{apiKeyId, type?, olderThan?}→ ورودیهای منطبق را فهرست میکند، در صورت نیاز آنها را بر اساس برچسب زمانیِ ایجاد پیش از یک زمان مشخص فیلتر میکند و سپس هرکدام را از طریقdeleteMemory()حذف میکند (این تابع بردارها را نیز از sqlite-vec و Qdrant حذف میکند).
برای جزئیات انتقال و دامنه، به MCP-SERVER.md مراجعه کنید.
داشبورد (استودیوی حافظه)
src/app/(dashboard)/dashboard/memory/page.tsx اکنون یک استودیوی ۳زبانه است:
زبانه: حافظهها
- کارت مفهومی (توضیح جمعشوندهٔ «نحوهٔ کار»).
- فهرست، جستوجو و صفحهبندی بلادرنگ (با تأخیر 300 ms).
- فیلتر نوع (
factual/episodic/procedural/semantic/ همه). - پنجرهٔ افزودن حافظه (کلید، محتوا، نوع).
- ویرایش درونخطی (دکمهٔ مداد →
PUT /api/memory/[id]). - حذف هر ردیف (همراه با کادر گفتوگوی تأیید).
- خروجی JSON از صفحهٔ فعلی؛ ورودی JSON از طریق انتخابگر فایل.
- کارتهای آماری:
totalEntries،tokensUsed،hitRate. - دکمهٔ «فشردهسازی موارد قدیمی» →
POST /api/memory/summarize(اجرای آزمایشی ابتدا تعداد نامزدها را نمایش میدهد و سپس تأیید میگیرد). - یک نشانگر سبز/قرمز سلامت که توسط
GET /api/memory/healthکنترل میشود.
زبانه: محیط آزمایش
- ورودی پرسوجو + انتخابگر راهبرد (دقیق / معنایی / ترکیبی) + بودجهٔ توکن.
- «شبیهسازی» →
POST /api/memory/retrieve-preview— نتایج رتبهبندیشده را همراه باscore،tier،tokens،vecScore،ftsScoreنمایش میدهد. - پنل تفکیک که نشان میدهد از کدام منبع تعبیهسازی / مخزن برداری استفاده شده و آیا بازگشت به روش جایگزین رخ داده است یا خیر.
زبانه: موتور
- پنل وضعیت موتور (نشان FTS5 کلیدواژه، نشان تعبیهسازی، نشان مخزن برداری، نشان سلامت Qdrant، نشان بازرتبهبندی).
- دکمهٔ «بازنمایهسازی اکنون» →
POST /api/memory/reindex. - انتخابگر منبع تعبیهسازی (خودکار / راهدور / ایستا / تبدیلگرها + کلیدهای تغییر وضعیت).
- کارت پیکربندی Qdrant (کلید فعالسازی، میزبان/درگاه/مجموعه/کلید، آزمایش اتصال، آزمایش جستوجوی معنایی، پاکسازی).
- کارت پیکربندی بازرتبهبندی (کلید فعالسازی، انتخابگر ارائهدهنده/مدل).
تنظیمات حافظه و Qdrant همچنین برای رابط تنظیمات قدیمی/سراسری در
/dashboard/settings → Memory & Skills (MemorySkillsTab.tsx) قرار دارند.
ذخیرهسازی موقت
src/lib/memory/store.ts برای خواندنهای getMemory(id) یک حافظهٔ نهان
درونفرایندی تقریباً مبتنی بر LRU نگه میدارد
(MEMORY_CACHE_TTL = 1 min، MEMORY_MAX_CACHE_SIZE = 500، با حذف 20 %
از قدیمیترین موارد). همچنین یک لایهٔ عمومی حافظهٔ نهان کلید/مقدار با نام
memoryCache (src/lib/memory/cache.ts) و متدهای get/set/invalidate
وجود دارد که فراخوانهایی با نیاز به حافظهٔ نهان محدودهبندیشدهٔ اختصاصی
از آن استفاده میکنند (LRU با 1 000 ورودی و TTL پیشفرض 5 min).
حریم خصوصی و چرخهٔ عمر
- مالکیت حافظه بر اساس شناسهٔ کلید API است (
resolveMemoryOwnerIdدرchatCore.ts). بدونapiKeyInfo.id، نه بازیابی، نه تزریق و نه استخراج اجرا نمیشود. - ورودیهایی که
expires_atآنها در آینده است، از نتایج بازیابی حذف میشوند؛ ورودیهای قدیمیتر ازretentionDaysنیز توسط عبارتcreated_at >= cutoffدرretrieveMemoriesکنار گذاشته میشوند. - برای حذف دائمی، از
DELETE /api/memory/[id]یاomniroute_memory_clearاستفاده کنید. - استخراج بهصورت بدون انتظار برای نتیجه، از طریق
setImmediateانجام میشود؛ خطاها باmemory.extraction.background.failedثبت میشوند و هرگز به فراخواننده نمایش داده نمیشوند. - رفتوبرگشتهای اعتبارسنجی (
verifyExtractionPipeline)، ورودیهای آزمایشی خود را در یک بلوکfinallyپاکسازی میکنند.
همچنین ببینید
- SKILLS.md — تنظیم
skillsEnabledتعاریف ابزارها را همراه با حافظه تزریق میکند. - MCP-SERVER.md — انتقال / محدودههای MCP.
- API_REFERENCE.md — سطح گستردهتر API.
- ماژولهای منبع:
src/lib/memory/types.ts,schemas.tssrc/lib/memory/store.ts,retrieval.ts,injection.ts,reindex.tssrc/lib/memory/extraction.ts,summarization.ts,verify.tssrc/lib/memory/settings.ts,qdrant.ts,cache.tssrc/lib/memory/vectorStore.ts— sqlite-vec + روش ترکیبی RRFsrc/lib/memory/embedding/index.ts— لایهٔ تعبیهسازی چندمنبعیsrc/lib/memory/embedding/types.ts,remote.ts,staticPotion.ts,transformersLocal.ts,cache.tssrc/shared/schemas/memory.ts— طرحوارههای Zod برای بدنهٔ تمام APIهای حافظهsrc/shared/schemas/qdrant.ts— طرحوارههای Zod برای تنظیمات/عملیات Qdrantsrc/lib/db/memoryVec.ts— عملیات CRUD برایmemory_vec_metasrc/lib/db/migrations/015_create_memories.sql,022_add_memory_fts5.sql,023_fix_memory_fts_uuid.sql,083_memory_vec.sqlsrc/app/api/memory/route.ts,[id]/route.ts,health/route.tssrc/app/api/memory/retrieve-preview/route.tssrc/app/api/memory/engine-status/route.tssrc/app/api/memory/embedding-providers/route.tssrc/app/api/memory/summarize/route.tssrc/app/api/memory/reindex/route.tssrc/app/api/settings/memory/route.tssrc/app/api/settings/qdrant/route.ts+ مسیرهای فرعیsrc/app/(dashboard)/dashboard/memory/— رابط کاربری Studio (صفحه + مؤلفهها + زبانهها + هوکها)open-sse/handlers/chatCore.ts(سیمکشی تزریق / استخراج)open-sse/mcp-server/tools/memoryTools.ts
انتخاب ارائهدهندهٔ تعبیهسازی (v3.8.16+)
موتور حافظهٔ OmniRoute از چهار منبع تعبیهسازی (src/lib/memory/embedding/) پشتیبانی میکند. هرکدام از نظر تأخیر، هزینه، کیفیت مدل و پیچیدگی راهاندازی موازنههای متفاوتی دارند.
منابع تعبیهسازی
| ارائهدهنده | منبع | تأخیر | هزینه | کیفیت | راهاندازی |
|---|---|---|---|---|---|
transformers |
مدل محلی ONNX (Xenova/all-MiniLM-L6-v2) | ~50-150ms (CPU) | رایگان | خوب | فقط npm install |
static |
بردارهای ازپیشمحاسبهشده (کششده) | <1ms | رایگان | نامرتبط (وابسته به اصابت کش) | هیچکدام |
remote |
API مربوط به OpenAI / Cohere / Voyage | ~100-300ms | $0.02-0.10/1M توکن | عالی | کلید API |
auto |
بهترین منبع موجود را هنگام اجرا انتخاب میکند | همانند منبع انتخابشده | رایگان | همانند منبع انتخابشده | هیچکدام |
| (کش) | لایهٔ LRU درونحافظهای روی هر منبع | <1ms (اصابت)، تأخیر کامل (عدم اصابت) | رایگان | همانند منبع زیربنایی | همیشه فعال (منبع قابلانتخاب نیست) |
درخت تصمیم
زمینهٔ استقرار شما چیست؟
│
┌───────────┼───────────┬──────────────┐
│ │ │ │
توسعه/آزمایش تولید کوچک تولید بزرگ لبه / آفلاین
│ │ │ │
▼ ▼ ▼ ▼
transformers transformers remote (Qdrant) transformers
(رایگان، بدون API) (بهترین کیفیت) (بدون اینترنت)
│ │ │ │
└────────┬──┴───────────┴──────────────┘
│
▼
همیشه لایهٔ `cache` را روی آن اضافه کنید
(LruCache هر ارائهدهندهای را پوشش میدهد)
پیکربندی پایگاه داده و API
گزینههای تعبیهسازی حافظه از طریق API/رابط کاربری تنظیمات پیکربندی میشوند، نه متغیرهای محیطی. کلیدهای مرتبط پایگاه دادهٔ تنظیمات در بخش Settings (normalizeMemorySettings در src/lib/memory/settings.ts) عبارتاند از:
memoryEmbeddingSource:"transformers"(محلی)،"remote"(مبتنی بر API، مانند OpenAI)،"static"(ذخیرهساز خارجی)، یا"auto"memoryEmbeddingProviderModel: شناسهٔ مدل برای منابع remote/static (برای مثال،"text-embedding-3-small")memoryTransformersEnabled:true|falsememoryStaticEnabled:true|falsememoryVectorStore:"sqlite-vec"،"qdrant"، یا"auto"
مدل محلی (transformers)
برای اجرای مدلهای محلی، در داخل از transformers.js استفاده میکند:
# متغیرهای محیطی خواندهشده در کد (src/lib/memory/embedding/index.ts):
MEMORY_TRANSFORMERS_MODEL=Xenova/all-MiniLM-L6-v2 # مخزن مدل HF
MEMORY_STATIC_MODEL=minishlab/potion-base-8M # مدل static potion در HF
MEMORY_STATIC_CACHE_DIR=<DATA_DIR>/embeddings # پوشهٔ کش
کش تعبیهسازی LRU
کش بهطور پیشفرض همیشه فعال است و از طریق متغیرهای محیطی پیکربندی میشود:
MEMORY_EMBEDDING_CACHE_MAX=1000 # حداکثر موارد کششده
MEMORY_EMBEDDING_CACHE_TTL_MS=300000 # TTL (۵ دقیقه)
اعداد عملکردی
بنچمارک روی یک سرور معمولی x86 با ۴ هسته (متنها هرکدام حدود ۱۰۰ توکن):
| ارائهدهنده | p50 | p95 | p99 | هزینه / ۱ میلیون امبدینگ |
|---|---|---|---|---|
transformers (CPU) |
80ms | 180ms | 350ms | رایگان |
remote (OpenAI) |
120ms | 220ms | 400ms | حدود $0.02 (ada-002) / $0.13 (3-large) |
static (Qdrant) |
15ms | 30ms | 60ms | به میزبانی Qdrant بستگی دارد |
cache (اصابت) |
<1ms | <1ms | 2ms | رایگان |
الگوهای استخراج واقعیت (v3.8.16+)
ماژول extraction.ts (src/lib/memory/extraction.ts) از تطبیق الگو با عبارتهای منظم برای استخراج واقعیتهای ساختاریافته از پیامهای مکالمه استفاده میکند. درک این الگوها به شما کمک میکند کیفیت استخراج را برای مورد استفادهٔ خود تنظیم کنید.
دستهبندیهای پیشفرض الگوها
| دستهبندی | الگوی نمونه | موارد استخراجشده |
|---|---|---|
| PREFERENCE_PATTERNS | "من <X> را ترجیح میدهم"، "من <X> را دوست دارم"، "من از <X> متنفرم" |
ترجیحات کاربر |
| DECISION_PATTERNS | "من از <X> استفاده خواهم کرد"، "تصمیم گرفتم <X>"، "من <X> را انتخاب کردم" |
تصمیمهای کاربر (رویدادی) |
| PATTERN_PATTERNS | "من معمولاً <X>"، "من همیشه <X>"، "من هرگز <X>" |
الگوهای رفتاری پایدار |
الگوهای نمونه (سادهشده)
// از src/lib/memory/extraction.ts
const PREFERENCE_PATTERNS = [
/\bI\s+(?:really\s+)?prefer\s+([^.,\n]+)/gi,
/\bI\s+(?:really\s+)?like\s+([^.,\n]+)/gi,
/\bI\s+(?:hate|dislike|avoid)\s+([^.,\n]+)/gi,
];
const DECISION_PATTERNS = [
/\bI'?(?:ll|will)\s+use\s+([^.,\n]+)/gi,
/\bI\s+(?:have\s+)?decided\s+(?:to\s+)?([^.,\n]+)/gi,
];
const PATTERN_PATTERNS = [/\bI\s+usually\s+([^.,\n]+)/gi, /\bI\s+always\s+([^.,\n]+)/gi];
چه چیزهایی استخراج میشوند
وقتی کاربری میگوید:
"من TypeScript را ترجیح میدهم. برای این پروژه از Postgres استفاده خواهم کرد. همیشه پیش از push کردن commit میکنم. Python را دوست ندارم." استخراج، ۴ حافظه تولید میکند:
کلید دستهبندی نوع محتوا preference:typescriptترجیح واقعی "TypeScript" decision:postgres_for_this_projectتصمیم رویدادی "Postgres برای این پروژه" pattern:commit_before_pushingالگو واقعی "commit پیش از pushing" preference:pythonترجیح واقعی "Python"
محدودیتهای استخراج
برای جلوگیری از استخراج کنترلنشده، محدودیتهای زیر اعمال میشوند:
| حداقل طول محتوا | ۳ نویسه | | حداکثر طول محتوا | ۵۰۰ نویسه |
چه زمانی استخراج را غیرفعال کنیم
هر زمان که حافظه فعال باشد، استخراج بهطور خودکار اجرا میشود؛ گزینهٔ جداگانهای
برای فعال یا غیرفعال کردن صرفاً استخراج وجود ندارد. برای خاموش کردن آن، حافظه را بهطور کامل غیرفعال کنید (enabled: false
از طریق PUT /api/settings/memory). در شرایط زیر این کار را در نظر بگیرید:
- حجم پیامهای شما زیاد است و هزینهٔ استخراج قابلتوجه است
- مکالمات شما عمدتاً موقتی هستند (گفتوگو، اشکالزدایی) و ارزش بلندمدتی ندارند
- از قبل زمینه را از طریق افزونههای سفارشی ثبت میکنید
تنظیم RRF ترکیبی (v3.8.16+)
الگوریتم ادغام رتبهٔ متقابل (RRF) نتایج FTS5 (کلیدواژهای) و برداری (معنایی) را ترکیب میکند. پارامتر k میزان وزنی را که به نتایج با رتبهٔ پایینتر داده میشود کنترل میکند.
فرمول
برای هر حافظهٔ کاندید، امتیاز RRF بهشکل زیر است:
RRF(d) = Σ 1 / (k + rank_i(d))
که در آن:
kمقدار ثابت است (مقدار پیشفرض 60)rank_i(d)رتبهٔ سندdدر iاُمین سامانهٔ بازیابی (FTS، برداری) است- جمع روی تمام سامانههای بازیابی انجام میشود
تأثیر k بر نتایج
مقدار k |
تأثیر | مناسب برای |
|---|---|---|
k=0 |
ادغام رتبهٔ خالص (بدون هموارسازی) | خط مبنای نظری |
k=10-30 |
وزندهی زیاد به نتایج برتر؛ رتبههای پایین مشارکت ناچیزی دارند | وقتی ۳ نتیجهٔ برتر معمولاً درست هستند |
k=60 (پیشفرض) |
متعادل — همهٔ ۱۰ نتیجهٔ برتر مشارکت معناداری دارند | بازیابی چندمنظوره |
k=100+ |
یکنواختتر — حتی نتایج با رتبهٔ پایین نیز در صورت ظاهر شدن در چند سامانه میتوانند غالب شوند | وقتی یادآوری > دقت اهمیت حیاتی دارد |
تنظیم عملی k
# پیشفرض
MEMORY_RRF_K=60
# دقت تهاجمی (حافظهٔ کوچک، اسناد کم)
MEMORY_RRF_K=20
# حداکثر یادآوری (حافظهٔ بزرگ، پرسوجوهای متنوع)
MEMORY_RRF_K=120
مثال با k=20:
- رتبهٔ ۱ FTS ← سهم
1/21 = 0.048 - رتبهٔ ۱۰ FTS ← سهم
1/30 = 0.033 - رتبهٔ ۱ برداری ← سهم
0.048 - حداکثر ترکیبی:
0.096
مثال با k=60:
- رتبهٔ ۱ FTS ← سهم
1/61 = 0.016 - رتبهٔ ۱۰ FTS ← سهم
1/70 = 0.014 - رتبهٔ ۱ برداری ← سهم
0.016 - حداکثر ترکیبی:
0.033
با k بزرگتر، تفاوت نسبی میان رتبهٔ ۱ و رتبهٔ ۱۰ کمتر میشود؛ بنابراین الگوریتم، بهجای اطمینان به رتبهٔ برتر، بیشتر به اجماع میان سامانههای بازیابی متکی خواهد بود.
چه زمانی k را تغییر دهیم
| نشانه | پیشنهاد |
|---|---|
| نتیجهٔ برتر همیشه برنده میشود، اما اشتباه است | k را کاهش دهید (مثلاً 20) — اطمینان به رتبهٔ برتر اهمیت بیشتری پیدا میکند |
| پاسخ درست در میان ۵ نتیجهٔ برتر است، اما رتبهٔ ۱ نیست | k را افزایش دهید (مثلاً 100) — امتیازدهی یکنواختتر به اجماع پاداش میدهد |
| یادآوری بالا است، اما دقت پایین است | k را کاهش دهید — رتبهبندی را دقیقتر کنید |
| یادآوری پایین است (اسناد مرتبط پیدا نمیشوند) | k را افزایش دهید — به اسناد با رتبهٔ پایینتر فرصت بدهید |
وزندهی RRF
ادغام رتبهٔ متقابل از وزنهای برابر برای رتبهٔ برداری معنایی و رتبهٔ جستوجوی متن کامل استفاده میکند:
RRF(d) = 1/(k + rank_vector) + 1/(k + rank_fts)
هیچ متغیر محیطی برای تنظیم وزنهای جداگانه وجود ندارد (MEMORY_RRF_VECTOR_WEIGHT/MEMORY_RRF_FTS_WEIGHT وجود ندارند).
راهبرد خلاصهسازی (v3.8.16+)
ماژول summarization.ts (src/lib/memory/summarization.ts) حافظههای قدیمیتر را فشرده میکند تا ضمن حفظ قابلیت بازیابی، مجموعهٔ فعال کوچک باقی بماند.
زمان فعالشدن خلاصهسازی
| عامل فعالسازی | آستانه (پیشفرض) |
|---|---|
| فعالسازی دستی از طریق API | نامرتبط |
مواردی که خلاصه میشوند
دو نقطهٔ ورود از summarization.ts صادر میشوند:
summarizeMemories(apiKeyId, sessionId?, maxTokens = 4000)— حافظههای یک نشست را در یک متن خلاصه که به بودجهٔ توکن محدود است، فشرده میکند.summarizeMemoriesOlderThan(apiKeyId, days, dryRun)— فشردهسازی مبتنی بر سن که توسط API استفاده میشود: تمام حافظههای قدیمیتر ازdaysرا انتخاب میکند، یک حافظهٔ خلاصه و فشرده از آنها میسازد و (هنگامی کهdryRunبرابر باfalseاست) موارد اصلی را حذف میکند. برای پیشنمایش مجموعهٔ نامزدها و مجموع توکنها، بدون تغییر هیچچیز،dryRun: trueرا ارسال کنید.
هیچ مرحلهای برای خوشهبندی بر اساس برچسب/کلید یا امتیازدهی «اصلی در برابر قابلخلاصهسازی» برای هر حافظه وجود ندارد — انتخاب صرفاً بر اساس حد آستانهٔ سن انجام میشود و متن خلاصه، شامل یک خط فشرده با پیشوند نوع برای هر نامزد است.
فعالسازی خلاصهسازی
خلاصهسازی دستی / اختیاری است — تنظیم autoSummarize بهصورت پیشفرض
false است، بنابراین هیچچیز بهطور خودکار فشرده نمیشود. آن را از طریق API فعال کنید:
curl -X POST http://localhost:20128/api/memory/summarize \
-H "Authorization: Bearer $OMNIROUTE_KEY"
برای غیرفعال نگهداشتن آن، کافی است autoSummarize را روی مقدار پیشفرضش (false) نگه دارید.
نکات مربوط به کیفیت خلاصهسازی
- ابتدا با
dryRunپیشنمایش بگیرید —summarizeMemoriesOlderThan(..., true)فهرست نامزدها و مجموع تعداد توکنها را برمیگرداند تا پیش از حذف موارد اصلی، بتوانید مواردی را که ادغام خواهند شد تأیید کنید. - اگر مجموعهٔ حافظهٔ بزرگی دارید، خلاصهسازی را در ساعات کمترافیک اجرا کنید — فراخوانی LLM بخش زمانبر عملیات است
# به سبک Cron: خلاصهسازی روزانه در ساعت ۳ بامداد
0 3 * * * curl -X POST http://localhost:20128/api/memory/summarize \
-H "Authorization: Bearer $OMNIROUTE_KEY"
الگوی ارائهدهندهٔ MemoryBackend
منبع مرجع:
src/lib/memory/backend.ts،src/lib/memory/genericBackend.ts،src/lib/memory/manager.tsآزمونها:src/lib/memory/__tests__/generic-backend.test.ts
الگوی ارائهدهندهٔ MemoryBackend یک لایهٔ انتزاعی قابلاتصال برای بکاند روی موتور حافظهٔ موجود معرفی میکند. بهجای وابستگی به یک پیادهسازی ذخیرهسازی واحد، سیستم حافظه اکنون از چندین بکاند (SQLite، Obsidian، Notion و بکاندهای سفارشی HTTP) با مسیریابی اصلی/جایگزین قابلپیکربندی پشتیبانی میکند.
معماری
┌──────────────────────────────────────────────────────────┐
│ مسیرهای API │
│ (src/app/api/memory/route.ts) │
└──────────────────────┬───────────────────────────────────┘
│
┌──────────────────────▼───────────────────────────────────┐
│ MemoryManager │
│ هماهنگکنندهٔ Singleton (manager.ts) │
│ │
│ اصلی ─────► بکاند A (برای مثال SQLite) │
│ جایگزین ──► بکاند B (برای مثال Obsidian) │
│ بکاند C (برای مثال Notion از طریق GenericBackend) │
└──────────────────────┬───────────────────────────────────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌──────────────────┐
│ بکاند │ │ بکاند │ │ بکاند │
│ SQLite │ │ Obsidian │ │ GenericMemory │
│ │ │ │ │ (HTTP) │
└────────────┘ └────────────┘ └──────────────────┘
رابط اصلی (backend.ts)
هر بکاند باید رابط MemoryBackend را پیادهسازی کند:
interface MemoryBackend {
readonly id: string;
readonly displayName: string;
// عملیات CRUD
create(input: CreateMemoryInput): Promise<Memory>;
get(id: string): Promise<Memory | null>;
update(id: string, updates: Partial<...>): Promise<boolean>;
delete(id: string): Promise<boolean>;
list(filter: MemoryFilter): Promise<{ data: Memory[]; total: number; byType: Record<string, number> }>;
// جستوجو
search(config: SearchConfig): Promise<Memory[]>;
// سلامت
health(): Promise<HealthCheckResult>;
// چرخهٔ حیات (اختیاری)
initialize?(): Promise<void>;
shutdown?(): Promise<void>;
}
MemoryManager (manager.ts)
هماهنگکنندهٔ Singleton که:
- بکاندها را از طریق
register(backend)ثبت میکند — هنگام راهاندازی ازindex.tsفراخوانی میشود - بکاند اصلی و جایگزینها را از طریق
configure(primary, fallbacks)پیکربندی میکند - عملیات CRUD/جستوجو را به بکاند اصلی مسیریابی میکند و در صورت شکست از زنجیرهٔ جایگزین استفاده میکند
- بهصورت دورهای سلامت تمام بکاندها را بررسی میکند
رفتار جایگزینی:
| عملیات | اصلی | جایگزینها |
|---|---|---|
create |
✅ فقط اصلی | ❌ |
get |
✅ ابتدا بکاند اصلی را امتحان میکند | ✅ اگر نتیجه null باشد، جایگزین |
update |
✅ فقط اصلی | ✅ همگامسازی بدون انتظار نتیجه |
delete |
✅ فقط اصلی | ✅ همگامسازی بدون انتظار نتیجه |
list |
✅ فقط اصلی | ❌ |
search |
✅ ابتدا اصلی | ✅ در صورت خطا، جایگزین |
GenericMemoryBackend (genericBackend.ts)
یک اتصالدهندهٔ عمومی HTTP که هر REST API را با MemoryBackend سازگار میکند. مناسب برای:
- Notion — اتصال از طریق Notion API
- Obsidian — اتصال از طریق Obsidian Local REST API
- بکاندهای سفارشی — هر سرویسی که یک API حافظهٔ RESTful ارائه میدهد
پیکربندی:
interface GenericBackendConfig {
baseUrl: string; // نشانی پایه API بکاند
apiKey?: string; // توکن Bearer برای احراز هویت
headers?: Record<string, string>; // هدرهای HTTP سفارشی
timeout?: number; // مهلت زمانی درخواست (پیشفرض: 30000ms)
backendType?: string; // برای ثبت گزارش
// بازنویسی نقاط پایانی (مقادیر پیشفرض از قراردادهای REST استفاده میکنند)
endpoints?: {
search?: string; // پیشفرض: "/memories/search"
create?: string; // پیشفرض: "/memories"
list?: string; // پیشفرض: "/memories"
get?: string; // پیشفرض: "/memories/{id}"
update?: string; // پیشفرض: "/memories/{id}"
delete?: string; // پیشفرض: "/memories/{id}"
health?: string; // پیشفرض: "/health"
};
// نگاشت نام پارامترهای کوئری
queryParams?: {
query?/apiKeyId?/limit?/offset?/strategy?/maxTokens?/type?/sessionId?/orderBy?/orderDir?/options?
};
// نگاشت نام پارامترهای مسیر
pathParams?: {
id?/memoryId?
};
}
بکاندهای شناختهشده در KNOWN_BACKENDS از پیش پیکربندی شدهاند:
createKnownBackend("obsidian"); // → GenericMemoryBackend با اشاره به localhost:27123
createKnownBackend("notion"); // → GenericMemoryBackend با اشاره به api.notion.com/v1
بکاندهای داخلی
SQLiteBackend (sqliteBackend.ts)
بکاند اصلی پیشفرض. ذخیرهساز حافظه مبتنی بر SQLite موجود را با استفاده از src/lib/memory/store.ts پوشش میدهد. هنگام راهاندازی بهطور خودکار ثبت میشود.
import { sqliteBackend } from "./sqliteBackend";
memoryManager.register(sqliteBackend);
ObsidianBackend (obsidianBackend.ts)
یکپارچهسازی موجود Obsidian (src/lib/memory/obsidianBackend.ts) را پوشش میدهد. از طریق Obsidian Local REST API به یک خزانه Obsidian متصل میشود.
تنظیمات
تنظیمات بکاند حافظه در جدول تنظیمات برنامه ذخیره شده و از طریق src/lib/memory/settings.ts مدیریت میشوند:
| تنظیم | کلید محیط/پیکربندی | پیشفرض | توضیحات |
|---|---|---|---|
| بکاند اصلی | memoryPrimaryBackend |
"sqlite" |
شناسه بکاند اصلی |
| بکاندهای جایگزین | memoryFallbackBackends |
[] |
شناسههای مرتبشده بکاندهای جایگزین |
| پیکربندیهای بکاند | memoryBackendConfigs |
{} |
بازنویسی پیکربندی برای هر بکاند |
تنظیمات از طریق normalizeMemorySettings() نرمالسازی و در getMemorySettings() کش میشوند.
جریان مقداردهی اولیه
راهاندازی برنامه
→ واردکردنهای index.ts (اثر جانبی): SQLiteBackend را ثبت میکند
→ initMemoryBackends() از چرخه حیات برنامه فراخوانی میشود:
1. بارگذاری تنظیمات (getMemorySettings)
2. پیکربندی بکاند اصلی و جایگزین
3. مقداردهی اولیه همه بکاندها (بررسی سلامت)
4. آماده دریافت درخواستها
افزودن یک بکاند جدید
- رابط
MemoryBackendرا پیادهسازی کنید درsrc/lib/memory/<name>Backend.ts - صادر کنید از
src/lib/memory/index.ts - ثبت کنید با
memoryManager.register(yourBackend)هنگام راهاندازی - پیکربندی کنید از طریق تنظیمات: مقدار
memoryPrimaryBackendرا روی شناسه بکاند خود تنظیم کنید - آزمایش کنید با استفاده از
src/lib/memory/__tests__/generic-backend.test.tsبهعنوان مرجع
نمونه: بکاند Brain
import { createGenericMemoryBackend } from "./genericBackend";
const brainBackend = createGenericMemoryBackend("brain", "BK-Brain", {
baseUrl: process.env.BRAIN_API_URL || "http://localhost:9099",
apiKey: process.env.BRAIN_API_KEY,
endpoints: {
search: "/api/memory/search",
create: "/api/memory",
health: "/api/health",
},
});
memoryManager.register(brainBackend);
اعتبارسنجی
آزمونهای واحد
npx vitest run src/lib/memory/__tests__/generic-backend.test.ts --reporter=verbose
خروجی مورد انتظار: 35 آزمون، همگی موفق که موارد زیر را پوشش میدهند:
- سازنده (2)
- بررسی سلامت (4) — موفقیت، خطای 500، خطای شبکه، تأخیر
- مقداردهی اولیه (2) — موفقیت، شکست
- ایجاد (2) — نقطه پایانی پیشفرض، نقطه پایانی سفارشی
- دریافت (4) — موفقیت، 404 ← null، پرتاب خطا برای موارد غیر از 404، پارامترهای مسیر سفارشی
- بهروزرسانی (2) — موفقیت، 404 ← false
- حذف (2) — موفقیت، 404 ← false
- فهرست (2) — پارامترهای کوئری، نامهای سفارشی پارامترها
- جستوجو (3) — پارامترهای کوئری، نقطه پایانی سفارشی، سریالسازی گزینهها
- هدرهای احراز هویت (2) — توکن Bearer، هدرهای سفارشی
- کارخانه (1)
بررسی نوع
npm run typecheck:core
مورد انتظار: 0 خطا.