Google ADK と Cloud Run を使用して Streamlit に RAG AI エージェントをデプロイする

1. はじめに

この Codelab では、コーヒー ショップ向けのインタラクティブな AI バリスタ エージェントを構築します。Google のオープンソースのAgent Development Kit(ADK)Gemini 3.5 Flash モデルを使用して、検索拡張生成(RAG)を実装し、エージェントの推奨事項をモックメニュー データセットにグラウンディングします。最後に、エージェントを Streamlit ユーザー インターフェースにラップして Cloud Run にデプロイします。

演習内容

  • コーヒー アイテム、タグ、アレルゲンを含む RAG データソース(menu.json)を作成します。
  • ADK LlmAgent を使用して AI エージェントを構築し、Python ツールを接続してメニューデータを読み込みます。
  • 会話履歴を管理する Streamlit チャット アプリケーションでエージェントをラップします。
  • ソースベースのデプロイを使用して、Streamlit アプリを Cloud Run にデプロイします。
  • RAG グラウンディングとアレルゲン認識をテストします。

アーキテクチャ図

必要なもの

  • ウェブブラウザ(Chrome など)。
  • 課金を有効にした Google Cloud プロジェクト
  • Python の基本的な知識。

この Codelab は、初心者を含むあらゆるレベルのデベロッパーを対象としています。

推定費用: 1.00 米ドル未満。

2. 始める前に

Google Cloud プロジェクトの作成

  1. Google Cloud コンソールで、Google Cloud プロジェクトを作成または選択 します。
  2. Cloud プロジェクトで課金が有効になっていることを確認します。

Cloud Shell の起動

  1. Google Cloud コンソールの上部にある「Cloud Shell をアクティブにする 」アイコンをクリックします。

Cloud Shell をアクティブにする

  1. 認証を検証します。

Cloud Shell の承認

  gcloud auth list
  1. アクティブなプロジェクトが設定されていることを確認します。
  gcloud config get project

表示されたプロジェクト ID が正しくない場合や、設定されていない場合は、次を実行します。

  gcloud config set project <YOUR_PROJECT_ID>

API を有効にする

次のコマンドを実行して、必要な API をすべて有効にします。

gcloud services enable \
 run.googleapis.com \
 aiplatform.googleapis.com \
 cloudbuild.googleapis.com

3. プロジェクトをセットアップする

このステップでは、プロジェクトの環境変数を初期化し、プロジェクトの作業ディレクトリを作成します。

  1. アクティブな Cloud Shell セッションで、次のプロジェクト環境変数を初期化します。
  export PROJECT_ID=$(gcloud config get-value project)

注: 最も近いリージョンを使用する

最も近いリージョンを見つけて、次のコマンドで insert-region-here に置き換えます。

  export REGION=[insert-region-here]
  1. coffee-barista-agent という名前の新しいプロジェクト ディレクトリを作成して移動します。
  mkdir coffee-barista-agent && cd coffee-barista-agent

4. モックメニュー データソースを作成する

AI バリスタをグラウンディングして、存在しないアイテムを幻覚しないようにするには、ローカル メニュー データセットを作成します。エージェントは、カスタムツールを使用して実行時にこのファイルを読み取ります。

  1. Cloud Shell エディタで menu.json を作成して開きます。
  cloudshell edit menu.json
  1. 次の JSON コンテンツをエディタに貼り付けて、ファイルを保存します。
