Xây dựng ứng dụng AI có tác nhân mà khách hàng có thể tương tác thông qua Telegram

1. Giới thiệu

Tạo trải nghiệm liền mạch, mang tính tương tác với AI tác nhân mà khách hàng có thể tương tác trực tiếp từ ứng dụng nhắn tin mà họ đã sử dụng. Khám phá cách phát triển và triển khai các ứng dụng thông minh chạy mượt mà trên các giao diện web và kênh nhắn tin hiện đại.

Sản phẩm bạn sẽ tạo ra

Tích hợp giữa "Trợ lý nhà hàng" đầy đủ chức năng (một ứng dụng dựa trên ADK do Gemini hỗ trợ, giúp thực khách duyệt xem thực đơn của nhà hàng và đặt chỗ) và ứng dụng trò chuyện Telegram. Bạn có thể tương tác với bot Telegram và yêu cầu nội dung mô tả bằng ngôn ngữ tự nhiên, chẳng hạn như "Tôi muốn ăn món cay và chay". Sau đó, bot sẽ kết nối với tác nhân ADK để đọc và ghi vào cơ sở dữ liệu Cloud SQL PostgreSQL hoàn toàn thông qua Bộ công cụ MCP dành cho cơ sở dữ liệu. Bộ công cụ này xử lý mọi hoạt động truy cập vào cơ sở dữ liệu (bao gồm cả việc tự động tạo vectơ nhúng để tìm kiếm vectơ). Trong khi đó, người dùng sẽ thấy bot đang xác nhận tin nhắn và nhập ... typing để phản hồi trong khi chờ tác nhân ADK trả về kết quả.

c1d28343ed68358a.png

Kiến thức bạn sẽ học được

  • Triển khai một ứng dụng "Trợ lý nhà hàng" đang hoạt động, đây là một ứng dụng dựa trên ADK và được hỗ trợ bởi Gemini
  • Thiết lập bot trò chuyện Telegram bằng BotFather
  • Viết ứng dụng Python để theo dõi lệnh gọi lại của bot
  • Gửi thao tác trò chuyện để cung cấp thông báo ... typing trong Telegram về tin nhắn của người dùng và thực hiện thăm dò ý kiến để gửi ... typing định kỳ trong khi chờ phản hồi thực
  • Gọi điểm cuối Restaurant Concierge cloud run để xử lý câu hỏi của người dùng
  • Xử lý dữ liệu trả về từ tác nhân ADK, gửi thông báo đến Telegram và đóng vùng đệm
  • Triển khai ứng dụng Python lên Cloud Run
  • Tương tác với bot Telegram

Điều kiện tiên quyết

2. Thiết lập môi trường – Tiếp tục từ lớp học lập trình trước

Nội dung mà chúng tôi cung cấp trong lớp học lập trình này thực sự là phần tiếp nối của lớp học lập trình tiên quyết này: Agentic RAG bằng ADK, Bộ công cụ MCP và Cloud SQL hoặc Các tác nhân ở quy mô lớn: Cấu trúc đa tác nhân với giao thức A2A trên Thời gian chạy tác nhân và tích hợp ADK. Bạn có thể tiếp tục công việc từ lớp học lập trình trước

Chúng ta có thể bắt đầu tạo trong thư mục làm việc của lớp học lập trình trước ( thư mục làm việc phải là build-agent-adk-toolbox-cloudsql hoặc adk-a2a-agent-runtime-starter). Để tránh nhầm lẫn, hãy đổi tên thư mục thành tên thư mục mà chúng ta dùng khi bắt đầu từ đầu

Nếu bạn đang tiếp tục từ phòng thí nghiệm Agentic RAG bằng ADK, Bộ công cụ MCP và Cloud SQL :

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

Nếu không, bạn đang tiếp tục từ phòng thí nghiệm Các tác nhân ở quy mô lớn: Cấu trúc đa tác nhân có giao thức A2A trên Thời gian chạy tác nhân và tích hợp ADK

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

