Галоўная / Артыкулы

Храненне довгастраўнай памяці між ніткамі ў LangGraph

Даўнэчасовая памяць у LangGraph: зберагчык іменаваных прастораў, функціі падставы/зямлі/пошуку, семантычны пошук у памяці, адмоўкі пад час запісу і стойкасць за дапамогою Postgres.

Выдавец: REACTAPP.TOP19 хв чытання4349 слоў

Крыніцы: Long Term Memory in LangGraph| Part 16| class 24 of LangGraph | cswithshahil

Чаму короткатэрмінная памяць задання недастаткова

Історыя чата, якая фіксуецца па пунктах, апавярэджае толькі адно задання. Рэальныя корыстувачы пераходзяць між разнымі заданнямі — у паўтаку дыягностуюць LangChain, у вторник плануюць паўтарку — таму інформацыя пра яных рассеюецца. Дзейная памяць (LTM) стварае надзеямуючыся базу даных: экстрактар выкарыстоўвае персональныя дакладніцы з кожнага крока, а пазней, пры адпаведзе на запит, выкарыстоўвае гэту базу пры рашэнні. Якщо раз вы выбераеце Python, а пазней запытваеце пра Фібаначчі, асистэнт можа зноў выкарыстоўваць гэты выбор без дадатковых паведамленняў.

Базы даных як паверхня памяці LangGraph

LangGraph рэалізуе дзейную памяць чераз базы даных. BaseStore визначае функціі стварэння, пошуку, рэдагавання і выдалення дадзеных. Паўзучыя рэшэнні: InMemoryStore для пратыпаў у RAM; PostgresStore і RedisStore, калі паўтарны запуск не должен стыраць памяць.

Аперэйшы самастоятельных баз даных

Спрытваець прасторы імена як папкі. Яны ў Python — туплы: ("users", "U1") або болей глыбокія структура ("users", "U1", "profile").

Ствараюцца за дапамою put(namespace, key, values). Адзін элемент можна выявіць за дапамою get, калі вядома клучовая значэнне; спіс прастора імена — за дапамою search.

from langgraph.store.memory import InMemoryStore
# creating a store
store = InMemoryStore()
# creating a namespace
namespace = ("user", "u1")

Стварэнне спомнаў

# Adding memories
store.put(namespace, "1", {"data": "User likes pizza"})
store.put(namespace, "2", {"data": "User prefers dark mode"})
# another namespace
namespace2 = ("user", "u2")

# Add memories
store.put(namespace2, "1", {"data": "User likes pasta"})
store.put(namespace2, "2", {"data": "User prefers grid style navigation"})

Выявленне адного спомна

# store.get(namespace, key)
store.get(namespace2, "2")
Item(namespace=['user', 'u2'], key='2', value={'data': 'User prefers grid style navigation'}, created_at='2026-01-06T15:05:16.376069+00:00', updated_at='2026-01-06T15:05:16.376070+00:00')

Выявленне всього ў прасторы імена

items = store.search(namespace2)

for item in items:
    print(item.value)
{'data': 'User likes pasta'}
{'data': 'User prefers grid style navigation'}

Семантычны пошук, калі колькасць дадзеных зрастае

Увядзенне ста фактав пра профіль у запит перагружвае модель. Індексавайце храніцу за дапамою моделі імбеддынга (на прыклад, text-embedding-3-small), каб search могла прыймаць query і limit, вяртаючы толькі вектары, блізкія да текущага запиту.

Semantic Search
from langchain_openai import OpenAIEmbeddings
from dotenv import load_dotenv

load_dotenv()

embedding_model = OpenAIEmbeddings(model='text-embedding-3-small')
store = InMemoryStore(index={'embed': embedding_model, 'dims':1536})
namespace = ('users', 'u1')
store.put(namespace, "1", {"data": "User prefers concise answers over long explanations"})
store.put(namespace, "2", {"data": "User likes examples in Python"})
store.put(namespace, "3", {"data": "User usually works late at night"})
store.put(namespace, "4", {"data": "User prefers dark mode in applications"})
store.put(namespace, "5", {"data": "User is learning machine learning"})
store.put(namespace, "6", {"data": "User dislikes overly theoretical explanations"})
store.put(namespace, "7", {"data": "User prefers step-by-step reasoning"})
store.put(namespace, "8", {"data": "User is based in India"})
store.put(namespace, "9", {"data": "User likes real-world analogies"})
store.put(namespace, "10", {"data": "User prefers bullet points over paragraphs"})
items = store.search(namespace, query="what is the user currently learning", limit=1)

