两个 notebook 的记忆存取路径:微软 AI Agent 入门课第 13 课
写 Agent 的人迟早会撞上同一件事:会话里聊得好好的,换个会话再问一遍,它什么都不记得了。你知道该上”长期记忆”,但真正卡住你的往往不是概念,而是三个很具体的问题——东西存在哪、什么时候写进去、什么时候读出来。
ai-agents-for-beginners 第 13 课把这三个问题摆在了两份代码里:13-agent-memory/13-agent-memory.ipynb 和 13-agent-memory/13-agent-memory-cognee.ipynb。两份都是 Python 版本,都基于 Microsoft Agent Framework,但存储后端完全不同。下面顺着代码走一遍这两条路径,重点是调用点落在哪一行。
前置条件:两个 notebook 要的东西不一样
先说这一段,因为两份 notebook 的 Setup 单元差异比看上去大,照着第一份配完环境去跑第二份会缺东西。
13-agent-memory.ipynb 的安装单元是:
%pip install agent-framework azure-ai-projects azure-identity python-dotenv -q
它的 Prerequisites 写明需要一个部署了 chat 模型的 Microsoft Foundry 项目、用 Azure CLI 登录(az login),以及两个环境变量 AZURE_AI_PROJECT_ENDPOINT 和 AZURE_AI_MODEL_DEPLOYMENT_NAME。代码里用 os.getenv 读它们,然后拼了一个 missing 列表,缺哪个就抛 ValueError 并把缺的变量名列出来——这个前置检查是它自己写的,不是框架给的。
13-agent-memory-cognee.ipynb 的安装单元多了一个依赖,也少了一个:
%pip install agent-framework azure-ai-projects azure-identity "cognee[redis]==0.4.0" -q
这里的版本 pin 是仓库当前 notebook 里写死的,随仓库更新可能变动。注意 python-dotenv 不在这条 pip 行里,但紧接着的导入单元里有 from dotenv import load_dotenv——仓库里没有对这处差异的说明,如果你是从干净环境起步的,这一点值得留意。
它的 Prerequisites 还多列了几条:Python 3.9+、本机跑一个 Redis 用于 session 管理、.env 里要有 LLM_API_KEY、以及 CACHING=true(notebook 原文写的是 Cognee sessions 需要它)。Redis 那条给的命令是:
docker run -d -p 6379:6379 redis
Windows 侧要额外说一句:这条命令依赖你本机有可用的 Docker 环境(一般是 Docker Desktop),这属于通用运行环境要求,不是课程仓写的内容,具体怎么装请以 Docker 官方说明为准。仓库这两份 notebook 的 Prerequisites 都要求先跑 az login,但它们在代码里取凭据用的类并不是同一个——这一点见下一节。
路线一:字典 + 两个 @tool 函数
13-agent-memory.ipynb 的客户端这样建:
client = FoundryChatClient(
project_endpoint=endpoint,
model=deployment_name,
credential=DefaultAzureCredential()
)
存储后端就是一行:
preference_store: dict[str, list[str]] = {}
一个进程内的字典,key 是 user_id,value 是偏好字符串列表。notebook 自己在 markdown 里写了这是模拟的持久化存储,生产里该换成数据库或向量索引。
写入调用点在 save_preference,召回调用点在 get_preferences,两个都用 @tool 装饰:
@tool(approval_mode="never_require")
def save_preference(
user_id: Annotated[str, "User identifier"],
preference: Annotated[str, "A travel preference to remember"],
) -> str:
"""Save a user travel preference to long-term memory."""
preference_store.setdefault(user_id, []).append(preference)
return f"✅ Stored: {preference}"
真正的落库动作就是 setdefault(user_id, []).append(preference),召回那边是 preference_store.get(user_id, []),然后拼成一段文本回给模型。注意这两个函数不是你的代码去调的,是模型决定调的——所以”什么时候写、什么时候读”这件事,实际写在 agent 的 instructions 里:
travel_agent = client.as_agent(
tools=[save_preference, get_preferences],
name="TravelBookingAssistant",
instructions=(
"You are a personalized travel booking assistant with long-term memory.\n"
"WORKFLOW:\n"
"1. When a user starts a conversation, call get_preferences() to check for saved information.\n"
...
),
)
这一点是这份 notebook 最值得抄走的结构:长期记忆的触发时机不是框架的钩子,而是提示词里的一条工作流约定。至于 approval_mode="never_require",仓库里 .agents/skills/deploying-scalable-agents/SKILL.md 用 @tool(approval_mode="always_require") 举了”大额退款需人工审批”的例子,两者是同一个参数的两个取值。
工作记忆那一侧则由框架提供:agent.create_session() 返回 AgentSession,同一个 session 传给连续几次 agent.run(),历史就在。notebook 里 Scenario 2 专门新建了 session_2 = travel_agent.create_session(),工作记忆清空、字典还在,这就是两层记忆的分界。
路线二:Cognee 的图谱路径
13-agent-memory-cognee.ipynb 的客户端变量名叫 provider,凭据类换成了 AzureCliCredential,而且环境变量是用 os.environ["AZURE_AI_PROJECT_ENDPOINT"] 直接下标取的——和第一份的 os.getenv + 手写检查不同,这里缺变量会直接抛 KeyError。
凭据类这处差异值得单独记一笔:第一份用的是 DefaultAzureCredential,第二份用的是 AzureCliCredential,两个类都来自 azure.identity,但仓库里没有解释为什么两份 notebook 选了不同的类。你要做的只是别把两份代码混着抄——尤其是在 Windows 上,如果你的登录状态只存在于某一种凭据来源里,换个类就可能拿不到身份。
存储位置由两行配置决定:
cognee.config.data_root_directory(str(DATA_ROOT))
cognee.config.system_root_directory(str(SYSTEM_ROOT))
DATA_ROOT 与 SYSTEM_ROOT 分别是 Path('.data_storage').resolve() 与 Path('.cognee_system').resolve(),resolve() 相对当前工作目录展开——在 Windows 上就是你启动 notebook 的那个盘符路径下的两个隐藏目录,换目录跑就是另一份存储。
写入这一段全在同一个单元里,三次 add() 之后跟一次 cognify():
await cognee.add(developer_intro, node_set=["developer_data"])
await cognee.add(human_agent_conversations, node_set=["developer_data"])
await cognee.add(python_zen_principles, node_set=["principles_data"])
await cognee.cognify()
cognee.add() 只是把原始文本放进去并打上 node_set 标签,把文本变成图谱的是 cognify()。之后还有一步 await cognee.memify(),notebook 自述它会分析已建好的图谱并生成规则。
召回调用点在两个 @tool 函数里,注意它们是 async def:
results = await cognee.search(
query_text=query,
query_type=SearchType.GRAPH_COMPLETION,
)
SearchType 从 cognee.modules.search.types 导入,这份 notebook 里只用到了 GRAPH_COMPLETION 这一个取值。第二个工具 search_principles 多传了两个参数做范围限定:
from cognee.modules.engine.models.node_set import NodeSet
results = await cognee.search(
query_text=query,
query_type=SearchType.GRAPH_COMPLETION,
node_type=NodeSet,
node_name=["principles_data"],
)
node_name 里的 "principles_data" 正是前面 cognee.add() 时打的标签——写入时的 node_set 和召回时的 node_name 是同一套字符串,这是这条路线里最容易接错的一处。以上片段均原样取自仓库 notebook,未经实测,以仓库最新代码为准。
边界:几处对不上的地方
README 与代码不一致。 13-agent-memory/README.md 的 Available Implementations 一节写着 13-agent-memory.ipynb “Implements memory using Mem0 and Azure AI Search”,但打开这份 notebook,代码里既没有 Mem0 也没有 Azure AI Search,长期记忆就是那个进程内字典。Mem0 与 Azure AI Search 在 README 正文里只是作为两种可选方案被介绍。以哪个为准,仓库里没有给出说明。
有一个工具定义了但没注册。 search_hotels 被 @tool 装饰、也被打印进了”Tools defined”那一行,但 client.as_agent(...) 的 tools=[save_preference, get_preferences] 里没有它——而同一段 instructions 的第 3 条却写着 “Use search_hotels() to find suitable options”。这两处白纸黑字摆在一起就是矛盾的,你自己照抄时记得把它补进 tools 列表。
Cognee 那份每跑一次就清空。 存储配置之后紧跟着:
await cognee.prune.prune_data()
await cognee.prune.prune_system(metadata=True)
notebook 自己把这一步的输出写成了 “configured and reset”。作为教学 notebook 这样做合理,但如果你把它当成长期记忆的起点代码搬走,这两行必须删掉,否则每次重启都是一张白纸。
Cognee 的 session 在代码里没出现。 notebook 的记忆类型表把短期记忆一栏标成 “Cognee session cache (Redis)“,Prerequisites 也要求 Redis 与 CACHING=true,但代码单元里我们没有找到调用 Cognee 会话 API 的地方,出现的只有 MAF 的 coding_agent.create_session()。这两者之间怎么衔接,仓库里没有找到相关说明。
还有一条通用的:两份 notebook 都会把你的对话内容发往云端模型服务,Cognee 那份还会用 LLM_API_KEY 走另一条模型调用链路,密钥与数据边界请自行评估。
怎么验证配对了
第一份 notebook 自带一个检查单元,直接把存储内容打出来:
print("📋 Preference store contents:")
for uid, prefs in preference_store.items():
print(f"\n User: {uid}")
for p in prefs:
print(f" - {p}")
字典里有东西,说明模型确实调到了 save_preference;是空的,那问题在提示词或工具注册上,不在存储上。再往下 Scenario 2 新建 session 复问一次,就能分辨记忆是来自会话历史还是来自那个字典。
第二份的验证手段是可视化:
from cognee import visualize_graph
await visualize_graph('./cognee_graph.html')
notebook 里这个单元的位置本身就是提示:它排在 cognify() 之后、@tool 函数与 search() 之前。也就是说先确认图里抽出了哪些实体和关系,再去问问题。文件名 ./cognee_graph.html 是相对路径,落在当前工作目录。想验证 node_name 那层过滤有没有生效,就用 search_principles 问一个只在 python_zen_principles 文本里出现的说法,再用 search_knowledge 问同一句做对照——两个工具的差别只有那两个多传的参数,其余完全一样,这是这份 notebook 里唯一能把过滤单独隔离出来看的地方。
最后提醒一句:上面引用的所有路径、变量名与依赖写法都是仓库当前的样子,课程本身持续更新,动手前请以仓库最新内容为准。
本文依据 github.com/microsoft/ai-agents-for-beginners 仓库于 2026-08-18 的公开内容整理,
事实来自仓库内的课程正文与代码示例。我们没有跑过文中涉及的代码,
因此不涉及运行结果、耗时与 Agent 实际表现的任何描述。
该课程持续更新,文中涉及的文件路径、依赖与接口写法随版本变动,请以仓库最新内容为准。
文中涉及的云端服务调用会产生费用并可能上传数据,请自行评估密钥与数据边界。
安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。