Sau đó, hãy thay đổi thư mục đang hoạt động của chúng ta thành thư mục đó

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

Sau đó, hãy xác minh rằng restaurant-agent của bạn đã được triển khai và có URL công khai để truy cập

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

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

Nếu truy cập được vào URL, bạn có thể chuyển sang phần tiếp theo: Create Telegram Bot

3. Thiết lập môi trường – Bắt đầu lại từ đầu bằng kho lưu trữ khởi động

Bước này chuẩn bị môi trường Cloud Shell, định cấu hình dự án trên đám mây Google Cloud và sao chép kho lưu trữ ban đầu.

Mở Cloud Shell

Mở Cloud Shell trong trình duyệt. Cloud Shell cung cấp một môi trường được định cấu hình sẵn với tất cả các công cụ bạn cần cho lớp học lập trình này. Nhấp vào Uỷ quyền khi được nhắc

Sau đó, nhấp vào "View" (Xem) -> "Terminal" (Thiết bị đầu cuối) để mở thiết bị đầu cuối.Giao diện của bạn sẽ trông tương tự như thế này

86307fac5da2f077.png

Đây sẽ là giao diện chính của chúng ta, IDE ở trên cùng, thiết bị đầu cuối ở dưới cùng

Thiết lập thư mục làm việc

Sao chép kho lưu trữ khởi đầu. Tất cả mã bạn viết trong lớp học lập trình này sẽ nằm ở đây:

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

Tạo tệp .env từ mẫu được cung cấp:

cp .env.example .env

Để đơn giản hoá việc thiết lập dự án trong thiết bị đầu cuối, hãy tải tập lệnh thiết lập dự án này xuống thư mục làm việc của bạn:

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

Chạy tập lệnh. Lệnh này xác minh tài khoản thanh toán dùng thử của bạn, tạo một dự án mới (hoặc xác thực một dự án hiện có), lưu mã dự án vào một tệp .env trong thư mục hiện tại và đặt dự án đang hoạt động trong gcloud.

bash setup_verify_trial_project.sh && source .env

Tập lệnh sẽ:

  1. Xác minh rằng bạn có một tài khoản thanh toán dùng thử đang hoạt động
  2. Kiểm tra xem có dự án nào trong .env hay không (nếu có)
  3. Tạo dự án mới hoặc sử dụng lại dự án hiện có
  4. Liên kết tài khoản thanh toán dùng thử với dự án của bạn
  5. Lưu mã dự án vào .env
  6. Đặt dự án làm dự án gcloud đang hoạt động

Xác minh rằng dự án được thiết lập đúng cách bằng cách kiểm tra văn bản màu vàng bên cạnh thư mục làm việc của bạn trong dấu nhắc của thiết bị đầu cuối Cloud Shell. Thẻ này phải hiển thị mã dự án của bạn.

5c515e235ee1179f.png

Thiết lập cơ sở hạ tầng cơ bản

Trước tiên, chúng ta cần cài đặt các phần phụ thuộc Python bằng uv. Đây là một trình quản lý dự án và gói Python nhanh được viết bằng Rust ( tài liệu về uv). Lớp học lập trình này sử dụng uv để duy trì dự án Python một cách nhanh chóng và đơn giản

uv sync

Sau đó, hãy chạy tập lệnh thiết lập đầy đủ để tạo phiên bản Cloud SQL, gieo dữ liệu và triển khai dịch vụ Hộp công cụ. Dịch vụ này sẽ đóng vai trò là trạng thái ban đầu của tác nhân nhà hàng

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

Thao tác này sẽ:

  • Tạo một phiên bản Cloud SQL và gieo hạt cơ sở dữ liệu (Giai đoạn 1)
  • Tạo cấu hình môi trường tác nhân và khởi động dịch vụ Toolbox cục bộ (Giai đoạn 2)
  • Triển khai các dịch vụ Toolbox và Agent lên Cloud Run (Giai đoạn 3)

Sau khi quá trình triển khai này hoàn tất, bạn có thể truy cập vào giao diện người dùng dành cho nhà phát triển ADK trên 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 ""