for item in items:
    print(item.value)
# output :{'data': 'User is learning machine learning'}
items = store.search(namespace, query="what are user's preferences", limit=3)

for item in items:
    print(item.value)
# output :
# {'data': 'User prefers dark mode in applications'}
# {'data': 'User prefers bullet points over paragraphs'}
# {'data': 'User prefers concise answers over long explanations'}

Чытанне толькі: персоналізацыя ў вузеле чату

Паўяжыце храніцу да вузла чату LangGraph, які прыме state, config (ID пользователя або тэаду) і store. Створыце прастор імена на аднойчыні з ID пользователя, абшукайце спамяты, запісайце іх у системны запит і паведаміце модель выкорыстоўваць вядомыя імены, проекты і тон — а таксама калькі актуальных дапаможніх пунктав.

Chatbot Reading Existing Memories
from dotenv import load_dotenv
load_dotenv()

from langchain_openai import ChatOpenAI
from langchain_core.runnables import RunnableConfig
from langchain_core.messages import SystemMessage

from langgraph.graph import StateGraph, START, END, MessagesState
from langgraph.store.memory import InMemoryStore
from langgraph.store.base import BaseStore
# ----------------------------
# 1) Create LTM store + seed memories (done BEFORE running the graph)
# ----------------------------
store = InMemoryStore()

user_id = "u1"

# Store user details as a single blob (simple for teaching)
# You can also split into multiple records; this keeps it easy.
user_details = ("user", user_id, "details")

store.put(user_details, "profile_1", {"data": "Name: Nitish"})
store.put(user_details, "profile_2", {"data": "Profession: Teaches AI on YouTube"})
store.put(user_details, "preference_1", {"data": "Prefers concise answers"})
store.put(user_details, "preference_2", {"data": "Likes examples in Python"})
store.put(user_details, "project_1", {"data": "Building MCP servers (Python-based project)"})
# ----------------------------
# 2) System prompt template (your prompt)
# ----------------------------
SYSTEM_PROMPT_TEMPLATE = """You are a helpful assistant with memory capabilities.
If user-specific memory is available, use it to personalize
your responses based on what you know about the user.

Your goal is to provide relevant, friendly, and tailored
assistance that reflects the user’s preferences, context, and past interactions.

If the user’s name or relevant personal context is available, always personalize your responses by:
    – Always Address the user by name (e.g., "Sure, Nitish...") when appropriate
    – Referencing known projects, tools, or preferences (e.g., "your MCP  server python based project")
    – Adjusting the tone to feel friendly, natural, and directly aimed at the user

Avoid generic phrasing when personalization is possible. For example, instead of "In TypeScript apps..."
say "Since your project is built with TypeScript..."

Use personalization especially in:
    – Greetings and transitions
    – Help or guidance tailored to tools and frameworks the user uses
    – Follow-up messages that continue from past context

Always ensure that personalization is based only on known user details and not assumed.

In the end suggest 3 relevant further questions based on the current response and user profile

The user’s memory (which may be empty) is provided as: {user_details_content}
"""
# ----------------------------
# 3) Build graph: START -> chat -> END (read-only LTM)
# ----------------------------
llm = ChatOpenAI(model="gpt-4o-mini")
def chat_node(state: MessagesState, config: RunnableConfig, store: BaseStore):

    user_id = config["configurable"]["user_id"]

    # Read-only: fetch user details memory (no writes)
    user_details = ("user", user_id, "details")
    items = store.search(user_details)

    # Convert memory items into a string blob for {user_details_content}
    # Keep it dead simple for teaching.
    if items:
        user_details_content = "\n".join(f"- {it.value.get('data', '')}" for it in items)
    else:
        user_details_content = ""  # prompt says it may be empty

    system_prompt = SYSTEM_PROMPT_TEMPLATE.format(
        user_details_content=user_details_content
    )

    system_msg = SystemMessage(content=system_prompt)

    response = llm.invoke([system_msg] + state["messages"])
    return {"messages": [response]}
builder = StateGraph(MessagesState)
builder.add_node("chat", chat_node)
builder.add_edge(START, "chat")
builder.add_edge("chat", END)

graph = builder.compile(store=store)

graph