[
  {
    "name": "Espresso Solo",
    "description": "A single shot of rich, bold espresso.",
    "price": 2.50,
    "tags": ["strong", "hot", "dairy-free", "sugar-free"],
    "allergens": []
  },
  {
    "name": "Oat Milk Honey Latte",
    "description": "Creamy steamed oat milk with espresso and a touch of honey.",
    "price": 5.00,
    "tags": ["sweet", "hot", "dairy-free"],
    "allergens": []
  },
  {
    "name": "Cold Brew Coffee",
    "description": "Smooth, slow-steeped cold brew served over ice.",
    "price": 4.00,
    "tags": ["strong", "cold", "dairy-free", "sugar-free"],
    "allergens": []
  },
  {
    "name": "Seasonal Pumpkin Latte",
    "description": "Spiced pumpkin sauce, espresso, and steamed milk, topped with whipped cream.",
    "price": 5.50,
    "tags": ["sweet", "hot", "seasonal"],
    "allergens": ["dairy"]
  },
  {
    "name": "Classic Croissant",
    "description": "Flaky, buttery traditional French pastry.",
    "price": 3.50,
    "tags": ["bakery", "savory"],
    "allergens": ["wheat", "dairy"]
  },
  {
    "name": "Vegan Blueberry Muffin",
    "description": "Soft, sweet muffin packed with real blueberries, entirely plant-based.",
    "price": 3.75,
    "tags": ["bakery", "sweet", "dairy-free", "vegan"],
    "allergens": ["wheat"]
  },
  {
    "name": "Nitro Cold Brew",
    "description": "Cold brew infused with nitrogen for a super smooth, creamy head.",
    "price": 4.50,
    "tags": ["strong", "cold", "dairy-free", "sugar-free"],
    "allergens": []
  },
  {
    "name": "Iced Caramel Macchiato",
    "description": "Chilled milk and vanilla syrup marked with espresso and caramel drizzle.",
    "price": 5.25,
    "tags": ["sweet", "cold"],
    "allergens": ["dairy"]
  }
]
  1. JSON ファイルの形式が正しいことを確認します。
  cat menu.json | python3 -m json.tool > /dev/null && echo "Valid JSON!"

💬 ディスカッション: ローカル JSON とライブ データベース

ライブ データベースではなく、シンプルなローカル menu.json ファイルを使用するのはなぜですか?

クイック チュートリアルやプロトタイプの場合、ローカル JSON ファイルを使用すると、初期データベースのセットアップ時間と複雑さを解消できます。ただし、実際のエンタープライズ本番環境アプリケーションでは、エージェントを Cloud Firestore、AlloyDB、Cloud SQL などのマネージド データベースに接続します。

ライブ データベースを使用すると、コーヒー ショップの管理者は、コンテナ イメージを再構築したり、アプリケーション コードを再デプロイしたりすることなく、季節限定のアイテムの追加、価格の更新、アレルゲンタグの調整を動的に行うことができます。この Codelab の後半で、ライブ データベースを省略可能なステップとして使用します。

5. ADK エージェントをビルドする

次に、必要なパッケージをインストールし、コア ADK エージェント ロジックを構築します。get_menu() ツールを定義して、LlmAgent に渡します。

  1. Cloud Shell エディタで requirements.txt を作成して開きます。
  cloudshell edit requirements.txt
  1. 次の依存関係をエディタに貼り付けて、ファイルを保存します。
google-adk==2.2.0
streamlit==1.58.0
  1. Cloud Shell エディタで agent.py を作成して開きます。
  cloudshell edit agent.py
  1. agent.py に次のコードを貼り付けます。
# agent.py
import json

from google.adk.agents import LlmAgent

# [START get_menu]
def get_menu() -> str:
    """Retrieves the coffee shop menu from menu.json.

    Returns:
        str: A JSON string representing the list of menu items.
    """
    try:
        with open("menu.json", "r") as f:
            menu_data = json.load(f)
            return json.dumps(menu_data)
    except Exception as e:
        return json.dumps({"error": f"Could not retrieve menu: {str(e)}"})
# [END get_menu]

# Create the barista agent
barista_agent = LlmAgent(
    name="barista_agent",
    model="gemini-3.5-flash",
    instruction="""You are a friendly barista at ☕ Coffee Shop.
Your job is to recommend drinks and pastries to customers based on their preferences.

Rules you MUST follow:
1.  You must recommend items ONLY from the menu returned by get_menu().
2.  Do NOT recommend or suggest any item that is not present in the menu.
3.  If a user's preference is vague or unclear, ask exactly ONE friendly clarifying question to narrow down what they want (e.g., cold or hot, sweet or strong, coffee or pastry).
4.  Be warm and welcoming, but remain professional.
5.  Ground your recommendations in the actual tags, descriptions, and allergens listed in the menu (e.g., if a user is dairy-free, recommend ONLY items tagged 'dairy-free' or with no dairy allergens).
""",
    tools=[get_menu]
)

