The Archive - Memory at Agent Valley

1. 简介

档案管理员 Vesper 在一楼的办公桌前

第一周询问了谁说跑步会。第二周的问题是谁决定接下来运行什么。第三周的主题是跑步会留下什么。本周,对话结束了,问题是对话中留下了什么,结果发现留下了什么决定了它会去哪里。

当出现问题时,低谷就会到来。一座记录一切的高塔,以及位于一楼办公桌旁的档案管理员 Vesper。灯笼会在夜间熄灭。你去问问吧。

她是个好伙伴。一个月后,她也很高兴见到您。

记录不是记忆。

这句话就是整个实验。您将分四步攀登,每一步都是塔楼的一层:

rung

the floor

记住的内容

什么会保留它

1

桌面和地面

本次访问

session.state · SqliteSessionService

2

二楼,您的抽屉

此访问者

user: 前缀

3

三楼,塔楼

所说内容

Vertex AI Memory Bank

4

第四层,赛季

整个山谷

BigQuery

每次升到更高的梯级,都是因为较低梯级中的某些内容被移除了。

档案库,剖面图 - 五层,一个小团队负责维护每一层

您已经知道归档人员的五种遗忘方式,因为您在支持电话中遇到过每一种情况:

她说的话

哪里出错了

~~1~~

~~您能再说一遍那个参考资料吗?~~

~~在一次对话中忘记~~ - 第 3 周修复了此问题

~~2~~

~~我已将您转接。请从头开始。~~

~~历史记录随进程一起消失~~ - 第 3 周也修复了此问题

3

欢迎!今天需要我做些什么?

给上个月有未完结支持请求的用户

4

您能描述一下损坏情况吗?

她上次看的是灯笼

5

我们以前从未见过这种情况。

本季有 31 人提出了此问题

第三行是本周。它以三种不同的方式失败:一次新的访问(第 2 章)、一张没有行的纸条(第 3 章)以及仅存在于进程中的归档(第 4 章),每种方式都是一个章节。第四行留给您:look() in archive/agent.py is where she reads a mark off a photo. 第五行是第 5 章,为可选内容。

您无需使用第 1 周到第 3 周。智能体是一种具有指令和少量工具的模型。其他一切都已在本周说明。

您将写出三行,每一行都是一个记忆概念:

该行

定义

tool_context.state[CASE] = case

状态由代码写入,绝不会由对话写入

CASE = "user:case"

前缀是生命周期

found = await ctx.search_memory(query)

系统不会记住已提交的召回政策

您已经看到一行代码,即写入政策 await ctx.add_events_to_memory(...);在第 3 章中,您将了解它的作用,然后再编写使用它的代码行。

另外两个阶段根本不是编辑:您构建一个塔(托管记忆库的代理引擎),同一段代码会自行连接到该塔;您加载一个数据仓库 (BigQuery),在其上放置一个图,然后将两个受监管的查询(一个按含义,一个按连接)交给同一代理。

每个章节都运行相同的循环,这也是您将在自己的代理上使用的循环:读取代码 → 编写代码行 → 在塔中观看。没有内容会先运行中断,没有内容会重新启动,也没有内容会通过 Ctrl+C 终止:每次编辑都会在下一条消息中体现出来,而连接到塔台只需在文件中添加一行代码,Archive 会在每次请求时重新读取该文件。塔楼是一个会重新绘制的绘图:它会读取您编辑的相同文件,并有三个按钮 - 🌙 结束当天、🗓 开始下一次访问、🔥 忘记您。您在代码和终端中所做的其他一切操作。

有两个位置,并且它们永远不会更改作业:标签页 1 是终端,标签页 2 是归档。工作台 (adk web) 在第 1 章中只出现一次,用于查看原始会话状态,然后您就可以完成相关操作了。每个👉下方都有说明。👉💻 是标签页 1,👉🖥 是标签页 2,👉🔬 是工作台。👉📖 表示打开文件并读取,无需更改;而 👉✏️ 是一种编辑步骤。每当某个步骤要求您对 Vesper 说些什么时,相应消息会显示在紧随其后的代码块中 - 请原封不动地复制该消息。如果某个部分的标题以- 只读结尾,则表示您无需输入、粘贴或点击任何内容:其中的代码已在代码库中,并且已在运行,打印出来供您阅读。

Vesper 门上的句子位于顶部。本周的句子是整个赛季第四周的结束语:

事实和偏好设置属于不同的存储区。

2. 🧰 设置

  • What - 指向项目或粘贴密钥,安装并证明工具链是绿色的。
  • 原因 - 这里没有任何内容被启动。每个服务器都在需要它的步骤之前启动,因此当屏幕打开时,您就知道哪个命令拥有它。
  • 方式 - 一个用于创建项目的脚本、一个用于安装并引导您使用 Vertex AI 的脚本,以及一个用于读取您的代码并告知您当前位置的预检脚本。