# ----------------------------
# 4) Run it (provide user_id in config)
# ----------------------------
config = {"configurable": {"user_id": "u1"}}

result = graph.invoke(
    {"messages": [{"role": "user", "content": "Explain gen ai in simple terms."}]},
    config,
)

print(result["messages"][-1].content)
# Sure, Nitish! Generative AI, or Gen AI, refers to a type of artificial intelligence that creates new content or data instead of just analyzing existing information. It can generate text, images, music, and more by learning patterns from large sets of data. For example, it can write a story, design an artwork, or compose a song based on the styles of various creators it has learned from.

#If you're considering using examples or applications of Gen AI for your YouTube teaching, there are plenty of interesting case studies you could explore, especially in creative fields.

#Here are a few questions to consider:

#1. Would you like to see specific examples of Gen AI in Python?
#2. Are you interested in exploring tools or libraries for building Gen AI projects?
#3. How would you like to incorporate Gen AI concepts into your teaching materials?

Стварэнне новых спамятаў пад час чату

Chatbot Creating New Memories
from dotenv import load_dotenv
load_dotenv()

import uuid
from typing import List
from pydantic import BaseModel, Field

from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage
from langchain_core.runnables import RunnableConfig

from langgraph.graph import StateGraph, START, END, MessagesState
from langgraph.store.memory import InMemoryStore
from langgraph.store.base import BaseStore
# ----------------------------
# 1) LTM store
# ----------------------------
store = InMemoryStore()
# ----------------------------
# 2) LLM that decides what to remember (structured output)
# ----------------------------
extractor_llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
class MemoryDecision(BaseModel):
    should_write: bool = Field(description="Whether to store any memories")
    memories: List[str] = Field(default_factory=list, description="Atomic user memories to store")
memory_extractor = extractor_llm.with_structured_output(MemoryDecision)
# ----------------------------
# 3) Graph: START -> remember -> END
#    (Creates memories, but does NOT use them to answer)
# ----------------------------
def remember_only_node(state: MessagesState, config: RunnableConfig, store: BaseStore):

    user_id = config["configurable"]["user_id"]

    namespace = ("user", user_id, "details")

    # take latest user message
    last_msg = state["messages"][-1].content

    # LLM decides what to store
    decision: MemoryDecision = memory_extractor.invoke(
        [
            SystemMessage(
                content=(
                    "Extract LONG-TERM memories from the user's message.\n"
                    "Only store stable, user-specific info (identity, preferences, ongoing projects).\n"
                    "Do NOT store transient info.\n"
                    "Return should_write=false if nothing is worth storing.\n"
                    "Each memory should be a short atomic sentence."
                )
            ),
            {"role": "user", "content": last_msg},
        ]
    )

    # Write to store (LTM)
    if decision.should_write:
        for mem in decision.memories:
            store.put(namespace, str(uuid.uuid4()), {"data": mem})

    # IMPORTANT: we are NOT using memory, not even responding with the LLM.
    # We just return a fixed acknowledgement.
    return {"messages": [{"role": "assistant", "content": "Noted."}]}
builder = StateGraph(MessagesState)
builder.add_node("remember", remember_only_node)
builder.add_edge(START, "remember")
builder.add_edge("remember", END)

graph = builder.compile(store=store)

graph

# ----------------------------
# 4) Demo
# ----------------------------
config = {"configurable": {"user_id": "u1"}}

res = graph.invoke({"messages": [{"role": "user", "content": "Hi my name is nitish"}]},config)
print("Assistant:", res["messages"][-1].content)
#output : Assistant: Noted.
res = graph.invoke({"messages": [{"role": "user", "content": "I teach AI on youtube"}]},config)
print("Assistant:", res["messages"][-1].content)
#output :Assistant: Noted.
res = graph.invoke({"messages": [{"role": "user", "content": "My favorite programming language is Python"}]},config)
print("Assistant:", res["messages"][-1].content)
# output :Assistant: Noted.
items = store.search(("user", "u1", "details"))

for item in items:
    print(item.value['data'])
#output:
#  User's name is Nitish.
#  User teaches AI on YouTube.
 # User's favorite programming language is Python.
  #User's name is Nitish.
#  User teaches AI on YouTube.
#  User's favorite programming language is Python.

