LangMem 简介¶
这是 LangMem 的早期预览,LangMem 是由 LangChain 构建的长期记忆服务,旨在帮助您轻松构建具有个性化用户体验的 LLM。我们将逐步介绍基本功能,作为对该服务的介绍,包括
- 创建内存类型
- 将消息发布到服务以触发内存形成
- 回忆内存以在您的机器人中使用。
通过将此功能融入您的聊天机器人,LangMem 将异步地帮助其了解用户的偏好和兴趣,从而提高其响应和推荐的质量。
0. 环境设置¶
我们为您创建了一个演示“快速入门”实例,供您在此笔记本中试用。
虽然 LangMem 仍处于私人 Alpha 阶段,但您需要从 LangChain 团队成员处获得对专用 LangMem 实例的访问权限,才能在此演示之外进行更个人的使用。请在 Slack 或 support@langchain.dev 上联系我们,以获取您的连接信息。
然后,您需要安装 SDK(我们在下面的示例聊天中也将使用 openai)。
%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:··········
配置好环境后,就可以创建您的客户端了。我们将连接到 OpenAI 和 Langmem。
import openai
from langmem import AsyncClient, Client
oai_client = openai.AsyncClient()
langmem_client = AsyncClient()
1. 创建自定义内存类型¶
内存类型允许您对应用程序域进行建模,以便您的机器人能够以适合您的用例的格式保留信息。
LangMem 支持用户
级和线程
级内存。
LangMem 支持 3 种主要的用户
内存类型
- 结构化的“用户状态”内存,用于推断和管理预先指定的用户信息
- 结构化的“用户追加状态”内存,用于推断与您的应用程序上下文相关的且可以语义查询的信息
- 非结构化用户语义记忆
非结构化语义记忆在每个部署中默认启用。我们将在下面看到更多内容。
启用结构化内存功能就像将模式发布到 LangMem 一样简单。然后,LangMem 会在每次向服务发送新消息时自动管理这些自定义配置文件。
用户状态¶
这是一个自定义用户信息。通过提供 JSON 模式或 pydantic 模型来实例化。
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
是一个追加状态(这意味着配置文件永远不会被覆盖),它允许您定义模式来表示单个内存,您可以稍后以语义方式查询这些内存。
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
,默认启用。如果您不打算使用它,可以将其关闭。
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")
线程摘要¶
LangMem 还支持线程级内存。我们将在下面创建它们。
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"
)
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]}"
以下是 1 个或多个用户与 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.
现在我们有了消息,我们可以将它们与 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)
获取消息¶
您可以通过该线程的 GET 端点获取 LangMem 线程中的所有消息。这样,LangMem 可以充当通用的聊天机器人后端。
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'}}
# 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. 在后续对话中使用¶
如您所见,我们已经从之前的对话中提取了一些有用的信息。我们假设您会在以后的对话中获取这些信息,以便为您的机器人提供有关用户的其他有用上下文信息。
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.
结论¶
在本教程中,你为两个用户保存了记忆,以跟踪他们的兴趣和其他属性。你只需将聊天信息发送到 LangMem 服务即可实现。然后,你自动触发更新以存储三种形式的长期记忆
- 用户状态“配置文件”,遵循你的自定义模式
- 用户追加状态,存储遵循你的自定义模式的原子记忆,并且可以语义查询。
- 通用知识作为语义三元组
你还可以跟踪线程范围内的摘要记忆,以帮助你组织对话线程。
最后,让我们清理工作!此演示是一个共享的工作区 :)
## 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"])