ADK×Agent RuntimeでMemory Bank機能を使用する

記事タイトルとURLをコピーする

G-gen の佐々木です。当記事では、Agent Development Kit(ADK)で開発した AI エージェントで Agent Runtime(旧称 : Vertex AI Agent Engine)の Memory Bank 機能を使用することで、セッション間で情報を保持できるエージェントを構築していきます。

構成

当記事では、Agent Development Kit(ADK)で定義した AI エージェントを Agent Runtime にデプロイし、また、フロントエンドとしてエージェントとやり取りを行うチャットボットを Cloud Run に構築していきます。

エージェントは、Agent Platform の Memory Bank 機能を使用するように構築します。これにより、一度チャットボットとの会話を終了した後でも、前の会話内容の一部を長期記憶として Memory Bank に保存しておくことができます。

また、Cloud Run では Identity-Aware Proxy(IAP)を有効化することで、Google アカウントで認証されたユーザーのみがチャットボットを利用できるようにします。その上で、IAP が付与する認証済みユーザーのメールアドレスを user_id として Memory Bank に送信することで、ユーザーごとの記憶を Memory Bank に蓄積できるようにします。

Memory Bank 機能を使用する Agent Runtime とチャットボットの構成

当記事で使用するもの

Agent Development Kit(ADK)

Agent Development Kit(ADK)は、Google Cloud が提供する AI エージェント構築のためのオープンソース フレームワークであり、単純なタスクをこなすエージェントから複数のエージェントが協働する複雑なワークフローまで容易に実装できます。

ADK には、Memory Bank と連携するための PreloadMemoryTool や、セッションイベントを Memory Bank に送信するための add_events_to_memory() といったユーティリティが標準で組み込まれており、長期記憶を扱うエージェントを少ないコードで実装できます。

Agent Runtime

Agent RuntimeGemini Enterprise Agent Platform(旧称 : Vertex AI、以下 Agent Platform と記載)で提供されるサービスの1つであり、AI エージェントの実行基盤を提供するフルマネージドサービスです。

Agent Runtime では、エージェントとのマルチターン会話を実現するための組み込みのセッション機能を利用することができるほか、Memory Bank のように、エージェントの機能拡張に必要な様々な機能が提供されています。

Agent Runtime の詳細については、以下の記事をご一読ください。

blog.g-gen.co.jp

Memory Bank

Memory Bank は Agent Platform が提供する長期記憶を実現するための機能です。Agent Runtime のセッション機能が一連の会話の中での短期的な記憶を扱うのに対し、Memory Bank はセッションをまたいで利用できる記憶を蓄積します。

Memory Bank は内部的に LLM を用いて、セッションのイベント(会話履歴)からユーザーの嗜好や事実を抽出・要約し、エージェントが認識しているユーザー ID(user_id)単位で保存します。これにより、エージェントとの会話(セッション)を一度中断しても、後の会話でユーザーごとにパーソナライズされた応答を返すことができるエージェントを構築できます。

エージェント側から以下の2つの操作を行うことで Memory Bank を利用できます。

  • 記憶の生成・保存: セッション中のイベントを Memory Bank に送信し、長期記憶として抽出・保存させる
  • 記憶の参照: 新しいセッションの開始時や会話中に、Memory Bank から関連する記憶を取得し、プロンプトに含めて LLM に渡す

ADK ではこれらの操作を簡単に行うための add_events_to_memory()PreloadMemoryTool といったユーティリティが提供されており、エージェントのコールバックやツールとして組み込むだけで Memory Bank を利用できます。

Cloud Run

Cloud Run は Google Cloud のマネージドなコンテナ実行環境でアプリケーションを実行できる、サーバーレス コンテナコンピューティング サービスです。

当記事ではユーザーがエージェントとやり取りするためのフロントエンドとして使用します。

Cloud Run の詳細については、以下の記事をご一読ください。

blog.g-gen.co.jp

Memory Bank を使用するエージェントの開発

エージェントの概要

当記事では、コーヒーに関する質問に回答する「コーヒーエージェント」を ADK で構築していきます。エージェントを Memory Bank と連携できるように実装することで、ユーザーごとの嗜好や過去のやり取りを長期記憶として蓄積できるようにします。

Agent Runtime を使用してエージェントを構築する

ディレクトリ構成

最終的なディレクトリ構成は以下の通りになります。coffee_agent ディレクトリで AI エージェントを実装していきます。