Для аўтаматычнага запісу патрэбны вузлы “памяць” пры перадачы паведамленняў у чат. Спецыяльны модель экстрактара вярта структураваны выхід MemoryDecision: чы гэта трэба запісаць і якія элементы. Без усунення дуплікацый павтарэнне фразы “мае імя ўсё тое ж…” стварае клонаваныя элементы. Апградуйце элементы за дапамою флага is_new, ўжываючы іх у супрацоўке з існуючымі даннымі памяці, і захаваўце толькі новыя.

Chatbot Creating New Memories(without Duplication)
from dotenv import load_dotenv
load_dotenv()

import uuid
from typing import List
from pydantic import BaseModel, Field

from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage
from langchain_core.runnables import RunnableConfig

from langgraph.graph import StateGraph, START, END, MessagesState
from langgraph.store.memory import InMemoryStore
from langgraph.store.base import BaseStore
# ----------------------------
# 1) LTM store
# ----------------------------
store = InMemoryStore()
# ----------------------------
# 2) LLMs
#    - memory_llm: extracts candidate memories + tells if each is NEW (no duplicate_of needed)
# ----------------------------
memory_llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
class MemoryItem(BaseModel):
    text: str = Field(description="Atomic user memory as a short sentence")
    is_new: bool = Field(description="True if this memory is NEW and should be stored. False if duplicate/already known.")
class MemoryDecision(BaseModel):
    should_write: bool = Field(description="Whether to store any memories")
    memories: List[MemoryItem] = Field(default_factory=list, description="Atomic user memories to store")
memory_extractor = memory_llm.with_structured_output(MemoryDecision)
MEMORY_PROMPT = """You are responsible for updating and maintaining accurate user memory.

CURRENT USER DETAILS (existing memories):
{user_details_content}

TASK:
- Review the user's latest message.
- Extract user-specific info worth storing long-term (identity, stable preferences, ongoing projects/goals).
- For each extracted item, set is_new=true ONLY if it adds NEW information compared to CURRENT USER DETAILS.
- If it is basically the same meaning as something already present, set is_new=false.
- Keep each memory as a short atomic sentence.
- No speculation; only facts stated by the user.
- If there is nothing memory-worthy, return an empty list.
"""
def chat_creates_memory_node(state: MessagesState, config: RunnableConfig, store: BaseStore):

    user_id = config["configurable"]["user_id"]

    namespace = ("user", user_id, "details")

    # A) Load existing memories
    existing_items = store.search(namespace)
    existing_texts = [it.value.get("data", "") for it in existing_items if it.value.get("data")]
    user_details_content = "\n".join(f"- {t}" for t in existing_texts) if existing_texts else "(empty)"

    # B) Latest user message
    last_text = state["messages"][-1]

    # C) LLM extracts memories + marks new vs duplicate
    decision: MemoryDecision = memory_extractor.invoke(
        [
            SystemMessage(content=MEMORY_PROMPT.format(user_details_content=user_details_content)),
            {"role": "user", "content": f"USER MESSAGE:\n{last_text}"},
        ]
    )

    # D) Store ONLY new memories
    if decision.should_write:
        for mem in decision.memories:
            if mem.is_new:
                store.put(namespace, str(uuid.uuid4()), {"data": mem.text})

    return {"messages": [{"role": "assistant", "content": "Noted."}]}
# ----------------------------
# 4) Build graph: START -> chat -> END
# ----------------------------
builder = StateGraph(MessagesState)
builder.add_node("chat", chat_creates_memory_node)
builder.add_edge(START, "chat")
builder.add_edge("chat", END)

graph = builder.compile(store=store)
config = {"configurable": {"user_id": "u1"}}
r1 = graph.invoke({"messages": [{"role": "user", "content": "My name is Nitish"}]}, config)
print("Assistant:", r1["messages"][-1].content)
# output: Assistant: Noted.
# Likely duplicate of the first memory -> is_new should be false -> saved=0
r2 = graph.invoke({"messages": [{"role": "user", "content": "I like Python for programming."}]}, config)
print("\nAssistant:", r2["messages"][-1].content)
# output:Assistant: Noted.
for it in store.search(("user", "u1", "details")):
    print(it.value['data'])
# output:The user's name is Nitish.
# output:Nitish likes Python for programming.

З’еднаны граф і захаванне дадзеных

Фінальная структура: пачатак → запамятаванне (экстракцыя і зберагчыце) → чат (ачытанне і адпаведзь) → заканчэнне. Агент выучаецца пад час розмовы і негайна вжывае новыя факты.

