The Archive — Memory at Agent Valley

1. はじめに

1 階のデスクにいるアーキビストのヴェスパー

第 1 週では、誰が何を実行するかを尋ねました。第 2 週では、次に実行するものを決定する人について説明しました。3 週目のテーマは「ランニングが残すもの」でした。今週、会話は終わりを迎えます。何が生き残るのかという疑問が残りますが、それがどのようなものかによって、どこへ行くかが決まります。

アーカイブは、問題が発生したときに谷が来る場所です。すべてを書き留める塔と、1 階の机にいる記録係の Vesper。夜になるとランタンの明かりが消えます。行って聞いてください。

彼女は良い仲間です。1 か月後、彼女はあなたに会えることを楽しみにしています。

記録は記憶ではない。

この文がラボ全体です。4 つの段を登ります。各段はタワーのフロアです。

rung

床

記憶している内容

何がそれを維持しているのか

1

デスクと 1 階

今回の訪問

session.state · SqliteSessionService

2

2 階の引き出し

この訪問者

user: 接頭辞

3

3 階、タワー

発言内容

Vertex AI Memory Bank

4

4 階、シーズン

谷全体

BigQuery

一段上がるごとに、下の段から何かが落ちてくる。

アーカイブ、切り開かれた - 5 階建て、少数のクルーがすべてを維持している

アーカイブ担当者の 5 つの忘却方法については、サポート電話で経験済みでしょう。

彼女が言うこと

何が問題だったか

~~1~~

~~もう一度参照情報を教えていただけますか?~~

~~1 回の会話内で忘れる~~ - 第 3 週で修正済み

~~2~~

~~転送いたしました。最初からやり直してください。~~

~~履歴がプロセスとともに消滅する~~ - 3 週目でこの問題も修正されました

3

ようこそ。ご用件をお聞かせください。

先月オープンケースがあったお客様

4

破損状況についてご説明いただけますか?

彼女は前回ランタンを見た

5

こんなの見たことがない。

今シーズンは 31 人からこの質問がありました

3 行目は今週です。新しい訪問(第 2 章)、スリップに線がない文(第 3 章)、プロセス内のみに存在するアーカイブ(第 4 章)の 3 つの異なる方法で失敗し、それぞれが 1 つの章になっています。4 行目はご自身で入力してください。archive/agent.py の look() は、写真のマークを読み取る場所です。5 行目はチャプター 5 で、省略可能です。

1 ~ 3 週目は必要ありません。エージェントは、指示といくつかのツールを備えたモデルです。それ以外については、今週の説明で十分でしょう。

3 行のコードを記述します。各行はメモリのコンセプトを表します。

行

概要

tool_context.state[CASE] = case

状態はコードによって書き込まれ、会話によって書き込まれることはありません

CASE = "user:case"

接頭辞はライフタイムです

found = await ctx.search_memory(query)

リコール ポリシーのファイルが記憶されない

読み取り専用の行がもう 1 つあります。書き込みポリシー await ctx.add_events_to_memory(...) です。第 3 章では、この行を使用する行を記述する前に、その動作を読み取ります。

他の 2 つのステップは編集ではありません。タワーを構築(メモリバンクをホストする Agent Engine)すると、同じコードが自動的に接続されます。また、ウェアハウスを読み込み(BigQuery)、グラフを重ねて、同じエージェントに 2 つのガバナンス対象クエリ(意味によるクエリと接続によるクエリ)を渡します。

どの章でも同じループが実行されます。このループは、独自のエージェントで使用するループです。コードを読み取る → 行を書き込む → タワーで確認する。最初に壊れたものが実行されることも、再起動されることも、Ctrl+C で終了されることもありません。すべての編集は次のメッセージで取得され、タワーの接続は、アーカイブがリクエストごとに再読み取りするファイルの 1 行です。タワーは、編集した同じファイルを読み取り、3 つのボタン(🌙: 1 日を終了、🗓: 次の訪問を開始、🔥: ユーザーを忘れる)を備えた、再描画を行う図面です。それ以外の操作はすべてコードとターミナルで行います。