from google.adk.apps import App

# Define the App object
app = App(
    name="coffee_barista_app",
    root_agent=barista_agent
)
  1. Cloud Shell エディタで app.py を作成して開きます。
  cloudshell edit app.py
  1. app.py に次のコードを貼り付けます。
# app.py
import streamlit as st
import json

# Set page config for a premium look
st.set_page_config(
    page_title="☕ Coffee Shop - Barista Bot",
    page_icon="☕",
    layout="wide",
    initial_sidebar_state="expanded"
)

# Custom CSS to make the header sticky (adapts to light/dark themes)
st.markdown("""
<style>
    div[data-testid="element-container"]:has(.header-container),
    div.element-container:has(.header-container) {
        position: sticky;
        top: 2.875rem;
        z-index: 999;
        background-color: transparent;
        padding-bottom: 10px;
    }
</style>
""", unsafe_allow_html=True)

# App Header (using inline styles for the permanent coffee theme look)
st.markdown("""
<div class="header-container" style="text-align: center; padding: 20px; background: linear-gradient(135deg, #8B5E3C, #6F4E37); color: white; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.1);">
    <h1 style="margin: 0; font-size: 2.5rem; font-weight: 700; color: white;">☕ ☕ Coffee Shop</h1>
    <p style="margin: 5px 0 0 0; font-size: 1.1rem; opacity: 0.9; color: white;">Your friendly AI Barista is ready to help you find the perfect drink or pastry!</p>
</div>
""", unsafe_allow_html=True)

# Load Menu for the sidebar
# [START load_menu]
try:
    with open("menu.json", "r") as f:
        menu_items = json.load(f)
except Exception as e:
    st.error(f"Error loading menu: {e}")
    menu_items = []
# [END load_menu]

# Sidebar Menu & Configuration
with st.sidebar:
    st.markdown("## ☕ Coffee Shop Menu")
    st.markdown("Explore our offerings and ask the barista for recommendations.")
    st.markdown("---")

    for item in menu_items:
        with st.container(border=True):
            st.markdown(f"**{item['name']}**  •  **${item['price']:.2f}**")
            st.caption(item['description'])

            # Tags & Allergens as native badges
            tags = " ".join([f"`{t}`" for t in item.get("tags", [])])
            if tags:
                st.markdown(tags)

            allergens = ", ".join(item.get("allergens", []))
            if allergens:
                st.markdown(f"⚠️ *Allergens: {allergens}*")

# Chat Interface
if "session_id" not in st.session_state:
    import uuid
    st.session_state.session_id = str(uuid.uuid4())

if "runner" not in st.session_state:
    from google.adk.runners import InMemoryRunner
    from agent import app
    st.session_state.runner = InMemoryRunner(app=app)

if "messages" not in st.session_state:
    st.session_state.messages = [
        {"role": "assistant", "content": "Welcome to ☕ Coffee Shop! What can I get started for you today?"}
    ]

# Display existing messages
for msg in st.session_state.messages:
    with st.chat_message(msg["role"]):
        st.markdown(msg["content"])

# User Input
if prompt := st.chat_input("Ask for recommendations (e.g., 'What dairy-free pastries do you have?')"):
    # Display user message
    with st.chat_message("user"):
        st.markdown(prompt)
    st.session_state.messages.append({"role": "user", "content": prompt})

    # Generate response
    with st.chat_message("assistant"):
        try:
            import asyncio

            # Run the ADK runner asynchronously using asyncio.run
            async def fetch_response():
                return await st.session_state.runner.run_debug(
                    prompt,
                    session_id=st.session_state.session_id
                )

            res_events = asyncio.run(fetch_response())

            response_text = "".join([
                part.text
                for event in res_events
                if event.content and event.content.parts
                for part in event.content.parts
                if part.text
            ])

            st.markdown(response_text)
            st.session_state.messages.append({"role": "assistant", "content": response_text})
        except Exception as e:
            st.error(f"Apologies, I ran into an error: {e}")

