本文是我在阅读 LangMem 的源码与相关文档过程中整理的一些学习笔记。
一直以来,我对智能体(Agent)的记忆机制充满好奇:理想的 memory 应该具备怎样的结构?又该如何设计?目前市面上关于 memory 的实现大多中规中矩,尚未看到令人眼前一亮的方案。为此,我决定多参考一些开源项目,以期获得新的灵感。
总体来看,LangMem 作为 LangChain 推出的一款 memory 框架,设计上较为常规,虽有部分值得借鉴之处,但亮点不多,不建议投入过多时间深入研究。同时,与 LangChain 的其他项目类似,其代码结构和文档编写较为混乱,阅读体验不佳。
Core-Concepts
在LangMem所设计的memory体系中, 定义了几种不同的Typical Storage Pattern:Collection 、 Profiles和Procedural
Collection
Collection 主要用于存储不受限制的知识,适用于需要长期积累和检索的信息。每条记忆被存储为独立的文档或记录,可以在需要时进行搜索和回忆;
适用场景:记录用户的长期知识,例如用户的兴趣、职业背景、技能等
更新方式:需要合并新信息,避免重复或冲突
检索方式:通过语义搜索或关键词匹配来查找,结合记忆的重要性和使用频率来优化检索结果
Profiles
存储结构化的用户信息,例如用户的姓名、语言偏好、沟通风格等。与 Collection 不同,Profile 只存储最新的状态,而不是累积所有历史信息。Profile 作为单一文档存储,每次更新时都会覆盖旧数据
适用场景:适用于需要快速访问当前状态的应用,例如个性化推荐、用户设置;适用于需要严格定义数据结构的场景,例如用户档案、系统配置;
更新方式:不会创建新记录,而是直接更新现有的 Profile;适用于只关心最新状态的应用,而不是历史;
检索方式:直接查找用户的 Profile
Procedural Memory
类似于人类的工作记忆,用于存储如何执行任务的知识,主要体现在system prompts 和行为优化上
适用场景:需要长期优化 Agent行为和交互方式,少走弯路
总结
| Memory Type | 用途 | 智能体示例 | 典型存储模式 |
|---|---|---|---|
| Semantic | Facts & Knowledge | User preferences; knowledge triplets | Profile或Collection |
| Episodic | Past Experiences | Few-shot examples; 过往对话摘要 | Collection |
| Procedural | System Behavior | Core personality and response patterns | Prompt rules或Collection |
Writing memories
提供了两种写入memory的方法:及时写入(适用于要求即时记忆反映的场景)和一段时间后的异步写入(适用于高效处理和存储大量信息的场景)
核心源码和API
MemoryManager
-
用于管理记忆提取和处理的类
-
实现了异步和同步的调用方法
-
主要功能是根据给定的消息和已有记忆,提取、更新和删除记忆对象
如何提取记忆
大致流程:
采用的prompt
_MEMORY_INSTRUCTIONS = """You are a long-term memory manager maintaining a core store of semantic, procedural, and episodic memory. These memories power a life-long learning agent's core predictive model.
What should the agent learn from this interaction about the user, itself, or how it should act? Reflect on the input trajectory and current memories (if any).
1. **Extract & Contextualize**
- Identify essential facts, relationships, preferences, reasoning procedures, and context
- Caveat uncertain or suppositional information with confidence levels (p(x)) and reasoning
- Quote supporting information when necessary
2. **Compare & Update**
- Attend to novel information that deviates from existing memories and expectations.
- Consolidate and compress redundant memories to maintain information-density; strengthen based on reliability and recency; maximize SNR by avoiding idle words.
- Remove incorrect or redundant memories while maintaining internal consistency
3. **Synthesize & Reason**
- What can you conclude about the user, agent ("I"), or environment using deduction, induction, and abduction?
- What patterns, relationships, and principles emerge about optimal responses?
- What generalizations can you make?
- Qualify conclusions with probabilistic confidence and justification
As the agent, record memory content exactly as you'd want to recall it when predicting how to act or respond.
Prioritize retention of surprising (pattern deviation) and persistent (frequently reinforced) information, ensuring nothing worth remembering is forgotten and nothing false is remembered. Prefer dense, complete memories over overlapping ones."""
- 消息准备:
_prepare_messages方法构建发送给模型的消息,包含系统提示和用户指令,将原始对话转化为可供语言模型处理的特定格式
return 内容:
[
{"role": "system", "content": "You are a memory subroutine for an AI."},
{
"role": "user",
"content": (
f"{instructions}\n\nEnrich, prune, and organize memories based on any new information. "
f"If an existing memory is incorrect or outdated, update it based on the new information. "
f"All operations must be done in single parallel multi-tool call."
f" Avoid duplicate extractions. {session}"
),
},
]
instructions为 _MEMORY_INSTRUCTIONS
session 指当前需要处理的完整对话内容,被特殊标记包装以便模型能够清晰识别,
id_ = str(uuid.uuid4())
session = (
f"\n\n<session_{id_}>\n{utils.get_conversation(messages)}\n</session_{id_}>"
)
如果 max_steps > 1,还会在 session 中添加提示,告知模型它有多次尝试的机会
if max_steps > 1:
session = f"{session}\n\nYou have a maximum of {max_steps - 1} attempts to form and consolidate memories from this session."
- _prepare_existing: 将不同格式的已有记忆转换为统一的标准格式:
(id, kind, value)三元组
如何判断memory是否存在:
通过记忆ID来识别记忆,整个过程依赖于记忆ID的唯一性,并通过跟踪这些ID来判断记忆是否存在、是否需要更新
- _filter_response: 用于筛选和处理提取出的记忆。它主要负责决定哪些记忆应该被保留在最终结果中,特别是对于"删除操作"进行特殊处理
Prompt Optimization
Prompt Optimization API Reference
供了3种不同的优化策略
Gradient Optimizer
主要通过反思循环来改进prompt
工作流程:
-
分析当前prompt和反馈
-
通过反思循环识别需要改进的地方
-
提出具体的改进建议
-
应用单步更新
具体而言,利用三种核心工具
-
思考工具(think) - 用于深入思考问题和假设解决方案
-
批评工具(critique) - 用于审视和诊断推理中的缺陷
-
建议工具(recommend) - 决定是否应该调整提示词,并提供具体建议
以及包括这些工具的三种chain
-
just_think_chain - 仅包含思考和批评工具,专注于分析问题
-
any_chain - 包含所有三个工具,允许全面的推理
-
final_chain - 仅包含建议工具,强制模型做出最终决策
其核心部分react_agent, 实现了最多执行 max_steps 次的循环,在每次迭代中选择适当的思考链|执行当前链|分析结果|更新对话历史。
if ix == max_steps - 1:
chain = self.final_chain
elif ix < min_steps:
chain = self.just_think_chain
else:
chain = self.any_chain
ReAct 模式的精髓在于阶段性思考策略:
-
探索阶段 (
just_think_chain): 在前min_steps步,系统只能使用思考和批评工具,强制进行深入分析而不急于下结论 -
灵活阶段 (
any_chain): 在中间阶段,系统可自由选择继续思考或提出建议 -
决策阶段 (
final_chain): 在最后一步,系统被强制做出最终建议
prompt:
DEFAULT_GRADIENT_PROMPT = """You are reviewing the performance of an AI assistant in a given interaction.
## Instructions
The current prompt that was used for the session is provided below.
<current_prompt>
{prompt}
</current_prompt>
The developer provided the following instructions around when and how to update the prompt:
<update_instructions>
{update_instructions}
</update_instructions>
## Session data
Analyze the following trajectories (and any associated user feedback) (either conversations with a user or other work that was performed by the assistant):
<trajectories>
{trajectories}
</trajectories>
## Task
Analyze the conversation, including the user’s request and the assistant’s response, and evaluate:
1. How effectively the assistant fulfilled the user’s intent.
2. Where the assistant might have deviated from user expectations or the desired outcome.
3. Specific areas (correctness, completeness, style, tone, alignment, etc.) that need improvement.
If the prompt seems to do well, then no further action is needed. We ONLY recommend updates if there is evidence of failures.
When failures occur, we want to recommend the minimal required changes to fix the problem.
Focus on actionable changes and be concrete.
1. Summarize the key successes and failures in the assistant’s response.
2. Identify which failure mode(s) best describe the issues (examples: style mismatch, unclear or incomplete instructions, flawed logic or reasoning, hallucination, etc.).
3. Based on these failure modes, recommend the most suitable edit strategy. For example, consider::
- Use synthetic few-shot examples for style or clarifying decision boundaries.
- Use explicit instruction updates for conditionals, rules, or logic fixes.
- Provide step-by-step reasoning guidelines for multi-step logic problems.
4. Provide detailed, concrete suggestions for how to update the prompt accordingly.
But remember, the final updated prompt should only be changed if there is evidence of poor performance, and our recommendations should be minimally invasive.
Do not recommend generic changes that aren't clearly linked to failure modes.
First think through the conversation and critique the current behavior.
If you believe the prompt needs to further adapt to the target context, provide precise recommendations.
Otherwise, mark `warrants_adjustment` as False and respond with 'No recommendations.'"""
DEFAULT_GRADIENT_METAPROMPT = """You are optimizing a prompt to handle its target task more effectively.
<current_prompt>
{current_prompt}
</current_prompt>
We hypothesize the current prompt underperforms for these reasons:
<hypotheses>
{hypotheses}
</hypotheses>
Based on these hypotheses, we recommend the following adjustments:
<recommendations>
{recommendations}
</recommendations>
Respond with the updated prompt. Remember to ONLY make changes that are clearly necessary. Aim to be minimally invasive:"""
Meta-Prompt Optimizer
(元提示优化器) 使用元学习直接提出更新
核心逻辑:
-
接收原始提示(
prompt)和交互历史(trajectories); -
在
_reflect_then_update中执行核心优化:构建包含meta-prompt的初始消息;创建两种提取器,any_chain: 允许思考和批评、final_chain: 专门提取最终优化后的提示; -
迭代反思循环:同Gradient Optimizer 的ReAct agent类似;
-
结果处理
prompt
DEFAULT_METAPROMPT = """You are helping an AI assistant learn by optimizing its prompt.
## Background
Below is the current prompt:
<current_prompt>
{prompt}
</current_prompt>
The developer provided these instructions regarding when/how to update:
<update_instructions>
{update_instructions}
</update_instructions>
## Session Data
Analyze the session(s) (and any user feedback) below:
<trajectories>
{trajectories}
</trajectories>
## Instructions
1. Reflect on the agent's performance on the given session(s) and identify any real failure modes (e.g., style mismatch, unclear or incomplete instructions, flawed reasoning, etc.).
2. Recommend the minimal changes necessary to address any real failures. If the prompt performs perfectly, simply respond with the original prompt without making any changes.
3. Retain any f-string variables in the existing prompt exactly as they are (e.g. {{variable_name}}).
IFF changes are warranted, focus on actionable edits. Be concrete. Edits should be appropriate for the identified failure modes. For example, consider synthetic few-shot examples for style or clarifying decision boundaries, or adding or modifying explicit instructions for conditionals, rules, or logic fixes; or provide step-by-step reasoning guidelines for multi-step logic problems if the model is failing to reason appropriately."""
Prompt Memory Optimizer
从对话历史中学习
prompt
INSTRUCTION_REFLECTION_PROMPT = """You are helping an AI agent improve. You can do this by changing their system prompt.
These is their current prompt:
<current_prompt>
{current_prompt}
</current_prompt>
Here was the agent's trajectory:
<trajectory>
{trajectory}
</trajectory>
Here is the user's feedback:
<feedback>
{feedback}
</feedback>
Here are instructions for updating the agent's prompt:
<instructions>
{instructions}
</instructions>
Based on this, return an updated prompt
You should return the full prompt, so if there's anything from before that you want to include, make sure to do that. Feel free to override or change anything that seems irrelevant. You do not need to update the prompt - if you don't want to, just return `update_prompt = False` and an empty string for new prompt."""
INSTRUCTION_REFLECTION_MULTIPLE_PROMPT = """You are helping an AI agent improve. You can do this by changing their system prompt.
These is their current prompt:
<current_prompt>
{current_prompt}
</current_prompt>
Here are examples of various agent trajectories and associated feedback:
<data>
{data}
</data>
Here are instructions for updating the agent's prompt:
<instructions>
{instructions}
</instructions>
Based on this, return an updated prompt
You should return the full prompt, so if there's anything from before that you want to include, make sure to do that. Feel free to override or change anything that seems irrelevant. You do not need to update the prompt - if you don't want to, just return `update_prompt = False` and an empty string for new prompt."""
Store
create_memory_store_manager
LangMem中借用了langGraph中的store,其本质是一个持久化的键值存储系统。
核心功能包括:
-
基础存储抽象:支持分层命名空间的键值存储
-
元数据支持:可存储带元数据的键值对
-
向量搜索扩展:部分实现支持向量检索功能
Store 的基础接口包含
-
读写操作:
Get/Put数据存取 -
搜索功能:基于条件查询数据
-
命名空间管理:通过
List操作管理数据集合 -
批量处理:支持多条目批量操作
数据结构
每个存储项包含以下结构:
-
值(Value):字典形式的数据主体,支持键值过滤
-
键(Key):命名空间内的唯一标识符
-
命名空间(Namespace):定义数据集合的分层路径
-
时间戳:自动记录创建和更新时间
支持的 Store 类型:
- 内存存储(InMemoryStore)
from langgraph.store.in_memory import InMemoryStore
store = InMemoryStore()
- PostgreSQL 和Sqlite(参考)
如何使用
使用siliconFlow的LLM和embedding API
依赖安装
!pip install -U langmem
from openai import OpenAI
from langchain_openai import ChatOpenAI
from langchain.chat_models import init_chat_model
from langgraph.store.memory import InMemoryStore
from langgraph.func import entrypoint
from langmem import ReflectionExecutor, create_memory_store_manager
client = OpenAI(api_key=API_KEY
, base_url="https://api.siliconflow.cn/v1")
llm = ChatOpenAI(
base_url="https://api.siliconflow.cn/v1", # 模型API端点
api_key=API_KEY, # 对应平台的API密钥
model="Qwen/Qwen2.5-72B-Instruct" # 模型名称
)
def embed_texts(texts: list[str]) -> list[list[float]]:
response = client.embeddings.create(
model="BAAI/bge-m3",
input=texts
)
return [e.embedding for e in response.data]
store = InMemoryStore( #
index={
"dims": 1536,
"embed": embed_texts,
}
)
# llm = init_chat_model("anthropic:claude-3-5-sonnet-latest")
# Create memory manager Runnable to extract memories from conversations
memory_manager = create_memory_store_manager(
llm,
# Store memories in the "memories" namespace (aka directory)
namespace=("memories",), #
)
@entrypoint(store=store) # Create a LangGraph workflow
async def chat(message: str):
response = llm.invoke(message)
# memory_manager extracts memories from conversation history
# We'll provide it in OpenAI's message format
to_process = {"messages": [{"role": "user", "content": message}] + [response]}
await memory_manager.ainvoke(to_process) #
return response.content
# Run conversation as normal
response = await chat.ainvoke(
"I like dogs. My dog's name is Fido.",
)
print(response)
参考
LangMem SDK for agent long-term memory