2 つの場所があり、ジョブが変更されることはありません。タブ 1 はターミナル、タブ 2 はアーカイブです。ワークベンチ(adk web)は、第 1 章でセッションの生の状態を確認するために 1 回だけ登場します。以下の各項目には、どの方法が該当するかが記載されています。👉👉💻 はタブ 1、👉🖥 はタブ 2、👉🔬 はワークベンチです。👉📖 は ファイルを開いて読み取るステップで、変更する必要はありません。👉✏️ は編集を行うステップです。Vesper に何かを言うように指示するステップでは、そのすぐ下のブロックに正確なメッセージが記載されています。それをそのままコピーしてください。見出しが — 読み取り専用で終わるセクションでは、入力、貼り付け、クリックを行う必要はありません。その中のコードはすでにリポジトリにあり、すでに実行されており、読み取り用に印刷されています。

ヴェスパーのドアの上にある文は、一番上の文です。今週の文は、シーズン全体の 4 週目の締めくくりです。

事実と好みは別のストアに属します。

2. 🧰 セットアップ

  • What - プロジェクトを指定するか、キーを貼り付けてインストールし、ツールチェーンが緑色であることを証明します。
  • 理由 - ここでは何も起動しません。各サーバーは、必要なステップの直前に起動するため、画面が開いたときに、どのコマンドがその画面を所有しているかがわかります。
  • 方法 - プロジェクトを作成するスクリプト、Vertex AI をインストールして指定するスクリプト、コードを読み取って現在地を知らせる事前フライト スクリプトの 3 つのスクリプト。

このラボのすべての処理は Cloud Shell で実行されます。Python、uv、gcloud はすでにインストールされており、Gemini には独自の認証情報を使用して Vertex AI 経由でアクセスします。このラボには API キーは一切含まれておらず、ノートパソコンにインストールするものもありません。

このラボで提供できないものが 1 つあります。それは、課金が設定された 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 分)

💻 1 つのスクリプトで、請求先アカウントを検索し、そのアカウントにプロジェクトを作成して、Cloud Shell をそのプロジェクトに設定します。1 週目、2 週目、3 週目のいずれかを実施した場合は、Agent Valley プロジェクトが最初に見つかり、再利用が提案されます。[はい] を選択すると、2 つ目の課金対象プロジェクトについて考える必要はありません。

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 は異なる数字になります。この関数では次の 4 つの処理が行われます。

請求

最新の [YYYY-MM-DD] GDP Credit: アカウント(ある場合)または開いている他の請求先アカウント

project

すでに所有している Agent Valley プロジェクト(1 週目、2 週目、3 週目のいずれか)を探し、再利用を提案します。それ以外の場合は、agent-valley-XXXX を作成し、その請求先アカウントをリンクします。

選択

ID を ~/project_id.txt に書き込み、gcloud config set project を実行します。この後、ID の入力を求められることはありません。

waits

プロジェクトが実際にサービングを許可されるまで、最大 2 分間ポーリングします。

🌐 同じプロジェクトがコンソールに表示されます。その ID は、ダッシュボードの [プロジェクト情報] カードに表示されます。

コンソールのダッシュボードに表示されるプロジェクト ID

5 · Vertex AI をインストールして指定する (約 3 分)

💻 uv をインストールし、ADK を Vertex AI に送信する 3 つの変数をエクスポートして、設定を実行します。

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 はべき等であり、何度でも安全に再実行できます。このメソッドは、次の 5 つの処理を順番に行います。

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 を実行します。アーカイブ自体が Next.js アプリであるため、第 1 章の bash valley.sh はダウンロードするものがありません。パッケージがすでに存在する場合はスキップされます。

.env

ラボが読み取る変数(GOOGLE_GENAI_USE_VERTEXAI、GOOGLE_CLOUD_PROJECT、GOOGLE_CLOUD_LOCATION、MEMORY_BANK_LOCATION、BQ_DATASET)を書き込み、API キーはどこにもありません

モデルチェック

Gemini に 1 回だけ実際の呼び出しを行うため、不正なプロジェクトは後で章の中で失敗するのではなく、設定時にここで失敗します。

