Créer et déployer un agent Pet Passport sur Cloud Run

1. Présentation

Dans cet atelier de programmation, vous allez apprendre à déployer l'application Pet Passport, un agent IA qui utilise le Model Context Protocol (MCP) pour combiner l'analyse de données et les services de localisation.

L'application aide les utilisateurs à planifier une journée parfaite avec leur chien en fonction de la popularité de la race à New York. L'agent utilise une chaîne de raisonnement "Macro-to-Micro" (du macro au micro) :

  1. Découverte stratégique (BigQuery) : identifie le code postal de New York où la population d'une race spécifique est la plus élevée.
  2. Exécution locale (Maps) : utilise ce code postal comme biais de localisation pour trouver des "cafés acceptant les animaux" et des "parcs pour chiens".
  3. Génération d'itinéraire : combine les données pour créer un itinéraire "Pet Passport" avec des liens cliquables et des images.

L'agent est créé à l'aide du framework google-adk et optimisé par Gemini.

Remarque : Le code complet du projet, y compris l'interface utilisateur frontend, est disponible sur GitHub. Dans cet atelier de programmation, nous nous concentrerons sur la logique de l'agent principal et la configuration de l'infrastructure.

2. Préparation

Tout d'abord, assurez-vous que votre environnement de développement est correctement configuré.

1. Authentification avec Google Cloud

Définissez votre projet Google Cloud actif et authentifiez-vous. Cela est nécessaire pour que l'agent puisse accéder à BigQuery et à d'autres services.

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

Remarque : Si vous rencontrez des erreurs concernant un autre projet lors de l'authentification, vous pouvez les contourner en désactivant le projet de quota et en le définissant manuellement :

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

2. Logiciels requis

Vous devez installer les logiciels suivants sur votre ordinateur local :

  • Python (version 3.13 ou ultérieure requise)
  • Git (pour télécharger le dépôt)

Télécharger le dépôt

Le code de ce projet est disponible dans le dépôt Google MCP. Clonez le dépôt et accédez au dossier du projet :

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

3. Installation

Maintenant que vous disposez des fichiers, configurons l'environnement Python.

  1. Créez un environnement virtuel : cela permet d'isoler vos dépendances.
    python3 -m venv .venv
    
  2. Activez l'environnement virtuel :
    • Sous Linux/macOS :
      source .venv/bin/activate
      
    • Sous Windows :
      .venv\Scripts\activate
      
  3. Installez les dépendances :
    pip install google-adk==1.28.0 python-dotenv google-genai pillow uvicorn
    

Activer les API Cloud

Activez les API suivantes dans votre projet :

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

Sélectionner une région

Définissez la région en tant que variable d'environnement dans votre shell :

export REGION=us-central1

4. Obtenir les clés API

Pour utiliser les services Maps et Gemini, vous devez obtenir des clés API et les stocker dans un fichier .env à la racine du projet.

1. Clé API Google Maps

  1. Accédez à la console Google Cloud.
  2. Accédez à API et services > Identifiants.
  3. Cliquez sur Créer des identifiants > Clé API.
  4. Copiez la clé générée et ajoutez-la à votre fichier .env sous la forme MAPS_API_KEY=[YOUR_KEY].
  5. (Recommandé) Limitez la clé pour n'autoriser que les API Maps utilisées par le serveur MCP.

2. Clé API Gemini (AI Studio)

  1. Accédez à Google AI Studio.
  2. Cliquez sur Obtenir une clé API ou accédez à la section des clés API.
  3. Cliquez sur Créer une clé API.
  4. Copiez la clé et ajoutez-la à votre fichier .env sous la forme GEMINI_API_KEY=[YOUR_KEY].

5. Installer des dépendances

Créez un fichier requirements.txt dans le dossier petpassport/ :

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

6. Authentifier les serveurs MCP

Cette application s'appuie sur des serveurs MCP (Model Context Protocol) pour interagir avec Google Maps et BigQuery. Pour authentifier ces serveurs, vous devez configurer les variables d'environnement et les en-têtes appropriés.

  1. Google Maps MCP : nécessite une clé API Google Maps valide transmise dans l'en-tête X-Goog-Api-Key.
  2. BigQuery MCP : nécessite des identifiants OAuth avec accès au service BigQuery. L'agent utilise le compte de service de calcul par défaut lorsqu'il s'exécute sur Cloud Run, ou vos identifiants locaux lorsqu'il s'exécute en local.

Nous fournissons un script de configuration setup/setup_env.sh dans le dépôt qui vous aide à configurer ces variables dans votre fichier .env.

7. Créer la table BigQuery

Avant que l'agent puisse interroger les données de licence pour chiens, nous devons créer l'ensemble de données et la table dans BigQuery, puis charger les données.

