Як побудувати агента зі стійкою пам’яттю для багатосеансових LLM додатків

Як створити AI-агента з довгостроковою пам'яттю для збереження контексту між кількома сесіями LLM

Більшість LLM-чатботів страждають від «амнезії» — кожен новий сеанс розпочинається з чистого аркуша, і користувач змушений щоразу пояснювати контекст заново. Цей туторіал покаже, як побудувати агента, який пам’ятає факти між сесіями, використовуючи векторну базу даних і LangChain. Реалізація займе близько 2-3 годин, якщо ти вже маєш базовий досвід із Python. Для старту потрібні акаунти OpenAI та Pinecone, а також Python 3.11+.

🛠️ Що знадобиться

  • Python 3.11+ — основна мова реалізації; переконайся, що встановлено через python --version
  • OpenAI API (GPT-4o) — мозок агента; потрібен платний акаунт, приблизно $5 вистачить для розробки
  • Pinecone — безкоштовний план дозволяє зберігати до 100K векторів, цього достатньо для старту
  • LangChain 0.3+ — фреймворк для оркестрації агента та пам’яті; безкоштовний, відкритий код
  • LangGraph — бібліотека для побудови stateful-агентів поверх LangChain; безкоштовна
  • Redis (локально або Upstash) — зберігає короткострокову пам’ять сесії; Upstash має безкоштовний tier

📋 Покрокова інструкція

Крок 1: Налаштування середовища та встановлення залежностей

Відкрий термінал і створи нову папку проєкту: mkdir memory-agent && cd memory-agent. Потім створи віртуальне середовище командою python -m venv venv і активуй його: на macOS/Linux — source venv/bin/activate, на Windows — venv\Scripts\activate. Встанови всі необхідні пакети однією командою: pip install langchain==0.3.* langgraph openai pinecone-client redis python-dotenv tiktoken. Створи файл .env у корені проєкту і додай туди три рядки: OPENAI_API_KEY=sk-..., PINECONE_API_KEY=pcsk-..., REDIS_URL=redis://localhost:6379 — ці ключі отримаєш на наступному кроці.

Крок 2: Отримання API-ключів та створення Pinecone індексу

Зайди на platform.openai.com → натисни «API Keys» у лівому меню → клікни «Create new secret key» → скопіюй ключ і встав у .env. Далі зайди на app.pinecone.io → після реєстрації натисни «Create Index» → введи назву agent-memory → обери «Dimensions: 1536» (це відповідає моделі text-embedding-3-small) → «Metric: cosine» → натисни «Create Index». Після створення перейди в «API Keys» у лівому сайдбарі → скопіюй ключ у .env. Підводний камінь: безкоштовний план Pinecone дозволяє лише один індекс — не створюй зайвих під час тестів.

Крок 3: Реалізація модуля пам’яті

Створи файл memory.py і встав наступний код. Цей модуль відповідає за запис і читання довгострокової пам’яті через Pinecone:

from pinecone import Pinecone
from openai import OpenAI
from dotenv import load_dotenv
import os, uuid, json
from datetime import datetime

load_dotenv()

pc = Pinecone(api_key=os.getenv("PINECONE_API_KEY"))
index = pc.Index("agent-memory")
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

def embed_text(text: str) -> list:
    response = client.embeddings.create(
        model="text-embedding-3-small",
        input=text
    )
    return response.data[0].embedding

def save_memory(user_id: str, content: str, memory_type: str = "fact"):
    vector = embed_text(content)
    metadata = {
        "user_id": user_id,
        "content": content,
        "type": memory_type,
        "timestamp": datetime.utcnow().isoformat()
    }
    index.upsert(vectors=[{
        "id": str(uuid.uuid4()),
        "values": vector,
        "metadata": metadata
    }], namespace=user_id)

def recall_memory(user_id: str, query: str, top_k: int = 5) -> list:
    vector = embed_text(query)
    results = index.query(
        vector=vector,
        top_k=top_k,
        namespace=user_id,
        include_metadata=True
    )
    return [match.metadata["content"] for match in results.matches]

Функція save_memory перетворює текст на вектор і зберігає його в Pinecone з прив’язкою до конкретного user_id через namespace — це дозволяє ізолювати пам’ять різних користувачів.

Крок 4: Побудова агента з LangGraph

Створи файл agent.py. Тут ми зберемо агента, який перед кожною відповіддю підтягує релевантні спогади і після кожного обміну зберігає нові факти:

from langgraph.graph import StateGraph, END
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage
from memory import save_memory, recall_memory
from typing import TypedDict, List
import os
from dotenv import load_dotenv

load_dotenv()

llm = ChatOpenAI(model="gpt-4o", temperature=0.7,
                 api_key=os.getenv("OPENAI_API_KEY"))

class AgentState(TypedDict):
    user_id: str
    user_input: str
    memories: List[str]
    response: str

def retrieve_memories(state: AgentState) -> AgentState:
    memories = recall_memory(state["user_id"], state["user_input"])
    state["memories"] = memories
    return state

def generate_response(state: AgentState) -> AgentState:
    memory_context = "\n".join(f"- {m}" for m in state["memories"])
    system_prompt = f"""Ти корисний асистент з довгостроковою пам'яттю.
Ось що ти пам'ятаєш про цього користувача:
{memory_context if memory_context else 'Поки що нічого.'}
Використовуй ці знання у відповідях природно."""

    messages = [
        SystemMessage(content=system_prompt),
        HumanMessage(content=state["user_input"])
    ]
    result = llm.invoke(messages)
    state["response"] = result.content
    return state