6 · グリーンであることを証明する (約 1 分)

💻 プリフライトを実行します。すべてのティックは実際のプローブであり、推測ではありません。

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

👀 プリフライトでは、認証情報が機能することを証明するためにモデルに 1 つの質問をし、Archive のパッケージが配置されていることを確認してから、コードを読み取って今週の進行状況バーを出力します。

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 つはすでにチェックされています。これらは、読み取り専用のものです。3 つは書く線、1 つは建てる塔です。ラボの任意の時点でプリフライトを再度実行すると、現在地がわかります。プリフライトはチェックリストではなく、ファイルを読み取ります。

3. Session、State、SqliteSessionService

  • 内容 - 1 行で記述します。ワークベンチを 1 回起動して、スリップが埋められるのを確認します。次に、タワーで同じことを行います。その後、夜間も動作し続ける理由を読みます。
  • 理由 - 記憶について最初に知っておくべきことは、何もしなければ何も記憶されないということです。誰かが書く。
  • 方法 - agent.py の 1 行を変更するだけで、他は変更しません。

EDIT ONE

👉📖 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 はユーザーの音声を完璧に聞き取ります。4 つの write_down 呼び出しが実行され、それぞれが "written" に応答し、セッション状態は空のままになります。これは、何もセッション状態に到達しなかったためです。彼女はあなたの話を聞いていました。何も書き込まれていません。

👉✏️ archive/agent.py の write_down の中で、1 行の先頭にある #(# とその後のスペース)を削除します。変更前:

    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}}

保存] をクリックします。

スリップが埋め込まれます

👉💻 ワークベンチを起動します。これは、ラボで 1 回だけ開きます。セッションの状態を未加工の JSON として表示する唯一のウィンドウです。

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

👉🔬 [ウェブでプレビュー] → [ポート 8000 でプレビュー] を選択してワークベンチを開きます([ウェブでプレビュー] ボタンは、Cloud Shell の右上にある窓の上に目があるアイコンです)。[archive] を選択し、何も入力する前に左側のパネルで [State] をクリックします。

👀 空です。まだ誰も発言していません。

👉🔬 次のメッセージを 1 つにまとめて送信します。

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

👀 4 つの write_down チップが発火します。いずれかをクリックして JSON を読み取ります: "written": {"no": "k7f2"}。[State] をもう一度クリックします。

4 つの項目、4 つのフィールド、1 つのメッセージ

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

👀 1 つのメッセージ内で空の状態から満杯の状態に変化します。それが彼女が覚えていることであり、それは勝手に現れたものではありません。その行を記述したのはあなたです。これがメカニズムのすべてです。tool_context.state に書き込むプレーンな関数と、訪問者がスリップの行に一致する発言をしたときにいつでもそれを使用するように指示する命令です。

👉🔬 彼女に尋ねます。

What's my case number?

👀 k7f2. 彼女は伝票を読み上げます。EDIT ONE がなければ、サポート リストの 1 行目にある 1 つの会話の中で、彼女はあなたにそれを求めていたでしょう。

2 つのガードは、紙のフォームであるスリップです。4 つの短い行がそれぞれ 1 回ずつ書かれています。見た目以上に重要なのです。これらのキーがないと、モデルは会話全体を symptom に注ぎ込むことができることを発見し、第 3 章(そのポイント全体が「スリップにはその行がない」である章)は静かに真実ではなくなります。ストアの形状は、それを書き込むコードによって強制適用されるか、強制適用されません。

タワーに移動します

👉💻 ワークベンチは完了しました。実行したままにするか、タブを閉じます。ラボの残りの部分はアーカイブにあります。2 つ目のターミナルタブ(タブ 2 - アーカイブ)を開きます。

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

👉🖥 [ウェブでプレビュー] > [ポートを変更] を選択してアーカイブを開き、「3440」と入力して [変更してプレビュー] をクリックします。3440 はウェブ プレビューのクイック リストにありません(デフォルトは 8080 です)。そのため、今回は手動で設定する必要があります。[▶ 開始]、[The Archive] の順に押します。玄関にいます。名前と種族を尋ねてきた人を選んで、中に入ります。

