Cloud Run でペット パスポート エージェントをビルドしてデプロイする

1. 概要

このコードラボでは、Model Context Protocol(MCP)を使用してデータ分析と位置情報サービスを組み合わせる AI エージェントであるペット パスポート アプリをデプロイする方法について説明します。

このアプリは、ニューヨーク市の犬種の人気に基づいて、犬との完璧な外出プランを立てるのに役立ちます。エージェントは「マクロからミクロ」の推論チェーンを使用します。

  1. 戦略的検出(BigQuery): 特定の犬種で人口が最も多いニューヨーク市の郵便番号を特定します。
  2. ローカル実行(マップ): その郵便番号を位置情報のバイアスとして使用して、「ペット同伴可のカフェ」と「ドッグパーク」を見つけます。
  3. 旅程の生成: データを組み合わせて、クリック可能なリンクと画像を含む「ペット パスポート」の旅程を作成します。

このエージェントは google-adk フレームワークを使用して構築され、Gemini を搭載しています。

注: フロントエンド UI を含むプロジェクト コード全体は、GitHub で入手できます。この Codelab では、エージェントのコアロジックとインフラストラクチャの設定に焦点を当てます。

2. 設定と要件

まず、開発環境が正しく設定されていることを確認しましょう。

1. Google Cloud での認証

アクティブな Google Cloud プロジェクトを設定して認証します。エージェントが BigQuery やその他のサービスにアクセスするには、これが必要です。

gcloud config set project [YOUR-PROJECT-ID]
gcloud auth application-default login --project [YOUR-PROJECT-ID]

注: 認証中に別のプロジェクトに関するエラーが発生した場合は、割り当てプロジェクトを無効にして手動で設定することで回避できます。

gcloud auth application-default login --disable-quota-project
gcloud auth application-default set-quota-project [YOUR-PROJECT-ID]

2. ソフトウェア要件

ローカルマシンに次のソフトウェアがインストールされている必要があります。

  • Python (バージョン 3.13 以降が必要)
  • Git (リポジトリをダウンロードするため)

リポジトリをダウンロードする

このプロジェクトのコードは、Google MCP リポジトリで入手できます。リポジトリをクローンして、プロジェクト フォルダに移動します。

git clone https://github.com/google/mcp.git
cd examples/petpassport

3. インストール

ファイルが用意できたので、Python 環境を設定しましょう。

  1. 仮想環境を作成する: これにより、依存関係が分離されます。
    python3 -m venv .venv
    
  2. 仮想環境を有効にする:
    • Linux/macOS の場合:
      source .venv/bin/activate
      
    • Windows の場合:
      .venv\Scripts\activate
      
  3. 依存関係をインストールします。
    pip install google-adk==1.28.0 python-dotenv google-genai pillow uvicorn
    

Cloud APIs を有効にする

プロジェクトで次の API を有効にします。

gcloud services enable \
  bigquery.googleapis.com \
  aiplatform.googleapis.com \
  artifactregistry.googleapis.com \
  cloudbuild.googleapis.com \
  run.googleapis.com \
  storage.googleapis.com

地域の選択

シェルでリージョンを環境変数として設定します。

export REGION=us-central1

4. API キーを取得する

Maps サービスと Gemini サービスを使用するには、API キーを取得してプロジェクト ルートの .env ファイルに保存する必要があります。

1. Google Maps API キー

  1. Google Cloud コンソール に移動します。
  2. [API とサービス] > [認証情報] に移動します。
  3. [認証情報を作成] > [API キー] をクリックします。
  4. 生成されたキーをコピーして、.env ファイルに MAPS_API_KEY=[YOUR_KEY] として追加します。
  5. (推奨)MCP サーバーで使用される Maps API のみを許可するようにキーを制限します。

2. Gemini API キー(AI Studio)

  1. Google AI Studio に移動します。
  2. [API キーを取得] をクリックするか、[API キー] セクションに移動します。
  3. [API キーを作成] をクリックします。
  4. キーをコピーして、.env ファイルに GEMINI_API_KEY=[YOUR_KEY] として追加します。

5. 依存関係のインストール

petpassport/ フォルダに requirements.txt ファイルを作成します。

google-adk==1.28.0
python-dotenv
google-genai
pillow

6. MCP サーバーを認証する

このアプリケーションは、Model Context Protocol(MCP)サーバーを使用して Google マップと BigQuery を操作します。これらのサーバーを認証するには、適切な環境変数とヘッダーを構成する必要があります。

  1. Google Maps MCP: X-Goog-Api-Key ヘッダーで渡される有効な Maps API キーが必要です。
  2. BigQuery MCP: BigQuery サービスにアクセスできる OAuth 認証情報が必要です。Cloud Run で実行する場合はデフォルトの Compute サービス アカウントを使用し、ローカルで実行する場合はローカルの認証情報を使用します。

