AI-агент с памятью: краткосрочные сводки и векторная долговременная память на FAISS
В этом руководстве показано, как создать AI-агента, который не только общается, но и запоминает. Комбинация лёгкой LLM, FAISS-поиска по векторам и механизма суммаризации даёт краткосрочный контекст в виде сводок и сжатую векторную долговременную память. Примеры кода показывают, как делать эмбеддинги, индексировать, дистиллировать факты и сжимать контекст, чтобы диалоги оставались быстрыми и релевантными.
Ключевые компоненты
- Лёгкая LLM для генерации и дистилляции
- Эмбеддинги от sentence-transformers (MiniLM) для семантических векторов
- FAISS для быстрого поиска по похожести и извлечения памяти
- Этап дистилляции, который решает, что сохранять в долговременной памяти
- Периодическая суммаризация для сжатия краткосрочного контекста
Установка и импорты
!pip -q install transformers accelerate bitsandbytes sentence-transformers faiss-cpu
import os, json, time, uuid, math, re
from datetime import datetime
import torch, faiss
from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline, BitsAndBytesConfig
from sentence_transformers import SentenceTransformer
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
Загрузка LLM
def load_llm(model_name="TinyLlama/TinyLlama-1.1B-Chat-v1.0"):
try:
if DEVICE=="cuda":
bnb=BitsAndBytesConfig(load_in_4bit=True,bnb_4bit_compute_dtype=torch.bfloat16,bnb_4bit_quant_type="nf4")
tok=AutoTokenizer.from_pretrained(model_name, use_fast=True)
mdl=AutoModelForCausalLM.from_pretrained(model_name, quantization_config=bnb, device_map="auto")
else:
tok=AutoTokenizer.from_pretrained(model_name, use_fast=True)
mdl=AutoModelForCausalLM.from_pretrained(model_name, torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32, low_cpu_mem_usage=True)
return pipeline("text-generation", model=mdl, tokenizer=tok, device=0 if DEVICE=="cuda" else -1, do_sample=True)
except Exception as e:
raise RuntimeError(f"Failed to load LLM: {e}")
Векторная долговременная память
class VectorMemory:
def __init__(self, path="/content/agent_memory.json", dim=384):
self.path=path; self.dim=dim; self.items=[]
self.embedder=SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2", device=DEVICE)
self.index=faiss.IndexFlatIP(dim)
if os.path.exists(path):
data=json.load(open(path))
self.items=data.get("items",[])
if self.items:
X=torch.tensor([x["emb"] for x in self.items], dtype=torch.float32).numpy()
self.index.add(X)
def _emb(self, text):
v=self.embedder.encode([text], normalize_embeddings=True)[0]
return v.tolist()
def add(self, text, meta=None):
e=self._emb(text); self.index.add(torch.tensor([e]).numpy())
rec={"id":str(uuid.uuid4()),"text":text,"meta":meta or {}, "emb":e}
self.items.append(rec); self._save(); return rec["id"]
def search(self, query, k=5, thresh=0.25):
if len(self.items)==0: return []
q=self.embedder.encode([query], normalize_embeddings=True)
D,I=self.index.search(q, min(k, len(self.items)))
out=[]
for d,i in zip(D[0],I[0]):
if i==-1: continue
if d>=thresh: out.append((d,self.items[i]))
return out
def _save(self):
slim=[{k:v for k,v in it.items()} for it in self.items]
json.dump({"items":slim}, open(self.path,"w"), indent=2)
Хелперы, подсказки и дистилляция
def now_iso(): return datetime.now().isoformat(timespec="seconds")
def clamp(txt, n=1600): return txt if len(txt)<=n else txt[:n]+" …"
def strip_json(s):
m=re.search(r"\{.*\}", s, flags=re.S);
return m.group(0) if m else None
SYS_GUIDE = (
"You are a helpful, concise assistant with memory. Use provided MEMORY when relevant. "
"Prefer facts from MEMORY over guesses. Answer directly; keep code blocks tight. If unsure, say so."
)
SUMMARIZE_PROMPT = lambda convo: f"Summarize the conversation below in 4-6 bullet points focusing on stable facts and tasks:\n\n{convo}\n\nSummary:"
DISTILL_PROMPT = lambda user: (
f"""Decide if the USER text contains durable info worth long-term memory (preferences, identity, projects, deadlines, facts).
Return compact JSON only: {{"save": true/false, "memory": "one-sentence memory"}}.
USER: {user}""")
MemoryAgent и оркестрация
class MemoryAgent:
def __init__(self):
self.llm=load_llm()
self.mem=VectorMemory()
self.turns=[]
self.summary=""
self.max_turns=10
def _gen(self, prompt, max_new_tokens=256, temp=0.7):
out=self.llm(prompt, max_new_tokens=max_new_tokens, temperature=temp, top_p=0.95, num_return_sequences=1, pad_token_id=self.llm.tokenizer.eos_token_id)[0]["generated_text"]
return out[len(prompt):].strip() if out.startswith(prompt) else out.strip()
def _chat_prompt(self, user, memory_context):
convo="\n".join([f"{r.upper()}: {t}" for r,t in self.turns[-8:]])
sys=f"System: {SYS_GUIDE}\nTime: {now_iso()}\n\n"
mem = f"MEMORY (relevant excerpts):\n{memory_context}\n\n" if memory_context else ""
summ=f"CONTEXT SUMMARY:\n{self.summary}\n\n" if self.summary else ""
return sys+mem+summ+convo+f"\nUSER: {user}\nASSISTANT:"
def _distill_and_store(self, user):
try:
raw=self._gen(DISTILL_PROMPT(user), max_new_tokens=120, temp=0.1)
js=strip_json(raw)
if js:
obj=json.loads(js)
if obj.get("save") and obj.get("memory"):
self.mem.add(obj["memory"], {"ts":now_iso(),"source":"distilled"})
return True, obj["memory"]
except Exception: pass
if re.search(r"\b(my name is|call me|I like|deadline|due|email|phone|working on|prefer|timezone|birthday|goal|exam)\b", user, flags=re.I):
m=f"User said: {clamp(user,120)}"
self.mem.add(m, {"ts":now_iso(),"source":"heuristic"})
return True, m
return False, ""
def _maybe_summarize(self):
if len(self.turns)>self.max_turns:
convo="\n".join([f"{r}: {t}" for r,t in self.turns])
s=self._gen(SUMMARIZE_PROMPT(clamp(convo, 3500)), max_new_tokens=180, temp=0.2)
self.summary=s; self.turns=self.turns[-4:]
def recall(self, query, k=5):
hits=self.mem.search(query, k=k)
return "\n".join([f"- ({d:.2f}) {h['text']} [meta={h['meta']}]" for d,h in hits])
def ask(self, user):
self.turns.append(("user", user))
saved, memline = self._distill_and_store(user)
mem_ctx=self.recall(user, k=6)
prompt=self._chat_prompt(user, mem_ctx)
reply=self._gen(prompt)
self.turns.append(("assistant", reply))
self._maybe_summarize()
status=f" memory_saved: {saved}; " + (f"note: {memline}" if saved else "note: -")
print(f"\nUSER: {user}\nASSISTANT: {reply}\n{status}")
return reply
Запуск агента
agent=MemoryAgent()
print(" Agent ready. Try these:\n")
agent.ask("Hi! My name is Nicolaus, I prefer being called Nik. I'm preparing for UPSC in 2027.")
agent.ask("Also, I work at Visa in analytics and love concise answers.")
agent.ask("What's my exam year and how should you address me next time?")
agent.ask("Reminder: I like agentic RAG tutorials with single-file Colab code.")
agent.ask("Given my prefs, suggest a study focus for this week in one paragraph.")
Агент сохраняет дистиллированные факты на диск, извлекает релевантную память по запросу и суммирует разговоры, когда буфер краткосрочных сообщений растёт. Такой подход делает помощника более персональным и последовательным, а также служит фундаментом для расширения схем памяти, добавления контроля доступа или замены эмбеддингов и LLM для других компромиссов.