Merged Workflow
from dotenv import load_dotenv
load_dotenv()

import uuid
from typing import List
from pydantic import BaseModel, Field

from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage
from langchain_core.runnables import RunnableConfig

from langgraph.graph import StateGraph, START, END, MessagesState
from langgraph.store.memory import InMemoryStore
from langgraph.store.base import BaseStore
# ----------------------------
# 1) LTM store (START EMPTY)
# ----------------------------
store = InMemoryStore()
# ----------------------------
# 2) System prompt
# ----------------------------
SYSTEM_PROMPT_TEMPLATE = """You are a helpful assistant with memory capabilities.
If user-specific memory is available, use it to personalize
your responses based on what you know about the user.

Your goal is to provide relevant, friendly, and tailored
assistance that reflects the user’s preferences, context, and past interactions.

If the user’s name or relevant personal context is available, always personalize your responses by:
    – Always Address the user by name (e.g., "Sure, Nitish...") when appropriate
    – Referencing known projects, tools, or preferences (e.g., "your MCP server python based project")
    – Adjusting the tone to feel friendly, natural, and directly aimed at the user

Avoid generic phrasing when personalization is possible.

Use personalization especially in:
    – Greetings and transitions
    – Help or guidance tailored to tools and frameworks the user uses
    – Follow-up messages that continue from past context

Always ensure that personalization is based only on known user details and not assumed.

In the end suggest 3 relevant further questions based on the current response and user profile

The user’s memory (which may be empty) is provided as: {user_details_content}
"""
# ----------------------------
# 3) Memory extraction LLM
# ----------------------------
memory_llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
class MemoryItem(BaseModel):
    text: str = Field(description="Atomic user memory")
    is_new: bool = Field(description="True if new, false if duplicate")
class MemoryDecision(BaseModel):
    should_write: bool
    memories: List[MemoryItem] = Field(default_factory=list)
memory_extractor = memory_llm.with_structured_output(MemoryDecision)
MEMORY_PROMPT = """You are responsible for updating and maintaining accurate user memory.

CURRENT USER DETAILS (existing memories):
{user_details_content}

TASK:
- Review the user's latest message.
- Extract user-specific info worth storing long-term (identity, stable preferences, ongoing projects/goals).
- For each extracted item, set is_new=true ONLY if it adds NEW information compared to CURRENT USER DETAILS.
- If it is basically the same meaning as something already present, set is_new=false.
- Keep each memory as a short atomic sentence.
- No speculation; only facts stated by the user.
- If there is nothing memory-worthy, return should_write=false and an empty list.
"""
# ----------------------------
# 4) Node 1: remember
# ----------------------------
def remember_node(state: MessagesState, config: RunnableConfig, *, store: BaseStore):
    user_id = config["configurable"]["user_id"]
    ns = ("user", user_id, "details")

    # existing memory
    items = store.search(ns)
    existing = "\n".join(it.value["data"] for it in items) if items else "(empty)"

    # last user message
    last_msg = state["messages"][-1].content

    decision: MemoryDecision = memory_extractor.invoke(
        [
            SystemMessage(content=MEMORY_PROMPT.format(user_details_content=existing)),
            {"role": "user", "content": last_msg},
        ]
    )

    if decision.should_write:
        for mem in decision.memories:
            if mem.is_new:
                store.put(ns, str(uuid.uuid4()), {"data": mem.text})

    return {}  # no message change
# ----------------------------
# 5) Node 2: chat
# ----------------------------
chat_llm = ChatOpenAI(model="gpt-4o-mini")
def chat_node(state: MessagesState, config: RunnableConfig, *, store: BaseStore):
    user_id = config["configurable"]["user_id"]
    ns = ("user", user_id, "details")

    items = store.search(ns)
    user_details = "\n".join(it.value["data"] for it in items) if items else ""

    system_msg = SystemMessage(
        content=SYSTEM_PROMPT_TEMPLATE.format(
            user_details_content=user_details or "(empty)"
        )
    )

    response = chat_llm.invoke([system_msg] + state["messages"])
    return {"messages": [response]}
# ----------------------------
# 6) Graph
# ----------------------------
builder = StateGraph(MessagesState)
builder.add_node("remember", remember_node)
builder.add_node("chat", chat_node)

builder.add_edge(START, "remember")
builder.add_edge("remember", "chat")
builder.add_edge("chat", END)

