יצירת אפליקציית AI אקטיבי שאיתו הלקוחות יכולים לקיים אינטראקציה באמצעות טלגרם

1. מבוא

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

מה תפַתחו

שילוב בין אפליקציה מלאה מבוססת-ADK שמופעלת על ידי Gemini ועוזרת לסועדים לעיין בתפריט של מסעדה ולהזמין מקומות, לבין אפליקציית הצ'אט Telegram. אתם יכולים לקיים אינטראקציה עם בוט Telegram ולבקש תיאורים בשפה טבעית כמו "אני רוצה משהו חריף וצמחוני". הבוט יתחבר לסוכן ADK שקורא מתוך מסד נתונים של Cloud SQL PostgreSQL וכותב בו, והכול דרך MCP Toolbox for Databases, שמטפל בכל הגישה למסד הנתונים – כולל יצירה אוטומטית של הטמעה לחיפוש וקטורי. בינתיים, המשתמש יוכל לראות שהבוט מאשר את קבלת ההודעה ומקליד ... typing לתשובה בזמן ההמתנה לחזרה מסוכן ADK.

c1d28343ed68358a.png

מה תלמדו

  • פריסת אפליקציה פעילה מבוססת-ADK בשם 'מלצר וירטואלי במסעדה' שמבוססת על Gemini
  • הגדרת בוט צ'אט של Telegram באמצעות BotFather
  • כתיבת אפליקציות Python להאזנה ל-webhook של הבוט
  • שליחת פעולת צ'אט כדי לספק התראה ... typing בטלגרם על הודעת משתמש, וביצוע דגימה כדי לשלוח ... typing מעת לעת בזמן ההמתנה לתשובה האמיתית
  • התקשרות לנקודת הקצה של Cloud Run כדי לעבד את הפנייה של המשתמשRestaurant Concierge
  • טיפול בחזרה מסוכן ADK ושליחת הודעה ל-Telegram וסגירת המאגר
  • פריסת אפליקציית Python ב-Cloud Run
  • איך מתקשרים עם בוט טלגרם

דרישות מוקדמות

2. הגדרת סביבה – המשך מה-codelab הקודם

התיאורים שאנחנו מספקים ב-Codelab הזה הם למעשה המשך של ה-Codelab הזה בנושא RAG אג'נטי עם ADK,‏ MCP Toolbox ו-Cloud SQL או של ה-Codelab הזה בנושא סוכנים בהיקף נרחב: ארכיטקטורה מרובת סוכנים עם פרוטוקול A2A ב-Agent Runtime ושילוב ADK. אפשר להמשיך לעבוד מהנקודה שבה הפסקתם ב-codelab הקודם

אפשר להתחיל לבנות בספריית העבודה של ה-codelab הקודם ( ספריית העבודה צריכה להיות build-agent-adk-toolbox-cloudsql או adk-a2a-agent-runtime-starter). כדי למנוע בלבול, נשנה את שם הספרייה לשם הספרייה שבה נשתמש כשהתחלנו מחדש.

אם אתם ממשיכים משיעור ה-Lab‏ Agentic RAG with ADK, MCP Toolbox, and Cloud SQL :

mv ~/build-agent-adk-toolbox-cloudsql ~/build-agent-adk-telegram

אחרת, אם אתם ממשיכים משיעור ה-Lab‏ Agents at Scale: Multi-Agent Architecture with A2A Protocol on Agent Runtime and ADK Integration

mv ~/adk-a2a-agent-runtime-starter ~/build-agent-adk-telegram

לאחר מכן, משנים את ספריית העבודה לספרייה הזו

cloudshell workspace ~/build-agent-adk-telegram && cd ~/build-agent-adk-telegram
source .env

לאחר מכן, מוודאים ש-restaurant-agent כבר נפרס ושיש לו כתובת URL ציבורית שאפשר לגשת אליה

AGENT_URL=$(gcloud run services describe restaurant-agent \
    --region="$REGION" \
    --format='value(status.url)')

echo "      ✓ Agent service deployed"
echo "      Agent URL: $AGENT_URL"
echo ""

אם יש לכם גישה לכתובת ה-URL, אתם יכולים לעבור לקטע הבא: Create Telegram Bot

3. הגדרת סביבה – התחלה חדשה עם מאגר המתחילים

בשלב הזה מכינים את סביבת Cloud Shell, מגדירים את הפרויקט ב-Google Cloud ומשכפלים את מאגר התבניות.