リポジトリには、.env ファイルでこれらの変数を構成するのに役立つ設定スクリプト setup/setup_env.sh が用意されています。

7. BigQuery テーブルを作成する

エージェントが犬のライセンス データをクエリするには、BigQuery でデータセットとテーブルを作成してデータを読み込む必要があります。

次の手順を実行する設定スクリプト setup/setup_bigquery.sh を用意しています。

  1. 生データを保存する pet-passport-data-[PROJECT_ID] という名前の Cloud Storage バケットを作成します。
  2. ニューヨーク市の犬のライセンスに関する一般公開データセット(CSV)をダウンロードします。
  3. CSV をバケットにアップロードします。
  4. nyc_dogs という名前の BigQuery データセットを作成します。
  5. バケットからデータセット内の licenses というテーブルにデータを読み込みます。

設定スクリプトを実行するには、ターミナルで次のコマンドを実行します。

bash setup/setup_bigquery.sh

8. MCP サーバーに接続する

このアプリの重要な部分は、MCP を使用してデータとサービスに接続することです。このセクションでは、petpassport/tools.py というファイルで BigQuery と Google マップの MCP ツールセットを構成します。

tools.py コードを完成させる

MCP ツールセットと、画像とデータの永続化のためのカスタムツールを含む、tools.py の完全な実装を次に示します。バケットの解決をモジュールレベルに移動することで、冗長性を減らすようにこのコードを最適化しました。

import os
import dotenv
import google.auth
import time
import datetime
from google.cloud import storage
from PIL import Image
from google import genai
from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset
from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams 

MAPS_MCP_URL = "https://mapstools.googleapis.com/mcp" 
BIGQUERY_MCP_URL = "https://bigquery.googleapis.com/mcp" 

PROJECT_ID = os.getenv('GOOGLE_CLOUD_PROJECT', 'project_not_set')
BUCKET_NAME = f"pet-passport-data-{PROJECT_ID}" 

def get_maps_mcp_toolset():
    dotenv.load_dotenv()
    maps_api_key = os.getenv('MAPS_API_KEY', 'no_api_found')
    
    tools = MCPToolset(
        connection_params=StreamableHTTPConnectionParams(
            url=MAPS_MCP_URL,
            headers={    
                "X-Goog-Api-Key": maps_api_key
            },
            timeout=30.0,          
            sse_read_timeout=300.0
        )
    )
    print("Maps MCP Toolset configured.")
    return tools


def get_bigquery_mcp_toolset():   
    credentials, project_id = google.auth.default(
            scopes=["https://www.googleapis.com/auth/bigquery"]
    )

    credentials.refresh(google.auth.transport.requests.Request())
    oauth_token = credentials.token
        
    HEADERS_WITH_OAUTH = {
        "Authorization": f"Bearer {oauth_token}",
        "x-goog-user-project": project_id
    }

    tools = MCPToolset(
        connection_params=StreamableHTTPConnectionParams(
            url=BIGQUERY_MCP_URL,
            headers=HEADERS_WITH_OAUTH,
            timeout=30.0,          
            sse_read_timeout=300.0
        )
    )
    print("BigQuery MCP Toolset configured.")
    return tools

def generate_pet_passport_photo(prompt: str, image_path: str = None) -> str:
    """Generates an image using gemini-3.1-flash-image-preview based on a prompt and a reference image."""
    client = genai.Client()
    output_path = f"/tmp/pet_passport_{int(time.time())}.png"
    
    try:
        image = Image.open(image_path)
        response = client.models.generate_content(
            model="gemini-3.1-flash-image-preview",
            contents=[prompt, image],
        )
        
        for part in response.parts:
            if part.inline_data is not None:
                generated_image = part.as_image()
                generated_image.save(output_path)
                
                # Upload to GCS and generate signed URL
                try:
                    storage_client = storage.Client()
                    bucket = storage_client.bucket(BUCKET_NAME)
                    blob_name = os.path.basename(output_path)
                    blob = bucket.blob(blob_name)
                    
                    blob.upload_from_filename(output_path)
                    
                    url = blob.generate_signed_url(
                        version="v4",
                        expiration=datetime.timedelta(hours=24),
                        method="GET",
                    )
                    return url
                except Exception as e:
                    print(f"Error uploading image to GCS: {e}")
                    return output_path
                
        raise ValueError("No image was returned by the model.")
    except Exception as e:
        print(f"Error generating image: {e}")
        raise

def save_pet_passport(user_id: str, breed: str, postal_code: str, route_details: str, image_paths: list[str] = None) -> str:
    """Appends the generated itinerary to the user's history in GCS."""
    try:
        storage_client = storage.Client()
        bucket = storage_client.bucket(BUCKET_NAME)
        blob = bucket.blob(f"user-{user_id}.json")
        
        # Download existing or start fresh
        # ... (Implementation details hidden for brevity) ...
        return "Success"
    except Exception as e:
        print(f"Error saving path: {e}")
        raise

