פיתוח ופריסה של סוכן לדרכון חיות מחמד ב-Cloud Run

1. סקירה כללית

בשיעור ה-Codelab הזה תלמדו איך לפרוס את האפליקציה Pet Passport, סוכן AI שמשתמש ב-Model Context Protocol‏ (MCP) כדי לשלב ניתוח נתונים ושירותי מיקום.

האפליקציה עוזרת למשתמשים לתכנן יום מושלם עם הכלב שלהם על סמך פופולריות הגזע בניו יורק. הסוכן משתמש בשרשרת של נימוקים מסוג 'מאקרו למיקרו':

  1. גילוי אסטרטגי (BigQuery): זיהוי מיקוד לפי מיקוד בניו יורק עם האוכלוסייה הגדולה ביותר לגזע ספציפי.
  2. ביצוע מקומי (מפות): המערכת משתמשת במיקוד הזה כהטיה למיקום כדי למצוא "בתי קפה שמקבלים בהם חיות מחמד" ו "פארקים לכלבים".
  3. יצירת מסלול טיול: שילוב הנתונים ליצירת מסלול טיול עם קישורים ותמונות שאפשר ללחוץ עליהם.

הסוכן מבוסס על מסגרת google-adk ומופעל על ידי Gemini.

הערה: הקוד המלא של הפרויקט, כולל ממשק המשתמש של הקצה הקדמי, זמין ב-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 API

מפעילים את ממשקי ה-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

כדי להשתמש בשירותים של מפות Google ו-Gemini, צריך לקבל מפתחות API ולאחסן אותם בקובץ .env בספריית הבסיס של הפרויקט.

1. מפתח Google Maps API

  1. נכנסים אל מסוף Google Cloud.
  2. עוברים אל APIs & Services > Credentials.
  3. לוחצים על Create Credentials (יצירת פרטי כניסה) > API key (מפתח API).
  4. מעתיקים את המפתח שנוצר ומוסיפים אותו לקובץ .env בתור MAPS_API_KEY=[YOUR_KEY].
  5. (מומלץ) מגבילים את המפתח כך שרק ממשקי ה-API של מפות Google שבהם משתמש שרת ה-MCP יוכלו להשתמש בו.

2. מפתח Gemini API‏ (AI Studio)

  1. עוברים אל Google AI Studio.
  2. לוחצים על Get API key או עוברים לקטע API keys.
  3. לוחצים על יצירת מפתח API.
  4. מעתיקים את המפתח ומוסיפים אותו לקובץ .env בתור GEMINI_API_KEY=[YOUR_KEY].

5. התקנת יחסי תלות

יוצרים קובץ requirements.txt בתיקייה petpassport/:

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

6. אימות שרתי MCP

האפליקציה הזו מסתמכת על שרתים של Model Context Protocol‏ (MCP) כדי ליצור אינטראקציה עם מפות Google ו-BigQuery. כדי לאמת את השרתים האלה, צריך להגדיר את משתני הסביבה והכותרות המתאימים.

  1. Google Maps MCP: נדרש מפתח API תקין של Maps שמועבר בכותרת X-Goog-Api-Key.
  2. BigQuery MCP: נדרשים פרטי כניסה של OAuth עם גישה לשירות BigQuery. הסוכן משתמש בחשבון השירות שמוגדר כברירת מחדל בשירותי המחשוב כשמריצים אותו ב-Cloud Run, או בפרטי הכניסה המקומיים כשמריצים אותו באופן מקומי.

אנחנו מספקים סקריפט הגדרה setup/setup_env.sh במאגר שעוזר להגדיר את המשתנים האלה בקובץ .env.

7. יצירת הטבלה ב-BigQuery

כדי שהסוכן יוכל להריץ שאילתות על נתוני רישיונות לכלבים, צריך ליצור את מערך הנתונים והטבלה ב-BigQuery ולטעון את הנתונים.

אנחנו מספקים סקריפט הגדרה setup/setup_bigquery.sh שמבצע את הפעולות הבאות:

  1. יוצר קטגוריה של Cloud Storage בשם pet-passport-data-[PROJECT_ID] לאחסון הנתונים הגולמיים.
  2. הורדה של קבוצת הנתונים הציבורית של רישיונות לכלבים בניו יורק (CSV).
  3. הקובץ CSV יועלה לקטגוריה.
  4. יוצר מערך נתונים ב-BigQuery בשם nyc_dogs.
  5. הנתונים נטענים מהקטגוריה לטבלה בשם licenses במערך הנתונים.

כדי להריץ את סקריפט ההגדרה, מריצים את הפקודה הבאה במסוף:

bash setup/setup_bigquery.sh

8. התחברות לשרתי MCP

חלק מרכזי באפליקציה הזו הוא השימוש ב-MCP כדי להתחבר לנתונים ולשירותים. בקטע הזה תגדירו את ערכות הכלים של MCP ל-BigQuery ולמפות Google בקובץ שנקרא petpassport/tools.py.

השלמת הקוד tools.py

זו ההטמעה המלאה של tools.py, כולל ערכות כלי MCP וכלים מותאמים אישית לשימור תמונות ונתונים. ביצענו אופטימיזציה של הקוד הזה כדי לצמצם את היתירויות, והעברנו את ההחלטה לגבי הקטגוריה לרמת המודול:

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_toolset ו-get_bigquery_mcp_toolset מגדירים את לקוחות ה-MCP עם נקודות קצה נכונות וכותרות אימות.
  • generate_pet_passport_photo משתמש ב-Gemini כדי ליצור סצנה ומעלה את התוצאה ל-Google Cloud Storage, ומחזיר כתובת URL חתומה לקצה הקדמי כדי שהיא תישמר גם אחרי הפעלה מחדש של השרת.

9. יצירת הסוכן

אחרי שמגדירים את הכלים, הגיע הזמן לבנות את ה"מוח" של הסוכן. תשתמשו בערכה לפיתוח סוכנים (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, אחר כך מפות Google) ואוסרות באופן מוחלט על הזיות או על הצגת מסלולי הליכה שמובילים לעומס.

10. הרצת האפליקציה באופן מקומי

לפני שפורסים את האפליקציה ב-Cloud Run, מומלץ לבדוק אותה באופן מקומי.

  1. מוודאים שאתם נמצאים בספריית הפרויקט:
    cd examples/petpassport
    
  2. מפעילים את שרת FastAPI: אנחנו משתמשים ב-uvicorn כדי להריץ את האפליקציה. נקודת הכניסה היא main.py בתיקייה petpassport.
    uvicorn petpassport.main:app --reload
    
  3. פתיחת ממשק המשתמש: עוברים אל 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

הגדרת משתני סביבה

אחרי הפריסה, עוברים לשירות Cloud Run במסוף Google Cloud ומגדירים את משתני הסביבה הבאים בכרטיסייה Variables & Secrets (משתנים וסודות):

  • MAPS_API_KEY: מפתח Google Maps API.
  • GOOGLE_CLOUD_PROJECT: מזהה הפרויקט.
  • PROJECT_ID: מזהה הפרויקט (יש תמיכה בעודפות במודולים מדור קודם).

12. הנחיות לדוגמה

אפשר לנסות לקיים אינטראקציה עם הסוכן שפרסתם באמצעות ההנחיות הבאות:

  1. רגיל: "I want to go for a walk with my Golden Retriever in NYC near 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