פתיחת Cloud Shell

פותחים את Cloud Shell בדפדפן. ‫Cloud Shell מספקת סביבה שהוגדרה מראש עם כל הכלים שדרושים ל-Codelab הזה. כשמופיעה בקשה, לוחצים על Authorize.

אחר כך לוחצים על View (תצוגה) -> Terminal (טרמינל) כדי לפתוח את הטרמינל.הממשק אמור להיראות בערך כך:

86307fac5da2f077.png

זה יהיה הממשק הראשי שלנו, סביבת הפיתוח המשולבת (IDE) בחלק העליון והטרמינל בחלק התחתון

הגדרת ספריית העבודה

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

rm -rf ~/build-agent-adk-telegram
git clone https://github.com/alphinside/adk-a2a-agent-runtime-starter.git build-agent-adk-telegram
cloudshell workspace ~/build-agent-adk-telegram && cd ~/build-agent-adk-telegram

יוצרים את קובץ .env מהתבנית שסופקה:

cp .env.example .env

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

curl -sL https://raw.githubusercontent.com/alphinside/cloud-trial-project-setup/main/setup_verify_trial_project.sh -o setup_verify_trial_project.sh

מריצים את הסקריפט. הוא מאמת את החשבון לחיוב של תקופת הניסיון, יוצר פרויקט חדש (או מאמת פרויקט קיים), שומר את מזהה הפרויקט בקובץ .env בספרייה הנוכחית ומגדיר את הפרויקט הפעיל ב-gcloud.

bash setup_verify_trial_project.sh && source .env

הסקריפט:

  1. אימות שיש לכם חשבון לחיוב עם תקופת ניסיון פעילה
  2. בודקים אם יש פרויקט קיים ב-.env (אם יש)
  3. יוצרים פרויקט חדש או משתמשים בפרויקט קיים
  4. קישור החשבון לחיוב בתקופת הניסיון לפרויקט
  5. שומרים את מזהה הפרויקט ב-.env
  6. הגדרת הפרויקט כפרויקט פעיל ב-gcloud

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

5c515e235ee1179f.png

הגדרה של תשתית Starter

קודם כל, נצטרך להתקין יחסי תלות של Python באמצעות uv, שהוא מנהל מהיר של חבילות ופרויקטים של Python שנכתב ב-Rust ( מסמכי uv). בשיעור Codelab הזה אנחנו משתמשים בו כדי לשמור על מהירות ופשטות בתחזוקת פרויקט Python.

uv sync

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

bash scripts/full_setup.sh > logs/full_setup.log 2>&1 &

כתוצאה מכך, יקרו הדברים הבאים:

  • יצירת מכונה של Cloud SQL ואכלוס מסד הנתונים (שלב 1)
  • יצירת הגדרת סביבת סוכן והפעלת שירות Toolbox מקומי (שלב 2)
  • פריסת שירותי Toolbox וסוכן ב-Cloud Run (שלב 3)

אחרי שהפריסה הזו תסתיים, תוכלו לגשת לממשק המשתמש של ADK Dev בכתובת ה-URL של Cloud Run

source .env
AGENT_URL=$(gcloud run services describe restaurant-agent \
    --region="$REGION" \
    --format='value(status.url)')

echo "      ✓ Agent service deployed"
echo "      Agent URL: $AGENT_URL"
echo ""

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

What Italian dishes do you have?

או,

I want something spicy and creamy

עכשיו, הפעולה הבאה היא איך אפשר לעבור מממשק פיתוח אתרים בלבד לערוץ הודעות בטלגרם

4. יצירת בוט לטלגרם

‫Telegram היא פלטפורמת הודעות חינמית מוכרת, שמשמשת בעיקר ליצירת אינטראקציה עם קהילות. אחת הסיבות לכך היא שהיא מציעה דרכים רבות לשילוב קל, ולכן אנשים יכולים ליצור בקלות בוט משלהם עם מגוון רחב של פונקציות שונות.

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

שימוש ב-BotFather ליצירת בוט משלכם

פותחים דפדפן אינטרנט ונכנסים לכתובת https://telegram.me/BotFather כדי להתחיל ליצור בוט משלכם לטלגרם.

1b817e758c699a79.png

איך מתחילים אינטראקציה עם BotFather

ad3daa08e73502db.png

שליחת הפקודה ‎ /start