.
├── coffee_agent
│   ├── agent.py
│   ├── .env
│   └── __init__.py
├── pyproject.toml  # 自動で作成
└── uv.lock  # 自動で作成

ADK ではエージェントのパッケージ(ここでは coffee_agent ディレクトリ)内に agent.py を配置し、そこにツール関数とエージェント定義を実装します。

プロジェクトの準備

エージェント開発用のディレクトリでプロジェクトを初期化します。

# uv プロジェクト初期化
$ uv init --no-readme   
  
# パッケージの追加
$ uv add "google-adk>=1.29.0"

次に、エージェント用のパッケージディレクトリ(coffee_agent)を作成し、ADK がパッケージとして認識できるように __init__.py を配置します。__init__.py では agent モジュールをインポートしておくことで、adk コマンドからエージェントを参照できるようになります。

# エージェントのパッケージディレクトリを作成
$ mkdir coffee_agent
  
# __init__.py を作成
$ cat <<EOF > coffee_agent/__init__.py
from . import agent
EOF

エージェントのソースコード(agent.py)

当記事では、Google 検索でコーヒーに関する情報を調べるサブエージェントをツールとして内包するエージェントを構築し、Memory Bank との連携機能を組み込みます。

Memory Bank と連携するために、以下の2つの仕組みを利用しています。

  • PreloadMemoryTool: ADK が標準提供するツールで、エージェントの実行開始時に Memory Bank から user_id に紐付く記憶を取得し、プロンプトに自動で挿入します。エージェントは過去の会話の文脈を踏まえた応答が可能になります。
  • after_agent_callback: エージェントの応答が完了した直後に呼び出されるコールバックです。callback_context.add_events_to_memory() を通じて、直近のセッションイベントを Memory Bank に送信し、記憶として抽出・保存させています。

generate_memories_callback では callback_context.session.events[-5:-1] のように直近のイベントをスライスして送信しています。これにより、応答完了時の最終イベントを除外しつつ、ユーザーの発話とエージェントの応答を含む直近のやり取りのみを Memory Bank に送信します。

from google.adk.agents import Agent
from google.adk.tools import google_search
from google.adk.tools.agent_tool import AgentTool
from google.adk.agents.callback_context import CallbackContext
from google.adk.tools.preload_memory_tool import PreloadMemoryTool
  
  
# 記憶を Memory Bank に保存するコールバック関数
async def generate_memories_callback(callback_context: CallbackContext):
    # イベント単位で Memory Bank に送信する
    await callback_context.add_events_to_memory(
        events=callback_context.session.events[-5:-1])
    return None
  
  
# Web 検索用エージェント
search_agent = Agent(
    name="search_agent",
    model="gemini-2.5-flash",
    description="Google検索でコーヒーに関する情報を調べるエージェント",
    instruction="ユーザーの質問に対してGoogle検索を使って情報を収集し、日本語で回答してください。",
    tools=[google_search]
)
  
# ルートエージェント
root_agent = Agent(
    name="coffee_agent",
    model="gemini-2.5-flash",
    description="コーヒーに関する情報を収集するエージェント",
    instruction="""あなたはコーヒーの専門家アシスタントです。
ユーザーからの質問に対して、search_agentを活用しながらコーヒーに関する正確で有益な情報を提供してください。

対応できるトピックの例:
- コーヒー豆の産地・品種・特徴
- 抽出方法(ドリップ、エスプレッソ、フレンチプレスなど)
- 焙煎度合いと味わいの違い
- カフェやコーヒーショップの情報
- コーヒーの健康効果や歴史
- ラテアートやバリスタの技術

回答は日本語で、わかりやすく丁寧に行ってください。""",
    tools=[
        AgentTool(agent=search_agent),
        PreloadMemoryTool()
    ],
    after_agent_callback=generate_memories_callback
)
  

Agent Runtime にエージェントをデプロイ

Google Cloud の認証と設定

デプロイの前に、Google Cloud CLI での認証を行っておきます。

# プロジェクト ID を環境変数にセット
$ export PROJECT_ID=<プロジェクトID>
  
# 認証
$ gcloud auth login
$ gcloud auth application-default login
  
# プロジェクトの設定
$ gcloud config set project $PROJECT_ID

.env ファイルの作成