Mở giao diện người dùng dành cho nhà phát triển ADK, chọn restaurant_agent rồi kiểm thử bằng các truy vấn như ví dụ sau:

What Italian dishes do you have?

Hoặc

I want something spicy and creamy

Giờ đây, hành động tiếp theo là làm cách nào để chuyển từ chỉ có giao diện phát triển web sang kênh nhắn tin Telegram

4. Tạo bot Telegram

Telegram là một nền tảng nhắn tin miễn phí nổi tiếng và được sử dụng rộng rãi cho hoạt động tương tác dựa trên cộng đồng. Một trong những lý do là nền tảng này cung cấp nhiều cách để tích hợp một cách dễ dàng. Do đó, mọi người có thể dễ dàng tạo bot của riêng mình với nhiều chức năng đa dạng.

Trong trường hợp này, chúng ta sẽ sử dụng BotFather để tạo bot của riêng mình lần đầu tiên. Xin lưu ý rằng mặc dù chúng ta đang sử dụng Telegram cho phiên này, nhưng bạn có thể sử dụng phương thức tương tự cho WhatsApp hoặc các nền tảng nhắn tin khác mà bạn chọn.

Sử dụng BotFather để tạo bot của riêng bạn

Truy cập https://telegram.me/BotFather bằng trình duyệt web để bắt đầu tạo bot Telegram của riêng bạn.

1b817e758c699a79.png

Bắt đầu tương tác với The BotFather

ad3daa08e73502db.png

Gửi lệnh /start

Để bắt đầu sử dụng BotFather và bắt đầu tạo bot đầu tiên, bạn cần gửi thông báo /start đến BotFather. Sau đó, BotFather sẽ chia sẻ tất cả các lệnh mà bot này có để bạn tương tác thêm.

/start

Bắt đầu tạo bot bằng lệnh /newbot

Hãy tạo bot mới bằng cách gửi lệnh /newbot đến BotFather. Công cụ này sẽ yêu cầu bạn đặt tên cho bot, sau đó yêu cầu bạn cung cấp cho bot username. Yêu cầu này luôn đòi hỏi bot phải kết thúc bằng bot. Ví dụ: TetrisBot hoặc tetris_bot. Giá trị này phải là duy nhất.

1f6a74f494d48986.png

Sau khi tạo bot thành công, bạn sẽ nhận được thông báo sau đây từ 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

Hãy lưu ý đến YOUR_TELEGRAM_API_KEY, chúng ta sẽ sử dụng nó trong phần tiếp theo.

5. Phát triển ứng dụng Webhook Telegram

Hãy chuẩn bị thư mục làm việc để bắt đầu phát triển ứng dụng webhook telegram

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

Thêm các phần phụ thuộc bắt buộc

Tạo tập lệnh requirements.txt với nội dung sau để cung cấp các phần phụ thuộc thích hợp cho tập lệnh trình nghe webhook của Telegram.

cloudshell edit ./telegram-integration/requirements.txt

Sau đó, hãy thêm các phần phụ thuộc sau

python-telegram-bot[webhooks]
httpx

Tạo tập lệnh cho Trình nghe webhook của Telegram

Sau khi chúng ta cài đặt phần phụ thuộc. Giờ đây, chúng ta có thể tạo một tập lệnh Python main.py cho ứng dụng tích hợp

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

Sau đó, sao chép mã sau vào tệp đó

# ./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()

Tìm hiểu về mã tích hợp Bot Telegram

23b346f5ceb4712a.png

Khi người dùng gửi một thông báo, quy trình sau đây sẽ chạy trong handle_message()

Bước 1: Xác định danh tính và phiên

Bot này liên kết Mã nhận dạng người dùng Telegram với các giá trị nhận dạng ADK riêng biệt để phân biệt các phiên người dùng:

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

Bước 2: Trạng thái "Đang nhập" không đồng bộ (Dòng 53–58)