graph = builder.compile(store=store)

graph

# ----------------------------
# 7) Demo
# ----------------------------
config = {"configurable": {"user_id": "u1"}}
result = graph.invoke({"messages": [{"role": "user", "content": "Hi, my name is Nitish"}]}, config)
result['messages'][-1].content
# output:'Hi Nitish! It’s great to meet you. How can I assist you today?'

for it in store.search(("user", "u1", "details")):
    print(it.value["data"])
result = graph.invoke({"messages": [{"role": "user", "content": "I teach AI on YouTube"}]}, config)
print(result['messages'][-1].content)
# output:That's great, Nitish! Teaching AI on YouTube must be really fulfilling. What topics do you cover in your videos? Are there any specific projects or concepts you find your audience is particularly interested in?
for it in store.search(("user", "u1", "details")):
    print(it.value["data"])
# output:User's name is Nitish.
# output:Nitish teaches AI on YouTube.
result = graph.invoke({"messages": [{"role": "user", "content": "Explain GenAI simply"}]}, config)
print(result['messages'][-1].content)
# output:Sure, Nitish! Generative AI, often referred to as GenAI, is a type of artificial intelligence that can create new content based on the data it's trained on. This includes generating text, images, music, and even video.

# output:Here's a simple breakdown:

# output:1. **Learning from Examples:** GenAI is trained on vast amounts of data, learning the patterns, styles, and structures of the content it sees.

# output:2. **Creating Content:** Once trained, it can generate new content that resembles what it learned. For example, it can write a story in a specific style, create artwork, or compose music.

# output:3. **Applications:** It's used in various fields, from helping writers brainstorm ideas to producing artwork and even customer service chatbots that can engage with users in a conversational manner.

# output:If you're planning to teach this topic on your YouTube channel, emphasizing practical examples and real-world applications could be very engaging for your audience!

Do you want to explore specific applications of GenAI, tools for using it, or perhaps the ethics involved?
for it in store.search(("user", "u1", "details")):
    print(it.value["data"])
# output:User's name is Nitish.
# output:Nitish teaches AI on YouTube.

InMemoryStore занікае, калі процес завершаецца. Запускайце Postgres (часта через Docker), інсталюйце пакеты для чэкпоінтаў/зберагчыця дадзеных Postgres і заменіце ўсё на рэалізацыю, якая базуецца на з’ўязку. Пасля повнага перзапуску запыт да базы дадзеных仍 должны вяртаць захаваныя данні профіля.

services:
  postgres:
    image: postgres:16
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: postgres
    ports:
      - "5442:5432"
! pip install -U "psycopg[binary,pool]" langgraph langgraph-checkpoint-postgres
from dotenv import load_dotenv
load_dotenv()

import uuid
from typing import List
from pydantic import BaseModel, Field

from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage
from langchain_core.runnables import RunnableConfig

from langgraph.graph import StateGraph, START, END, MessagesState
from langgraph.store.postgres import PostgresStore
from langgraph.store.base import BaseStore
# ----------------------------
# 2) System prompt
# ----------------------------
SYSTEM_PROMPT_TEMPLATE = """You are a helpful assistant with memory capabilities.
If user-specific memory is available, use it to personalize
your responses based on what you know about the user.

Your goal is to provide relevant, friendly, and tailored
assistance that reflects the user’s preferences, context, and past interactions.

If the user’s name or relevant personal context is available, always personalize your responses by:
    – Always Address the user by name (e.g., "Sure, Nitish...") when appropriate
    – Referencing known projects, tools, or preferences (e.g., "your MCP server python based project")
    – Adjusting the tone to feel friendly, natural, and directly aimed at the user

Avoid generic phrasing when personalization is possible.

Use personalization especially in:
    – Greetings and transitions
    – Help or guidance tailored to tools and frameworks the user uses
    – Follow-up messages that continue from past context

Always ensure that personalization is based only on known user details and not assumed.

In the end suggest 3 relevant further questions based on the current response and user profile

The user’s memory (which may be empty) is provided as: {user_details_content}
"""
# ----------------------------
# 3) Memory extraction LLM
# ----------------------------
memory_llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
class MemoryItem(BaseModel):
    text: str = Field(description="Atomic user memory")
    is_new: bool = Field(description="True if new, false if duplicate")
class MemoryDecision(BaseModel):
    should_write: bool
    memories: List[MemoryItem] = Field(default_factory=list)