כדי להתחיל להשתמש ב-BotFather וליצור את הבוט הראשון, צריך להפעיל את ההודעה /start אל BotFather. לאחר מכן, הוא ישתף את כל הפקודות שזמינות לכם כדי להמשיך את האינטראקציה איתו.

/start

איך מתחילים ליצור בוט באמצעות הפקודה /newbot

כדי ליצור בוט חדש, שולחים את הפקודה /newbot אל BotFather. תתבקשו לתת שם לבוט, ואז תתבקשו לתת לבוט username, שתמיד צריך להסתיים ב-bot . לדוגמה, TetrisBot או tetris_bot. הערך הזה צריך להיות ייחודי.

1f6a74f494d48986.png

אחרי שהבוט ייווצר בהצלחה, תקבלו את ההודעה הבאה מ-BotFather

Done! Congratulations on your new bot. You will find it at t.me/AdkTelegramTest_bot. You can now add a description, about section and profile picture for your bot, see /help for a list of commands. By the way, when you've finished creating your cool bot, ping our Bot Support if you want a better username for it. Just make sure the bot is fully operational before you do this.

Use this token to access the HTTP API:
<YOUR_TELEGRAM_API_KEY>
Keep your token secure and store it safely, it can be used by anyone to control your bot.

For a description of the Bot API, see this page: https://core.telegram.org/bots/api

חשוב לשים לב ל-YOUR_TELEGRAM_API_KEY כי נשתמש בו בקטע הבא.

5. פיתוח אפליקציית Telegram Webhook

נתחיל להכין את ספריית העבודה כדי לפתח את אפליקציית ה-webhook של טלגרם

mkdir ~/build-agent-adk-telegram/telegram-integration
cd ~/build-agent-adk-telegram

הוספת יחסי תלות נדרשים

כדי לספק תלות מתאימה לסקריפט של מאזין ה-webhook של Telegram, יוצרים סקריפט requirements.txt עם התוכן הבא.

cloudshell edit ./telegram-integration/requirements.txt

לאחר מכן מוסיפים את יחסי התלות הבאים

python-telegram-bot[webhooks]
httpx

יצירת סקריפט עבור Telegram Webhook Listener

אחרי שמתקינים את יחסי התלות. עכשיו אפשר ליצור סקריפט Python main.py לאפליקציית השילוב

cloudshell edit ~/build-agent-adk-telegram/telegram-integration/main.py

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

# ./telegram-integration/main.py

import asyncio
import os
import sys
from telegram import Update
from telegram.ext import Application, CommandHandler, MessageHandler, filters, CallbackContext
from telegram.constants import ChatAction
import httpx

# Read token from environment variable
TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN")
ADK_SERVER_URL = os.environ.get("ADK_SERVER_URL", "http://localhost:8000")
ADK_APP_NAME = os.environ.get("ADK_APP_NAME", "restaurant_agent")

# Parse base URL out of ADK_SERVER_URL
BASE_URL = ADK_SERVER_URL.rstrip('/')
if BASE_URL.endswith('/run'):
    BASE_URL = BASE_URL[:-4]
elif BASE_URL.endswith('/query'):
    BASE_URL = BASE_URL[:-6]

if not TOKEN:
    print("Error: TELEGRAM_BOT_TOKEN environment variable not set.")
    print("Please set it before running the application.")
    sys.exit(1)

async def start(update: Update, context: CallbackContext) -> None:
    """Send a message when the command /start is issued."""
    await update.message.reply_text('Hi! I am your ADK Integration Bot. Send me a message and I will forward it to the ADK server.')

async def send_typing_loop(chat_id: int, bot, stop_event: asyncio.Event):
    """Send typing action periodically until the stop event is set."""
    while not stop_event.is_set():
        try:
            await bot.send_chat_action(chat_id=chat_id, action=ChatAction.TYPING)
            # The research suggested repeating every 4 seconds
            await asyncio.sleep(4)
        except Exception as e:
            print(f"Error sending chat action: {e}")
            await asyncio.sleep(1) # Wait a bit before retrying if error