エージェントの実行時に Gemini Enterprise Agent Platform(旧称 : Vertex AI、以下 Agent Platform と記載)を利用するための環境変数を、coffee_agent ディレクトリ配下の .env ファイルに設定します。ADK は実行時にこのファイルを自動で読み込みます。

# coffee_agent/.env を作成
$ cat <<EOF > coffee_agent/.env
GOOGLE_GENAI_USE_VERTEXAI=1
GOOGLE_CLOUD_PROJECT=$PROJECT_ID
GOOGLE_CLOUD_LOCATION=asia-northeast1
EOF
  • GOOGLE_GENAI_USE_VERTEXAI: 1 を指定することで、Gemini API ではなく Agent Platform 経由で Gemini モデルを利用します
  • GOOGLE_CLOUD_PROJECT: エージェントをデプロイする Google Cloud プロジェクト ID
  • GOOGLE_CLOUD_LOCATION: Agent Runtime およびモデルを利用するリージョン

Agent Runtime へのデプロイ

adk deploy コマンドを使用して、Agent Runtime にエージェントをデプロイします。

# Agent Runtime にエージェントをデプロイ
$ uv run adk deploy agent_engine \
    --project=$PROJECT_ID \
    --region=asia-northeast1 \
    --display_name="Coffee Agent" \
    coffee_agent

デプロイが成功すると、以下のように Agent Runtime のリソース名が出力されます。

✅ Created agent engine: projects/<プロジェクト番号>/locations/asia-northeast1/reasoningEngines/<エージェント固有の数字>

このリソース名は、後続の動作確認やチャットボットのデプロイ時に使用するため、シェル変数にセットしておきます。

# 環境変数にリソース名をセット
$ export RESOURCE_NAME=projects/<プロジェクト番号>/locations/asia-northeast1/reasoningEngines/<エージェント固有の数字>

なお、すでにデプロイ済みの Agent Runtime を更新する場合は、--agent_engine_id オプションでリソース名を指定して同じ adk deploy コマンドを実行します。

# 既存の Agent Runtime を更新する場合
$ uv run adk deploy agent_engine \
    --project=$PROJECT_ID \
    --region=asia-northeast1 \
    --agent_engine_id=$RESOURCE_NAME \
    --display_name="Coffee Agent" \
    coffee_agent

動作確認(ADK Web)

ローカルで ADK Web UI を起動し、Memory Bank と連携した状態でエージェントの動作を確認します。--memory_service_uri オプションに Agent Runtime のリソース名を指定することで、ローカルの Web UI から Agent Runtime の Memory Bank をエージェントの長期記憶ストアとして利用できます(エージェントはローカルで実行し、Memory Bank 用途でのみ Agent Runtime にアクセスしている状態)。

# Memory Bank を指定して ADK Web UI を起動
$ uv run adk web --memory_service_uri=agentengine://$RESOURCE_NAME

ブラウザで http://localhost:8000 を開き、チャットでエージェントと会話します。adk web から起動した場合、Memory Bank には user という固定のユーザー ID で記憶が保存されていきます。

まず最初のセッションでは、コーヒーの好み(例: 「私は酸味の強いコーヒーが好きです。おすすめのコーヒー豆はありますか」)をエージェントに伝えます。

最初のセッションでエージェントに好みを伝える

Agent Runtime のコンソールから Memory Bank の中身を確認することができます。ADK Web のユーザー(user)に関する記憶として、「私は酸味の強いコーヒーが好きです。」という情報が記録されています。

Memory Bank に好みに関する情報が記録されている

その後、ADK Web UI 上で新しいセッションを開始し、「私の好みに合う抽出方法を教えて」などと質問することで、Memory Bank に保存されている個人的な好みに関する情報を踏まえた応答が返ってくることを確認できます。

最初のセッションで伝えた好みに関する情報が新しいセッションに引き継がれている

フロントエンドの構築

フロントエンドの概要

機械学習モデルのデモ用 Web UI を容易に作成できる Gradio という Python ライブラリを使用してチャットボットを実装します。

チャットボット Cloud Run にデプロイし、Web サービスとして公開できるようにします。Cloud Run では Identity-Aware Proxy(IAP)を有効化し、Google アカウントで認証されたユーザーのみがアクセスできるようにします。

IAP による認証つきのチャットボットを構築する

チャットボットの開発

ディレクトリ構成