Để đảm bảo trải nghiệm người dùng có độ phản hồi cao trong khi tác nhân ADK xử lý câu lệnh (có thể mất vài giây), bot sẽ bắt đầu một vòng lặp không đồng bộ ở chế độ nền:

  • asyncio.Event được khởi tạo dưới dạng stop_event.
  • asyncio.create_task tạo ra send_typing_loop(...) ở chế độ nền.
  • Vòng lặp sẽ gửi thao tác ChatAction.TYPING đến Telegram sau mỗi 4 giây cho đến khi stop_event được đặt.

Bước 3: Xác minh và tạo phiên ADK (Dòng 61 – 72)

Trước khi thực thi tác nhân, bot sẽ kiểm tra xem đã có phiên nào hay chưa:

  1. Gửi yêu cầu GET đến /apps/{appName}/users/{userId}/sessions/{sessionId}.
  2. Nếu phản hồi là 404 Not Found, thì yêu cầu này sẽ tạo phiên thông qua yêu cầu POST đến cùng một URL với nội dung JSON trống.
  3. Nếu một trạng thái khác với 200 hoặc 404 được trả về, thì một ngoại lệ sẽ được gửi.

Bước 4: Gửi yêu cầu đến trợ lý (Dòng 74 – 85)

Tải trọng thông báo được chuyển tiếp đến điểm cuối /run của ADK:

  • Điểm cuối: POST /run
  • Thời gian chờ yêu cầu được đặt thành 60.0 giây để cho phép suy luận phức tạp hoặc độ trễ ở nguồn.
  • Cấu trúc tải trọng:
{
  "appName": "restaurant_agent",
  "userId": "tg_<user_id>",
  "sessionId": "tg_sess_<user_id>",
  "newMessage": {
    "role": "user",
    "parts": [{"text": "<user_message>"}]
  }
}

Bước 5: Phân tích cú pháp Phản hồi (Dòng 87–101)

Máy chủ ADK trả về danh sách các sự kiện thông báo. Bot kiểm tra mảng được trả về:

  • Thao tác này sẽ truy xuất sự kiện cuối cùng trong danh sách (events[-1]).
  • Thao tác này sẽ chuyển đến nội dung văn bản thông qua event["content"]["parts"][0]["text"].
  • Nếu không có sự kiện nào được trả về hoặc thiếu cấu trúc văn bản, thì hệ thống sẽ đặt một văn bản giữ chỗ mang tính mô tả.

Bước 6: Phân tích và gửi phản hồi (Dòng 103 – 111)

  • Trong khối finally, stop_event được đặt, ngăn chặn vòng lặp thao tác nhập.
  • Bot sẽ chờ hoàn tất quá trình typing_task để đảm bảo tài nguyên được dọn dẹp.
  • Cuối cùng, bot sẽ trả lời cuộc trò chuyện trên Telegram bằng văn bản phản hồi đã phân tích cú pháp.

6. Triển khai ứng dụng Telegram Webhook lên Cloud Run

Tiếp theo, chúng ta sẽ triển khai Trình nghe Webhook Telegram cho Cloud Run để bot của chúng ta có thể giao tiếp với trình nghe này

Tạo tệp Docker

Trước tiên, chúng ta cần tạo Dockerfile.

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

Sau đó, sao chép mã sau vào tệp đó

# 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"]

Dịch vụ này được chứa trong vùng chứa bằng cách sử dụng python:3.11-slim để giữ cho dấu vết hình ảnh nhỏ:

  • Cài đặt các phần phụ thuộc từ requirements.txt (python-telegram-bot[webhooks]httpx).
  • Hiển thị cổng tiêu chuẩn 8080.
  • Ra mắt python main.py.

Chuẩn bị các biến môi trường

Sau đó, hãy kiểm tra lại xem tác nhân của chúng ta đã triển khai thành công hay chưa

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

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

Tiếp theo, hãy đặt TELEGRAM_BOT_TOKEN mà chúng ta đã lấy trước đó vào .env

echo "TELEGRAM_BOT_TOKEN=YOUR_TELEGRAM_API_KEY" >> .env