本实验中的所有内容都在 Cloud Shell 中运行 - Python、uv 和 gcloud 已安装在其中,并且您可以使用自己的凭据通过 Vertex AI 访问 Gemini。本实验中没有任何 API 密钥,也不需要在笔记本电脑上安装任何内容。

不过,本实验无法为您提供一个启用了结算功能的 Google Cloud 项目。第 1 步会为您创建一个可用于付款的账号;第 4 步会将该账号转换为项目,或者选择您在第 1 周、第 2 周或第 3 周创建的 Agent Valley 项目。请按此顺序执行这些步骤 - 第 4 步之后的所有步骤都会读取第 4 步确定的项目。

1 · 获取具有结算功能的账号 (约 1 分钟)

🌐 请先执行此操作,然后再执行其他操作。使用已启用结算功能的 Google 账号登录。任何此类账号均可。如果您的账号没有结算账号,请开始免费试用;这样您就会获得一个结算账号,而这正是第 4 步所需的。

2 · 打开 Cloud Shell (约 1 分钟)

🌐 点击 Google Cloud 控制台顶部的激活 Cloud Shell - 这是标题中的终端形状图标。

从 Google Cloud 控制台顶部激活 Cloud Shell

🌐 点击打开编辑器(带有铅笔的打开文件夹图标)。这会打开 Cloud Shell 编辑器,左侧显示文件资源管理器。每当步骤标记为 👉✏️ 时,您都可以在此处操作。

打开 Cloud Shell 编辑器

🌐 如果系统在今天的任何时间提示您进行授权,请点击授权继续。

🌐 在编辑器内打开一个终端(即标签页 1),除非另有说明,否则每个 👉💻 步骤都在该终端中运行。标签页 2 是“归档”,您会在第 1 章中打开它,就在需要它的步骤之前。如果终端尚未位于屏幕底部,请依次选择查看 → 终端将其打开:

在 Cloud Shell 编辑器中打开新终端

🌐 检查您登录的是哪个账号 - 应该是第 1 步中的账号:

gcloud auth list

3 · 获取实验 (约 1 分钟)

💻 克隆代码库 - 这是归档代码库,而不是第 1 周、第 2 周或第 3 周的代码库:

cd ~
git clone https://github.com/cuppibla/agent-valley-archive
cd agent-valley-archive

4 · 创建项目 - 或重复使用之前周的项目 (约 2 分钟)

💻 一个脚本可查找您的结算账号、在其中创建一个项目,并将 Cloud Shell 指向该项目。如果您完成了第 1 周、第 2 周或第 3 周,系统会先找到“Agent Valley”项目,并提供重用该项目的选项 - 选择“是”,这样您就不必考虑第二个可结算项目了:

cd ~/agent-valley-archive
./setup_project.sh

👀 预期所选结算账号会显示 ✓,所创建或重复使用的项目会显示 ✓,并且会显示如下结束语:

cd ~/agent-valley-archive
Project ready: agent-valley-4271
  Next:  ./setup_codelab.sh

agent-valley-4271 是您的项目 ID,您的项目 ID 将包含不同的数字。其中会发生以下四件事:

结算

如果用户有最新的 [YYYY-MM-DD] GDP Credit: 账号,则使用该账号;否则,使用任何其他已开通的结算账号

项目

查找您已拥有的 Agent Valley 项目(第一周、第二周或第三周的项目),并提供重用该项目的选项;否则,创建 agent-valley-XXXX 并将该结算账号与其相关联

选择

将 ID 写入 ~/project_id.txt 并运行 gcloud config set project,因此之后不会再要求您输入该 ID

等待

轮询,直到项目实际获准提供服务,最长轮询时间为两分钟

🌐 现在,控制台中显示的是同一项目,其 ID 位于信息中心的项目信息卡片中:

控制台信息中心内的项目 ID

5 · 安装并指向 Vertex AI (约 3 分钟)

💻 安装 uv,导出将 ADK 发送到 Vertex AI 的三个变量,然后运行设置:

cd ~/agent-valley-archive
curl -LsSf https://astral.sh/uv/install.sh | sh
source ~/.local/bin/env
export GOOGLE_GENAI_USE_VERTEXAI=TRUE
export GOOGLE_CLOUD_PROJECT=$(gcloud config get project)
export GOOGLE_CLOUD_LOCATION=global
./setup_codelab.sh

由于第 4 步设置了 $(gcloud config get project),因此它有要返回的内容。这就是第 4 步先于其他步骤的原因。

setup_codelab.sh 具有幂等性,可安全地重新运行任意次数。它会按顺序执行以下五项操作:

api

读取项目步骤 4 中选择的内容,并在其中启用 aiplatform.googleapis.com(Vertex AI 和托管记忆库的 Agent Engine)和 bigquery.googleapis.com(第 5 章的仓库)