塔、切り開く

👀 1 つの建物。Vesper は 1 階のデスクにいます。そこで話します。彼女の上には、1 階、2 階、3 階、そして暗い屋根裏部屋があります。4 つの段は 4 つのフロアです。使い魔は 1 階に座っています。そこまでしか登っていないからです。図の横の列には、各階に現在ある店舗が記載されています。これは、冒頭の表と同じ 4 つの店舗が同じ順序で記載されています。右側の項目はボタンではありません。

👉🖥 会話の下のボックスに、同じ 4 つの項目(このメッセージ)を貼り付けて、Enter キーを押します。

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

机の上に 4 本の線、その横に同じ 4 本の線

👀 デスクを見ます。スリップは、描画に 1 行ずつ書き込まれ、その横の スリップの下にすべて書き込まれます。同じツール、同じ状態: タワーは write_down が書き込んだ内容を描画しているだけで、ラボの残りの部分でも同じ処理を続けます。

She remembers overnight - 読み取り専用

入力した内容と、その内容が何であるか

行

概要

どこに住んでいるか

tool_context.state[CASE] = case

セッションの状態 - コードによって書き込まれ、指示によって読み取られる

デスク · session.state

SqliteSessionService(DB) - 読み取り専用

セッション ストア - プロセスではなくファイル内の訪問

1 階 · archive.db

このラングは 1 回の訪問に達し、終了時間を超えて存続します。次のアクセス時には存在しません。

4. ユーザーの状態

  • 何 - 1 か月後に戻ると、スリップがなくなっています。5 人のキャラクターがあなたを追いかけます。
  • 理由 - セッションは 1 回のアクセスです。次のアクセスは新しいセッションであり、前のアクセスを認識していません。
  • How - state.py の 5 文字。

1 か月後。はじめまして。

ここを押します

👉🖥 [🗓 1 か月後] をクリックします。これは、同じユーザーの新しいセッションです。3 週目の「新しい会話を開始」と同じです。

アーカイブへようこそ。ご用件をお聞かせください。

👉🖥 タワーを確認します。1 階には別の本が置いてあります。2 階の引き出しは空です。机の上の紙は空白です。

1 階に別の本、引き出しは空、スリップは空白

👀 理由: スリップはセッション状態に書き込まれ、セッションは今回のアクセスです。セッション数とユーザー数を変更します。何も指示されていないため、何も引き継がれません。最後の訪問はファイルに残っています(1 階の最初の本です)。ただし、スリップは 1 回の訪問で有効なキーの下に書き込まれています。

編集 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"

保存] をタップします。再起動なし - アーカイブはメッセージごとにコードを再インポートします。

👉🖥 同じ 4 つの項目をもう一度貼り付けます。このメッセージをそのまま貼り付けます。

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

👉🖥 次に、[🗓 1 か月後] をもう一度押します(以前と同じボタンです)。

名前が記載された引き出し

👀 2 階の引き出しにあなたの名前が書かれており、その中に 4 つのフィールドがあります。机の上のメモは、あなたが部屋に入ったときに引き出しから取り出した 4 行のメモと同じものです。お供が 1 階上がりました。同じキー、すべてのアクセス、1 つの値: これがプレフィックスの役割です。

接頭辞はライフタイムです。

接頭辞

寿命

どの階

(なし)

今回の訪問

デスク

user:

このユーザーがアクセスするたび

2 階の引き出し

app:

すべての人に、永遠に

4 階の一部

temp:

このターンのみ - 階段を上ることはありません

—

テーブル 1 つ、引き出し 4 つ。この章では、そのうちの 1 つについて説明します。

彼女はあなたのことを知っています

👉🖥 この訪問は新規で、まだ何も発言していません。次のように尋ねます。

What did I bring in last time?