def store_memory(state: AgentState) -> AgentState:
    combined = f"Користувач сказав: {state['user_input']}. Відповідь: {state['response']}"
    save_memory(state["user_id"], combined)
    return state

graph = StateGraph(AgentState)
graph.add_node("retrieve", retrieve_memories)
graph.add_node("generate", generate_response)
graph.add_node("store", store_memory)
graph.set_entry_point("retrieve")
graph.add_edge("retrieve", "generate")
graph.add_edge("generate", "store")
graph.add_edge("store", END)
agent = graph.compile()

def chat(user_id: str, message: str) -> str:
    result = agent.invoke({
        "user_id": user_id,
        "user_input": message,
        "memories": [],
        "response": ""
    })
    return result["response"]

Крок 5: Тестування та запуск агента

Створи файл main.py для інтерактивного чату та запусти його командою python main.py:

from agent import chat

USER_ID = "user_ukraine_001"

print("Агент з пам'яттю запущено. Введи 'вихід' для завершення.\n")
while True:
    user_input = input("Ти: ").strip()
    if user_input.lower() in ["вихід", "exit", "quit"]:
        break
    response = chat(USER_ID, user_input)
    print(f"Агент: {response}\n")

Протестуй стійкість пам’яті: у першій сесії скажи агенту «Мене звати Олексій, я розробник із Києва». Зупини програму (Ctrl+C) і запусти знову. Напиши просто «Що ти знаєш про мене?» — агент має відповісти, використовуючи збережені факти. Якщо відповідь містить твоє ім’я та місто — система пам’яті працює коректно.

⚠️ Типові помилки та як їх уникнути

  • Неправильна розмірність векторів — якщо обрав модель text-embedding-ada-002 (1536 dims) але в Pinecone вказав 768, отримаєш помилку. Завжди перевіряй: text-embedding-3-small = 1536, text-embedding-3-large = 3072
  • Накопичення нерелевантної пам’яті — без фільтрації агент зберігає все підряд, включно з дрібницями. Додай логіку: зберігай лише повідомлення довжиною понад 20 символів і лише якщо вони містять факти (можна попередньо класифікувати через LLM)
  • Витік пам’яті між користувачами — якщо забудеш передавати namespace=user_id у Pinecone-запитах, всі користувачі бачитимуть чужі дані. Перевір кожен виклик index.query() і index.upsert()
  • Перевищення контекстного вікна — якщо top_k=20 і кожен спогад довгий, system prompt може перевищити ліміт токенів. Тримай top_k у межах 5-7 і обрізай текст спогадів до 200 символів

💡 Поради для кращого результату

По-перше, додай окремий тип пам’яті для «ключових фактів» — зберігай їх з вищим пріоритетом через додаткове поле importance: "high" у metadata і фільтруй за ним у першу чергу. По-друге, раз на тиждень запускай «консолідацію» — передавай всі спогади користувача в GPT-4o з проханням стиснути їх у короткий профіль (200 слів), і зберігай цей профіль як окремий вектор з типом summary. По-третє, для продакшну додай TTL-логіку: зберігай у metadata поле expires_at і видаляй застарілі спогади через Pinecone Metadata Filtering. По-четверте, логуй усі операції з пам’яттю у файл — це допоможе дебажити випадки, коли агент «забув» важливе або запам’ятав зайве.

❓ Часті запитання (FAQ)

1. Чи можна замінити Pinecone на безкоштовну альтернативу?
Так, Chroma або Qdrant чудово підходять для локальної розробки. Встанови Qdrant через Docker: docker run -p 6333:6333 qdrant/qdrant, а потім заміни pinecone-client на qdrant-client у коді. Для продакшну Pinecone все одно надійніший через керований хостинг.

2. Як обмежити доступ до чужої пам’яті?
Використовуй namespace у Pinecone — кожен user_id отримує ізольований простір. Додатково хеш user_id перед використанням як namespace: import hashlib; ns = hashlib.sha256(user_id.encode()).hexdigest()[:16] — це приховає реальні ідентифікатори.

3. Скільки коштуватиме в продакшні на 1000 користувачів?
Pinecone Starter безкоштовний до 100K векторів — це приблизно 20 спогадів на користувача. OpenAI embeddings коштують $0.02 за 1M токенів — практично безкоштовно. Основні витрати — GPT-4o: ~$0.01-0.05 за розмову залежно від довжини.

4. Що робити, якщо агент «галюцинує» спогади?
Додай score-threshold у Pinecone query: якщо match.score < 0.75 — не передавай цей спогад у контекст. Низький score означає, що знайдений вектор мало схожий на запит, і краще нічого не підказати, ніж підказати невірне.

5. Як додати підтримку кількох мов у пам’яті?
Модель text-embedding-3-small вже підтримує мультимовність — вона коректно знаходить семантично схожі тексти навіть якщо запит і спогад написані різними мовами. Нічого додатково налаштовувати не потрібно.

🏁 Підсумок

Ти побудував повноцінного LLM-агента з персистентною пам’яттю: агент зберігає факти між сесіями у векторній базі Pinecone, семантично шукає релевантні спогади перед кожною відповіддю, і керується LangGraph-графом для чіткого розподілу відповідальності між вузлами retrieve → generate → store.

Починай прямо зараз із кроку 2 — створи акаунти на OpenAI і Pinecone, отримай ключі і запусти базову версію. Як тільки побачиш, що агент «пам’ятає» тебе між запусками програми — додавай власну логіку фільтрації та консолідації спогадів.

РОЗСИЛКА

📬 Щотижневий AI-дайджест

Найкращі статті про ШІ та автоматизацію — без спаму, лише суть

Без спаму · Відписатись будь-коли

Telegram