フロントエンドはエージェントとは別のディレクトリで構築します。最終的なディレクトリ構成は以下の通りになります。app.py にチャットボットを実装していきます。

.
├── app.py
├── Dockerfile
├── pyproject.toml  # 自動で作成
└── uv.lock  # 自動で作成

プロジェクトの準備

エージェントとは別のディレクトリで uv プロジェクトを初期化します。

# uv プロジェクト初期化
$ uv init --no-readme
  
# パッケージの追加
$ uv add "google-cloud-aiplatform[agent-engines]>=1.142.0" "gradio>=5.29"

app.py

app.py では以下の処理を実装しています。

  • vertexai.init() で Agent Platform に接続し、agent_engines.get() で Agent Runtime にデプロイしたエージェントを取得
  • IAP が付与するリクエストヘッダ(x-goog-authenticated-user-email)からユーザーのメールアドレスを取り出し、Agent Runtime のセッションおよび Memory Bank の user_id として使用
  • Agent Runtime のセッション機能(create_session)を使い、ユーザーごとにマルチターンの会話を管理
  • agent.stream_query() でエージェントにメッセージを送信し、ストリーミングで応答を受信
  • gr.Blocks で Gradio のチャット UI を構築

Memory Bank の記憶は user_id ごとに分離して保存されるため、user_id の決め方がそのままユーザーごとのパーソナライズの単位となります。ここでは IAP が付与する認証済みメールアドレスをそのまま user_id として用いることで、ブラウザやデバイスをまたいでも同一ユーザーであれば一貫した記憶を参照できる構成にしています。

import os
  
import gradio as gr
import vertexai
from vertexai import agent_engines
  
AGENT_ENGINE_ID = os.environ["AGENT_ENGINE_ID"]
# Identity-Aware Proxy (IAP) が認証済みユーザーのメールアドレスを付与するヘッダ
IAP_EMAIL_HEADER = "x-goog-authenticated-user-email"
  
  
def get_agent():
    vertexai.init(
        project=os.environ.get("GOOGLE_CLOUD_PROJECT"),
        location=os.environ.get("GOOGLE_CLOUD_LOCATION"),
    )
    return agent_engines.get(AGENT_ENGINE_ID)
  
  
agent = get_agent()
  
  
# IAP から渡されるリクエストヘッダを元にユーザーを一意に特定する
def get_user_id(request: gr.Request) -> str:
    # IAP は "accounts.google.com:user@example.com" の形式で付与するため、
    # プレフィックスを除去してメールアドレス部分のみを取り出す
    raw = request.headers.get(IAP_EMAIL_HEADER, "")
    return raw.split(":", 1)[1] if ":" in raw else raw
  
  
def chat(message: str, history: list, session_state: dict, request: gr.Request) -> tuple[str, dict]:
    # IAP で認証されたユーザーのメールアドレスをそのまま Agent の user_id として利用する
    user_id = get_user_id(request)
    if not user_id:
        raise gr.Error("IAPからユーザー情報を取得できませんでした。")
    session_state["user_id"] = user_id
  
    session_id = session_state.get("session_id")
    if not session_id:
        session = agent.create_session(user_id=user_id)
        session_id = session["id"]
        session_state["session_id"] = session_id
  
    response_text = ""
    for event in agent.stream_query(
        message=message,
        user_id=user_id,
        session_id=session_id,
    ):
        if event.get("content") and event["content"].get("parts"):
            for part in event["content"]["parts"]:
                if part.get("text"):
                    response_text += part["text"]
                    yield response_text, session_state
  
  