👀 ランタンをお持ち込みいただいたと記録されています。ケース k7f2 です。q7 とマークされており、夜間に電源が切れると記載されています。この訪問は 1 件のメッセージで構成されており、そのメッセージには名前が付けられていません。これは、ユーザーレベルの状態が機能していることを示しています。訪問者が言葉を発する前に、すべての訪問で user: キーが存在します。

引き出しには、スリップの 4 行のみが格納されます。スリップに線がない場合、試したこと、なぜ気にするのか、新しいものを希望するかどうかを伝えても、どこにも届かず、1 か月後には消えてしまいます。ドロワーが月をまたいでいる。文はそうではありません。第 3 章では、その点について説明します。

入力した内容と、その内容が何であるか

行

概要

どこに住んでいるか

CASE = "user:case"

プレフィックスはライフタイムです。同じキーが、訪問ではなく訪問者にスコープ設定されます。

2 階 · user: 状態

この rung は 1 人の訪問者にリーチします。ドロワーは追従しますが、会話は追従しません。

5. InMemoryMemoryService

  • 何 - 1 日を記録する行を読み上げ、質問する行を書き込み、終了時刻と質問を 1 つずつ書き込みます。
  • 理由 - スリップはフォームです。フォームには指定した行が含まれます。人は行単位で話すわけではありません。発言を記録するだけでは、記憶の半分しか残せません。
  • 方法 - file を読み、recall に 1 行、🌙、🗓、質問を 1 つずつ入力します。

閉店時間。彼女は棚の横を歩いて戻ります。

帰宅途中の行動 - 読み取り専用

Filed is not remembered(Filed は記憶されない)

この行で、日を棚に置きます。何も差し引かれません。カードが満載の棚は、誰も見なければ空の棚とまったく同じです。コードがリリースされると、誰も見なくなります。recall は、すべての質問で Vesper の前に実行されるノードですが、タワーに何も尋ねません。必要なときに思い出せないストレージはメモリではなく、ログです。残りの半分は、あなたが書くものです。

編集 3 - 質問

👉📖 archive/agent.py で、file のすぐ上の関数である recall を見つけます。これは、すべての質問に対して 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 の中で、1 行の先頭にある #(# とその後のスペース)を削除します。変更前:

        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 であるため、今は何も行いません。

保存] をタップします。再起動は不要です。

2 つの行、2 つのポリシー。そのうちの 1 つはあなたが作成しました。書き込みポリシーは、保持する内容とタイミング(終了時刻、終日)を決定します。リコール ポリシーは、いつ、何を使って(すべての質問、質問自体)検索するかを決定します。ADK は動詞(add_events_to_memory、search_memory)を提供し、ポリシーには関与しません。それが正しい分割であり、質問を書くのはあなたの役割です。

閉店時間

👉🖥 伝票に記入欄がない 3 つの項目を伝えます。1 つまたは 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.

ここを押します

👉🖥 次に 🌙 を押します。本日は以上です。

単語が上昇する

👀 デスクを見ます。あなたが言った 3 つのものが、スリップから離れて浮き上がり、3 階に着地します。タワーの横のカードの下に、送信したメッセージごとに 1 つのカードが quote マーク付きで表示され、その上に文言が 1 語ずつ表示されます。また、その上に「このプロセス」と表示されます。着陸するまで、デスクのランプは点灯したままです。

1 か月後、彼女は知る

👉🖥 [🗓 1 か月後] をクリックし、次のように尋ねます。

What have I already tried?

👀 高い位置に設置してみたが、効果がなかった。彼女は知っています。スリップにはそのための行がなく、これまでもありませんでした。そのため、引き出しはこの質問に答えられませんでした。recall はカードを見つけて ctx.state["recalled"] に書き込み、その指示を読み取りました。1 行の違いです。

そして、それはすべて彼女の頭の中にある

タワーの横にあるカードの [このプロセス] を確認します。現在、メモリは InMemoryMemoryService(bash valley.sh 内の辞書)であり、そのプロセスと同じ期間だけ存在します。今夜は塔を閉め、明日は開けてください。本は 1 階、引き出しは 2 階、3 階は暗いです。ケースはファイルに残っています。ユーザーが言ったことはプロセスに保存されていましたが、そのプロセスはなくなりました。