async def handle_message(update: Update, context: CallbackContext) -> None:
    """Handle incoming user messages."""
    user_message = update.message.text
    chat_id = update.message.chat_id
    raw_user_id = str(update.message.from_user.id)
    
    # Derive unique user_id and session_id for this user
    user_id = f"tg_{raw_user_id}"
    session_id = f"tg_sess_{raw_user_id}"

    print(f"Received message from {user_id}: {user_message}")

    # Create a stop event for the typing loop
    stop_event = asyncio.Event()
    
    # Start the typing loop as a background task
    typing_task = asyncio.create_task(send_typing_loop(chat_id, context.bot, stop_event))

    try:
        async with httpx.AsyncClient() as client:
            # 1. Check if the session exists
            session_url = f"{BASE_URL}/apps/{ADK_APP_NAME}/users/{user_id}/sessions/{session_id}"
            session_check = await client.get(session_url, timeout=10.0)
            
            if session_check.status_code == 404:
                # 2. If session doesn't exist, create it
                print(f"Session {session_id} not found. Creating session...")
                session_create = await client.post(session_url, json={}, timeout=10.0)
                if session_create.status_code != 200:
                    raise Exception(f"Failed to create session: {session_create.status_code} {session_create.text}")
            elif session_check.status_code != 200:
                raise Exception(f"Error checking session: {session_check.status_code} {session_check.text}")
            
            # 3. Run the ADK agent
            run_url = f"{BASE_URL}/run"
            payload = {
                "appName": ADK_APP_NAME,
                "userId": user_id,
                "sessionId": session_id,
                "newMessage": {
                    "role": "user",
                    "parts": [{"text": user_message}]
                }
            }
            response = await client.post(run_url, json=payload, timeout=60.0)
            
        if response.status_code == 200:
            events = response.json()
            if isinstance(events, list) and len(events) > 0:
                # The last event contains the final text response
                last_event = events[-1]
                content = last_event.get("content", {})
                parts = content.get("parts", [])
                if parts and "text" in parts[0]:
                    reply_text = parts[0]["text"]
                else:
                    reply_text = "ADK agent returned an empty or non-text response."
            else:
                reply_text = "No events returned from ADK agent."
        else:
            reply_text = f"Error communicating with ADK server (Status: {response.status_code})."
            
    except Exception as e:
        reply_text = f"Failed to connect to ADK server: {e}"
    finally:
        # Stop the typing loop
        stop_event.set()
        await typing_task

    # Send the final response back to the user
    await update.message.reply_text(reply_text)

def main() -> None:
    """Start the bot."""
    # Create the Application and pass it your bot's token.
    application = Application.builder().token(TOKEN).build()

    # on different commands - answer in Telegram
    application.add_handler(CommandHandler("start", start))

    # on non command i.e message - echo the message on Telegram
    application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message))

    # Check if running in webhook mode (e.g., on Cloud Run)
    port = os.environ.get("PORT")
    service_url = os.environ.get("SERVICE_URL")

    if port and service_url:
        if not service_url.startswith("http"):
            service_url = f"https://{service_url}"
        
        print(f"Starting bot in WEBHOOK mode on port {port} with url {service_url}")
        
        application.run_webhook(
            listen="0.0.0.0",
            port=int(port),
            url_path=TOKEN,
            webhook_url=f"{service_url}/{TOKEN}",
            allowed_updates=Update.ALL_TYPES
        )
    else:
        print("Starting bot in POLLING mode")
        # Run the bot until the user presses Ctrl-C
        application.run_polling(allowed_updates=Update.ALL_TYPES)


if __name__ == "__main__":
    main()

הסבר על קוד השילוב של בוט Telegram

23b346f5ceb4712a.png

כשמשתמש שולח הודעה, צינור הנתונים הבא מופעל בקטע handle_message()

שלב 1: זיהוי וקביעת סשן

הבוט ממפה את מזהה המשתמש ב-Telegram למזהים ייחודיים של ADK כדי לשמור על סשנים נפרדים של משתמשים:

user_id = f"tg_{raw_user_id}"
session_id = f"tg_sess_{raw_user_id}"

שלב 2: סטטוס 'הקלדה' אסינכרוני (שורות 53 עד 58)

כדי להבטיח חוויית משתמש רספונסיבית מאוד בזמן שהסוכן של ADK מעבד את ההנחיה (תהליך שיכול להימשך כמה שניות), הבוט מתחיל לולאה אסינכרונית ברקע:

  • המופע asyncio.Event נוצר בתור stop_event.
  • asyncio.create_task יוצרת את send_typing_loop(...) ברקע.
  • הלולאה שולחת פעולה של ChatAction.TYPING ל-Telegram כל 4 שניות עד שהמשתנה stop_event מוגדר.

