איך פורסים אפליקציית frontend של gradio שקוראת לסוכן backend ADK, שניהם פועלים ב-Cloud Run

1. מבוא

סקירה כללית

בשיעור הזה תפרסו סוכן ADK ב-Cloud Run כשירות קצה עורפי, ואז תפרסו ממשק משתמש קצה חזיתי של gradio לסוכן ADK כשירות שני של Cloud Run. ב-codelab הזה מוסבר איך לדרוש אימות לשירות הסוכן של ADK ולבצע אליו קריאות מאומתות משירות הקצה העורפי של gradio.

מה תלמדו

  • איך פורסים סוכן ADK ב-Cloud Run
  • איך פורסים אפליקציית Gradio ב-Cloud Run
  • איך מבצעים קריאות מאומתות משירות לשירות ב-Cloud Run

2. הפעלת ממשקי API

קודם מגדירים את הפרויקט ב-Google Cloud.

gcloud config set project <YOUR_PROJECT_ID>

כדי לוודא מהו הפרויקט שלכם ב-Google Cloud, מריצים את הפקודה הבאה:

gcloud config get-value project

כדי לבצע את ה-codelab הזה, צריך להפעיל את ממשקי ה-API הבאים:

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

3. הגדרה ודרישות

בקטע הזה יוצרים כמה חשבונות שירות ומעניקים להם את התפקידים המתאימים ב-IAM. לכל שירות Cloud Run יהיה חשבון שירות משלו.

קודם כל, מגדירים משתני סביבה ל-Codelab הזה, שישמשו לאורך כל ה-Codelab.

export PROJECT_ID=<YOUR_PROJECT_ID>
export REGION=<YOUR_REGION>

export SERVICE_ACCOUNT_ADK="adk-agent-cr"
export SERVICE_ACCOUNT_ADDRESS_ADK=$SERVICE_ACCOUNT_ADK@$PROJECT_ID.iam.gserviceaccount.com

export SERVICE_ACCOUNT_GRADIO="adk-agent-gradio"
export SERVICE_ACCOUNT_ADDRESS_GRADIO=$SERVICE_ACCOUNT_GRADIO@$PROJECT_ID.iam.gserviceaccount.com

export AGENT_APP_NAME="multi_tool_agent"

בשלב הבא, יוצרים את חשבון השירות לסוכן ADK.

gcloud iam service-accounts create $SERVICE_ACCOUNT_ADK \
--display-name="Service account for adk agent on cloud run"

נותנים לחשבון השירות של ADK את התפקיד Vertex AI User

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_ADDRESS_ADK" \
  --role="roles/aiplatform.user"

עכשיו יוצרים את חשבון השירות לחלק הקדמי של Gradio

gcloud iam service-accounts create $SERVICE_ACCOUNT_GRADIO \
  --display-name="Service account for gradio frontend cloud run"

בנוסף, צריך להעניק לחלק הקדמי של Gradio את התפקיד Cloud Run Invoker, שיאפשר לו לקרוא לסוכן ADK שמתארח ב-Cloud Run.

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_ADDRESS_GRADIO" \
  --role="roles/run.invoker"

4. יצירת אפליקציית ADK

בשלב הבא תיצרו את הקוד של אפליקציית המדריך למתחילים של ADK.

הערה: בסוף שיעור ה-Lab, מבנה הקבצים צריך להיראות כך:

- codelab-gradio-adk  <-- you'll deploy the ADK agent from here
  - gradio-frontend
    - app.py
    - requirements.txt
  - multi_tool_agent  <-- you'll deploy the gradio app from here
    - __init__.py
    - agent.py
    - requirements.txt

קודם יוצרים ספרייה ל-codelab כולו

mkdir codelab-gradio-adk
cd codelab-gradio-adk

עכשיו יוצרים ספרייה לשירות הסוכן של ADK.

mkdir multi_tool_agent && cd multi_tool_agent

יוצרים קובץ __init__.py עם התוכן הבא:

from . import agent

יוצרים קובץ requirements.txt:

google-adk

יוצרים קובץ בשם agent.py

import datetime
from zoneinfo import ZoneInfo
from google.adk.agents import Agent

def get_weather(city: str) -> dict:
    """Retrieves the current weather report for a specified city.

    Args:
        city (str): The name of the city for which to retrieve the weather report.

    Returns:
        dict: status and result or error msg.
    """
    if city.lower() == "new york":
        return {
            "status": "success",
            "report": (
                "The weather in New York is sunny with a temperature of 25 degrees"
                " Celsius (77 degrees Fahrenheit)."
            ),
        }
    else:
        return {
            "status": "error",
            "error_message": f"Weather information for '{city}' is not available.",
        }