with gr.Blocks(
    title="コーヒーエージェント",
    fill_height=True,
    css="""
        .title-row { text-align: center; margin-bottom: 0; }
        .caption-row { text-align: center; margin-top: 0; color: #666; font-size: 0.9em; }
        .input-row { position: sticky; bottom: 0; background: var(--background-fill-primary); padding: 10px 0; }
    """,
) as demo:
    gr.Markdown(
        "<h1 class='title-row'>☕ コーヒーエージェント</h1>"
        "<p class='caption-row'>コーヒーに関するあれこれをお答えします</p>"
    )
  
    session_state = gr.State(value={})
  
    chatbot = gr.Chatbot(
        show_label=False,
        scale=1,
        avatar_images=(None, "https://em-content.zobj.net/source/google/412/hot-beverage_2615.png"),
        placeholder="質問を入力すると、ここに会話が表示されます",
    )
    with gr.Row(elem_classes="input-row"):
        textbox = gr.Textbox(
            placeholder="コーヒーについて質問してください(例: エスプレッソとドリップの違いは?)",
            show_label=False,
            container=False,
            scale=7,
        )
  
    def respond(message, history, session_state, request: gr.Request):
        history = history + [
            {"role": "user", "content": message},
        ]
        yield history, session_state, gr.update(value="", interactive=False)
  
        assistant_text = ""
        for text, updated_state in chat(message, history, session_state, request):
            assistant_text = text
            session_state = updated_state
            yield (
                history + [{"role": "assistant", "content": assistant_text}],
                session_state,
                gr.update(interactive=False),
            )
  
        yield (
            history + [{"role": "assistant", "content": assistant_text}],
            session_state,
            gr.update(interactive=True),
        )
  
    textbox.submit(
        respond,
        inputs=[textbox, chatbot, session_state],
        outputs=[chatbot, session_state, textbox],
    )
  
if __name__ == "__main__":
    port = int(os.environ.get("PORT", 8080))
    demo.launch(server_name="0.0.0.0", server_port=port)

Dockerfile

Cloud Run にデプロイするためのコンテナイメージを定義します。uv の公式イメージからバイナリをコピーし、依存パッケージのインストールとアプリケーションの起動を行います。

FROM python:3.14-slim
  
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
  
WORKDIR /app
  
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev
  
COPY app.py .
  
EXPOSE 8080
  
CMD ["uv", "run", "python", "app.py"]

OAuth 同意画面の構成

Cloud Run で IAP を有効化すると、Cloud Run 上のサービスへのアクセス時に Google アカウントでのログインが求められるようになり、許可されたユーザーのみがチャットボットを利用できます。

プロジェクトで OAuth 同意画面の構成をまだ行っていない場合、以下のドキュメントを参照して実施してください。

Cloud Run へのデプロイ

サービスアカウントの作成

Cloud Run 用のカスタムサービスアカウントを作成し、Agent Runtime へのアクセスに必要な Agent Platform ユーザーroles/aiplatform.user)ロールを付与します。

# サービスアカウントの作成
$ gcloud iam service-accounts create coffee-agent-frontend \
  --display-name="Coffee Agent Frontend"
  
# Agent Platform User ロールの付与
$ gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:coffee-agent-frontend@${PROJECT_ID}.iam.gserviceaccount.com" \
  --role="roles/aiplatform.user"

デプロイ

gcloud run deploy コマンドで Cloud Run にデプロイします。--source オプションを指定すると、Cloud Build によるコンテナイメージのビルドとデプロイが自動で行われます。

--no-allow-unauthenticated--iap を指定することで、IAP で認証されたユーザーのみがアクセスできるようにしています。--service-account で先ほど作成したカスタムサービスアカウントを指定します。また、--set-env-vars でデプロイ済みの Agent Runtime のリソース名を環境変数として渡します。

$ gcloud run deploy coffee-agent-frontend \
  --source . \
  --region asia-northeast1 \
  --set-env-vars "GOOGLE_CLOUD_PROJECT=$PROJECT_ID,GOOGLE_CLOUD_LOCATION=asia-northeast1,AGENT_ENGINE_ID=$RESOURCE_NAME" \
  --service-account coffee-agent-frontend@${PROJECT_ID}.iam.gserviceaccount.com \
  --cpu 1 \
  --memory 1Gi \
  --no-allow-unauthenticated \
  --iap

動作確認

デプロイが完了したら、Cloud Run サービスの URL にブラウザでアクセスします。

# デプロイ後に出力されるサービス URL
Service URL: https://lawapi-frontend-<プロジェクト番号>.asia-northeast1.run.app

IAP が有効化されているため、Google アカウントでのログインが求められます。IAP で保護されたウェブアプリ ユーザーroles/iap.httpsResourceAccessor)ロールが付与されたユーザーでログインします。

Google アカウントでログインする

まず最初のセッションでは、普段飲むコーヒーについての情報(例: 「私は中浅煎りのコスタリカをよく飲みます。おすすめの抽出方法を教えてください」)をエージェントに伝えます。

最初のセッションで普段飲んでいるコーヒー豆についての情報をエージェントに伝える

Memory Bank の中身を確認すると、IAP でログインしたユーザーのメールアドレスを user_id として、「私は中浅煎りのコスタリカをよく飲みます。」という情報を保持する記憶が作成されていることがわかります。