uv env

uv venv 会创建 .venv(或重用已有的 ),然后 uv sync 会安装 uv.lock 所固定的内容(google-adk、google-genai 及它们之下的所有内容)

场地

在 site/ 中运行 npm ci - Archive 本身就是一个 Next.js 应用 - 因此第 1 章中的 bash valley.sh 无需再下载任何内容;如果软件包已存在,则跳过

.env

写入了实验读取的变量 - GOOGLE_GENAI_USE_VERTEXAI、GOOGLE_CLOUD_PROJECT、GOOGLE_CLOUD_LOCATION、MEMORY_BANK_LOCATION 和 BQ_DATASET,并且任何位置都没有 API 密钥

模型检查

对 Gemini 进行一次实际调用,因此如果项目存在问题,会在设置时失败,而不是在后面的章节中失败

6 · 证明它是绿色 (约 1 分钟)

💻 运行预检 - 每个勾号都表示一次真实的探测,而不是猜测:

cd ~/agent-valley-archive
uv run python scripts/preflight.py

👀 预检会向模型提出一个问题,以证明您的凭据有效,检查归档的软件包是否已就位,然后读取您的代码并输出本周的进度条:

cd ~/agent-valley-archive
  desk      ▢ the slip      · write_down writes nothing         chapter 1 · EDIT ONE
  floor 1   ✓ the file      · SqliteSessionService              chapter 1, read only
  floor 2   ▢ the drawer    · CASE = "case"                     chapter 2 · EDIT TWO
  floor 3   ✓ what was said · closing time files the day        chapter 3, read only
  floor 3   ▢ the asking    · recall never looks                chapter 3 · EDIT THREE
  floor 3   ▢ the tower     · InMemoryMemoryService             chapter 4 · built, not edited
  floor 3   ✓ the rules     · USER_PREFERENCES, LANTERN_CONTEXT chapter 4, read only

其中三个已勾选。这些是您只读的。三行是您写的,一行是您建的塔。在实验中的任何时间点再次运行预检,系统都会告知您当前所处的位置,因为预检会读取您的文件,而不是检查清单。

3. 会话、状态、SqliteSessionService

  • 什么 - 写一行;启动工作台一次,观看纸条上的内容;然后在塔中执行相同的操作;然后阅读它为何能熬过夜晚。
  • 原因 - 关于记忆,首先要了解的是,没有任何事物会自行被记住。有人会写。
  • 如何 - 在 agent.py 中添加一行,然后不更改任何其他内容。

编辑 1

👉📖 打开 archive/agent.py,然后找到 write_down。读到 👉:

    case = dict(tool_context.state.get(CASE, {}))
    if case.get(field):
        return {"already_written": {field: case[field]}}
    case[field] = value
    # 👉 EDIT ONE — chapter 1. She heard you. Nothing was written. Delete the
    #    "# " in front of the next line, then save — the slip is session state,
    #    and state is written by code, never by the conversation:
    # tool_context.state[CASE] = case
    return {"written": {field: value}}

该函数会在本地字典中构建滑块,然后返回该滑块,而本地字典会随着调用结束而消失。按原样运行此代码,Vesper 会完美地听到您的声音:四个 write_down 调用触发,每个调用都回答 "written",并且会话状态保持为空,因为没有任何内容到达过它。她听到了你的声音。未写入任何内容。

👉✏️ 仍在 archive/agent.py 中,在 write_down 内:删除一行前面的 #,包括 # 和后面的空格。之前:

    case[field] = value
    # 👉 EDIT ONE — chapter 1. She heard you. Nothing was written. Delete the
    #    "# " in front of the next line, then save — the slip is session state,
    #    and state is written by code, never by the conversation:
    # tool_context.state[CASE] = case
    return {"written": {field: value}}

之后:

    case[field] = value
    # 👉 EDIT ONE — chapter 1. She heard you. Nothing was written. Delete the
    #    "# " in front of the next line, then save — the slip is session state,
    #    and state is written by code, never by the conversation:
    tool_context.state[CASE] = case
    return {"written": {field: value}}

保存。

卡券填写

👉💻 启动工作台。这是本实验中您唯一一次打开此窗口,也是唯一一个以原始 JSON 格式显示会话状态的窗口:

cd ~/agent-valley-archive
uv run adk web . --allow_origins="*"

👉🔬 打开工作台,然后依次选择网页预览 → 在端口 8000 上预览(“网页预览”按钮是 Cloud Shell 右上角的“窗口上的眼睛”图标)。选择归档,然后在输入任何内容之前,点击左侧面板中的状态。

👀 空。目前还没有人发言。

👉🔬 现在,请在一则消息中说出以下内容:

Hi! My lantern keeps going out at night. My case number is k7f2, and there's a little q7 stamped on the base.