Sau đó, hãy điền dữ liệu .env bằng các giá trị khác mà chúng ta cần.

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

Tạo tập lệnh triển khai

Hãy tạo tập lệnh triển khai để cung cấp các bước kiểm tra hoàn chỉnh và triển khai ứng dụng vào Cloud Run

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

Và sao chép mã sau đây vào tệp

#!/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."

Tập lệnh triển khai kép (deploy.sh)

Khi triển khai đến Google Cloud Run, bot cần chỉ định URL riêng (SERVICE_URL) trong môi trường của mình để có thể đăng ký URL đó làm mục tiêu webhook với Telegram. Để giải quyết sự phụ thuộc vòng tròn này (URL không xác định cho đến khi triển khai, nhưng dịch vụ yêu cầu URL khởi động mà không gặp lỗi kiểm tra tình trạng), deploy.sh sẽ thực hiện quy trình triển khai gồm 2 giai đoạn:

  1. Bước 1: Triển khai ban đầu: Khởi động vùng chứa bằng một DNS giữ chỗ (https://google.com) để dịch vụ khởi động thành công, liên kết với cổng cục bộ và vượt qua các quy trình kiểm tra tình trạng ban đầu của Cloud Run.
  2. Bước 2: Tìm nạp URL: Trích xuất theo chương trình điểm cuối Cloud Run mới tạo bằng cách sử dụng gcloud run services describe.
  3. Bước 3: Cập nhật cấu hình: Cập nhật các biến môi trường bằng URL thực tế của dịch vụ đang hoạt động. Thao tác này sẽ kích hoạt một bản cập nhật luân phiên sạch trong Cloud Run và đăng ký an toàn mục tiêu webhook chính xác bằng Telegram API.

Triển khai lên Cloud Run

Tập lệnh triển khai sẽ in URL của Nhân viên hỗ trợ. Mở tệp này trong trình duyệt để truy cập vào cùng một giao diện người dùng dành cho nhà phát triển ADK đang chạy trên Cloud Run.

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

Nếu mọi thứ diễn ra suôn sẻ, đây là thời điểm bạn có thể bắt đầu trò chuyện với bot ngay trong ứng dụng trò chuyện Telegram, tìm bot mà bạn vừa tạo và bắt đầu tương tác với bot đó:

What Italian dishes do you have?

Hoặc

I want something spicy and creamy

Xem trạng thái gửi của bot "...đang nhập" và sau đó, bot sẽ sớm trả về thông báo từ ADK mà bạn đã tạo trước đó!

c62fd4016ddd3c9b.png

7. Xin chúc mừng!

Bạn đã tạo, triển khai và tích hợp hoàn toàn tác nhân AI ADK trợ lý thực đơn thông minh với Telegram thông qua giao tiếp máy khách-máy chủ HTTP, đồng thời cho phép mọi người truy vấn thực đơn yêu thích và đặt chỗ tại nhà hàng.

Kiến thức bạn học được

  • Triển khai và định cấu hình Trợ lý nhà hàng, tác nhân dựa trên ADK và Bộ công cụ MCP cho Cloud Run
  • Cách thiết lập bot Telegram bằng BotFather
  • Cách viết tập lệnh Python để theo dõi webhook Telegram và tương tác với tác nhân ADK để truyền truy vấn và phản hồi của người dùng một cách phù hợp
  • Cách triển khai "... typing" trong Telegram để báo hiệu rằng tin nhắn đang được xử lý dưới dạng ý kiến phản hồi theo thời gian thực cho người dùng trong khi chờ tác nhân ADK phản hồi.
  • Cách triển khai tập lệnh Python vào Cloud Run và có thể tương tác với tập lệnh đó

Dọn dẹp

Để tránh bị tính phí vào tài khoản Google Cloud của bạn, hãy xoá các tài nguyên đã tạo trong lớp học lập trình này.

gcloud projects delete $GOOGLE_CLOUD_PROJECT

Cách 2: Xoá từng tài nguyên

# 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