שלב 3: אימות ויצירה של סשן ADK (שורות 61-72)

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

  1. שליחת בקשת GET אל /apps/{appName}/users/{userId}/sessions/{sessionId}.
  2. אם התגובה היא 404 Not Found, המערכת יוצרת את הסשן באמצעות בקשת POST לאותה כתובת URL עם גוף JSON ריק.
  3. אם מוחזר סטטוס ששונה מ-200 או מ-404, נוצר חריג.

שלב 4: שליחת בקשה לסוכן (שורות 74 עד 85)

מטען הייעודי (payload) של ההודעה מועבר לנקודת הקצה של ה-ADK‏ /run:

  • נקודת קצה: POST /run
  • הזמן הקצוב לתפוגת הבקשה מוגדר ל-60.0 שניות כדי לאפשר חשיבה רציונלית מורכבת או זמן טעינה במעלה הזרם.
  • מבנה המטען הייעודי (payload):
{
  "appName": "restaurant_agent",
  "userId": "tg_<user_id>",
  "sessionId": "tg_sess_<user_id>",
  "newMessage": {
    "role": "user",
    "parts": [{"text": "<user_message>"}]
  }
}

שלב 5: ניתוח התגובה (שורות 87 עד 101)

שרת ה-ADK מחזיר רשימה של אירועי הודעות. הבוט בודק את המערך שמוחזר:

  • הפונקציה מאחזרת את האירוע האחרון ברשימה (events[-1]).
  • המעבר לתוכן הטקסט מתבצע באמצעות event["content"]["parts"][0]["text"].
  • אם לא מוחזרים אירועים או אם חסרה מבנה טקסט, מוצג טקסט placeholder תיאורי.

שלב 6: ניתוח ושיגור תגובה (שורות 103 עד 111)

  • בבלוק finally, הערך stop_event מוגדר, והלולאה של פעולת ההקלדה נעצרת.
  • הבוט ממתין להשלמת typing_task כדי לוודא שהמשאבים נקיים.
  • לבסוף, הבוט משיב לשיחה ב-Telegram עם הטקסט של התשובה המנותחת.

6. פריסת אפליקציית ה-Webhook של Telegram ב-Cloud Run

בשלב הבא נציב את Telegram Webhook Listener ב-Cloud Run, כדי שהבוט יוכל לתקשר איתו

יצירת קובץ Dockerfile

קודם צריך ליצור את קובץ ה-Dockerfile.

cloudshell edit ~/build-agent-adk-telegram/telegram-integration/Dockerfile

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

# Use an official Python runtime as a parent image
FROM python:3.11-slim

# Prevent Python from writing pyc files to disc and buffering stdout/stderr
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

# Set the working directory in the container
WORKDIR /app

