
Memo'nun hafıza sistemi, her konuşma için ilgili bağlamı otomatik olarak hatırlamak amacıyla Retrieval-Augmented Generation (RAG) kullanır. Bu sayfa, mesajdan hafıza erişimine kadar olan teknik işlem hattını açıklar.
Kullanıcı mesajı
│
▼
┌──────────────────────┐
│ 1. Gömme │ → Metni vektöre dönüştür (nomic-embed-text-v1.5)
└──────────────────────┘
│
▼
┌──────────────────────┐
│ 2. Anlamsal Arama │ → Vektör benzerliği (kosinüs) + FTS5 anahtar kelime araması
└──────────────────────┘
│
▼
┌──────────────────────┐
│ 3. RRF Birleştirme │ → Reciprocal Rank Fusion — vektör + FTS sonuçlarını birleştir
└──────────────────────┘
│
▼
┌──────────────────────┐
│ 4. Bağlam Paketleme │ → En iyi N anıyı sistem prompt'una ekle
└──────────────────────┘
│
▼
┌──────────────────────┐
│ 5. LLM Yanıtı │ → Model hafıza bağlamı ile yanıt üretir
└──────────────────────┘
│
▼
┌──────────────────────┐
│ 6. Kalıcılaştır │ → Gelecek erişim için yeni etkileşimi sakla
└──────────────────────┘
Her mesaj, anlamsal benzerlik için özelleşmiş 137M parametreli bir model olan nomic-embed-text-v1.5 kullanılarak yoğun bir vektöre (gömme) dönüştürülür. Bu model dahili llama.cpp sunucusu aracılığıyla yerel olarak çalışır.
Uzun mesajlar, 50 kelimelik örtüşme penceresi ile örtüşen 300 kelimelik parçalara bölünür:
// internal/memory/chunker.go
type Chunk struct {
Text string
Index int
WordLen int
}
func ChunkMessage(text string) []Chunk {
// Kelime sınırlarında böler, parça başına 300 kelime, 50 kelime örtüşme
}
Her parça bağımsız olarak gömülür ve saklanır. Önceden uzun mesajlar tek blok olarak saklanırdı — ortadaki bilgiler sıklıkla kaçırılırdı.
Saklama sırasında gömme vektörü hem kullanıcı mesajı hem de asistan yanıtının birleştirilmesinden hesaplanır. Bu, "fiyatlandırma hakkında ne önermiştin?" gibi sorguların, kullanıcı mesajı tek başına yanıtı içermese bile doğru anıyı bulmasını sağlar.
Hafıza erişimi eşzamanlı olarak iki arama stratejisi kullanır:
Yaklaşık en yakın komşu (ANN) indeksi olarak vec0 sanal tablosu ile sqlite-vec kullanır:
SELECT rowid, distance
FROM memory_vec
WHERE embedding MATCH ?
ORDER BY distance ASC
LIMIT ?
Uzaklık kosinüs benzerliği kullanılarak hesaplanır. Sonuçlar anlamsal eşleşmelerdir — tam kelimeler farklı olsa bile kavramsal olarak ilişkili içerik.
unicode61 belirteçleyici ile SQLite FTS5 kullanır:
SELECT rowid, rank
FROM memory_fts
WHERE content MATCH ?
ORDER BY rank
LIMIT ?
Her kelime bağımsız olarak eşleştirilir (ifade olarak değil). Bu, tam terimleri, isimleri, tarihleri ve teknik jargonu yakalar.
Erişim aday havuzu dinamik olarak boyutlandırılır:
candidatePool := min(max(topK * 5, 20), 100)
Daha önce 15'te sabitti — birkaç bin girdinin ötesindeki büyük hafıza tabanları için fazlasıyla küçüktü.
Son 3 kullanıcı mesajı ek bağlam olarak eklenir. "Bunun hakkında ne demiştik?" gibi kısa takipler artık hiçbir şey döndürmek yerine ilgili anıları bulur.
7 kelimeden uzun sorgular için, ilk 5 kelimeden konu çapası olarak ikinci bir gömme vektörü hesaplanır. Her iki gömme vektörü ayrı ayrı aranır ve RRF ile birleştirilir, böylece ana konu cümle ortasında gömülü olduğunda geri çağırma iyileşir.
Vektör arama ve FTS sonuçları Reciprocal Rank Fusion kullanılarak birleştirilir:
RRF_skor(d) = Σ 1 / (k + sıra_i(d))
Burada k = 60 (sıra farklılıklarını yumuşatan bir sönümleme sabiti). Her iki sonuç kümesinde de görünen anılar en üste çıkarılır. Bir kalite filtresi, eşiğin altındaki düşük güvenli eşleşmeleri atar.
En iyi N eşleşen anı (genellikle 5–10, yapılandırılabilir) biçimlendirilir ve sistem prompt'una eklenir:
[İlgili Anılar]
- 2024-06-15: Kullanıcı, ücretsiz katmanlı kullanım tabanlı fiyatlandırmaya karar verdi.
(eşleşme: hibrit, önem: 5)
- 2024-06-10: Kullanıcı, koltuk fiyatlandırmasının yerel-öncelikli bir araç için yanlış hissettirdiğini belirtti.
(eşleşme: vektör, önem: 4)
Her anı, zaman damgasını, eşleşme türünü ("vektör", "fts" veya "hibrit") ve önem puanını içerir.
LLM tam prompt'u alır — sistem prompt'u + ilgili anılar + konuşma geçmişi + kullanıcı mesajı — ve geçmiş bağlamla bilgilendirilmiş bir yanıt üretir.
Konuşma turundan sonra, alışveriş (kullanıcı mesajı + asistan yanıtı):
data/memory/memory.db dosyasında üç tabloda saklanır:memories — ham metin, üst veri, önem puanımemory_vec — vektör gömmeleri (vec0 sanal tablosu)memory_fts — tam metin arama indeksi (FTS5 sanal tablosu)Üç tablo da tek bir işlemde atomik olarak yazılır.
-- Metin depolama
CREATE TABLE memories (
id TEXT PRIMARY KEY,
content TEXT NOT NULL,
timestamp TEXT NOT NULL,
importance INTEGER DEFAULT 1,
retrieval_count INTEGER DEFAULT 0,
pinned INTEGER DEFAULT 0,
soft_deleted INTEGER DEFAULT 0,
deleted_at TEXT
);
-- Vektör indeksi (sqlite-vec vec0)
CREATE VIRTUAL TABLE memory_vec USING vec0(
embedding FLOAT[768]
);
-- Tam metin indeksi (FTS5)
CREATE VIRTUAL TABLE memory_fts USING fts5(
content,
tokenize='unicode61'
);
Her anının bir önem puanı vardır (1–5):
/remember) → önem 5, asla düşürülmezDüşüş döngüsü günlük arka plan işi olarak çalışır.
Günlük bir arka plan süreci yakın-benzer birikimi önler:
Bu, düşüş döngüsünden sonra çalışır ve sohbet yanıtlarını asla engellemez.
Ayarlar → Hafıza → Hata Ayıklama Araması: Herhangi bir sorgu yazın ve puanlar, zaman damgaları ve eşleşme türü rozetleriyle en iyi erişilen anıları görün.
Hafıza Analitik Paneli şunları gösterir:
| Komut | Eylem |
|---|---|
/remember <metin> |
Maksimum önemle sabitlenmiş bir anı kaydet |
/forget <desen> |
Desenle eşleşen anıları sil |
| İşlem | Tipik Gecikme |
|---|---|
| Gömme (300 kelimelik parça) | ~50–100ms |
| Vektör arama (10K anı) | ~5–15ms |
| FTS5 arama (10K anı) | ~1–3ms |
| RRF birleştirme | ~1ms |
| Tam işlem hattı (sorgu → sonuçlar) | ~60–120ms |
Performans, vec0 ANN indeksi sayesinde saklanan anı sayısı ile logaritmik olarak ölçeklenir. 100K girişlik bir hafıza tabanı, 10K'nın yaklaşık 2 katı arama gecikmesi ekler.