永続性はメモリではありません。

3 階には 2 つの問題があります。1 つはプロセス中であること、もう 1 つは事実ではなく引用であることです。次の章では、コードをまったく使用せずに両方を修正します。

読み取った内容、書き込んだ内容、その内容

行

概要

どこに保存されているか

await ctx.add_events_to_memory(events, custom_metadata=FILING) - 読み取り専用

書き込みポリシー - 1 日全体、閉店時間

3 階 · メモリ サービス

found = await ctx.search_memory(query) - 編集 3

リコール ポリシーについて、質問ごとに回答する

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

この段階では、発言内容が取得され、1 つのプロセス内の辞書にユーザー自身の言葉で保存されます。どちらも間違っています。次の章では、コードをまったく使用せずに両方を修正します。

6. MemoryBank

  • 内容 - 1 つのスクリプトでメモリバンクを構築します。すでに記述されている 2 行のコードで、事実の書き込みと読み取りを開始します。
  • 理由 - メモリ サービスはサービスです。アーカイブの保存場所は、エージェントの決定ではなくデプロイの決定です。エージェントを変更せずに変更します。
  • 方法 - make_tower.py を実行し、3 つの文を言い、🌙 を押し、🗓 を押し、1 つの質問をします。次に、書き込みと読み取りの読み取り専用のルックアップ。

タワーを構築する

第 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...")

1 つの SDK 呼び出し agent_engines.create() と、.env に書き込まれた 1 行。アーカイブはリクエストごとにそのファイルを再読み取りするため、再起動する必要はありません。

動画広告を

👉🖥 3 階を見てください。数秒以内にタワーが自動的に再描画されます。カードの下に [メモリバンク] と表示され、シェルフが空になっています。第 3 章の 3 つの引用はプロセス中でしたが、これはプロセスではありません。3 つの文をもう一度言います。1 つのメッセージか 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 秒ほどかかります。3 階を見てみましょう。

左: 彼女が頭の中で考えていたこと。右: タワーが保持するもの。

👀 カードは文ではありません。「夜にランタンで読書をしようとすると、ランタンが消えてしまう」や「ランタンを高いところに吊るしてみた」などの発言が、それぞれ日付とともに fact でマークされています。蒸留。入力されたのは会話です。出てきたのは事実です。

👉🖥 1 か月後に [🗓] を押します。新しい訪問なので、先ほど話した内容は会話に表示されなくなっています。そこで、カードと一言も共通点のない次の質問をします。

Should I just get a different lamp?

👀 ギフトだったため、ランタンの交換は希望されず、すでに高い場所に吊り下げてみたとのことですが、その言葉はすべてカードから発せられたもので、カードだけが彼女に「この訪問は 1 つのメッセージの古さです」と伝えることができました。類似性検索。タワーは単語ではなく意味に一致します。質問では「ランプ」と「異なる」とあり、カードには「ランタン」と「交換」とあります。このプロセスは完了しました。今夜タワーをシャットダウンすると、明日にはカードが利用可能になります。カードは bash valley.sh ではなく Agent Engine に保存されているためです。

書き込みと読み取り - 読み取り専用

忘れられる権利

ここを押します

👉🖥 メッセージ ボックスの下にある [🔥 forget me] を押します。

👀 3 つのフロアが暗くなり、1 つのフロアが暗くならない場合:

何が保持されているか

🔥 の後

3 階、カード

彼女が覚えていること - Memory Bank があなたの訪問から抽出した事実

消去済み - memories.delete、カードを 1 枚ずつ

2 階の引き出し

user:case: この訪問者を訪問ごとに追跡するスリップ

消去しました

デスク、スリップ

この訪問のケースのコピー

消去しました

1 階、書籍

訪問自体 - 訪問ごとに archive.db の 1 つのセッション、そのすべてのメッセージ

untouched - カウントは移動しません