Nous fournissons un script de configuration setup/setup_bigquery.sh qui effectue les étapes suivantes :

  1. Crée un bucket Cloud Storage nommé pet-passport-data-[PROJECT_ID] pour stocker les données brutes.
  2. Télécharge l'ensemble de données public NYC Dog Licensing (CSV).
  3. Importe le fichier CSV dans le bucket.
  4. Crée un ensemble de données BigQuery nommé nyc_dogs.
  5. Charge les données du bucket dans une table nommée licenses dans l'ensemble de données.

Pour exécuter le script de configuration, exécutez la commande suivante dans votre terminal :

bash setup/setup_bigquery.sh

8. Se connecter aux serveurs MCP

Une partie essentielle de cette application consiste à utiliser MCP pour se connecter aux données et aux services. Dans cette section, vous allez configurer les ensembles d'outils MCP pour BigQuery et Google Maps dans un fichier nommé petpassport/tools.py.

Code tools.py complet

Voici l'implémentation complète de tools.py, y compris les ensembles d'outils MCP et les outils personnalisés pour la persistance des images et des données. Nous avons optimisé ce code pour réduire les redondances en déplaçant la résolution du bucket au niveau du module :

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

Explication du code : tools.py

  • get_maps_mcp_toolset et get_bigquery_mcp_toolset configurent les clients MCP avec les points de terminaison et les en-têtes d'authentification appropriés.
  • generate_pet_passport_photo utilise Gemini pour créer une scène et importe le résultat dans Google Cloud Storage, en renvoyant une URL signée au frontend pour survivre aux redémarrages du serveur.

9. Créer l'agent

Maintenant que vos outils sont configurés, il est temps de créer le "cerveau" de l'agent. Vous utiliserez l'Agent Development Kit (ADK) pour créer un agent dans un fichier nommé petpassport/agent.py.

Code agent.py complet

Voici l'implémentation complète de agent.py, où nous définissons l'agent et ses instructions :

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]
)

Explication du code : agent.py

  • Nous importons tools directement (structure aplatie) pour prendre en charge l'environnement de conteneur.
  • L'agent est initialisé avec gemini-2.5-pro.
  • Les instructions définissent une chaîne de pensée stricte en plusieurs étapes (BigQuery d'abord, puis Maps) et interdisent strictement l'hallucination ou le rendu d'itinéraires à pied qui mènent à un encombrement.

10. Exécuter votre application en local

Avant de déployer sur Cloud Run, il est recommandé de tester l'application en local.

  1. Assurez-vous que vous vous trouvez dans le répertoire du projet :
    cd examples/petpassport
    
  2. Démarrez le serveur FastAPI : nous utilisons uvicorn pour exécuter l'application. Le point d'entrée est main.py dans le dossier petpassport.
    uvicorn petpassport.main:app --reload
    
  3. Ouvrez l'interface utilisateur : accédez à http://127.0.0.1:8000/ui/ dans votre navigateur pour interagir avec l'interface Pet Passport.

11. Déployer sur Cloud Run

Maintenant que votre agent est prêt, il est temps de le déployer sur Cloud Run. Nous utilisons directement la commande gcloud standard pour maintenir un contrôle strict sur l'environnement de conteneur.

Depuis le répertoire du projet, exécutez la commande suivante :

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

Configurer les variables d'environnement

Après le déploiement, accédez au service Cloud Run dans la console Google Cloud et définissez les variables d'environnement suivantes sous l'onglet Variables et secrets :

  • MAPS_API_KEY : votre clé API Google Maps.
  • GOOGLE_CLOUD_PROJECT : ID du projet.
  • PROJECT_ID : ID du projet (redondance prise en charge pour les modules hérités).

12. Exemples de requêtes

Essayez d'interagir avec l'agent déployé à l'aide des requêtes suivantes :

  1. Standard : "Je veux me promener avec mon Golden Retriever à New York, près du 10021. Trouve-nous un itinéraire avec un café."
  2. Race différente : "J'ai un bouledogue français et nous sommes dans l'Upper West Side (près du 10024). Suggère une courte promenade qui s'arrête dans un parc pour chiens populaire."
  3. Avec image : (importez une photo de votre chien) "Voici une photo de mon Corgi. Nous sommes près du 10013. Organise-nous une journée parfaite."

13. Libérer de l'espace

Pour éviter que des frais ne vous soient facturés pour les ressources utilisées dans ce tutoriel :

  • Supprimez le service Cloud Run : gcloud run services delete petpassport --region=$REGION
  • Supprimez le bucket GCS : gcloud storage rm -r gs://pet-passport-data-$PROJECT_ID