How to add semantic search to your agent’s memory#

This guide shows how to enable semantic search in your agent’s memory store. This lets search for items in the store by semantic similarity.

!!! tip Prerequisites This guide assumes familiarity with the memory in LangGraph.

Note: This notebook uses different namespaces (user_123, user_456, etc.) for different examples to avoid conflicts between stored memories. Each example demonstrates a specific feature in isolation.

%%capture --no-stderr
%pip install -U langgraph langchain-openai langchain
import getpass
import os


def _set_env(var: str):
    if not os.environ.get(var):
        os.environ[var] = getpass.getpass(f"{var}: ")


_set_env("OPENAI_API_KEY")

Next, create the store with an index configuration. By default, stores are configured without semantic/vector search. You can opt in to indexing items when creating the store by providing an IndexConfig to the store’s constructor. If your store class does not implement this interface, or if you do not pass in an index configuration, semantic search is disabled, and all index arguments passed to put or aput will have no effect. Below is an example.

from langchain.embeddings import init_embeddings
from langgraph.store.redis import RedisStore
from langgraph.store.base import IndexConfig

# Create Redis store with semantic search enabled
embeddings = init_embeddings("openai:text-embedding-3-small")

# Set up Redis connection
REDIS_URI = "redis://redis:6379"

# Create index configuration for vector search
index_config: IndexConfig = {
    "dims": 1536,
    "embed": embeddings,
    "ann_index_config": {
        "vector_type": "vector",
    },
    "distance_type": "cosine",
}

# Initialize the Redis store
redis_store = None
with RedisStore.from_conn_string(REDIS_URI, index=index_config) as s:
    s.setup()
    redis_store = s

store = redis_store
00:44:20 langgraph.store.redis INFO   Redis standalone client detected for RedisStore.

Now let’s store some memories:

# Store some memories
store.put(("user_123", "memories"), "1", {"text": "I love pizza"})
store.put(("user_123", "memories"), "2", {"text": "I prefer Italian food"})
store.put(("user_123", "memories"), "3", {"text": "I don't like spicy food"})
store.put(("user_123", "memories"), "3", {"text": "I am studying econometrics"})
store.put(("user_123", "memories"), "3", {"text": "I am a plumber"})
00:44:21 httpx INFO   HTTP Request: POST https://api.openai.com/v1/embeddings "HTTP/1.1 200 OK"
00:44:21 httpx INFO   HTTP Request: POST https://api.openai.com/v1/embeddings "HTTP/1.1 200 OK"
00:44:21 httpx INFO   HTTP Request: POST https://api.openai.com/v1/embeddings "HTTP/1.1 200 OK"
00:44:21 httpx INFO   HTTP Request: POST https://api.openai.com/v1/embeddings "HTTP/1.1 200 OK"
00:44:22 httpx INFO   HTTP Request: POST https://api.openai.com/v1/embeddings "HTTP/1.1 200 OK"

Search memories using natural language:

# Find memories about food preferences
memories = store.search(("user_123", "memories"), query="I like food?", limit=5)

for memory in memories:
    print(f'Memory: {memory.value["text"]} (similarity: {memory.score})')
00:44:22 httpx INFO   HTTP Request: POST https://api.openai.com/v1/embeddings "HTTP/1.1 200 OK"
Memory: I prefer Italian food (similarity: 0.464826762676)
Memory: I love pizza (similarity: 0.35514843463900003)
Memory: I am a plumber (similarity: 0.15569871664)

Using in your agent#

Add semantic search to any node by injecting the store.

from langchain.chat_models import init_chat_model
from langgraph.store.base import BaseStore
from langgraph.checkpoint.redis import RedisSaver

from langgraph.graph import START, MessagesState, StateGraph

llm = init_chat_model("openai:gpt-4o-mini")


def chat(state, *, store: BaseStore):
    # Search based on user's last message
    items = store.search(
        ("user_123", "memories"), query=state["messages"][-1].content, limit=2
    )
    memories = "\n".join(item.value["text"] for item in items)
    memories = f"## Memories of user\n{memories}" if memories else ""
    response = llm.invoke(
        [
            {"role": "system", "content": f"You are a helpful assistant.\n{memories}"},
            *state["messages"],
        ]
    )
    return {"messages": [response]}


# Set up Redis connection for checkpointer
REDIS_URI = "redis://redis:6379"
checkpointer = None
with RedisSaver.from_conn_string(REDIS_URI) as cp:
    cp.setup()
    checkpointer = cp