書籍は、訪問がいつ行われ、どのような内容だったかの記録です。カードと引き出しは、彼女の記憶です。つまり、彼女が訪問から得たものを次の訪問に持ち込むものです。忘れられる権利のリクエストは、記憶(個人に関する事実と、それに付随する状態)に関するものです。訪問の記録も削除するかどうかは、監査証跡やサポート履歴など、別の理由で個別に決定されます。このラボでは、意図的に記録を残して、記録と記憶は同じものではなく、一方を忘れても他方には影響しないことを確認できるようにしています。これは、ヴェスパーのドアの向こう側から見た文です。

ADK メモリ抽象化には add_events_to_memory と search_memory があります。削除機能はありません。削除はガバナンス アクションであり、エージェント アクションではないため、モデルが誤ってアクセスできないように、アプリケーション(未加工のクライアントの archive/tower.py、memories.delete)に存在します。エージェントが忘れることができるかどうかは、エージェントが記憶できるかどうかと同様に、設計するものです。

作成した内容と読んだ内容、およびその内容

そのこと

概要

どこに住んでいるか

make_tower.py → AGENT_ENGINE in .env - ビルド済み、編集なし、再起動なし

メモリサービス(サービスとして): エージェント外のデプロイの決定

Agent Engine の Vertex AI メモリバンク

tower_service(engine) - 読み取り専用

同じ Runner(memory_service=...) スロット、別の家

archive/service.py

FILING の "allowed_topics": TOPICS - 読み取り専用

保持される可能性のあるもの(書き込み時に決定され、プロンプトには含まれない)

archive/topics.py + タワー独自の構成

🔥 → memories.delete - 押されたが、書き込まれていない

忘却はアプリケーション アクションです。ADK のメモリ API には削除がありません

archive/tower.py

この段は、発言内容に達しています。1 日が過ぎ、トピックで許容される範囲で事実が抽出され、1 階の本は動かなかった。

7. GraphRAG

省略可。

  • 内容 - 1 つのスクリプトで、バレーの過去のデータを BigQuery に読み込み、ウェアハウス内でエンベディングを生成し、行を移動せずにテーブル上にグラフを配置します。次に、Vesper に 2 つの質問をします。Vesper には、2 つの管理されたクエリがあります。1 つは類似した音を検索するクエリ、もう 1 つは関連するものを検索するクエリです。
  • 理由 - 似たような音は、接続されているのと同じではありません。ベクトルは検索場所を特定し、グラフは関連付けられたものを特定します。回答には、たどったパスが含まれている必要があります。
  • 方法 - bash scripts/season.sh、1 回。この章の残りの部分は読み取り専用です。

Vesper のタワーは、構築によって you((app_name, user_id))にスコープ設定されています。他のユーザーのアクセスは彼女の記憶ではなく、検索を強化してもその事実は変わりません。谷の所有物で、谷が倉庫に保管しています。今シーズン、31 人の訪問者が夜間に q7 ランタンが消えることについて問い合わせてきましたが、そのうちの 1 人が原因を特定しました。この章では、そのウェアハウスを読み込み、2 つの固定クエリを渡します。

グラフ RAG

RAG は「取得して生成」します。適切なレコードを見つけてモデルに渡し、モデルにそのレコードから回答させます。プレーン RAG は類似性によって取得します。質問を埋め込み、最も近いレコードを取得します。これは、質問に似たものを探し、第 4 章の塔が止まった場所です。Graph RAG は、エッジ(ウェアハウスにすでに存在するキー)を、見つかった類似性から、それに付加されているものまでたどる 2 番目の検索ステップを追加します。この 2 つの手順は異なる質問に答えるものであり、この章では両方の質問をします。

解説

回答する質問

how

この章では

類似性

どこを確認すればよいですか?

質問を埋め込む、VECTOR_SEARCH

season_search - 訪問者がマークを持っていない場合

トラバーサル

何が接続されているか

MATCH プロパティ グラフのパス

season_known_issue - 1 つの固定 MATCH

世代

訪問者に何を伝えればよいですか?

モデル。パスが引用として使用されます。

彼女の答え

実行

シーズンは、サポート システムがエクスポートするのと同じように、season/ のプレーンな CSV(訪問者、アイテム、質問、修正、シーズン)として送信されます。1 つのスクリプトでウェアハウスまで実行します。この章で実行するのはこのスクリプトのみです。

