Minnesretrieval: när ska minnet hämtas?
Kunna designa när och hur minnen hämtas och mäta om de faktiskt används.
Förkunskaper
Intuition
Att ha ett minne är en sak. Att veta när det ska användas är en annan, och svårare.
Tre strategier:
| Strategi | Hur | Problem |
|---|---|---|
| Alltid hämta | sök i minnet vid varje meddelande | dyrt, och irrelevanta minnen stör |
| Modellen bestämmer | ge den ett sök_minne-verktyg | den glömmer ofta att använda det |
| Utlösare | hämta vid vissa signaler | kräver att signalerna är rätt |
I praktiken används en kombination: hämta alltid billigt och brett, men filtrera hårt på relevans innan något läggs i prompten.
Den avgörande insikten: ett irrelevant minne är sämre än inget minne alls. Det tar plats i kontexten, drar modellens uppmärksamhet, och kan få den att svara på fel fråga. Precisionen är viktigare än recallen här — tvärtemot i vanlig RAG.
Formellt
Vad som ska vägas in i rangordningen:
| Faktor | Varför |
|---|---|
| Semantisk likhet | handlar minnet om samma sak? |
| Färskhet | nyare minnen är oftast mer relevanta |
| Viktighet | vissa minnen är centrala (namn, mål, preferenser) |
| Användningsfrekvens | minnen som ofta visat sig nyttiga |
En vanlig formel (från Generative Agents) kombinerar dem linjärt:
Färskheten avtar exponentiellt — samma form som glömskekurvan i plattformens mastery-modell.
Absolut tröskel, inte bara topp-k. Hämta de bästa och kräv att poängen överstiger en tröskel. Utan tröskeln får du alltid minnen, även när inget är relevant, och då skadar minnet mer än det hjälper.
Mät om minnena faktiskt används. Det är den mätning som nästan alltid saknas:
| Mått | Hur |
|---|---|
| Hämtningsfrekvens | andel turer där något hämtas |
| Användningsgrad | refererar svaret till det hämtade? |
| Nyttograd | blir svaret bättre med än utan? (A/B på samma fråga) |
| Störningsgrad | blir svaret sämre med minnet? |
| Precision@k | andel hämtade minnen som var relevanta |
Nyttograden mäts enklast med ablation: kör samma fråga med och utan de hämtade minnena och låt en domare jämföra. Är skillnaden nära noll gör minnessystemet ingen nytta, oavsett hur bra retrievalmåtten ser ut.
Konflikthantering. Två minnen kan motsäga varandra — «jag gillar inte matte» från i våras och «matte är faktiskt kul nu» från förra veckan. Regler som fungerar:
- Nyare vinner vid direkt motsägelse.
- Explicita uttalanden väger tyngre än härledda slutsatser.
- Vid osäkerhet: ta med båda och låt modellen se motsägelsen.
Den tredje är ofta bäst — modellen kan hantera «du sa X i våras men Y förra veckan» mer graciöst än ett system som tyst väljer.
Skrivsidan är minst lika viktig. Spara inte varje yttrande. Extrahera det som är värt att minnas — beslut, preferenser, mål, återkommande svårigheter — och skriv en kort, sökbar sammanfattning. Ett minne som är svårt att hitta är inget minne.
Kod
import math, time
from dataclasses import dataclass, field
@dataclass
class Minne:
text: str
inbaddning: list
skapad: float
viktighet: float = 0.5 # 0–1, satt vid skrivning
anvant: int = 0
senast_anvant: float = 0.0
class Minnesbank:
def __init__(self, alfa=0.6, beta=0.2, gamma=0.2, halveringstid_dygn=30.0):
self.minnen: list[Minne] = []
self.alfa, self.beta, self.gamma = alfa, beta, gamma
self.lam = math.log(2) / (halveringstid_dygn * 86400)
def skriv(self, text, inbaddning, viktighet=0.5):
self.minnen.append(Minne(text, inbaddning, time.time(), viktighet))
def _poang(self, m, fraga_vektor, nu):
likhet = sum(a * b for a, b in zip(m.inbaddning, fraga_vektor))
farskhet = math.exp(-self.lam * (nu - m.skapad))
return self.alfa * likhet + self.beta * farskhet + self.gamma * m.viktighet
def hamta(self, fraga_vektor, k=3, troskel=0.45):
nu = time.time()
poangsatta = [(self._poang(m, fraga_vektor, nu), m) for m in self.minnen]
poangsatta.sort(key=lambda p: -p[0])
# BÅDE topp-k OCH absolut tröskel — annars hämtas alltid k stycken
valda = [(p, m) for p, m in poangsatta[:k] if p >= troskel]
for _, m in valda:
m.anvant += 1
m.senast_anvant = nu
return valda
# Mät nytta med ablation: samma fråga med och utan minnena
def nyttograd(llm, domare, fall, bank):
battre = samre = lika = 0
for f in fall:
minnen = bank.hamta(f["fraga_vektor"])
med = llm(f["fraga"], kontext=[m.text for _, m in minnen])
utan = llm(f["fraga"], kontext=[])
dom = domare(f["fraga"], med, utan) # "med" | "utan" | "lika"
battre += dom == "med"; samre += dom == "utan"; lika += dom == "lika"
n = len(fall)
return {"battre_med_minne": round(battre / n, 3),
"samre_med_minne": round(samre / n, 3),
"lika": round(lika / n, 3),
"nettonytta": round((battre - samre) / n, 3)}
# nettonytta nära 0 → minnessystemet gör ingen skillnad
# samre_med_minne högt → irrelevanta minnen stör
# Konflikthantering: visa motsägelsen i stället för att välja tyst
def formatera_minnen(valda):
rader = []
for p, m in valda:
dagar = int((time.time() - m.skapad) / 86400)
rader.append(f"- (för {dagar} dagar sedan, relevans {p:.2f}) {m.text}")
return "Tidigare i era samtal:\n" + "\n".join(rader)
# Skrivsidan: extrahera, spara inte allt
EXTRAHERA = """Vad i det här samtalet är värt att minnas till nästa gång?
Svara JSON: {"minnen": [{"text": "...", "viktighet": 0.0-1.0}]}
Ta med beslut, preferenser, mål och återkommande svårigheter.
Ta INTE med småprat, personuppgifter eller sådant som gäller bara i dag."""
nyttograd är den mätning som avgör om minnessystemet är värt sin komplexitet. Ett system med utmärkt precision@3 men nettonytta 0,02 gör i praktiken ingenting.
Behärskning innebär
- Avgör när minne ska hämtas
- Väljer och rangordnar minnen
- Mäter om hämtade minnen faktiskt används
Logga in för att göra övningarna och bygga upp din behärskning.
Källor
- arXiv — Generative Agents: Interactive Simulacra of Human Behavior — arXiv (öppen åtkomst; licens per artikel)
- arXiv — MemGPT: Towards LLMs as Operating Systems — arXiv (öppen åtkomst; licens per artikel)
- arXiv — Lost in the Middle: How Language Models Use Long Contexts — arXiv (öppen åtkomst; licens per artikel)