💬 ディスカッション: モデルのトレードオフと検索トークンの効率

メニュー全体をエージェントのシステム手順に貼り付けるのではなく、関数ツールを呼び出してメニューを取得するのはなぜですか?

トークン エコノミー!プロンプトに 8 個のアイテムを配置するのは安価ですが、コーヒー ショップがカスタム材料を含む 500 個のアイテムに拡大した場合はどうなるでしょうか?大きなデータセットをシステム プロンプトに直接貼り付けると、プロンプト トークン数が膨らみ、クエリごとにトランザクション費用と API レスポンス レイテンシが増加します。

ADK ツールを使用すると、エージェントは必要な場合にのみメニューの読み取りを動的に要求します。LLM は関連するメニューデータのみをコンテキストとして受け取り、プロンプト トークンサイズを最小限に抑えます。

💬 ディスカッション: メモリの状態と本番環境ストア

Streamlit の st.session_state に保存されているチャット履歴は、ユーザーがブラウザタブを閉じても保持されますか?

いいえ。st.session_state は完全にメモリ内で、アクティブなブラウザ接続に固有です。ユーザーがページを更新するかタブを閉じると、バリスタとの会話履歴は失われます。

本番環境アプリケーションの場合は、ADK ランナーを Cloud Firestore や Redis などの永続ストレージ バックエンドに接続します。ADK には、ページの再読み込みやデバイス間でチャット履歴を簡単に保存して再開できる組み込みのサービス抽象化(SessionService など)が用意されています。

6. Cloud Run にエージェントをデプロイする

Cloud Run の組み込み Buildpacks を使用して、ソースから Streamlit アプリケーションを直接デプロイします。最小権限の原則に従って、デフォルトの Compute Engine サービス アカウントを使用するのではなく、専用のカスタム サービス アカウントを使用して作成してデプロイします。

  1. 専用のサービス アカウントを作成します。
  gcloud iam service-accounts create barista-agent-sa \
    --description="Service account for Coffee Barista ADK agent on Cloud Run" \
    --display-name="Barista Agent Service Account"
  1. Gemini Enterprise Agent Platform ユーザーロール(roles/aiplatform.user)を新しいサービス アカウントに付与します。
  gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member="serviceAccount:barista-agent-sa@$PROJECT_ID.iam.gserviceaccount.com" \
    --role="roles/aiplatform.user"
  1. gcloud run deploy を使用してサービスをデプロイし、--service-account フラグを使用して新しいサービス アカウントのメールアドレスを渡します。
gcloud run deploy coffee-barista \
  --source . \
  --region $REGION \
  --allow-unauthenticated \
  --labels dev-tutorial=codelab-streamlit-rag-adk \
  --command "/cnb/lifecycle/launcher" \
  --args "sh,-c,python3 -m streamlit run app.py --server.port=\$PORT --server.address=0.0.0.0 --server.enableCORS=false --server.enableXsrfProtection=false" \
  --service-account "barista-agent-sa@$PROJECT_ID.iam.gserviceaccount.com" \
  --set-env-vars GOOGLE_GENAI_USE_VERTEXAI=TRUE,GOOGLE_CLOUD_PROJECT=$PROJECT_ID,GOOGLE_CLOUD_LOCATION=global
  1. デプロイが完了したら、コマンド出力でサービス URL を確認します。

💬 ディスカッション: コンテナとソースのデプロイ、IAM セキュリティ

Dockerfile や Procfile を作成せずに、gcloud run deploy --source を使用して Cloud Run にデプロイしました。Cloud Run は、Python アプリをコンパイルして実行する方法をどのように把握したのですか?

Cloud Run は、Buildpacks を使用してリポジトリを分析します。requirements.txt と Python ソースファイルが存在することを検出すると、エンジンは Python ランタイム コンテナを自動的にコンパイルしてパッケージ化します。

カスタム Dockerfile を作成すると、コンテナのシステム パッケージとベースレイヤを完全に制御できます。Procfile は、コンテナを完全に構成せずに起動コマンドを宣言する簡単な方法です。ただし、迅速なデプロイには、ソース(--source)からのデプロイが非常に効率的です。