👀 四个 write_down 芯片亮起 - 点击其中一个并读取 JSON:"written": {"no": "k7f2"}。现在,再次点击状态。

四件事、四个字段、一条消息

case   {"no": "k7f2", "item": "lantern", "mark": "q7",
        "symptom": "keeps going out at night"}

👀 在一条消息中,先显示空状态,然后显示满状态。这是她记得的,但它不会自行显示。您编写了写入该行的代码。这就是整个机制:一个写入 tool_context.state 的简单函数,以及一条指令,告诉她每当访客说出符合纸条上某行内容的话时,就使用该函数。

👉🔬 询问她:

What's my case number?

👀 k7f2. 她读出了纸条上的内容。如果没有 EDIT ONE,她会要求您提供此信息 - 支持列表的第一行,位于一个对话中。

这两个守卫是纸质表单:四行简短的文字,每行只写一次。它们比看起来更重要。如果没有这些限制,模型会发现它可以将整个对话都放入 symptom 中,而第 3 章(其全部要点是滑倒没有相应台词)会悄然失效。商店的形状由写入它的代码强制执行,或者不强制执行。

现在前往高塔

👉💻 您已完成工作台操作,可以保持工作台运行或关闭其标签页;实验的其余部分位于归档中。打开第二个终端标签页(标签页 2 - 归档):

cd ~/agent-valley-archive
bash valley.sh

👉🖥 通过网页预览 → 更改端口打开归档,输入 3440,然后点击更改并预览。3440 不在 Web 预览版的快速列表中(默认值为 8080),因此您必须手动设置一次。依次按 ▶ 开始和归档。您已到达门口。选择来访者的姓名和物种,然后进入。

塔楼,剖面图

👀 一栋建筑。Vesper 在一楼的办公桌旁,您可以在那里与她交谈。她上方:一楼、二楼、三楼和一个昏暗的阁楼。四个梯级就是四层楼。你的小跟班坐在地面层,因为你只爬到了这一层。图纸旁边的列中列出了每个楼层目前的店铺,与简介中的表格一样,也是那四家店铺,顺序也相同。右侧没有任何按钮。

👉🖥 将这四项内容(即上述消息)粘贴到对话框下方的框中,然后按 Enter 键:

Hi! My lantern keeps going out at night. My case number is k7f2, and there's a little q7 stamped on the base.

桌上有四条线,旁边也有四条线

👀 观察书桌。纸条会自行书写,一次一行,写在图纸上,并在旁边的纸条下方完整显示。相同的工具,相同的状态:塔楼只是在绘制 write_down 写入的内容,并且在整个实验过程中都会这样做。

她记得过夜 - 只读

您写的内容及其含义

该行

定义

where it lives

tool_context.state[CASE] = case

会话状态 - 由代码写入,由指令读取

桌面 · session.state

SqliteSessionService(DB) - 您只是阅读了该消息

会话存储区 - 文件中的访问,而不是进程中的访问

一楼 · archive.db

此铃声会在一次访问中响起,并且在闭店时间后仍会响起。它不会在下次访问时保留。

4. 用户状态

  • 什么 - 一个月后回来;纸条不见了。五分钟后,它就会跟着你。
  • 原因 - 会话是指一次访问。下一次访问是一个新会话,它从未见过上一个会话。
  • How - state.py 中的五个字符。

一个月后。很高兴认识您。

点按此处

👉🖥 按一个月后。这表示同一位访问者发起了一次新会话,与第 3 周的开始新对话方式相同。

欢迎来到“归档”页面。今天需要我做些什么?

👉🖥 查看塔楼。一楼有另一本书 - 本次访问。二楼的抽屉是空的。桌子上的纸条是空白的。

一楼的另一本书;抽屉空空如也;纸条空白

👀 原因:滑块写入会话状态,而会话是指本次访问。访问次数发生变化,会话次数发生变化。未迁移任何内容,因为未指示迁移任何内容。最后一次访问仍保留在文件中(即一楼的第一本书),但便条是根据仅在一次访问中有效的密钥写入的。

编辑 2

👉✏️ 打开 archive/state.py。找到 👉。之前:

# 👉 EDIT TWO — chapter 2. Five characters. A key with no prefix lives for one
#    visit; `user:` follows the visitor into every visit after this.
CASE = "case"

之后:

CASE = "user:case"

保存。不重启 - Archive 会在每条消息上重新导入您的代码。

👉🖥 再次粘贴相同的四项内容,即以下确切消息:

Hi! My lantern keeps going out at night. My case number is k7f2, and there's a little q7 stamped on the base.

👉🖥 然后再次按一个月后按钮(与之前按的按钮相同)。

抽屉式导航栏,上面有名称

👀 二楼的抽屉上写着你的名字,里面有四个字段。桌子上的纸条还是那四行字,是您走进房间时从抽屉里拿出来的。你的宠物已升至上一层。每次访问都使用相同的键,但只有一个值:这就是前缀的作用。