IAP でログインしたユーザーのメールアドレスを user_id として記憶が作成される

再度、別ブラウザから同じユーザーを使用してチャットボットにログインして、「私の好みに合うコーヒー豆を探してください」と伝えてみます。前回伝えたコーヒーの好みが Memory Bank に記憶されているため、その情報を元にした回答が返ってきます。

ユーザーごとに保存された好みに関する情報が別のセッションに引き継がれている

Memory Bank のカスタマイズ

カスタマイズの概要

Memory Bank はデフォルト設定でもユーザーの嗜好などを自動的に抽出して長期記憶として保存してくれますが、実際にエージェントとやり取りを繰り返してみると、思うように抽出されない場合もあります。

Memory Bank では、カスタマイズ設定を適用することで、抽出する情報の種類や挙動をユースケースに合わせて調整することができます。

Memory Bank では主に以下の項目をカスタマイズできます。

項目 説明
トピック Memory Bank が保存すべきと判断する情報の種類を定義します。Google Cloud が提供する managed トピックUSER_PERSONAL_INFOUSER_PREFERENCESKEY_CONVERSATION_DETAILSEXPLICIT_INSTRUCTIONS)と、ラベルと抽出指示を自分で定義できる custom トピックの2種類があります。
生成モデル 記憶の生成に使用する LLM を指定できます(デフォルトは gemini-2.5-flash)。
埋め込みモデル 記憶の検索や統合の判定に使用する埋め込みモデル(embedding model)を指定できます(デフォルトは text-embedding-005)。日本語など英語以外の会話を扱う場合は、gemini-embedding-001text-multilingual-embedding-002 といった多言語対応モデルを指定することで検索品質を向上できます。
有効期限(TTL) 生成・更新された記憶の有効期限を自動設定するルールを定義できます。
Few-shot Examples 抽出してほしい記憶の例をいくつか与えることで、Memory Bank の抽出挙動を調整できます。

カスタマイズ項目の詳細については、以下のドキュメントを参照してください。

カスタマイズ用スクリプト(update.py)の作成

adk コマンドからは Memory Bank のカスタマイズを直接適用することはできないため、Agent Platform SDK を使用するスクリプトで、デプロイ済みの Agent Runtime インスタンスを更新します。

ここではコーヒーエージェントのユースケースに合わせて、コーヒーに関する情報を重点的に抽出するための custom トピックを定義します。具体的には、以下の5つのトピックを Memory Bank に登録します。

トピック 種類 抽出対象
USER_PREFERENCES managed ユーザーの一般的な嗜好
coffee_taste_preferences custom 好む / 苦手な味わいの傾向(酸味・苦味・フレーバーノート・焙煎度合いなど)
brewing_methods custom 普段使用している抽出方法と器具
favorite_beans_and_origins custom 好みの豆の産地・品種・銘柄・ロースター
coffee_habits_and_restrictions custom 飲用習慣やカフェイン制限などの制約

また、日本語での会話における検索の品質を高めるため、similarity_search_config で多言語対応の埋め込みモデル gemini-embedding-001 を指定します。

スクリプトの内容は以下のようになります。client.agent_engines.update()context_spec.memory_bank_config.customization_configs を渡すことで、デプロイ済みの Agent Runtime インスタンスに対してカスタマイズ設定のみを反映できます。

import os
import vertexai
from vertexai.types import (
    MemoryBankCustomizationConfig as CustomizationConfig,
    MemoryBankCustomizationConfigMemoryTopic as MemoryTopic,
    MemoryBankCustomizationConfigMemoryTopicCustomMemoryTopic as CustomMemoryTopic,
    MemoryBankCustomizationConfigMemoryTopicManagedMemoryTopic as ManagedMemoryTopic,
    ManagedTopicEnum,
)
  
client = vertexai.Client(
    project=os.getenv("PROJECT_ID"),
    location=os.getenv("LOCATION", "asia-northeast1"),
)
  