コードの説明: tools.py

  • get_maps_mcp_toolsetget_bigquery_mcp_toolset は、正しいエンドポイントと認証ヘッダーを使用して MCP クライアントを構成します。
  • generate_pet_passport_photo は Gemini を使用してシーンを作成し、その結果を Google Cloud Storage にアップロードして、サーバーの再起動後も存続する署名付き URL をフロントエンドに返します。

9. エージェントを作成する

ツールの構成が完了したので、エージェントの「頭脳」を構築しましょう。Agent Development Kit(ADK)を使用して、petpassport/agent.py というファイルにエージェントを作成します。

agent.py コードを完成させる

エージェントとその手順を定義する agent.py の完全な実装を次に示します。

import os
import dotenv
import tools
from google.adk.agents import LlmAgent

dotenv.load_dotenv()

PROJECT_ID = os.getenv('GOOGLE_CLOUD_PROJECT', 'project_not_set')

maps_toolset = tools.get_maps_mcp_toolset()
bigquery_toolset = tools.get_bigquery_mcp_toolset()

root_agent = LlmAgent(
    model='gemini-2.5-pro',
    name='root_agent',
    instruction=f"""
        You are the Pet Passport Agent. Your goal is to help users find a fun walking route for their dog in NYC.
        
        When given a breed and a postal code, follow this flow:
        1. **Strategic Discovery:** Use BigQuery to find the most popular neighborhood for that breed in NYC.
        2. **Local Execution:** Use Maps to build a walking route with specific places (parks, cafes) in that area.
        
        **NO DIRECTIONS LINKS:** You must NOT include a Google Maps directions link (e.g., `https://www.google.com/maps/dir/...`) in your final response. Only provide links to individual places.
        
        After generating the itinerary, you MUST call the `save_pet_passport` tool to save this path to the user's profile. Pass a clean summary of the itinerary as `route_details`. The summary should include details (like rating, description from maps).
    """,
    tools=[maps_toolset, bigquery_toolset, tools.generate_pet_passport_photo, tools.save_pet_passport]
)

コードの説明: agent.py

  • コンテナ環境をサポートするために、tools を直接(フラットな構造)インポートします。
  • エージェントは gemini-2.5-pro で初期化されます。
  • 手順では、厳密なマルチステップの思考チェーン(最初に BigQuery、次に Maps)を定義し、混乱を招くウォーキング ルートのハルシネーションやレンダリングを厳禁します。

10. アプリケーションをローカルで実行する

Cloud Run にデプロイする前に、アプリケーションをローカルでテストすることをおすすめします。

  1. プロジェクト ディレクトリにいることを確認します。
    cd examples/petpassport
    
  2. FastAPI サーバーを起動します。uvicorn を使用してアプリを実行します。エントリ ポイントは petpassport フォルダ内の main.py です。
    uvicorn petpassport.main:app --reload
    
  3. UI を開く: ブラウザで http://127.0.0.1:8000/ui/ に移動して、ペット パスポート インターフェースを操作します。

11. Cloud Run にデプロイする

エージェントの準備ができたので、Cloud Run にデプロイしましょう。コンテナ環境を厳密に制御するために、標準の gcloud コマンドを直接使用します。

プロジェクト ディレクトリから次のコマンドを実行します。

gcloud run deploy petpassport \
  --source petpassport \
  --region $REGION \
  --allow-unauthenticated \
  --labels dev-tutorial=google-mcp

環境変数を構成する

デプロイ後、Google Cloud コンソールで Cloud Run サービスに移動し、[変数とシークレット] タブで次の環境変数を設定します。

  • MAPS_API_KEY: Google Maps API キー。
  • GOOGLE_CLOUD_PROJECT: プロジェクト ID。
  • PROJECT_ID: プロジェクト ID(レガシー モジュールで冗長性がサポートされています)。

12. プロンプトの例

次のプロンプトを使用して、デプロイされたエージェントを操作してみてください。

  1. スタンダード: 「10021 の近くのニューヨーク市でゴールデン リトリーバーと散歩に行きたい。カフェのあるルートを探して。」
  2. 別の犬種: 「フレンチ ブルドッグを飼っていて、アッパー ウェスト サイド(10024 の近く)に住んでいます。人気のドッグパークに立ち寄る短い散歩コースを提案して。
  3. 画像付き: (犬の写真をアップロード)「コーギーの写真です。10013 の近くに住んでいます。完璧な外出プランを立てて。」

13. クリーンアップ

このチュートリアルで使用したリソースについて課金されないようにするには:

  • Cloud Run サービスを削除する: gcloud run services delete petpassport --region=$REGION
  • GCS バケットを削除する: gcloud storage rm -r gs://pet-passport-data-$PROJECT_ID