前缀是生命周期。

前缀

存活时间

哪个楼层

(无)

本次访问

办公桌

user:

相应访问者进行的每次访问

二楼,您的抽屉

app:

所有人,永久

四楼的一部分

temp:

仅限此回合 - 甚至不能爬楼梯

—

一张桌子,四个抽屉。本章将介绍其中一种。

她现在认识你了

👉🖥 这是全新的访问,您尚未在其中说过任何话。询问她:

What did I bring in last time?

👀 我这里有记录,您带入了一盏灯笼,案件编号为 k7f2。该设备标记为 q7,您提到它在夜间会不断断开连接。抽屉里出来的每一句话,而不是对话 - 这次访问只有一条消息,而这条消息没有名称。这就是用户级状态在发挥作用:在访问者说出任何字词之前,系统中就已存在 user: 键,并且每次访问都会存在。

抽屉里放着纸条上的四行字,没有其他东西。您在纸条上写的内容(没有划线的)——您尝试了什么、您为什么关心、您是否想要新的纸条——没有去处,一个月后就会消失。抽屉式导航栏跨越了月份。句子则没有。第 3 章将介绍相关内容。

您写的内容及其含义

该行

定义

where it lives

CASE = "user:case"

前缀是生命周期 - 相同的键,但范围限定为访问者而非访问

二楼 · user: 州

此梯级可覆盖 1 位访问者。抽屉会跟随您,但对话仍不会跟随。

5. InMemoryMemoryService

  • 什么 - 朗读记录当天的行,写下提问的行;然后是结束时间和问题。
  • 原因 - 凭证是一种表单。表单包含您提供的行。人们说话时不会逐行说,而记录他们说的话只是记住他们说的话的一半。
  • 方式 - 朗读 file;recall 中的一行;然后朗读 1 个 🌙、1 个 🗓、1 个问题。

结束营业时间。她走回货架旁。

她回家途中的活动 - 只读

系统不会记住已提交的申请

该行代码将日期放在搁架上。它不会从该值中减去任何内容。如果没人看,放满卡片的架子和空架子一样没用。而代码发布后,没人看:recall,在每个问题上先于 Vesper 运行的节点,不会向塔提出任何问题。在需要时无法回忆起来的存储内容不是记忆,而是日志。而“看”是另一半,由您来书写。

第三次编辑 - 提出要求

👉📖 在 archive/agent.py 中,找到 recall(位于 file 上方的函数)。它是一个简单的函数节点,可在 Vesper 之前针对每个问题运行,而不是由模型决定何时查找,这意味着它找不到任何内容的时间也会显示出来。读到 👉:

    if query.strip():
        try:
            # 👉 EDIT THREE — chapter 3. Filed is not remembered: the cards are on
            #    the shelf and nobody is looking at them. Delete the "# " in front
            #    of the next line, then save:
            # found = await ctx.search_memory(query)
            pass
        except ValueError:
            # no memory service wired on this runner — degrade, do not crash
            pass

👉✏️ 仍在 archive/agent.py 中,在 recall 内:删除一行前面的 #,即 # 和后面的空格。之前:

        try:
            # 👉 EDIT THREE — chapter 3. Filed is not remembered: the cards are on
            #    the shelf and nobody is looking at them. Delete the "# " in front
            #    of the next line, then save:
            # found = await ctx.search_memory(query)
            pass
        except ValueError:

之后:

        try:
            # 👉 EDIT THREE — chapter 3. Filed is not remembered: the cards are on
            #    the shelf and nobody is looking at them. Delete the "# " in front
            #    of the next line, then save:
            found = await ctx.search_memory(query)
            pass
        except ValueError:

将 pass 保留在原位置,它现在不执行任何操作,因为 found 已经高于 None。

保存。无需重新启动。

两行代码,两项政策,其中一项是您编写的。写入政策决定了保留哪些数据以及何时保留(收盘时间、全天)。召回政策决定了何时查看以及查看什么(每个问题、问题本身)。ADK 会提供动词(add_events_to_memory、search_memory),但不会参与政策制定。这是正确的拆分方式,因此您需要撰写“询问”部分。

打烊时间

👉🖥 说出三件小纸条上没有列出的物品。一条消息或三条消息:

I read by it at night, so night is exactly when I need it.
I've tried hanging it higher. No change.
I don't want a new one — it was a gift.

点按此处

👉🖥 然后按🌙 今天的内容到此结束。

字词上升

👀 观察书桌。您说的三件事从纸条上抬起并向上漂浮,然后降落在三楼。在塔旁边的卡片下方,系统会列出您发送的每条消息对应的卡片(标记为 quote),其中包含您的句子(一字不差)以及上方的此流程。直到他们降落,桌子上的灯才会熄灭。

一个月后,她知道了

👉🖥 按 🗓 一个月后,然后问:

What have I already tried?

👀 您尝试过将它挂得更高,但没有效果。她知道。该纸条上没有相关行,也从未有过,因此抽屉不可能回答这个问题;recall 找到了卡片,将其写入 ctx.state["recalled"],并写下了她的指令。一条线是差异。

而这一切都只是她脑海中的想象

查看塔旁边的卡片:在此过程中。目前,内存是 InMemoryMemoryService,即 bash valley.sh 内的一个字典,其存在时间与相应进程完全相同。今晚关闭塔楼,明天打开:书在 1 楼,抽屉在 2 楼,3 楼是黑的。相应诉讼请求已保留在文件中。您所说的内容已在流程中,但流程已结束。

持久性不是内存。

第三层楼存在两个问题:它处于流程中,并且是您自己的话,属于引用,而不是事实。下一章将修复这两个问题,而且完全不涉及代码。

您读过的内容、写过的内容以及内容类型

该行

定义

where it lives

await ctx.add_events_to_memory(events, custom_metadata=FILING) - 您只是阅读了该消息

写入政策 - 一整天,在收盘时

三楼 · 记忆服务

found = await ctx.search_memory(query) - EDIT THREE

召回政策 - 每个问题,在她回答之前

recall() → ctx.state["recalled"]

此层级会接收所说的话,并以您自己的语言将其保存在一个进程内的字典中。这两半都是错误的,下一章将完全不使用代码来修复这两个错误。

6. MemoryBank

  • 内容 - 一个脚本构建记忆库;您已有的两行代码开始向记忆库写入事实并将其读回。
  • 原因 - 内存服务是一种服务。归档的存储位置是部署决策,而不是代理决策,您可以在不触及代理的情况下更改它。
  • 如何 - 运行 make_tower.py,说三句话,按 🌙,按 🗓,问一个问题。然后,只读查看写入和读取。

建造塔楼

第 3 章的第 3 层是 bash valley.sh 内的 Python 字典。记忆库是该底层功能的受管版本:它位于项目中的 Agent Engine 实例上,可保留从当天提取的事实,而不是您的句子,并且通过含义而不是共享字词来查找这些事实。您无需将 Vesper 部署到 Agent Engine 即可使用它 - 未部署任何内容的实例仍会托管记忆库,而这正是脚本构建的内容:一座无人居住的塔楼,其唯一的工作是保管卡片。

👉💻 在第 1 个标签页(约 1 分钟后):

cd ~/agent-valley-archive
uv run python scripts/make_tower.py

👀 预期:

  building the tower in YOUR_PROJECT / us-central1 — about a minute...
  built, with the two topics in archive/topics.py configured on it.

  resource: projects/.../locations/us-central1/reasoningEngines/6792...
  AGENT_ENGINE written to .env

  Next — nothing to restart. The Archive reads .env on every message:
    look at floor three — under the cards it now says Memory Bank.
    (adk web, if you use it:  --memory_service_uri "agentengine://projects/.../reasoningEngines/6792...")

一次 SDK 调用 (agent_engines.create()) 和写入 .env 的一行内容。归档会在每次请求时重新读取该文件,因此无需重启。

观看其运作方式

👉🖥 查看三楼 - 大约在几秒钟内,塔楼会自行重新绘制。在卡片下方,现在显示的是记忆库,并且搁架是空的:第 3 章中的三段引文正在处理中,而这并不是处理过程。再次说出这三句话,可以是一条消息,也可以是三条消息:

I read by it at night, so night is exactly when I need it.
I've tried hanging it higher. No change.
I don't want a new one — it was a gift.

👉🖥 按🌙 今天的内容到此结束。现在,关闭时间大约需要 8 秒 - 塔正在提取。然后看看三楼。

左图:她记在脑海中的内容。右侧:塔保留的内容。

👀 卡片不是句子。他们会说出类似这样的话:我晚上用灯笼看书,但灯笼总是熄灭,以及我已经试过把灯笼挂得更高了,每句话都标有日期 fact。蒸馏。输入的是对话。结果是事实。

👉🖥 按下一个月后的日期 - 一次新的访问,因此您刚才说的任何内容都不再显示在对话中 - 然后向她提出以下问题,该问题与卡片中的内容没有任何共同之处:

Should I just get a different lamp?

👀 由于这是礼物,您提到不想更换灯笼,并且已经尝试过将其挂得更高。卡片上的每个字都是她从卡片上看到的,只有卡片才能告诉她:这次访问是“一条消息”前的访问。相似度搜索。塔牌匹配的是含义,而不是字词 - 问题中是“灯”和“不同”,卡片中是“灯笼”和“替换”。现在,它已不在流程中:今晚关闭塔楼,明天卡片就会显示,因为它们位于 Agent Engine 中,而不是 bash valley.sh 中。

写入和读取 - 只读

被遗忘权