memory_extractor = memory_llm.with_structured_output(MemoryDecision)
MEMORY_PROMPT = """You are responsible for updating and maintaining accurate user memory.

CURRENT USER DETAILS (existing memories):
{user_details_content}

TASK:
- Review the user's latest message.
- Extract user-specific info worth storing long-term (identity, stable preferences, ongoing projects/goals).
- For each extracted item, set is_new=true ONLY if it adds NEW information compared to CURRENT USER DETAILS.
- If it is basically the same meaning as something already present, set is_new=false.
- Keep each memory as a short atomic sentence.
- No speculation; only facts stated by the user.
- If there is nothing memory-worthy, return should_write=false and an empty list.
"""
# ----------------------------
# 3) Nodes
# ----------------------------
def remember_node(state: MessagesState, config: RunnableConfig, *, store: BaseStore):
    user_id = config["configurable"]["user_id"]
    ns = ("user", user_id, "details")

    # existing memory (all items under namespace)
    items = store.search(ns)
    existing = "\n".join(it.value.get("data", "") for it in items) if items else "(empty)"

    # latest user message
    last_text = state["messages"][-1].content

    decision: MemoryDecision = memory_extractor.invoke(
        [
            SystemMessage(content=MEMORY_PROMPT.format(user_details_content=existing)),
            {"role": "user", "content": last_text},
        ]
    )

    if decision.should_write:
        for mem in decision.memories:
            if mem.is_new and mem.text.strip():
                store.put(ns, str(uuid.uuid4()), {"data": mem.text.strip()})

    return {}
chat_llm = ChatOpenAI(model="gpt-4o-mini")
def chat_node(state: MessagesState, config: RunnableConfig, *, store: BaseStore):
    user_id = config["configurable"]["user_id"]
    ns = ("user", user_id, "details")

    items = store.search(ns)
    user_details = "\n".join(it.value.get("data", "") for it in items) if items else ""

    system_msg = SystemMessage(
        content=SYSTEM_PROMPT_TEMPLATE.format(user_details_content=user_details or "(empty)")
    )

    response = chat_llm.invoke([system_msg] + state["messages"])
    return {"messages": [response]}
# ----------------------------
# 4) Build graph
# ----------------------------
builder = StateGraph(MessagesState)
builder.add_node("remember", remember_node)
builder.add_node("chat", chat_node)
builder.add_edge(START, "remember")
builder.add_edge("remember", "chat")
builder.add_edge("chat", END)
<langgraph.graph.state.StateGraph at 0x110ce4a90>
# ----------------------------
# 5) Use PostgresStore (PERSISTENT LTM)
# ----------------------------
DB_URI = "postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable"

with PostgresStore.from_conn_string(DB_URI) as store:
    # IMPORTANT: run ONCE the first time you use this database
    store.setup()

    graph = builder.compile(store=store)

    config = {"configurable": {"user_id": "u1"}}

    graph.invoke({"messages": [{"role": "user", "content": "Hi, my name is Nitish"}]}, config)
    graph.invoke({"messages": [{"role": "user", "content": "I teach AI on YouTube"}]}, config)

    out = graph.invoke({"messages": [{"role": "user", "content": "Explain GenAI simply"}]}, config)
    print(out["messages"][-1].content)

    print("\n--- Stored Memories (from Postgres) ---")
    for it in store.search(("user", "u1", "details")):
        print(it.value["data"])
# output:Sure, Nitish! Generative AI, or GenAI, refers to a type of artificial intelligence that can create new content, like images, text, music, or even code, on its own. It learns from a large dataset and uses patterns it recognizes to generate something that resembles what it has seen before.

# output:For example, if you input a topic or a few words into a generative AI model, it can produce a coherent piece of writing or generate a unique piece of art. Popular examples include models like GPT-3 for text and DALL-E for images.

# output:The key point is that GenAI is designed to take what it has learned and create new, original outputs based on that learning. If you’re discussing this concept in your YouTube teachings, it might be interesting for your audience to see how GenAI can assist in creative processes or even in automating certain tasks.

# output:Would you like to dive deeper into a specific application of GenAI, or perhaps explore its implications for teaching AI?

# output:--- Stored Memories (from Postgres) ---
# output:Nitish teaches AI on YouTube.
# output:User's name is Nitish.
Check Persistence
from langgraph.store.postgres import PostgresStore

