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