点按此处

👉🖥 在消息框下方,按 🔥 忘记我。

👀 三个楼层变暗,一个楼层未变暗:

它包含的内容

在 🔥 之后

第三层,卡片

她记得的内容 - 记忆库从您的就诊记录中提取的事实

消失 - memories.delete,一次一张卡片

二楼的抽屉

user:case,在访问者每次访问时都随之移动的滑块

已清除

办公桌、纸条

相应支持请求的本次访问会话副本

已清除

一楼,图书

访问本身 - 每次访问在 archive.db 中产生一个会话,会话中的每条消息

未触及 - 数量不会移动

这些记录是访问记录,其中包含访问发生的时间和访问期间的对话内容。卡片和抽屉是她的记忆:她从这些访问中获得了什么,并将其带到下一次访问中。被遗忘权请求涉及的是记忆,即与某人有关的事实以及随之而来的状态。是否保留访问记录是另一项决定,出于其他原因(例如审核跟踪记录、支持历史记录)而做出。本实验特意保留了访问记录,以便您亲眼看到这两者之间的区别:记录和记忆不是一回事,忘记其中一个不会影响另一个。这是从另一侧看到的 Vesper 门上的句子。

ADK 内存抽象具有 add_events_to_memory 和 search_memory。没有删除选项。遗忘是一种治理操作,而不是代理操作,因此它位于应用(原始客户端上的 archive/tower.py、memories.delete)中,模型无法意外访问它。代理是否可以遗忘,与代理是否可以记忆一样,都是由您设计的。

您构建和阅读的内容,以及这些内容是什么

事物

定义

where it lives

make_tower.py → .env 中的 AGENT_ENGINE - 已构建,未编辑,未重启

内存服务(作为服务):部署决策,在代理之外

Agent Engine 上的 Vertex AI Memory Bank

tower_service(engine) - 您只能阅读它

同一 Runner(memory_service=...) 时段,不同住宅

archive/service.py

FILING 中的 "allowed_topics": TOPICS — 只读

在写入时决定保留哪些内容,而不是在提示中决定

archive/topics.py + 基站自身的配置

🔥 → memories.delete - 按下,但未写入

遗忘是一种应用操作;ADK 的内存 API 没有删除

archive/tower.py

此阶段达到的是所说的话。一天过去了,一个事实出来了,它被提炼成主题允许的程度,而一楼的书从未移动过。

7. GraphRAG

可选。

  • 内容 - 一个脚本将山谷的过去加载到 BigQuery 中,在数据仓库内部生成嵌入内容,并在表格上放置一个图,而无需移动任何行;然后向 Vesper 提出两个问题,Vesper 有两个受监管的查询:一个用于查找相似的声音,一个用于查找相关内容。
  • 原因 - 声音相似并不等同于已连接。向量用于查找要查看的位置;图用于查找附加的内容;答案必须包含其所经过的路径。
  • 方式 - bash scripts/season.sh,一次。本章中的所有其他内容均为只读。

Vesper 的塔楼在建造时就已限定为您的专用塔楼。(app_name, user_id)其他人的访问记录不是她的记忆,再怎么努力搜索也不会改变这一点。它们属于山谷,山谷将它们存放在仓库中。本赛季有 31 位游客来访是因为夜间 q7 灯笼熄灭,其中一位找到了原因。此章节会加载该仓库,并向其中提供两个固定查询。

Graph RAG

RAG 是检索,然后生成:找到正确的记录,将其交给模型,让模型根据这些记录回答问题。普通 RAG 通过相似度进行检索 - 嵌入问题,提取最接近的记录。这样一来,系统会找到听起来像该问题的内容,而第 4 章的塔楼就停在了这里。图 RAG 会添加第二个检索步骤,该步骤会根据已找到的任何相似性,从数据仓库已有的边(即键)开始,检索与该边关联的任何内容。这两个步骤回答了不同的问题,本章将引导您提出这两个问题:

步骤

回答的问题

怎么

在本章中

相似度

我应该在哪里查看?

嵌入问题,VECTOR_SEARCH

season_search - 当访客未携带标记时

遍历

与此相关的是什么?

MATCH 属性图中的路径

season_known_issue - 一个固定 MATCH

更新的

我该如何告知访问者?

模型,并以路径作为引用

她的回答

运行应用

该数据集以纯 CSV 格式提供,包含 season/(访问者、商品、请求、修复、季)五个文件,与支持系统导出这些文件的方式相同。一个脚本将它们带到仓库,这是您在本章中运行的唯一脚本。

👉💻 在标签页 1 中(大约需要 3 分钟;它会打印每个步骤,并在第一个失败的步骤处停止):

cd ~/agent-valley-archive
bash scripts/season.sh

👀 结束时,塔楼上的四楼亮起,旁边的图例显示开放。

构建的内容 - 只读

她如何阅读该信息 - 只读