# Install system dependencies if needed
RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    && rm -rf /var/lib/apt/lists/*

# Copy the dependencies file to the working directory
COPY requirements.txt .

# Install any needed packages specified in requirements.txt
RUN pip install --no-cache-dir -r requirements.txt

# Copy the rest of the application code
COPY main.py .

# Expose the port that Cloud Run will provide via environment variable
EXPOSE 8080

# Run main.py when the container launches
CMD ["python", "main.py"]

השירות מועבר לקונטיינר באמצעות python:3.11-slim כדי לשמור על גודל קטן של קובץ האימג':

  • מתקין יחסי תלות מ-requirements.txt (python-telegram-bot[webhooks] ו-httpx).
  • חושף יציאה רגילה 8080.
  • השקות python main.py.

הכנת משתני הסביבה

אחרי זה, נבדוק שוב אם הסוכן שלנו נפרס בהצלחה

AGENT_URL=$(gcloud run services describe restaurant-agent \
    --region="$REGION" \
    --format='value(status.url)')

echo "      ✓ Agent service deployed"
echo "      Agent URL: $AGENT_URL"
echo ""

בשלב הבא, נזין את ה-TELEGRAM_BOT_TOKEN שקיבלנו קודם ל-.env

echo "TELEGRAM_BOT_TOKEN=YOUR_TELEGRAM_API_KEY" >> .env

לאחר מכן, נמלא את נתוני .env בערכים אחרים שדרושים לנו.

echo "ADK_SERVER_URL=$AGENT_URL" >> .env
echo "ADK_APP_NAME=restaurant_agent" >> .env
echo "SERVICE_NAME=telegram-integration" >> .env
source .env

יצירת סקריפט פריסה

ניצור סקריפט פריסה שיבצע בדיקות מלאות ויפרוס את האפליקציה ב-Cloud Run

cloudshell edit ~/build-agent-adk-telegram/telegram-integration/deploy.sh

מעתיקים את הקוד הבא לקובץ

#!/usr/bin/env bash
# ./telegram-integration/deploy.sh

# Exit immediately if a command exits with a non-zero status
set -euo pipefail

# Color codes for neat terminal output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0;37m' # No Color
# Load environment variables from .env if it exists
if [ -f .env ]; then
    echo -e "${GREEN}✔ Loading environment variables from .env...${NC}"
    export $(grep -v '^#' .env | xargs)
fi

echo -e "${BLUE}====================================================${NC}"
echo -e "${BLUE}   Google Cloud Run Deployment: Telegram Bot        ${NC}"
echo -e "${BLUE}====================================================${NC}"

# 1. Check for gcloud CLI
if ! command -v gcloud &> /dev/null; then
    echo -e "${RED}Error: 'gcloud' CLI is not installed.${NC}"
    echo "Please install the Google Cloud SDK and try again."
    echo "See: https://cloud.google.com/sdk/docs/install"
    exit 1
fi

# 2. Check active gcloud account/auth
ACTIVE_ACCOUNT=$(gcloud auth list --filter=status:ACTIVE --format="value(account)" 2>/dev/null || true)
if [ -z "$ACTIVE_ACCOUNT" ]; then
    echo -e "${RED}Error: No active Google Cloud account found.${NC}"
    echo "Please run: gcloud auth login"
    exit 1
fi

# 3. Detect / Prompt for GCP Project
DEFAULT_PROJECT=${GCP_PROJECT_ID:-$(gcloud config get-value project 2>/dev/null || true)}
if [ -n "${DEFAULT_PROJECT}" ]; then
    echo -e "${GREEN}✔ Using GCP Project: $DEFAULT_PROJECT${NC}"
    GCP_PROJECT="$DEFAULT_PROJECT"
else
    echo -n "Enter GCP Project ID: "
    read -r GCP_PROJECT
fi

if [ -z "$GCP_PROJECT" ]; then
    echo -e "${RED}Error: GCP Project ID is required.${NC}"
    exit 1
fi

# Set active project
gcloud config set project "$GCP_PROJECT" &> /dev/null

# 4. Configure Service Parameters
DEFAULT_SERVICE=${SERVICE_NAME:-"telegram-integration"}
if [ -n "${SERVICE_NAME:-}" ]; then
    echo -e "${GREEN}✔ Using Cloud Run Service Name: $SERVICE_NAME${NC}"
else
    echo -n "Enter Cloud Run Service Name [Default: $DEFAULT_SERVICE]: "
    read -r SERVICE_NAME
    SERVICE_NAME=${SERVICE_NAME:-$DEFAULT_SERVICE}
fi

DEFAULT_REGION=${REGION:-"us-central1"}
if [ -n "${REGION:-}" ]; then
    echo -e "${GREEN}✔ Using Cloud Run Region: $REGION${NC}"
else
    echo -n "Enter Cloud Run Region [Default: $DEFAULT_REGION]: "
    read -r REGION
    REGION=${REGION:-$DEFAULT_REGION}
fi

DEFAULT_ADK_APP=${ADK_APP_NAME:-"restaurant_agent"}
if [ -n "${ADK_APP_NAME:-}" ]; then
    echo -e "${GREEN}✔ Using ADK App Name: $ADK_APP_NAME${NC}"
    ADK_APP="$ADK_APP_NAME"
else
    echo -n "Enter ADK App Name [Default: $DEFAULT_ADK_APP]: "
    read -r ADK_APP
    ADK_APP=${ADK_APP:-$DEFAULT_ADK_APP}
fi

# 5. Retrieve/Prompt for Telegram Bot Token
if [ -n "${TELEGRAM_BOT_TOKEN:-}" ]; then
    echo -e "${GREEN}✔ Found TELEGRAM_BOT_TOKEN in environment.${NC}"
    BOT_TOKEN="$TELEGRAM_BOT_TOKEN"
else
    echo -e "${YELLOW}TELEGRAM_BOT_TOKEN is not set in your environment.${NC}"
    echo -n "Enter your Telegram Bot Token (input will be hidden): "
    read -s -r BOT_TOKEN
    echo ""
fi

if [ -z "$BOT_TOKEN" ]; then
    echo -e "${RED}Error: Telegram Bot Token is required.${NC}"
    exit 1
fi

# 6. Retrieve/Prompt for ADK Server URL
DEFAULT_ADK_URL="http://localhost:8000"
if [ -n "${ADK_SERVER_URL:-}" ]; then
    echo -e "${GREEN}✔ Found ADK_SERVER_URL in environment: $ADK_SERVER_URL${NC}"
    ADK_URL="$ADK_SERVER_URL"
else
    echo -n "Enter your ADK Server URL [Default: $DEFAULT_ADK_URL]: "
    read -r ADK_URL
    ADK_URL=${ADK_URL:-$DEFAULT_ADK_URL}
fi

# Enable required GCP services
echo -e "\n${YELLOW}Checking and enabling required GCP services...${NC}"
gcloud services enable run.googleapis.com cloudbuild.googleapis.com artifactregistry.googleapis.com --project "$GCP_PROJECT"

# Determine source directory dynamically
SOURCE_DIR="."
if [ -d "telegram-integration" ]; then
    SOURCE_DIR="telegram-integration"
    echo -e "${GREEN}✔ Found source directory: telegram-integration${NC}"
elif [ -f "Dockerfile" ]; then
    SOURCE_DIR="."
    echo -e "${GREEN}✔ Dockerfile found in current directory. Using current directory as source.${NC}"
else
    echo -e "${RED}Error: Could not find source directory 'telegram-integration' or Dockerfile in current directory.${NC}"
    exit 1
fi

# 7. First-pass Deployment with placeholder SERVICE_URL
# This boots the container in Webhook mode (so health check binds to port)
# but uses a high-reliability placeholder URL (google.com) to pass DNS verification checks.
echo -e "\n${YELLOW}Deploying to Cloud Run (Step 1/2: Initial Deploy)...${NC}"
gcloud run deploy "$SERVICE_NAME" \
  --source "$SOURCE_DIR" \
  --region "$REGION" \
  --allow-unauthenticated \
  --set-env-vars "TELEGRAM_BOT_TOKEN=$BOT_TOKEN,ADK_SERVER_URL=$ADK_URL,ADK_APP_NAME=$ADK_APP,SERVICE_URL=https://google.com" \
  --project "$GCP_PROJECT"

# 8. Retrieve the actual service URL
echo -e "\n${YELLOW}Retrieving service URL...${NC}"
SERVICE_URL=$(gcloud run services describe "$SERVICE_NAME" --region "$REGION" --project "$GCP_PROJECT" --format 'value(status.url)')
echo -e "${GREEN}✔ Service URL is: $SERVICE_URL${NC}"

# 9. Update service environment variables with the real SERVICE_URL
# This triggers a rolling update and registers the correct webhook with Telegram automatically!
echo -e "\n${YELLOW}Updating configuration with final Webhook URL (Step 2/2)...${NC}"
gcloud run services update "$SERVICE_NAME" \
  --region "$REGION" \
  --set-env-vars "TELEGRAM_BOT_TOKEN=$BOT_TOKEN,ADK_SERVER_URL=$ADK_URL,ADK_APP_NAME=$ADK_APP,SERVICE_URL=$SERVICE_URL" \
  --project "$GCP_PROJECT"

echo -e "\n${GREEN}====================================================${NC}"
echo -e "${GREEN}   Deployment Completed Successfully! 🎉            ${NC}"
echo -e "${GREEN}====================================================${NC}"
echo -e "Service Name:   ${BLUE}$SERVICE_NAME${NC}"
echo -e "Region:         ${BLUE}$REGION${NC}"
echo -e "Active URL:     ${BLUE}$SERVICE_URL${NC}"
echo -e "Webhook Path:   ${BLUE}$SERVICE_URL/<bot-token>${NC}"
echo -e "ADK Backend:    ${BLUE}$ADK_URL${NC}"
echo -e "ADK App Name:   ${BLUE}$ADK_APP${NC}"
echo -e "${GREEN}====================================================${NC}"
echo "Your Telegram Bot has been configured to use webhooks."
echo "Any message sent to your bot will now trigger this Cloud Run instance."

סקריפט פריסה כפולה (deploy.sh)

כשפורסים את הבוט ב-Google Cloud Run, הבוט צריך לציין את כתובת ה-URL שלו (SERVICE_URL) בסביבה שלו כדי שיוכל לרשום אותה כיעד של ה-webhook ב-Telegram. כדי לפתור את התלות המעגלית הזו (כתובת ה-URL לא ידועה עד הפריסה, אבל השירות דורש את כתובת ה-URL כדי להפעיל את האתחול בלי שייכשלו בדיקות תקינות), deploy.sh מבצע פריסה דו-שלבית:

  1. שלב 1: פריסה ראשונית: המערכת מפעילה את הקונטיינר עם DNS של placeholder ‏ (https://google.com) כדי שהשירות יופעל בהצלחה, יתחבר ליציאה המקומית ויעבור את בדיקות התקינות הראשוניות של Cloud Run.
  2. שלב 2: אחזור כתובת URL: חילוץ תוכניתי של נקודת הקצה החדשה שנוצרה ב-Cloud Run באמצעות gcloud run services describe.
  3. שלב 3: עדכון ההגדרה: מעדכנים את משתני הסביבה עם כתובת ה-URL של השירות בפועל. הפעולה הזו מפעילה עדכון בהדרגה נקי ב-Cloud Run ורושמת בבטחה את יעד ה-webhook הנכון ב-Telegram API.

פריסה ב-Cloud Run

סקריפט הפריסה מדפיס את כתובת ה-URL של הסוכן. פותחים אותו בדפדפן כדי לגשת לאותו ממשק משתמש של ADK dev שפועל ב-Cloud Run.

cd ~/build-agent-adk-telegram
bash ./telegram-integration/deploy.sh

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

What Italian dishes do you have?

או,

I want something spicy and creamy

תראו את הסטטוס של הבוט '...כותב', ואז בקרוב הוא יחזיר את ההודעה מ-ADK שיצרתם קודם.

c62fd4016ddd3c9b.png

7. מעולה!

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

מה למדתם

  • פריסה והגדרה של סוכן מבוסס-ADK, של כלי MCP Toolbox ושל סוכן מסעדת היוקרה ב-Cloud Run
  • איך מגדירים בוט של Telegram באמצעות BotFather
  • איך לכתוב סקריפטים של Python כדי להאזין ל-webhook של Telegram וליצור אינטראקציה עם סוכן ADK כדי להעביר את השאילתה והתשובה של המשתמש בהתאם
  • איך מטמיעים את "... typing" ב-Telegram כדי לסמן שההודעות עוברות עיבוד כמשוב בזמן אמת למשתמשים בזמן שהם מחכים לתגובה מהסוכן של ADK.
  • איך פורסים את סקריפט Python ב-Cloud Run ויוצרים איתו אינטראקציה

פינוי נפח

כדי להימנע מחיובים בחשבון Google Cloud, מוחקים את המשאבים שנוצרו ב-codelab הזה.

gcloud projects delete $GOOGLE_CLOUD_PROJECT

אפשרות 2: מחיקת משאבים ספציפיים

# If you follow from previous A2A Agent Runtime codelab
# Delete the Agent Runtime deployment (skip if not found)
uv run python -c "
import vertexai
from google.genai import types
vertexai.init(project='$GOOGLE_CLOUD_PROJECT', location='$REGION')
client = vertexai.Client(
    project='$GOOGLE_CLOUD_PROJECT', location='$REGION',
    http_options=types.HttpOptions(api_version='v1beta1'),
)
try:
    agent = client.agent_engines.get(name='$RESERVATION_AGENT_RESOURCE_NAME')
    agent.delete(force=True)
    print('Agent Runtime deployment deleted.')
except Exception as e:
    print(f'No agent deployment found or already deleted, skipping. ({e})')
"

# Delete GCS staging bucket (skip if STAGING_BUCKET is not set)
if [ -n "$STAGING_BUCKET" ]; then
  gsutil rm -r gs://$STAGING_BUCKET
else
  echo "STAGING_BUCKET not set, skipping bucket deletion."
fi

# Delete Cloud Run services
gcloud run services delete restaurant-agent --region=$REGION --quiet
gcloud run services delete toolbox-service --region=$REGION --quiet
gcloud run services delete telegram-integration --region=$REGION --quiet

# Delete Cloud SQL instance
gcloud sql instances delete $DB_INSTANCE --quiet