デフォルトの Compute Engine サービス アカウントを使用するのではなく、カスタム サービス アカウント barista-agent-sa を作成する追加の手順を行ったのはなぜですか?

安全第一デフォルトの Compute Engine サービス アカウントには、デフォルトで非常に幅広い編集者の権限が付与されています。デフォルトのサービス アカウントで Cloud Run コンテナを実行すると、アプリにセキュリティ バグがある場合、攻撃者が Google Cloud プロジェクト内の他のリソースを読み取り、書き込み、削除する可能性があります。

専用のサービス アカウントを作成し、roles/aiplatform.user ロールのみを割り当てることで、最小権限の原則に従います。アプリは Gemini を呼び出すために必要なアクセス権のみを持ち、それ以上のアクセス権はありません。

7. RAG の動作をテストする

ウェブブラウザで Cloud Run サービスの URL を開き、AI バリスタに質問して、グラウンディングと安全性の制約をテストします。

  1. メニュー内のリクエスト: 「強い温かい飲み物を勧めてください」と尋ねます。期待される結果: エージェントはエスプレッソを推奨します。
  2. メニュー外のトラップ: 「抹茶フラペチーノはありますか?」と尋ねます。期待される結果: エージェントは丁寧に断り、メニューにないことを説明します。
  3. アレルゲンを認識するリクエスト: 「乳糖不耐症ですが、何か飲めますか?」と尋ねます。期待される結果: エージェントは、乳製品を含まないメニュー アイテム(オーツミルク ラテ、エスプレッソ、コールドブリューなど)のみを推奨します。カプチーノやクロワッサンは推奨しません。

RAG の動作をテストする

8. 省略可: ベクトル検索を使用して Firestore でエージェントをグラウンディングする

本番環境では、メニュー アイテムをローカルの menu.json ファイルに保存することは理想的ではありません。メニューを変更するたびに、コンテナ イメージを再構築して Cloud Run サービスを再デプロイする必要があるためです。

アプリケーションを動的でスケーラブルにするには、メニューデータを Cloud Firestore に移行し、ベクトル検索 を使用して、セマンティック類似性に基づいて最も関連性の高いメニュー アイテムのみを取得します。

ベクトル検索を使用した Firestore の統合

1. Firestore API を有効にしてデータベースを初期化する

次のコマンドを実行して Firestore API を有効にし、ネイティブ モードで coffee-menu という名前の Firestore データベースを作成します。

gcloud services enable firestore.googleapis.com

gcloud firestore databases create --database="coffee-menu" --location=$REGION

注: API の有効化が伝播するまでに 1 ~ 2 分かかることがあります。データベース作成コマンドで API [firestore.googleapis.com] not enabled on project... Would you like to enable and retry? と表示されたら、「Y」と入力して続行するか、1 分待ってからコマンドを再実行します。

2. メニューデータで Firestore をシードする

menu.json ファイルのメニュー アイテムを使用して Firestore データベースを迅速にシードするには、Cloud Shell で Python スクリプトをローカルで実行します。

  1. シード スクリプトを実行するには、Firestore と GenAI クライアント ライブラリを Cloud Shell にローカルにインストールします。
pip3 install google-cloud-firestore==2.27.0 google-genai==2.11.0
  1. シード スクリプト seed.py を作成します。
cloudshell edit seed.py
  1. seed.py に次のコードを貼り付けます。
# seed.py
import json
import os
from google import genai
from google.cloud import firestore
from google.cloud.firestore_v1.vector import Vector

db = firestore.Client(database="coffee-menu")
client = genai.Client(
   vertexai=True,
   project=os.environ.get("PROJECT_ID"),
   location=os.environ.get("REGION", "us-central1")
)

with open("menu.json", "r") as f:
   menu_items = json.load(f)