询问她

👉🖥 塔楼的四楼已开放。像任何访问者一样向她提问:

Has anyone else had a q7 lantern go out at night?

她回答了,最后一行是路径

👀 她回答说 - 另外 31 人… 大多数人说火焰很低,一小时后就熄灭了… 水獭马洛发现灯芯浸得太浅了,而她回答的最后一行就是路径。这是她的输出内容 - 请阅读,但不要发回给她:

mark q7 → 31 visits this season → 10 different things said → the wicks in the q7 batch were dipped too shallow — they drown in the oil and starve in an hour

路径即引用。其中的每一步都是图中的一条边,而边是关键,因此任何人都可以再次走这条路,并获得相同的 31。任何人都无法再次完成相似度得分测试。如果她说,在路径之前,由于项目没有图预留,因此行是通过 3 个联接返回的,则表示您的项目版本拒绝 GRAPH_TABLE,行和路径是相同的。

👉🖥 现在,请不要添加标记,发送以下内容:

Has anyone else had a lantern that keeps dying once the sun is down?

👀 她到达了相同的 31 级,并走了相同的路径。不同之处在于道路:season_search 先跑,找到 5 位声音相似的访客(每位访客都携带 q7),然后才走。“类似”表示未关联;“类似”表示要查找的位置。

加载的内容及其用途

事物

定义

where it lives

具有显式架构的 bq load - 运行,而非写入

仓库:包含名称和类型的列

archive.visitors / items / asks / fixes / seasons

ML.GENERATE_EMBEDDING + VECTOR_SEARCH

相似度步骤 - 要关注的方面

archive.ask_embeddings,embedder模型

CREATE PROPERTY GRAPH - 未移动任何行

遍历步骤 - 连接的内容;表成为节点,箭头

archive.season_graph

archive/season/__init__.py 中的 GQL - 一个形参

她唯一可以走的路线;路径是引用

season_known_issue(mark)

几十位访客不足以成为购买仓库的理由。本章介绍的是查询的形状,而不是规模。将此图放大一千倍,MATCH 不会改变一个字符。

8. 关闭

三条线、一座塔、一座仓库 - 四层。未重新启动任何内容。

梯级

你做了什么

购买的商品

ONE

tool_context.state[CASE] = case - 一次修改

存在滑块:状态由代码写入

二

CASE = "user:case" - 一次修改

随访抽屉

提交申请

await ctx.add_events_to_memory(..., custom_metadata=FILING) - 读取,但不写入

所说内容,保留

三

found = await ctx.search_memory(query) - 一次修改

保留了哪些内容,要求了哪些内容 - 不记得已提交的内容

塔

make_tower.py,然后是 .env 中的 AGENT_ENGINE - 通过一条线构建和连接,没有重启任何内容

一个蒸馏塔,熬过了漫漫长夜

规则

FILING 中的 allowed_topics — 读取,不写入

仅保留应保留的内容

the season

season.sh - 已加载,嵌入到位,上面铺着一张图,用一个 MATCH 走过

其他所有人的访问,按含义和连接

每个替换件替换了什么

ADK 2

您会改为编写什么内容

费用

session.state + 一种工具,由 SqliteSessionService 维护

聊天循环旁边的 dict,自行序列化到磁盘

模型无法读取该信息,您需要撰写简历和锁定

user: 前缀

用户表和每次轮次中的联接

每次都确定密钥的归属位置

函数节点中的 add_events_to_memory + search_memory

一个用于转储转写的 cron,以及一个用于决定何时查看转写的模型

它会倾倒,但不会提炼,而且您永远不会看到它没有看起来的时间

VertexAiMemoryBankService + allowed_topics

向量存储区、提取提示、整合作业,以及提示中的 “请勿记住琐事”

三项服务,并且该规则一直有效,直到模型认为情况并非如此

VECTOR_SEARCH + 一个固定的 GQL MATCH

导出、嵌入其他位置、图数据库、同步作业和模型写入 SQL

数据离开数据仓库以供理解,并且您会发现客户在执行

三个问题,在第一个回答为“是”时停止

  1. 明天会怎么样?否 → 不保留任何内容。它在对话中产生,也会在对话中消失。
  2. 是否有函数可以检查?是 → 抽屉。代码会写入它,每个回合都会读取它,并且任何句子都无法更改它。
  3. 否则 → 塔。从所说内容中提取,在被问及时查找,根据后续所说内容进行修订。

一句话

记录不是记忆。记忆是一条记录加上两项决策 - 要保留什么以及何时查看 - 而记忆的类型决定了它应该放在哪个楼层。

事实和偏好设置属于不同的存储区。

👀 每层楼都亮着灯。您的宠物会显示在顶部。护照上盖有“归档”印章,第五周开启夜市,Nix 在夜市中实时大声朗读剧本,塔楼知道的一切都会与你同在。