builder = StateGraph(MessagesState)
builder.add_node(chat)
builder.add_edge(START, "chat")
graph = builder.compile(checkpointer=checkpointer, store=store)

# Add required configuration parameters
config = {"configurable": {"thread_id": "semantic_search_thread"}}
for message, metadata in graph.stream(
        input={"messages": [{"role": "user", "content": "I'm hungry"}]},
        config=config,  # Add this line with required config
        stream_mode="messages",
):
    print(message.content, end="")
00:44:22 langgraph.checkpoint.redis INFO   Redis client is a standalone client
00:44:22 redisvl.index.index INFO   Index already exists, not overwriting.
00:44:22 redisvl.index.index INFO   Index already exists, not overwriting.
00:44:22 redisvl.index.index INFO   Index already exists, not overwriting.
00:44:22 httpx INFO   HTTP Request: POST https://api.openai.com/v1/embeddings "HTTP/1.1 200 OK"
00:44:23 httpx INFO   HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
What are you in the mood for? Since you love Italian food and pizza, would you like to order a pizza or perhaps make one at home?

Using in create_react_agent {#using-in-create-react-agent}#

Add semantic search to your tool calling agent by injecting the store in the prompt function. You can also use the store in a tool to let your agent manually store or search for memories.

import uuid
from typing import Optional

from langchain.chat_models import init_chat_model
from langgraph.prebuilt import InjectedStore
from langgraph.store.base import BaseStore
from langgraph.checkpoint.redis import RedisSaver
from typing_extensions import Annotated

from langgraph.prebuilt import create_react_agent


def prepare_messages(state, *, store: BaseStore):
    # Search based on user's last message
    items = store.search(
        ("user_123", "memories"), query=state["messages"][-1].content, limit=2
    )
    memories = "\n".join(item.value["text"] for item in items)
    memories = f"## Memories of user\n{memories}" if memories else ""
    return [
        {"role": "system", "content": f"You are a helpful assistant.\n{memories}"}
    ] + state["messages"]


# You can also use the store directly within a tool!
def upsert_memory(
        content: str,
        *,
        memory_id: Optional[uuid.UUID] = None,
        store: Annotated[BaseStore, InjectedStore],
):
    """Upsert a memory in the database."""
    # The LLM can use this tool to store a new memory
    mem_id = memory_id or uuid.uuid4()
    store.put(
        ("user_123", "memories"),
        key=str(mem_id),
        value={"text": content},
    )
    return f"Stored memory {mem_id}"


# Set up Redis connection for checkpointer
REDIS_URI = "redis://redis:6379"
checkpointer = None
with RedisSaver.from_conn_string(REDIS_URI) as cp:
    cp.setup()
    checkpointer = cp

agent = create_react_agent(
    init_chat_model("openai:gpt-4o-mini"),
    tools=[upsert_memory],
    # The 'prompt' function is run to prepare the messages for the LLM. It is called
    # right before each LLM call
    prompt=prepare_messages,
    checkpointer=checkpointer,
    store=store,
)
00:44:24 langgraph.checkpoint.redis INFO   Redis client is a standalone client
00:44:24 redisvl.index.index INFO   Index already exists, not overwriting.
00:44:24 redisvl.index.index INFO   Index already exists, not overwriting.
00:44:24 redisvl.index.index INFO   Index already exists, not overwriting.
/tmp/ipykernel_3503/975175168.py:50: LangGraphDeprecatedSinceV10: create_react_agent has been moved to `langchain.agents`. Please update your import to `from langchain.agents import create_agent`. Deprecated in LangGraph V1.0 to be removed in V2.0.
  agent = create_react_agent(
# Alternative approach using agent
config = {"configurable": {"thread_id": "semantic_search_thread_agent"}}
try:
    # Run the agent with proper configuration
    for message, metadata in agent.stream(
            input={"messages": [{"role": "user", "content": "Tell me about my food preferences based on my memories"}]},
            config=config,  # This is required for the checkpointer
            stream_mode="messages",
    ):
        print(message.content, end="")
except Exception as e:
    print(f"Error running agent: {e}")
    # Try with different configuration if needed
    config = {"configurable": {"thread_id": "semantic_search_thread_agent", "checkpoint_ns": "", "checkpoint_id": ""}}
    for message, metadata in agent.stream(
            input={"messages": [{"role": "user", "content": "Tell me about my food preferences based on my memories"}]},
            config=config,
            stream_mode="messages",
    ):
        print(message.content, end="")
00:44:24 httpx INFO   HTTP Request: POST https://api.openai.com/v1/embeddings "HTTP/1.1 200 OK"
00:44:25 httpx INFO   HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
Based on your memories, you prefer Italian food and you love pizza.

Advanced Usage#

Multi-vector indexing#

Store and search different aspects of memories separately to improve recall or omit certain fields from being indexed.

# Configure Redis store to embed both memory content and emotional context
REDIS_URI = "redis://redis:6379"
with RedisStore.from_conn_string(
        REDIS_URI,
        index={"embed": embeddings, "dims": 1536, "fields": ["memory", "emotional_context"]}
) as store:
    store.setup()

    # Store memories with different content/emotion pairs
    # Use a different namespace to avoid conflicts with previous examples
    store.put(
        ("user_456", "multi_vector_memories"),
        "mem1",
        {
            "memory": "Had pizza with friends at Mario's",
            "emotional_context": "felt happy and connected",
            "this_isnt_indexed": "I prefer ravioli though",
        },
    )
    store.put(
        ("user_456", "multi_vector_memories"),
        "mem2",
        {
            "memory": "Ate alone at home",
            "emotional_context": "felt a bit lonely",
            "this_isnt_indexed": "I like pie",
        },
    )

    # Search focusing on emotional state - matches mem2
    results = store.search(
        ("user_456", "multi_vector_memories"), query="times they felt isolated", limit=1
    )
    print("Expect mem 2")
    for r in results:
        print(f"Item: {r.key}; Score ({r.score})")
        print(f"Memory: {r.value['memory']}")
        print(f"Emotion: {r.value['emotional_context']}\n")

    # Search focusing on social eating - matches mem1
    print("Expect mem1")
    results = store.search(
        ("user_456", "multi_vector_memories"), query="fun pizza", limit=1
    )
    for r in results:
        print(f"Item: {r.key}; Score ({r.score})")
        print(f"Memory: {r.value['memory']}")
        print(f"Emotion: {r.value['emotional_context']}\n")

    print("Expect random lower score (ravioli not indexed)")
    results = store.search(
        ("user_456", "multi_vector_memories"), query="ravioli", limit=1
    )
    for r in results:
        print(f"Item: {r.key}; Score ({r.score})")
        print(f"Memory: {r.value['memory']}")
        print(f"Emotion: {r.value['emotional_context']}\n")
00:44:25 langgraph.store.redis INFO   Redis standalone client detected for RedisStore.
00:44:25 redisvl.index.index INFO   Index already exists, not overwriting.
00:44:25 redisvl.index.index INFO   Index already exists, not overwriting.
00:44:25 httpx INFO   HTTP Request: POST https://api.openai.com/v1/embeddings "HTTP/1.1 200 OK"
00:44:26 httpx INFO   HTTP Request: POST https://api.openai.com/v1/embeddings "HTTP/1.1 200 OK"
00:44:26 httpx INFO   HTTP Request: POST https://api.openai.com/v1/embeddings "HTTP/1.1 200 OK"
Expect mem 2
Item: mem2; Score (0.589555978775)
Memory: Ate alone at home
Emotion: felt a bit lonely

Expect mem1
00:44:26 httpx INFO   HTTP Request: POST https://api.openai.com/v1/embeddings "HTTP/1.1 200 OK"
Item: mem2; Score (0.23533296585100005)
Memory: Ate alone at home
Emotion: felt a bit lonely

Expect random lower score (ravioli not indexed)
00:44:26 httpx INFO   HTTP Request: POST https://api.openai.com/v1/embeddings "HTTP/1.1 200 OK"
Item: mem2; Score (0.15022909641299997)
Memory: Ate alone at home
Emotion: felt a bit lonely

Override fields at storage time#

You can override which fields to embed when storing a specific memory using put(..., index=[...fields]), regardless of the store’s default configuration.

REDIS_URI = "redis://redis:6379"
with RedisStore.from_conn_string(
        REDIS_URI,
        index={
            "embed": embeddings,
            "dims": 1536,
            "fields": ["memory"],
        }  # Default to embed memory field
) as store:
    store.setup()

    # Store one memory with default indexing
    # Use a different namespace to avoid conflicts with previous examples
    store.put(
        ("user_789", "override_field_memories"),
        "mem1",
        {"memory": "I love spicy food", "context": "At a Thai restaurant"},
    )

    # Store another overriding which fields to embed
    store.put(
        ("user_789", "override_field_memories"),
        "mem2",
        {"memory": "The restaurant was too loud", "context": "Dinner at an Italian place"},
        index=["context"],  # Override: only embed the context
    )

    # Search about food - matches mem1 (using default field)
    print("Expect mem1")
    results = store.search(
        ("user_789", "override_field_memories"), query="what food do they like", limit=1
    )
    for r in results:
        print(f"Item: {r.key}; Score ({r.score})")
        print(f"Memory: {r.value['memory']}")
        print(f"Context: {r.value['context']}\n")

    # Search about restaurant atmosphere - matches mem2 (using overridden field)
    print("Expect mem2")
    results = store.search(
        ("user_789", "override_field_memories"), query="restaurant environment", limit=1
    )
    for r in results:
        print(f"Item: {r.key}; Score ({r.score})")
        print(f"Memory: {r.value['memory']}")
        print(f"Context: {r.value['context']}\n")
00:44:26 langgraph.store.redis INFO   Redis standalone client detected for RedisStore.
00:44:26 redisvl.index.index INFO   Index already exists, not overwriting.
00:44:26 redisvl.index.index INFO   Index already exists, not overwriting.
00:44:27 httpx INFO   HTTP Request: POST https://api.openai.com/v1/embeddings "HTTP/1.1 200 OK"
00:44:27 httpx INFO   HTTP Request: POST https://api.openai.com/v1/embeddings "HTTP/1.1 200 OK"
Expect mem1
00:44:27 httpx INFO   HTTP Request: POST https://api.openai.com/v1/embeddings "HTTP/1.1 200 OK"
Item: mem1; Score (0.337496876717)
Memory: I love spicy food
Context: At a Thai restaurant

Expect mem2
00:44:27 httpx INFO   HTTP Request: POST https://api.openai.com/v1/embeddings "HTTP/1.1 200 OK"
Item: mem2; Score (0.36784017086)
Memory: The restaurant was too loud
Context: Dinner at an Italian place

Disable Indexing for Specific Memories#

Some memories shouldn’t be searchable by content. You can disable indexing for these while still storing them using put(..., index=False). Example:

REDIS_URI = "redis://redis:6379"
with RedisStore.from_conn_string(
        REDIS_URI,
        index={"embed": embeddings, "dims": 1536, "fields": ["memory"]}
) as store:
    store.setup()

    # Store a normal indexed memory
    # Use a different namespace to avoid conflicts with previous examples
    store.put(
        ("user_999", "disable_index_memories"),
        "mem1",
        {"memory": "I love chocolate ice cream", "type": "preference"},
    )

    # Store a system memory without indexing
    store.put(
        ("user_999", "disable_index_memories"),
        "mem2",
        {"memory": "User completed onboarding", "type": "system"},
        index=False,  # Disable indexing entirely
    )

    # Search about food preferences - finds mem1
    print("Expect mem1")
    results = store.search(("user_999", "disable_index_memories"), query="what food preferences", limit=1)
    for r in results:
        print(f"Item: {r.key}; Score ({r.score})")
        print(f"Memory: {r.value['memory']}")
        print(f"Type: {r.value['type']}\n")

    # Search about onboarding - won't find mem2 (not indexed)
    print("Expect low score (mem2 not indexed)")
    results = store.search(("user_999", "disable_index_memories"), query="onboarding status", limit=1)
    for r in results:
        print(f"Item: {r.key}; Score ({r.score})")
        print(f"Memory: {r.value['memory']}")
        print(f"Type: {r.value['type']}\n")
00:44:27 langgraph.store.redis INFO   Redis standalone client detected for RedisStore.
00:44:27 redisvl.index.index INFO   Index already exists, not overwriting.
00:44:27 redisvl.index.index INFO   Index already exists, not overwriting.
00:44:28 httpx INFO   HTTP Request: POST https://api.openai.com/v1/embeddings "HTTP/1.1 200 OK"
Expect mem1
00:44:28 httpx INFO   HTTP Request: POST https://api.openai.com/v1/embeddings "HTTP/1.1 200 OK"
Item: mem1; Score (0.322699844837)
Memory: I love chocolate ice cream
Type: preference

Expect low score (mem2 not indexed)
00:44:28 httpx INFO   HTTP Request: POST https://api.openai.com/v1/embeddings "HTTP/1.1 200 OK"
Item: mem1; Score (0.010241627692999966)
Memory: I love chocolate ice cream
Type: preference