for item in menu_items:
   # Use the name as the document ID
   doc_id = item["name"].lower().replace(" ", "-")

   # Generate text embedding using Vertex AI text-embedding-004 model
   text_to_embed = f"{item['name']}: {item['description']}"
   response = client.models.embed_content(
       model="text-embedding-004",
       contents=text_to_embed,
   )
   embedding = response.embeddings[0].values

   # Add embedding vector to the menu item data
   item["embedding"] = Vector(embedding)

   db.collection("menu").document(doc_id).set(item)

print("Firestore menu collection seeded with vector embeddings successfully!")
  1. スクリプトを実行します。
python3 seed.py

3. Firestore ベクトル インデックスを作成する

メニュー アイテムに対してベクトル検索を実行するには、Firestore データベースの embedding フィールドに複合ベクトル インデックスを作成する必要があります。

Cloud Shell ターミナルで次のコマンドを実行します。

gcloud firestore indexes composite create \
 --collection-group=menu \
 --query-scope=COLLECTION \
 --database="coffee-menu" \
 --field-config=field-path=embedding,vector-config='{"dimension":"768", "flat": "{}"}'

注: Firestore インデックスの作成はバックグラウンドで実行され、完了までに数分かかることがあります。インデックスの作成中に、Codelab の次のステップに進むことができます。

4. サービス アカウントに Firestore へのアクセス権を付与する

Cloud Run サービスが Firestore にクエリを実行するには、サービス アカウントに Cloud Datastore ユーザーroles/datastore.user)ロールを付与する必要があります。

gcloud projects add-iam-policy-binding $PROJECT_ID \
 --member="serviceAccount:barista-agent-sa@$PROJECT_ID.iam.gserviceaccount.com" \
 --role="roles/datastore.user"

注: ネイティブ モードで Cloud Firestore を使用していますが、Google Cloud は統合された Cloud Datastore IAM ロール(roles/datastore.viewer または roles/datastore.user)を使用してアクセス制御を管理します。

5. コードを更新する

次に、menu.json から読み取るのではなく、Firestore からメニューを取得するようにコードを更新します。

  1. Cloud Shell エディタで requirements.txt を開きます。
cloudshell edit requirements.txt
  1. Firestore と GenAI クライアント ライブラリをファイルの末尾に追加して保存します。
google-cloud-firestore==2.27.0
google-genai==2.11.0
  1. Cloud Shell エディタで agent.py を開きます。
cloudshell edit agent.py
  1. agent.py# [START get_menu] ブロックを見つけて、次の Firestore 実装に置き換えます(# [START get_menu] から # [END get_menu] まで)。
# [START get_menu]
from google import genai
from google.cloud import firestore
from google.cloud.firestore_v1.base_vector_query import DistanceMeasure
from google.cloud.firestore_v1.vector import Vector

def get_menu(query: str) -> str:
   """Retrieves coffee shop menu items matching the user's query.

   Args:
       query: The search query or preference to find matching menu items.

   Returns:
       str: A JSON string representing the list of top matching menu items.
   """
   try:
       # Initialize clients
       db = firestore.Client(database="coffee-menu")
       client = genai.Client()

       # Generate embedding for the search query
       response = client.models.embed_content(
           model="text-embedding-004",
           contents=query,
       )
       query_vector = response.embeddings[0].values

       # Search the Firestore database using Vector Search
       results = db.collection("menu").find_nearest(
           vector_field="embedding",
           query_vector=Vector(query_vector),
           distance_measure=DistanceMeasure.COSINE,
           limit=3,
       ).stream()

       menu_data = []
       for doc in results:
           item = doc.to_dict()
           # Remove embedding field to save tokens
           item.pop("embedding", None)
           menu_data.append(item)

       return json.dumps(menu_data)
   except Exception as e:
       return json.dumps({"error": f"Could not retrieve menu: {str(e)}"})
# [END get_menu]
  1. Cloud Shell エディタで app.py を開きます。
cloudshell edit app.py
  1. app.py# [START load_menu] ブロックを見つけて、次の Firestore ロードロジックに置き換えます(# [START load_menu] から # [END load_menu] まで)。
# [START load_menu]
from google.cloud import firestore