# Memory Bank に保存する記憶のトピック定義
memory_topics = [
    # ユーザーの好み(managed トピック)
    MemoryTopic(
        managed_memory_topic=ManagedMemoryTopic(
            managed_topic_enum=ManagedTopicEnum.USER_PREFERENCES
        )
    ),
    # 味わいの好み
    MemoryTopic(
        custom_memory_topic=CustomMemoryTopic(
            label="coffee_taste_preferences",
            description=(
                "ユーザーが好む / 苦手なコーヒーの味わいの傾向。"
                "酸味・苦味・甘み・ボディ感、フレーバーノート(フルーティ、"
                "ナッティ、チョコレートなど)、焙煎度合い(浅煎り / 中煎り / 深煎り)。"
            ),
        )
    ),
    # 抽出方法・器具
    MemoryTopic(
        custom_memory_topic=CustomMemoryTopic(
            label="brewing_methods",
            description=(
                "ユーザーが普段使っている、または興味のあるコーヒーの抽出方法と器具。"
                "ハンドドリップ、エスプレッソ、フレンチプレス、エアロプレス、サイフォンなど、"
                "および使用しているグラインダーやドリッパーなどの器具情報。"
            ),
        )
    ),
    # 好みの豆・産地
    MemoryTopic(
        custom_memory_topic=CustomMemoryTopic(
            label="favorite_beans_and_origins",
            description=(
                "ユーザーが好む / 過去に飲んだコーヒー豆の産地・品種・銘柄・ロースター。"
                "例: エチオピア イルガチェフェ、ゲイシャ、ブルーマウンテン、特定のロースター名など。"
            ),
        )
    ),
    # 飲用習慣・カフェイン制限
    MemoryTopic(
        custom_memory_topic=CustomMemoryTopic(
            label="coffee_habits_and_restrictions",
            description=(
                "ユーザーのコーヒーの飲用習慣(1日の杯数、飲む時間帯)、"
                "カフェイン制限の有無、デカフェ志向、乳製品アレルギーや代替ミルクの好みなど。"
            ),
        )
    ),
]
  
customization_config = CustomizationConfig(memory_topics=memory_topics)
  
# 類似性検索に使用する埋め込みモデル(多言語対応の gemini-embedding-001 を指定)
project = os.getenv("PROJECT_ID")
location = os.getenv("LOCATION", "asia-northeast1")
embedding_model = (
    f"projects/{project}/locations/{location}/publishers/google/models/gemini-embedding-001"
)
  
# 既存の Agent Runtime を Memory Bank カスタマイズ付きで更新
resource_name = os.environ["RESOURCE_NAME"]
  
agent_engine = client.agent_engines.update(
    name=resource_name,
    config={
        "context_spec": {
            "memory_bank_config": {
                "customization_configs": [customization_config],
                "similarity_search_config": {
                    "embedding_model": embedding_model,
                },
            },
        },
    },
)
  
print("Memory Bank customization applied.")
print(f"Resource Name: {agent_engine.api_resource.name}")

カスタマイズの適用

スクリプトを実行する際は、デプロイ済みの Agent Runtime のリソース名(projects/<プロジェクトID>/locations/<ロケーション>/reasoningEngines/<エージェントID>)を環境変数 RESOURCE_NAME にセットしておきます。

# 環境変数のセット
$ export PROJECT_ID=<プロジェクトID>
$ export LOCATION=asia-northeast1
$ export RESOURCE_NAME=<エージェントのリソース名>
  
# カスタマイズの適用
$ uv run python update.py

スクリプトの実行が成功すると、Agent Runtime インスタンスに Memory Bank のカスタマイズ設定が反映されます。

コンソールからは、類似性検索に使用する埋め込みモデルが gemini-embedding-001 に更新されていることが確認できます。

類似性検索に使用するモデルが変更されている

カスタマイズ後の動作確認

Cloud Run にデプロイしたチャットボットにログインし、「私は酸味が美味しいコーヒーが好きで、コスタリカ、パナマ、ケニア、エチオピアが特に好みです。ハンドドリップで1日に4杯ほど飲みます。好みに近いおすすめの豆を教えてください」のような内容でメッセージを送信してみます。

Memory Bank を確認すると、設定したトピックごとに記憶が作成されていることがわかります。

設定したトピックごとの記憶が Memory Bank に記録される

佐々木 駿太 (記事一覧)

G-gen 最北端、北海道在住のクラウドソリューション部エンジニア

2022年6月に G-gen にジョイン。Google Cloud Partner Top Engineer に選出(2024 / 2025 Fellow / 2026)。好きな Google Cloud プロダクトは Cloud Run。

趣味はコーヒー、小説(SF、ミステリ)、カラオケなど。