DB_URI = "postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable"

with PostgresStore.from_conn_string(DB_URI) as store:
    ns = ("user", "u1", "details")
    items = store.search(ns)

for it in items:
    print(it.value["data"])
# output: Nitish teaches AI on YouTube.
# output: User's name is Nitish.

Практычныя прынцыпы пад час паўтарання сітак LTM

Нэмаспекты здаюцца простымі, калі два функцыяны раздзеляюць ("users", user_id) і перазапішуць ключы адна другой. Лепей выбіраць туплы з дапамогай функцыянаў, такія як ("users", user_id, "prefs") замест ("users", user_id, "facts"), і задаць дакументацыю, хто можа пісаць кожны з іх. Ключы должны быть стабільнымі ідэнтыфікаторамі, а не рэчамі у вольным тексте; рэчы трэба размешчаць у значэнні пакета.

Якасць семантычнага пошуку залежыць ад выбору методу укладання дадзеных і ад таго, як сформульаваны “памяці”. Зберагчы “likes Python” і пазней выканаць запит на “preferred programming language” будзе працаваць толькі тады, калі простор укладання групавае гэтыя фразы. Періядычна перезамена методу укладання пасля апдэйтаў моделі — це нудныя, але неабходныя дзеяння, якія запобегаюць непазначальным заніжэнням якосці запамятоввання.

Для запісей шляхоў неабходны ліміты частоты. Экстрактар, який працюе з кожнай фразай корыстувальца, можа запалаваць базу дадзеных Postgres майже дублікатамі, нават пасля пераканальвання is_new, особліва калі модель выбірае обережную парадоксальная стратегію. Обмежыце колькасць запісей за раунд, ствараюце падсумкі напрыканцы сесіі і стежыце за ростам базы дадзеных па корыстувальцы. Для прыменнення ў практыцы супараднуйце довгастраўную базу дадзеных з кароткастраўным контрольным пунктам, южы тэкставыя дыялогі на адзінай нитцы і память профіля межа ніткамі залишаліся концэптуальна адзеленымі, нават якщо вони выкарыстоўваюць адну і тую ж інфраструктуру.

Гігіяна шляхоў чытання

Па чытанні, калі ў дэмавых версіях будзе працаваць функцыя персоналізацыі, ніколі не трэба завантажваць усю прастору імен у системны запрос. Неабяжна абмежыць колькасць запам’ятаваных дакументаў, пазначыць, якія ключы прастору імен спадварылі ўплыв, а таксама дазволіць аператару выключыць LTM для кожнага пользователя, калі падчас воскрэсцення інфармацыі ўтвораецца пачуць, што ёна некоректная. Функцыя персоналізацыі, якую нельга выключыць, становіцца справжнім кошмарам для падтрымкі. Таксама трэба раздзеліць “факты про прафіль” ад “падсумкі размов” у сховішчы, каб паслабленыя падсумкі не моглі стерць довгастойчывых праганаў корыстувальніка. Пераходзячы з InMemoryStore на Postgres, неабяжна рэпетыраваць процес копіювання на запас і воскрэсцення таблиц з меморыяй так сама, як і процес воскрэсцення пунктавых контролі — втрата LTM для корыстувальніка выглядае як амнезія, а не проста тэхнічны шум.

Навучанне выдлічвача, што значыць “важнае”

Запросы, які працуюць за принцыпам «зберагчы ўсё корыстнае», ствараюць некальканосны мусор. Лепш выбраць закрыты ўказальнік: ідэнтыфікацыя, стойкія прагаперы, актыўныя проекты, строгія абмежэння і чыста добровольныя нагадкі. Усё інша застаецца ў чэкпойнтары паводзя. Пакажце экстрактару тры пары дыалогаў з шумамі па прыкладу і мінімальны JSON з мэморыяй, які вы хочаце, і пракранцуйце гэтыя фіксатары ў CI, калі змянююцца запросы. Качасць дзейнаўскай мэморыяй — это рэсультат сумавання запроса і шыматы; ставіцеся да гэтага як да адной цалкы.

Аператары таксама должны практыкуваць способы выправлення «некальканосной мэморыяй»: выдаліць па ключу, зноў экстракаваць з праверанага выказвання і паказаць, што вузел чату больш не цітуюць застарэлы факт. Без такой практыкі дзейнаўская мэморыя стае толькі шухлядай для зберагчэння мусора.