Intro to LangMem¶
This is an early preview of LangMem, a longterm memory service built by LangChain designed help you eaily build personalized user experiences with LLMs. We will walk through the basic functionality as an orientation to the service, including:
- Creating memory types
- Posting messages to the service to trigger memory formation
- Recalling the memories for use in your bot.
My incorporating this in your chat bot, LangMem will asynchronously help it learn their preferences and interests to improve the quality of its responses and recommendations.
0. Environment Setup¶
We've created a demo 'quickstart' instance for you to try out in this notebook.
While LangMem is still in private alpha, you'll need to be given access to a dedicated LangMem instance from a member LangChain team for more personal use beyond this demo. Reach out on Slack or at support@langchain.dev to receive your connection information.
Then you'll want to install the sdk (we will also use openai in our example chat below).
%pip install -U --quiet langmem openai
import getpass
import os
# Update to use your instance's URL
os.environ["LANGMEM_API_URL"] = getpass.getpass("LANGMEM_API_URL")
# Update to your API Key
os.environ["LANGMEM_API_KEY"] = getpass.getpass("API_KEY")
# Used for the example chat bot later in this tutorial
os.environ["OPENAI_API_KEY"] = getpass.getpass("OPENAI_API_KEY:")
API_KEY·········· OPENAI_API_KEY:··········
With the environment configured, you can create your client. We will connect to OpenAI and to Langmem.
import openai
from langmem import AsyncClient, Client
oai_client = openai.AsyncClient()
langmem_client = AsyncClient()
1. Creating custom memory types¶
Memory types let you model your application domain so your bot can retain inforation in a format appropriate to your use case.
LangMem supports both user
level and thread
-level memories.
LangMem supports 3 primary types of user
memories:
- Structured "user state" memory to infer and manage a pre-specified user profile
- Structured "user append state" memory to infer information salient to your application context and query semantically
- Unstructured user semantic memory
The unstructured semantic memory is enabled by default in each deployment. We will see more of that below.
Enabling structured memory functions is as easy as posting a schema to LangMem. LangMem then automatically manages these custom profiles whenever new messages are sent to the service.
User State¶
This is a custom user profile. Instantiate by providing a JSON schema or pydantic model.
import json
from typing import List
from pydantic import BaseModel, Field
class Person(BaseModel):
name: str = Field(default=None, description="The name of the family member.")
relation: str = Field(
default=None, description="The relation of the family member to the user."
)
class UserProfile(BaseModel):
preferred_name: str = Field(default=None, description="The user's name.")
summary: str = Field(
default="",
description="A quick summary of how the user would describe themselves.",
)
interests: List[str] = Field(
default_factory=list,
description="Short (two to three word) descriptions of areas of particular interest for the user. This can be a concept, activity, or idea. Favor broad interests over specific ones.",
)
relationships: List[Person] = Field(
default_factory=Person,
description="A list of friends, family members, colleagues, and other relationships.",
)
other_info: List[str] = Field(
default_factory=list,
description="",
)
user_profile_memory = await langmem_client.create_memory_function(
UserProfile, target_type="user_state"
)
User Append State¶
As the name suggests, the user_append_state
is an append-only state (meaning the profile is never overwritten) that lets you define schema(s) to represent individual memories which you can later query semantically.
class CoreBelief(BaseModel):
belief: str = Field(
default="",
description="The belief the user has about the world, themselves, or anything else.",
)
why: str = Field(description="Why the user believes this.")
context: str = Field(
description="The raw context from the conversation that leads you to conclude that the user believes this."
)
belief_function = await langmem_client.create_memory_function(
CoreBelief, target_type="user_append_state"
)
class FormativeEvent(BaseModel):
event: str = Field(
default="",
description="The event that occurred. Must be important enough to be formative for the user.",
)
impact: str = Field(default="", description="How this event influenced the user.")
event_function = await langmem_client.create_memory_function(
FormativeEvent, target_type="user_append_state"
)
User Semantic Memory¶
There is also a triplet-based user_semantic_memory
that is enabled by default. You can turn it off if you don't plan to use it.
functions = langmem_client.list_memory_functions(target_type="user")
semantic_memory = None
async for func in functions:
if func["type"] == "user_semantic_memory":
semantic_memory = func
# Uncomment to disable the unstructured memory
# await langmem_client.update_memory_function(semantic_memory["id"], status="disabled")
Thread Summary¶
LangMem also supports thread-level memories. We will create them below.
class ConversationSummary(BaseModel):
title: str = Field(description="Distinct for the conversation.")
summary: str = Field(description="High level summary of the interactions.")
topic: List[str] = Field(
description="Tags for topics discussed in this conversation."
)
thread_summary_function = await langmem_client.create_memory_function(
ConversationSummary, target_type="thread_summary"
)
2. Starting a conversation¶
Memories are formed whenever your chat bot posts messages to the service.
Whenever a a user ID is provided in the message metadata, LangMem will automatically create a new user entry and start tracking memories for that user.
import uuid
johnny_user_id = uuid.uuid4()
jimmy_user_id = uuid.uuid4()
jimmy_username = f"jimmy-{uuid.uuid4().hex[:4]}"
johnny_username = f"johnny-{uuid.uuid4().hex[:4]}"
The following is an example conversation between 1 or more users and an AI¶
# Unique for a given converstaion
thread_id = uuid.uuid4()
async def completion(messages: list):
stripped_messages = [
{k: v for k, v in m.items() if k != "metadata"} for m in messages
]
return await oai_client.chat.completions.create(
model="gpt-3.5-turbo", messages=stripped_messages
)
messages = [
{"role": "system", "content": "You are a helpful AI assistant"},
{
"role": "user",
# Names are optional but should be consistent with a given user id, if provided
"name": jimmy_username,
"content": "Hey johnny have i ever told you about my older bro steve?",
"metadata": {
"user_id": str(jimmy_user_id),
},
},
{
"content": "no, you didn't, but I think he was friends with my younger sister sueann",
"role": "user",
"name": johnny_username,
"metadata": {
"user_id": str(johnny_user_id),
},
},
{
"content": "yeah me and him used to play stickball down in the park before school started. I think it was in 1980",
"role": "user",
"name": jimmy_username,
"metadata": {
"user_id": str(jimmy_user_id),
},
},
{
"content": "That was totally 1979! I remember because i was stuck at home all summer.",
"role": "user",
"name": "Jeanne",
# If the user ID isn't provided, we treat this as a guest message and won't assign memories to the user
},
{
"content": "That was so long ago. I have gotten old and gained 200 pounds since then. I can't even remember who was president. @ai, who was the president in 1980?",
"role": "user",
"name": johnny_username,
"metadata": {
"user_id": str(johnny_user_id),
},
},
{
"content": "The president of the United States in 1980 was Jimmy Carter.",
"role": "assistant",
},
{
"content": "Wow ya i forgot that. Stickleball really impacted my life. It's how i first met Jeanne! wonder how my life would have turned out if it hadn't happened that way.",
"role": "user",
"name": jimmy_username,
"metadata": {
"user_id": str(jimmy_user_id),
},
},
{
"content": "Yeah wow. That was a big year! @ai could you remind me what else was going on that year?",
"role": "user",
"name": johnny_username,
"metadata": {
"user_id": str(johnny_user_id),
},
},
]
result = await completion(messages)
messages.append(result.choices[0].message)
print(result.choices[0].message.content)
In 1980, some other significant events included the eruption of Mount St. Helens in Washington state, the United States boycotting the Summer Olympics in Moscow, the launch of CNN (Cable News Network), and the assassination of former Beatle John Lennon. It was definitely a year filled with memorable events.
Now that we have the messages, we can share them with LangMem.¶
await langmem_client.add_messages(thread_id=thread_id, messages=messages)
# LangMem will automatically process memories after some delay (~60 seconds), but we can eagerly process the memories as well
await langmem_client.trigger_all_for_thread(thread_id=thread_id)
# You could also trigger for a single user if you'd like
# await langmem_client.trigger_all_for_user(user_id=jimmy_user_id)
Fetch messages¶
You can fetch all the messages in a LangMem thread through that thread's GET endpoint. In this way, LangMem can act as a generic chat bot backend.
messages = langmem_client.list_messages(thread_id=thread_id)
async for message in messages:
print(message)
{'id': 'c478e641-dacb-4e3a-8cac-7d9574ed8056', 'content': 'You are a helpful AI assistant', 'timestamp': '2024-03-28T17:10:25.937750', 'user': {'user_id': None, 'user_name': None, 'role': 'system'}} {'id': 'b0776988-8113-4e2d-b62d-f827eee98867', 'content': 'Hey johnny have i ever told you about my older bro steve?', 'timestamp': '2024-03-28T17:10:25.937784', 'user': {'user_id': 'c1fd3d58-0e67-43ec-b688-1c59f41f79c6', 'user_name': 'jimmy-56c5', 'role': 'user'}} {'id': '1cd7a340-cebc-495f-8a0d-fb5965c57d40', 'content': "no, you didn't, but I think he was friends with my younger sister sueann", 'timestamp': '2024-03-28T17:10:25.937813', 'user': {'user_id': 'ceab7620-9bd2-45b6-b2e8-88848c0c3965', 'user_name': 'johnny-0fd1', 'role': 'user'}} {'id': '03c4975c-2656-457e-9851-dd8ee1496216', 'content': 'yeah me and him used to play stickball down in the park before school started. I think it was in 1980', 'timestamp': '2024-03-28T17:10:25.937834', 'user': {'user_id': 'c1fd3d58-0e67-43ec-b688-1c59f41f79c6', 'user_name': 'jimmy-56c5', 'role': 'user'}} {'id': '56a87add-1ef3-44f5-9d5c-b3a4f5796063', 'content': 'That was totally 1979! I remember because i was stuck at home all summer.', 'timestamp': '2024-03-28T17:10:25.937855', 'user': {'user_id': None, 'user_name': None, 'role': 'user'}} {'id': '339ad2ad-10be-4f19-91bd-4595044d1cdf', 'content': "That was so long ago. I have gotten old and gained 200 pounds since then. I can't even remember who was president. @ai, who was the president in 1980?", 'timestamp': '2024-03-28T17:10:25.941504', 'user': {'user_id': 'ceab7620-9bd2-45b6-b2e8-88848c0c3965', 'user_name': 'johnny-0fd1', 'role': 'user'}} {'id': '8dddbcaf-33b8-426e-a580-801d3f37823b', 'content': 'The president of the United States in 1980 was Jimmy Carter.', 'timestamp': '2024-03-28T17:10:25.941557', 'user': {'user_id': None, 'user_name': None, 'role': 'ai'}} {'id': '8d85bf30-10bb-46b0-9d75-c4afa9506398', 'content': "Wow ya i forgot that. Stickleball really impacted my life. It's how i first met Jeanne! wonder how my life would have turned out if it hadn't happened that way.", 'timestamp': '2024-03-28T17:10:25.941578', 'user': {'user_id': 'c1fd3d58-0e67-43ec-b688-1c59f41f79c6', 'user_name': 'jimmy-56c5', 'role': 'user'}} {'id': '3fa420f7-8a5c-46a2-a1fb-d9a4c12bdbbb', 'content': 'Yeah wow. That was a big year! @ai could you remind me what else was going on that year?', 'timestamp': '2024-03-28T17:10:25.941596', 'user': {'user_id': 'ceab7620-9bd2-45b6-b2e8-88848c0c3965', 'user_name': 'johnny-0fd1', 'role': 'user'}} {'id': 'd9854557-f966-465a-9f6b-f8a5d047c56b', 'content': 'In 1980, some other significant events included the eruption of Mount St. Helens in Washington state, the United States boycotting the Summer Olympics in Moscow, the launch of CNN (Cable News Network), and the assassination of former Beatle John Lennon. It was definitely a year filled with memorable events.', 'timestamp': '2024-03-28T17:10:25.941615', 'user': {'user_id': None, 'user_name': None, 'role': 'ai'}}
3. Query Memory¶
You can also query the user memory, once it's updated. This may take a few moments - please be patient 😊
To query the unstructured semantic memory, you can provide query text and the number of memories to return.
# Wait a few moments for the memories to process. If this is empty, you'll likely have to wait a bit longer
mems = None
while not mems:
mem_response = await langmem_client.query_user_memory(
user_id=jimmy_user_id, text="stickleball", k=3
)
mems = mem_response["memories"]
mems
[{'id': 'b6a742c8-736d-40d4-8109-b704bc472c67', 'created_at': '2024-03-28T16:52:39.364396Z', 'last_accessed': '2024-03-28T17:10:43.774991Z', 'text': 'jimmy-56c5 met Jeanne through stickball impacted life significantly', 'content': {'subject': 'c1fd3d58-0e67-43ec-b688-1c59f41f79c6', 'predicate': 'met Jeanne through stickball', 'object': 'impacted life significantly'}, 'scores': {'recency': 0.9998505211700639, 'importance': 1.0, 'relevance': 0.6725992391294546}}, {'id': 'dac7b7fb-e63a-4763-af4e-3669f25ad76f', 'created_at': '2024-03-28T16:38:03.900300Z', 'last_accessed': '2024-03-28T17:10:43.774991Z', 'text': 'jimmy-56c5 played stickball in the park before school 1979', 'content': {'subject': 'c1fd3d58-0e67-43ec-b688-1c59f41f79c6', 'predicate': 'played stickball in the park before school', 'object': '1979'}, 'scores': {'recency': 0.9998109278725811, 'importance': 0.5714285714285714, 'relevance': 0.9951020385149173}}, {'id': 'e4400a4d-5df7-4741-abe4-30f85c752a5f', 'created_at': '2024-03-28T16:38:03.900300Z', 'last_accessed': '2024-03-28T17:10:43.774991Z', 'text': 'jimmy-56c5 has older brother Steve', 'content': {'subject': 'c1fd3d58-0e67-43ec-b688-1c59f41f79c6', 'predicate': 'has older brother', 'object': 'Steve'}, 'scores': {'recency': 0.9997723259170942, 'importance': 0.8571428571428571, 'relevance': 0.1416941375483881}}]
# In a similar way, you can include
# different `user_append_state` memory results
# in the ranked response
mems = await langmem_client.query_user_memory(
user_id=jimmy_user_id,
text="stickleball",
k=3,
memory_function_ids=[belief_function["id"], event_function["id"]],
)
mems
{'user_id': 'c1fd3d58-0e67-43ec-b688-1c59f41f79c6', 'memories': [{'id': '16ad398b-2c3e-461e-9688-ca6cebfa4704', 'created_at': '2024-03-28T17:10:28.376135Z', 'last_accessed': '2024-03-28T17:10:44.920281Z', 'text': '', 'content': {'event': 'Playing stickball in the park before school in 1980', 'impact': 'This activity was significant for the user as it was how they first met Jeanne, suggesting it had a profound impact on their life.'}, 'scores': {'recency': 1.0, 'importance': 0.5, 'relevance': 0.8382047058016829}}, {'id': 'a87a5e04-f628-45f0-a6cc-41e35061f013', 'created_at': '2024-03-28T17:09:46.501123Z', 'last_accessed': '2024-03-28T17:10:25.875205Z', 'text': '', 'content': {'event': 'played stickball in the park with older brother Steve before school started', 'impact': 'This memory is likely a cherished one, reflecting a close relationship with his brother and a fondness for simpler times.'}, 'scores': {'recency': 0.0, 'importance': 0.5, 'relevance': 1.0}}, {'id': '24dfeaeb-e2f4-4591-9bc9-f00a63465379', 'created_at': '2024-03-28T17:09:46.488723Z', 'last_accessed': '2024-03-28T17:10:44.920281Z', 'text': '', 'content': {'belief': 'I have an older brother named Steve', 'why': 'The user explicitly mentioned having an older brother named Steve.', 'context': 'Hey johnny have i ever told you about my older bro steve?'}, 'scores': {'recency': 0.9999455514820141, 'importance': 0.5, 'relevance': 0.0}}], 'state': [], 'distilled': None}
# For user state (profile) memories, you can make a faster and simple get request
user_state = None
while not user_state:
user_state = await langmem_client.get_user_memory(
user_id=jimmy_user_id, memory_function_id=user_profile_memory["id"]
)
user_state
{'other_info': ['played stickball in the park before school around 1980', "Stickleball really impacted my life. It's how i first met Jeanne"], 'relationships': [{'name': 'Steve', 'relation': 'older brother'}], 'preferred_name': 'Johnny'}
# You can list all the thread summary memories for a given thread as well!
await langmem_client.list_thread_memory(thread_id)
[{'memory_function_id': 'ddcf937c-b201-4cc1-9779-987e2af683ef', 'memory_function_name': 'ConversationSummary', 'memory_function_type': 'thread_summary', 'target': '/threads/33c69599-c86c-4f8a-a117-a88b153ee9de', 'output': {'title': 'Reminiscing About the Past and Historical Events of 1980', 'topic': ['Personal Memories', 'Historical Events', '1980'], 'summary': "Two users reminisce about the past, specifically playing stickball in the park around 1979 or 1980. One user mentions gaining weight over the years and asks the AI who was the president in 1980, to which the AI responds with Jimmy Carter. The conversation touches on how stickball impacted one user's life, leading to meeting someone named Jeanne. The other user asks for a reminder of significant events from 1980, and the AI lists the eruption of Mount St. Helens, the U.S. boycotting the Summer Olympics, the launch of CNN, and the assassination of John Lennon."}}]
4. Use in a later conversations¶
As you can see, we've extracted some useful information from the previous conversation. We imagine you would fetch these facts in later conversations to provide your bot with additional helpful context about the user.
async def completion_with_memory(messages: list, user_id: uuid.UUID):
memories = await langmem_client.query_user_memory(
user_id=user_id, text=messages[-1]["content"], k=3
)
facts = "\n".join([mem["text"] for mem in memories["memories"]])
system_prompt = {
"role": "system",
"content": f"You are a helpful assistant. You know the following facts about the user with which you are conversing.\n\n{facts}",
}
return await completion([system_prompt] + messages)
messages = [
{
"role": "user",
"name": jimmy_username,
"content": "Hi there! I'm curious what you remember. What's my brother's name?",
"metadata": {"user_id": jimmy_user_id},
}
]
res = await completion_with_memory(messages, user_id=jimmy_user_id)
print(res.choices[0].message.content)
Your older brother's name is Steve.
Conclusion¶
In this walkthrough, you saved memories for two users to track their interests and other attributes. You did so simply by sending your chat messages to the LangMem service. You then automatically triggered updates to store long-term memories of three forms:
- User state "profiles", which follow your custom schema
- User append state, which store atomic memories following your custom schema and can be queried semantically.
- General-purpose knowledge as semantic triplets
You also tracked thread-scoped summary memories to help you organize your conversational threads.
Finally, let's clean up our work! This demo is a shared workspace :)
## Cleanup
functions = langmem_client.list_memory_functions()
async for func in functions:
if func["type"] == "user_semantic_memory":
continue
await langmem_client.delete_memory_function(func["id"])