def get_current_time(city: str) -> dict:
    """Returns the current time in a specified city.

    Args:
        city (str): The name of the city for which to retrieve the current time.

    Returns:
        dict: status and result or error msg.
    """

    if city.lower() == "new york":
        tz_identifier = "America/New_York"
    else:
        return {
            "status": "error",
            "error_message": (
                f"Sorry, I don't have timezone information for {city}."
            ),
        }

    tz = ZoneInfo(tz_identifier)
    now = datetime.datetime.now(tz)
    report = (
        f'The current time in {city} is {now.strftime("%Y-%m-%d %H:%M:%S %Z%z")}'
    )
    return {"status": "success", "report": report}


root_agent = Agent(
    name="weather_time_agent",
    model="gemini-2.5-flash",
    description=(
        "Agent to answer questions about the time and weather in a city."
    ),
    instruction=(
        "You are a helpful agent who can answer user questions about the time and weather in a city."
    ),
    tools=[get_weather, get_current_time],
)

5. פריסת סוכן ה-ADK

בקטע הזה נסביר איך לפרוס את סוכן ה-ADK ב-Cloud Run. לאחר מכן תאמתו שהפריסה פעלה באמצעות ממשק המשתמש האינטרנטי למפתחים שסופק על ידי ADK. לבסוף, תצטרכו לבצע אימות של השיחות לשירות הזה.

עוברים לתיקיית ההורה.

הערה: קוד הסוכן של ADK חייב לכלול את התיקייה multi_tool_agent כתיקיית הבסיס שלו.

cd ..

קודם יוצרים את שירות Cloud Run:

הערה: השדה --with_ui הוא אופציונלי לבדיקה באמצעות ממשק המשתמש למפתחים, כפי שמוצג בשלב הבא:

הערה: הפקודה -- מאפשרת להעביר דגלים של שורת הפקודה לפקודה הבסיסית gcloud run deploy.

הערה: הפקודה uvx --from מריצה פקודה מחבילת google-adk. הפקודה uvx תיצור סביבה וירטואלית זמנית, תתקין בה את google-adk, תריץ את הפקודה שצוינה ואז תבטל את הסביבה.

uvx --from google-adk \
adk deploy cloud_run \
    --project=$PROJECT_ID \
    --region=$REGION \
    --service_name=adk-agent-cr \
    --with_ui \
    ./multi_tool_agent \
    -- \
    --service-account=$SERVICE_ACCOUNT_ADDRESS_ADK \
    --allow-unauthenticated

לאחר מכן, שומרים את כתובת ה-URL כמשתנה סביבה שבו תשתמשו בחלק השני של ה-codelab הזה.

AGENT_SERVICE_URL=$(gcloud run services describe adk-agent-cr --region $REGION --format 'value(status.url)')

עכשיו, ננסה את הסוכן

פותחים את כתובת ה-URL של השירות בדפדפן האינטרנט ושואלים, tell me about the weather in new york. אמורה להתקבל תשובה שדומה לזו: "מזג האוויר בניו יורק הוא שמשי והטמפרטורה היא 25 מעלות צלזיוס (77 מעלות פרנהייט)".

לבסוף, מאבטחים את הנציג

עכשיו נגדיר גישה מאובטחת לסוכן. בקטע הבא תפרסו שירות Cloud Run שמבצע קריאה מאומתת לשירות הקצה העורפי הזה.

gcloud run services remove-iam-policy-binding adk-agent-cr \
  --member="allUsers" \
  --role="roles/run.invoker" \
  --region=$REGION

6. פריסת ממשק קצה של gradio

בשלב הזה יוצרים ממשק קצה של gradio לסוכן ADK

הערה: אפשר להשתמש באפליקציית gradio באותו שירות כמו סוכן ה-ADK. בשיעור הזה מוצגים 2 שירותים נפרדים כדי להראות איך לבצע קריאות מאומתות משירות לשירות ב-Cloud Run.

קודם כל, יוצרים אפליקציה לצד התיקייה multi_tool_agent

mkdir gradio-frontend && cd gradio-frontend

בשלב הבא, יוצרים קובץ requirements.txt שמכיל את הפרטים הבאים

gradio
requests
google-auth

עכשיו יוצרים קובץ app.py

import gradio as gr
import requests
import json
import uuid
import os
import google.auth.transport.requests
import google.oauth2.id_token

# https://weather-time-service2-392295011265.us-west4.run.app
BASE_URL = os.environ.get("AGENT_SERVICE_URL")

# multi_tool_agent
APP_NAME = os.environ.get("AGENT_APP_NAME")

# Generate a unique user ID for each session of the Gradio app
USER_ID = f"gradio-user-{uuid.uuid4()}"

# API Endpoints
CREATE_SESSION_URL = f"{BASE_URL}/apps/{APP_NAME}/users/{USER_ID}/sessions"
RUN_SSE_URL = f"{BASE_URL}/run_sse"