try:
   db = firestore.Client(database="coffee-menu")
   docs = db.collection("menu").stream()
   menu_items = []
   for doc in docs:
       item = doc.to_dict()
       item.pop("embedding", None)
       menu_items.append(item)
except Exception as e:
   st.error(f"Error loading menu from Firestore: {e}")
   menu_items = []
# [END load_menu]

6. Cloud Run に再デプロイする

更新したアプリケーションをデプロイします。

gcloud run deploy coffee-barista \
 --source . \
 --region $REGION \
 --allow-unauthenticated \
 --command "/cnb/lifecycle/launcher" \
 --args "sh,-c,python3 -m streamlit run app.py --server.port=\$PORT --server.address=0.0.0.0 --server.enableCORS=false --server.enableXsrfProtection=false" \
 --service-account "barista-agent-sa@$PROJECT_ID.iam.gserviceaccount.com" \
 --set-env-vars GOOGLE_GENAI_USE_VERTEXAI=TRUE,GOOGLE_CLOUD_PROJECT=$PROJECT_ID,GOOGLE_CLOUD_LOCATION=global

7. Firestore の統合を確認する

エージェントと Firestore の接続をテストするには、Firestore に新しいメニュー アイテムを直接追加し、エージェントがそれを推奨することを確認します。

  1. Cloud Shell で次のコマンドを実行して、Python を使用して Firestore の menu コレクションに新しいドキュメントを書き込みます。
python3 -c "
import os
from google import genai
from google.cloud import firestore
from google.cloud.firestore_v1.vector import Vector

db = firestore.Client(database='coffee-menu')
client = genai.Client(
   vertexai=True,
   project=os.environ.get('PROJECT_ID'),
   location=os.environ.get('REGION', 'us-central1')
)

name = 'Matcha Green Tea Latte'
desc = 'Creamy steamed milk infused with premium Japanese matcha powder.'
res = client.models.embed_content(
   model='text-embedding-004',
   contents=f'{name}: {desc}'
)
embedding = res.embeddings[0].values

db.collection('menu').document('matcha-latte').set({
   'name': name,
   'description': desc,
   'price': 5.50,
   'tags': ['sweet', 'hot', 'dairy-free'],
   'allergens': [],
   'embedding': Vector(embedding)
})
print('Successfully added Matcha Latte with vector embeddings!')
"
  1. ブラウザで Streamlit アプリを更新して、チャット セッションをクリアし、新しいデータベースの状態を読み込みます。
  2. 以下の点に注意してください。
    • サイドバー メニューに [抹茶グリーンティー ラテ] が自動的に表示されます。
    • チャットボットに「抹茶ドリンクはありますか?」と尋ねます。
    • エージェントは、追加したばかりの説明と価格で、新しい抹茶グリーンティー ラテ を正常に推奨する必要があります。これにより、エージェントがライブ Firestore データベースで直接クエリグラウンディングされていることが確認できます。

9. クリーンアップ

Google Cloud 請求先アカウントへの継続的な課金を回避するには、デプロイした Cloud Run サービスとカスタム サービス アカウントを削除します。

Cloud Run サービスを削除します。

gcloud run services delete coffee-barista --region $REGION --quiet

カスタム サービス アカウントを削除します。

gcloud iam service-accounts delete barista-agent-sa@$PROJECT_ID.iam.gserviceaccount.com --quiet

(省略可)Firestore データベースを削除します(作成した場合)。

gcloud firestore databases delete --database="coffee-menu" --quiet

省略可: プロジェクト全体を削除します。⚠️ このラボ専用のプロジェクトを作成した場合にのみ実行してください

gcloud projects delete $PROJECT_ID

10. 完了

おめでとうございます!Google の ADK と Cloud Run を使用して、検索拡張生成(RAG)AI バリスタ エージェントを構築してデプロイしました。

学習した内容

  • Python でシンプルな RAG ツールを構築する。
  • ADK LlmAgentInMemoryRunner を活用する。
  • Streamlit でステートフルなチャット エクスペリエンスを作成する。
  • ソースベースのビルドを使用して Streamlit を Cloud Run にデプロイする。

リファレンス ドキュメント