👉💻 タブ 1(約 3 分。各ステップが実行されるたびに印刷され、失敗した最初のステップで停止します):

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

👀 終了すると、タワーの 4 階が点灯し、横の凡例に「オープン」と表示されます。

構築されたもの - 読み取り専用

読み取り専用

彼女に聞いてみましょう。

👉🖥 タワーの 4 階が開いています。訪問者と同じように、彼女に道を聞きます。

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

彼女が回答し、最後の行はパスです。

👀 彼女は答えます。「31 人が... ほとんどの人が、炎は低く、1 時間後に消えたと言っています... カワウソのマーローは、芯が浅すぎることに気づきました。答えの最後の行がパスです。これが彼女の出力です。読んで、彼女に送り返さないでください。

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 を持っている)を見つけ、そのマークを取得してから歩きました。類似は接続されていません。類似は検索場所です。

読み込まれた内容とその内容

そのこと

概要

どこに住んでいるか

明示的なスキーマを含む 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 - 1 つのパラメータ

彼女が歩くことができる唯一の道。その道は引用符で囲まれている

season_known_issue(mark)

数十人の訪問者では、倉庫を購入する理由にはなりません。この章で説明するのは、規模ではなくクエリの形状です。このグラフを 1,000 倍に拡大しても、MATCH は 1 文字も変わりません。

8. 閉じる

3 つのライン、1 つのタワー、1 つの倉庫 - 4 階建て。何も再起動されませんでした。

段

どのようなアクティビティを行ったか

購入した内容

1 つの

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 - 1 行で構築および接続され、再起動は行われません

夜を生き延びる蒸留塔

ルール

FILING の allowed_topics - 既読、書き込みなし

保持すべきもののみを保持する

シーズン

season.sh - 読み込まれ、その場所に埋め込まれ、グラフが重ねられ、1 つの MATCH で歩行

他のユーザーのサイト訪問(意味別、接続別)

各ピースが置き換えたもの

ADK 2

代わりに記述する内容

費用

session.state + 1 個のツール、SqliteSessionService が保持

チャットループの横にある dict を自分でディスクに pickle します

モデルが読み取れないため、再開とロックを記述します。

user: 接頭辞

ユーザー テーブルと、すべてのターンでの結合

キーの所属先を毎回決定する

関数ノードの add_events_to_memory + search_memory

文字起こしをダンプする cron と、いつ検索するかを決定するモデル

ダンプは行われるが、蒸留は行われない。また、ルックアップが行われなかった時間は表示されない。

VertexAiMemoryBankService + allowed_topics

ベクトル ストア、抽出プロンプト、統合ジョブ、プロンプト内の「トリビアを記憶しないでください」

3 つのサービスがあり、モデルが別の判断を下すまでルールが適用されます。

VECTOR_SEARCH + 1 つの固定 GQL MATCH

エクスポート、他の場所への埋め込み、グラフ データベース、同期ジョブ、モデルの SQL の書き込み

データがウェアハウスから出てきて、理解される。お客様が

3 つの質問、最初の「はい」で終了

  1. 明日、このことは重要になるだろうか?No → 何も保持しない。会話の中で生き、会話の中で死ぬ。
  2. 関数で確認できますか?はい → 引き出し。コードが書き込み、すべてのターンで読み取り、どの文も変更できません。
  3. それ以外の場合 → タワー。発言内容から抽出され、リクエストされたときに検索され、次の発言内容によって修正されます。

1 文

記録は思い出ではありません。メモリは、記録と 2 つの決定(何を保持するか、いつ参照するか)で構成され、どのようなものかによってどのフロアに配置されるかが決まります。

事実と好みは別のストアに属します。

👀 すべての階が点灯しています。上部にフレンドが表示されます。アーカイブ スタンプがパスポートに押され、5 週目にはナイトマーケットがオープンします。ここでは、Nix がステージをリアルタイムで大声で運営し、タワーが知っているすべての情報があなたに提供されます。