def get_id_token():
    """Get an ID token to authenticate with the other Cloud Run service."""
    audience = BASE_URL
    request = google.auth.transport.requests.Request()
    id_token = google.oauth2.id_token.fetch_id_token(request, audience)
    return id_token

def create_session() -> str | None:
    """Creates a new session and returns the session ID."""
    try:
        id_token = get_id_token()
        headers = {"Authorization": f"Bearer {id_token}"}
        response = requests.post(CREATE_SESSION_URL, headers=headers)
        response.raise_for_status()
        return response.json().get("id")
    except Exception as e:
        print(f"Error creating session: {e}")
        return None

def query_agent(prompt: str):
    """Sends a prompt to the agent and returns the streamed response."""
    session_id = create_session()
    if not session_id:
        return "Error: Could not create a session."

    id_token = get_id_token()
    headers = {
        "Content-Type": "application/json",
        "Accept": "text/event-stream",
        "Authorization": f"Bearer {id_token}",
    }
    payload = {
        "app_name": APP_NAME,
        "user_id": USER_ID,
        "session_id": session_id,
        "new_message": {"role": "user", "parts": [{"text": prompt}]},
        "streaming": True
    }

    full_response = ""
    try:
        with requests.post(RUN_SSE_URL, headers=headers, json=payload, stream=True) as response:
            response.raise_for_status()
            for chunk in response.iter_lines():
                if chunk and chunk.decode('utf-8').startswith('data:'):
                    json_data = chunk.decode('utf-8')[len('data:'):].strip()
                    try:
                        data = json.loads(json_data)
                        text = data.get("content", {}).get("parts", [{}])[0].get("text", "")
                        if text:
                            full_response = text
                    except json.JSONDecodeError:
                        pass # Ignore chunks that are not valid JSON
        return full_response
    except requests.exceptions.RequestException as e:
        return f"An error occurred: {e}"

iface = gr.Interface(
    fn=query_agent,
    inputs=gr.Textbox(lines=2, placeholder="e.g., What's the weather in new york?"),
    outputs="text",
    title="Weather and Time Agent",
    description="Ask a question about the weather or time in a specific location.",
)

if __name__ == "__main__":
    iface.launch()

7. פריסה ובדיקה של אפליקציית gradio

בשלב הזה תפרסו את אפליקציית הקצה העורפי של gradio ב-Cloud Run.

מוודאים שאתם בספרייה של אפליקציית gradio.

pwd

אמור להופיע codelab-gradio-adk/gradio-frontend

עכשיו פורסים את אפליקציית gradio.

הערה: למרות ששירות ה-frontend של gradio הוא אתר שזמין לכולם, שירות ה-backend דורש אימות. כדי להמחיש למה כדאי לעשות את זה, אפשר להוסיף אימות משתמשים (למשל Firebase Auth) לשירות הקצה הזה, ואז לאפשר רק למשתמשים שמחוברים לבצע קריאות לשירות העורפי.

gcloud run deploy my-adk-gradio-frontend \
--source . \
--region $REGION \
--allow-unauthenticated \
--set-env-vars AGENT_SERVICE_URL=$AGENT_SERVICE_URL,AGENT_APP_NAME=$AGENT_APP_NAME \
--service-account=$SERVICE_ACCOUNT_ADDRESS_GRADIO

אחרי הפריסה, שואלים את השאלה what's the weather in new york? ואמורה להתקבל תשובה שדומה ל-The weather in New York is sunny with a temperature of 25 degrees Celsius (77 degrees Fahrenheit).

8. מעולה!

כל הכבוד, סיימתם את ה-Codelab!

מומלץ לעיין בתיעוד בנושא אירוח של אפליקציות וסוכנים מבוססי-AI.

מה נכלל

  • איך פורסים סוכן ADK ב-Cloud Run
  • איך פורסים אפליקציית Gradio ב-Cloud Run
  • איך מבצעים קריאות מאומתות משירות לשירות ב-Cloud Run

9. הסרת המשאבים

כדי להימנע מחיובים לא מכוונים, למשל אם שירותי Cloud Run מופעלים בטעות יותר פעמים מההקצאה החודשית של הפעלות Cloud Run בתוכנית החינמית, אפשר למחוק את שירות Cloud Run שיצרתם בשלב 6.

כדי למחוק את שירותי Cloud Run, נכנסים אל Cloud Run Cloud Console בכתובת https://console.cloud.google.com/run ומוחקים את השירותים my-adk-gradio-frontend ו-adk-agent-cr.

כדי למחוק את הפרויקט כולו, עוברים אל Manage Resources (ניהול משאבים), בוחרים את הפרויקט שיצרתם בשלב 2 ולוחצים על Delete (מחיקה). אם מוחקים את הפרויקט, צריך לשנות את הפרויקט ב-Cloud SDK. כדי לראות את רשימת כל הפרויקטים הזמינים, מריצים את הפקודה gcloud projects list.