كيفية نشر تطبيق واجهة أمامية من Gradio يستدعي وكيل ADK للواجهة الخلفية، وكلاهما يعملان على Cloud Run

1. مقدمة

نظرة عامة

في هذا الدرس التطبيقي حول الترميز، ستنشر وكيل ADK على Cloud Run كخدمة خلفية، ثم ستنشر واجهة أمامية لـ gradio لوكيل ADK كخدمة ثانية على Cloud Run. يوضّح هذا الدرس العملي كيفية طلب المصادقة على خدمة وكيل "حزمة تطوير التطبيقات" (ADK) وإجراء مكالمات مصادَق عليها إليها من خدمة الواجهة الأمامية Gradio.

أهداف الدورة التعليمية

  • كيفية نشر وكيل ADK على Cloud Run
  • كيفية نشر تطبيق Gradio على Cloud Run
  • كيفية إجراء طلبات مصادقة من خدمة إلى خدمة في Cloud Run

2. تفعيل واجهات برمجة التطبيقات

أولاً، اضبط مشروعك على Google Cloud.

gcloud config set project <YOUR_PROJECT_ID>

يمكنك تأكيد مشروعك على Google Cloud من خلال تنفيذ الأمر التالي:

gcloud config get-value project

يتطلّب هذا الدرس العملي تفعيل واجهات برمجة التطبيقات التالية:

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

3- الإعداد والمتطلبات

في هذا القسم، ستنشئ حسابَي خدمة وتمنحهما أدوار إدارة الهوية وإمكانية الوصول المناسبة. سيكون لكل خدمة Cloud Run حساب خدمة خاص بها.

أولاً، اضبط متغيّرات البيئة لهذا الدرس التطبيقي حول الترميز التي سيتم استخدامها في جميع أنحائه.

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"

ومنح حساب خدمة "حزمة تطوير التطبيقات" دور "مستخدم Vertex AI"

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"، ما سيسمح لها باستدعاء وكيل ADK المستضاف على Cloud Run.

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

4. إنشاء تطبيق ADK

في الخطوة التالية، ستنشئ الرمز لتطبيق ADK Quickstart.

ملاحظة: في نهاية الدرس التطبيقي، يجب أن تبدو بنية الملف على النحو التالي:

- 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

أولاً، أنشئ دليلاً لنموذج البرمجة الكامل

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

في هذا القسم، ستنشر وكيل "حزمة تطوير إعلانات Google" على Cloud Run. بعد ذلك، ستتحقّق من أنّ عملية النشر قد نجحت باستخدام واجهة مستخدم الويب الخاصة بالمطوّرين التي توفّرها "حزمة تطوير التطبيقات". أخيرًا، ستحتاج إلى إجراء مكالمات مصادق عليها مع هذه الخدمة.

انتقِل إلى المجلد الرئيسي.

ملاحظة: يجب أن يتضمّن رمز وكيل 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 كمتغير بيئة ستستخدمه في الجزء الثاني من هذا الدرس العملي.

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. يوفر هذا الدرس العملي المستند إلى الترميز خدمتين منفصلتين لتوضيح كيفية إجراء طلبات مصادقة من خدمة إلى خدمة في 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.

ملاحظة: على الرغم من أنّ خدمة واجهة المستخدم الأمامية هذه من Gradio هي موقع إلكتروني متاح للجميع، تتطلّب خدمة الخلفية المصادقة. لتوضيح سبب رغبتك في إجراء ذلك، يمكنك إضافة مصادقة المستخدم (مثل 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. تهانينا!

تهانينا على إكمال هذا الدرس العملي.

ننصحك بمراجعة المستندات حول استضافة تطبيقات وعملاء مستنِدين إلى الذكاء الاصطناعي.

المواضيع التي تناولناها

  • كيفية نشر وكيل 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.

لحذف المشروع بأكمله، انتقِل إلى إدارة المراجع، واختَر المشروع الذي أنشأته في الخطوة 2، ثم انقر على "حذف". إذا حذفت المشروع، عليك تغيير المشاريع في Cloud SDK. يمكنك الاطّلاع على قائمة بجميع المشاريع المتاحة من خلال تنفيذ gcloud projects list.