איך בונים מערכת מרובת סוכנים

1. מבוא

סקירה כללית

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

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

תבנו מערכת ליצירת קורסים שתכלול:

  1. סוכן מחקר: שימוש ב-google_search כדי למצוא מידע עדכני.
  2. סוכן שופט: מבקר את המחקר כדי לבדוק את האיכות והשלמות שלו.
  3. סוכן ליצירת תוכן: הופך את המחקר לקורס מובנה.
  4. Orchestrator Agent: ניהול תהליך העבודה והתקשורת בין המומחים האלה.

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

  • ידע בסיסי ב-Python.
  • היכרות עם מסוף Google Cloud.

הפעולות שתבצעו:

  • הגדרת סוכן שמשתמש בכלי (researcher) שיכול לחפש באינטרנט.
  • הטמעת פלט מובנה באמצעות Pydantic עבור judge.
  • התחברות לסוכנים מרוחקים באמצעות פרוטוקול Agent-to-Agent (A2A).
  • בונים LoopAgent כדי ליצור לולאת משוב בין החוקר לבין השופט.
  • מריצים את המערכת המבוזרת באופן מקומי באמצעות ה-ADK.
  • פורסים את המערכת מרובת הסוכנים ב-Google Cloud Run.

עקרונות של ארכיטקטורה ותזמור

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

עיצוב המערכת

תרשים ארכיטקטורה

תזמור באמצעות סוכנים

סוכנים רגילים (כמו חוקר) מבצעים עבודה. סוכני תזמור (כמו LoopAgent או SequentialAgent) מנהלים סוכנים אחרים. אין להם כלים משלהם, והכלי שלהם הוא הקצאת הרשאות.

  1. LoopAgent: הפעולה הזו דומה ללולאת while בקוד. הוא מריץ רצף של סוכנים שוב ושוב עד שתנאי מסוים מתקיים (או עד שמגיעים למספר המקסימלי של איטרציות). אנחנו משתמשים בזה בלולאת המחקר:
    • חוקר מוצא מידע.
    • Judge (שופט) מבקר אותו.
    • אם Judge אומר 'Fail', ‏ EscalationChecker מאפשר ללולאה להמשיך.
    • אם Judge אומר 'Pass', ‏ EscalationChecker מפסיק את הלולאה.
  2. SequentialAgent: הפעולה הזו דומה להרצת סקריפט רגיל. הוא מפעיל סוכנים אחד אחרי השני. אנחנו משתמשים בזה בשביל פייפליין ברמה גבוהה:
    • קודם מריצים את Research Loop (עד שהוא מסתיים עם נתונים טובים).
    • לאחר מכן, מפעילים את כלי יצירת התוכן (כדי לכתוב את הקורס).

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

2. הגדרה

הגדרת הסביבה

  1. פותחים את Cloud Shell: לוחצים על הסמל Activate Cloud Shell (הפעלת Cloud Shell) בפינה הימנית העליונה של מסוף Google Cloud.

קבלת קוד לתחילת הדרך

  1. משכפלים את מאגר המתחילים לספריית הבית:
    cd ~
    git clone --depth 1 --filter=blob:none --sparse https://github.com/GoogleCloudPlatform/devrel-demos.git temp-repo && cd temp-repo && git sparse-checkout set agents/build-with-ai/production-ready-ai/prai-roadshow-lab-1-starter && cd .. && mv temp-repo/agents/build-with-ai/production-ready-ai/prai-roadshow-lab-1-starter . && rm -rf temp-repo
    cd prai-roadshow-lab-1-starter
    
  2. מפעילים ממשקי API: מריצים את הפקודה הבאה כדי להפעיל את שירותי Google Cloud הנדרשים:
    gcloud services enable \
        run.googleapis.com \
        artifactregistry.googleapis.com \
        cloudbuild.googleapis.com \
        aiplatform.googleapis.com \
        compute.googleapis.com
    
  3. פותחים את התיקייה הזו בכלי העריכה.

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

אנחנו משתמשים ב-uv לניהול מהיר של יחסי תלות.

  1. מתקינים את יחסי התלות של הפרויקט:
    # Ensure you have uv installed: pip install uv
    uv sync
    
  2. מגדירים משתני סביבה.
    • טיפ: אפשר למצוא את מזהה הפרויקט בלוח הבקרה של Cloud Console או על ידי הפעלת הפקודה gcloud config get-value project.
    ניצור קובץ .env לאחסון המשתנים האלה, כדי שתוכלו לטעון אותם מחדש בקלות אם הסשן יתנתק.
    cat <<EOF > .env
    export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
    export GOOGLE_CLOUD_LOCATION=us-central1
    export GOOGLE_GENAI_USE_VERTEXAI=true
    EOF
    
  3. מקור משתני הסביבה:
    source .env
    
    אזהרה: משתני סביבה לא נשמרים בין הפעלות חדשות של הטרמינל. אם פותחים כרטיסיית טרמינל חדשה, מריצים את הפקודה source .env כדי לשחזר אותן.

3. 🕵️ סוכן המחקר

סוכן מחקר

החוקר הוא מומחה. התפקיד היחיד שלו הוא למצוא מידע. כדי לעשות את זה, היא צריכה גישה לכלי: חיפוש Google.

למה כדאי להפריד את הכלי 'חוקר'?

ניתוח מעמיק: למה לא להשתמש רק בסוכן אחד שיעשה הכול?

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

  1. אם אתם עובדים ב-Cloud Shell, מריצים את הפקודה הבאה כדי לפתוח את Cloud Shell Editor:
    cloudshell workspace .
    
    אם אתם עובדים בסביבה המקומית, פותחים את סביבת הפיתוח המשולבת (IDE) המועדפת עליכם.
  2. פתיחת agents/researcher/agent.py.
  3. יוצג שלד עם הערה מסוג TODO.
  4. מוסיפים את הקוד הבא כדי להגדיר את סוכן researcher:
    # ... existing imports ...
    
    # Define the Researcher Agent
    researcher = Agent(
        name="researcher",
        model=MODEL,
        description="Gathers information on a topic using Google Search.",
        instruction="""
        You are an expert researcher. Your goal is to find comprehensive and accurate information on the user's topic.
        Use the `google_search` tool to find relevant information.
        Summarize your findings clearly.
        If you receive feedback that your research is insufficient, use the feedback to refine your next search.
        """,
        tools=[google_search],
    )
    
    root_agent = researcher
    

מושג מפתח: שימוש בכלי

שימו לב שאנחנו מעבירים את tools=[google_search]. ה-ADK מטפל במורכבות של תיאור הכלי הזה ל-LLM. כשהמודל מחליט שהוא צריך מידע, הוא יוצר קריאה מובנית לכלי, ה-ADK מפעיל את פונקציית Python‏ google_search ומחזיר את התוצאה למודל.

4. ‫⚖️ סוכן השופט

נציג שופט

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

פלט מובנה

הסבר מפורט: כדי להפוך תהליכי עבודה לאוטומטיים, אנחנו צריכים פלט צפוי. קשה לנתח ביקורת טקסט ארוכה ומבולבלת באופן אוטומטי. על ידי אכיפה של סכימת JSON (באמצעות Pydantic), אנחנו מוודאים שהרכיב Judge מחזיר ערך בוליאני pass או fail שהקוד שלנו יכול לפעול על פיו באופן מהימן.

  1. פתיחת agents/judge/agent.py.
  2. הגדרת סכימת JudgeFeedback וסוכן judge.
    # 1. Define the Schema
    class JudgeFeedback(BaseModel):
        """Structured feedback from the Judge agent."""
        status: Literal["pass", "fail"] = Field(
            description="Whether the research is sufficient ('pass') or needs more work ('fail')."
        )
        feedback: str = Field(
            description="Detailed feedback on what is missing. If 'pass', a brief confirmation."
        )
    
    # 2. Define the Agent
    judge = Agent(
        name="judge",
        model=MODEL,
        description="Evaluates research findings for completeness and accuracy.",
        instruction="""
        You are a strict editor.
        Evaluate the 'research_findings' against the user's original request.
        If the findings are missing key info, return status='fail'.
        If they are comprehensive, return status='pass'.
        """,
        output_schema=JudgeFeedback,
        # Disallow delegation because it should only output the schema
        disallow_transfer_to_parent=True,
        disallow_transfer_to_peers=True,
    )
    
    root_agent = judge
    

מושג מרכזי: הגבלת התנהגות הסוכן

הגדרנו את disallow_transfer_to_parent=True ואת disallow_transfer_to_peers=True. הפעולה הזו מאלצת את השופט להחזיר רק את הנתונים המובנים JudgeFeedback. הוא לא יכול להחליט 'לשוחח' עם המשתמש או להעביר את הטיפול לסוכן אחר. כך הוא הופך לרכיב דטרמיניסטי בתהליך הלוגי שלנו.

5. ‫🧪 בדיקה בבידוד

לפני שמקשרים אותם, אפשר לוודא שכל נציג עובד. ה-ADK מאפשר להפעיל סוכנים בנפרד.

מושג מרכזי: סביבת זמן הריצה האינטראקטיבית

adk run יוצר סביבה קלה שבה אתם ה "משתמשים". כך תוכלו לבדוק את ההוראות של הסוכן ואת השימוש בכלי בנפרד. אם הסוכן נכשל כאן (למשל, אם הוא לא יכול להשתמש בחיפוש Google), הוא ייכשל בוודאות בתיאום.

  1. מריצים את הכלי 'חוקר' באופן אינטראקטיבי. שימו לב שאנחנו מציינים את ספריית הסוכן הספציפית:
    # This runs the researcher agent in interactive mode
    uv run adk run agents/researcher
    
  2. בהנחיה בצ'אט, מקלידים:
    Find the population of Tokyo in 2020
    
    הוא צריך להשתמש בכלי של חיפוש Google ולהחזיר את התשובה.הערה: אם מוצגת שגיאה שמציינת שהפרויקט, המיקום והשימוש ב-Vertex לא מוגדרים, צריך לוודא שמזהה הפרויקט מוגדר ולהריץ את הפקודה הבאה:
    export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
    export GOOGLE_CLOUD_LOCATION=us-central1
    export GOOGLE_GENAI_USE_VERTEXAI=true
    
  3. יוצאים מהצ'אט (Ctrl+C).
  4. מריצים את Judge באופן אינטראקטיבי:
    uv run adk run agents/judge
    
  5. בהנחיה לצ'אט, מדמים את הקלט:
    Topic: Tokyo. Findings: Tokyo is a city.
    
    התשובה צריכה להיות status='fail' כי הממצאים קצרים מדי.

6. ✍️ סוכן ליצירת תוכן

הכלי ליצירת תוכן

כלי יצירת התוכן הוא הכותב היצירתי. הוא לוקח את המחקר שאושר והופך אותו לקורס.

  1. פתיחת agents/content_builder/agent.py.
  2. מגדירים את הסוכן content_builder.
    content_builder = Agent(
        name="content_builder",
        model=MODEL,
        description="Transforms research findings into a structured course.",
        instruction="""
        You are an expert course creator.
        Take the approved 'research_findings' and transform them into a well-structured, engaging course module.
    
        **Formatting Rules:**
        1. Start with a main title using a single `#` (H1).
        2. Use `##` (H2) for main section headings.
        3. Use bullet points and clear paragraphs.
        4. Maintain a professional but engaging tone.
    
        Ensure the content directly addresses the user's original request.
        """,
    )
    root_agent = content_builder
    

מושג מפתח: העברת הקשר

יכול להיות שתשאלו: "איך כלי יצירת התוכן יודע מה כלי המחקר מצא?" ב-ADK, סוכנים בצינור משתפים session.state. בהמשך, ב-Orchestrator, נגדיר את Researcher ואת Judge כך שהפלט שלהם יישמר במצב המשותף הזה. ההיסטוריה הזו נגישה להנחיה בכלי ליצירת תוכן.

7. ‫🎻 כלי התזמור

סוכן לניהול תזמור

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

🌐 הארכיטקטורה: סוכן לסוכן (A2A)

ארכיטקטורת A2A

בשיעור ה-Lab הזה נבנה מערכת מבוזרת. במקום להריץ את כל הסוכנים בתהליך Python יחיד, אנחנו פורסים אותם כמיקרו-שירותים עצמאיים. כך כל סוכן יכול להתרחב באופן עצמאי ולהיכשל בלי לגרום לקריסה של המערכת כולה.

כדי לאפשר את זה, אנחנו משתמשים בפרוטוקול Agent-to-Agent (A2A).

פרוטוקול A2A

מידע נוסף: במערכת ייצור, הסוכנים פועלים בשרתים שונים (או אפילו בעננים שונים). פרוטוקול A2A יוצר דרך סטנדרטית לסוכני AI לגלות אחד את השני ולתקשר ביניהם באמצעות HTTP. ‫RemoteA2aAgent הוא לקוח ה-ADK של הפרוטוקול הזה.

  1. פתיחת agents/orchestrator/agent.py.
  2. מאתרים את התגובה # TODO: Define connections to remote agents או את הקטע של הגדרות הסוכן המרוחק.
  3. מוסיפים את הקוד הבא כדי להגדיר את החיבורים. חשוב למקם את הקוד הזה אחרי הייבוא ולפני הגדרות אחרות של סוכנים.
    # ... existing code ...
    
    # Connect to the Researcher (Localhost port 8001)
    researcher_url = os.environ.get("RESEARCHER_AGENT_CARD_URL", "http://localhost:8001/a2a/agent/.well-known/agent-card.json")
    researcher = RemoteA2aAgent(
        name="researcher",
        agent_card=researcher_url,
        description="Gathers information using Google Search.",
        # IMPORTANT: Save the output to state for the Judge to see
        after_agent_callback=create_save_output_callback("research_findings"),
        # IMPORTANT: Use authenticated client for communication
        httpx_client=create_authenticated_client(researcher_url)
    )
    
    # Connect to the Judge (Localhost port 8002)
    judge_url = os.environ.get("JUDGE_AGENT_CARD_URL", "http://localhost:8002/a2a/agent/.well-known/agent-card.json")
    judge = RemoteA2aAgent(
        name="judge",
        agent_card=judge_url,
        description="Evaluates research.",
        after_agent_callback=create_save_output_callback("judge_feedback"),
        httpx_client=create_authenticated_client(judge_url)
    )
    
    # Content Builder (Localhost port 8003)
    content_builder_url = os.environ.get("CONTENT_BUILDER_AGENT_CARD_URL", "http://localhost:8003/a2a/agent/.well-known/agent-card.json")
    content_builder = RemoteA2aAgent(
        name="content_builder",
        agent_card=content_builder_url,
        description="Builds the course.",
        httpx_client=create_authenticated_client(content_builder_url)
    )
    

8. 🛑 הכלי לבדיקת העברה לטיפול ברמה גבוהה יותר

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

לוגיקה מותאמת אישית עם BaseAgent

הסבר מפורט: לא כל הנציגים משתמשים ב-LLM. לפעמים צריך לוגיקה פשוטה של Python. ‫BaseAgent מאפשר לכם להגדיר סוכן שמריץ רק קוד. במקרה הזה, אנחנו בודקים את מצב הסשן ומשתמשים ב-EventActions(escalate=True) כדי לסמן ל-LoopAgent להפסיק.

  1. עדיין ב-agents/orchestrator/agent.py.
  2. מחפשים את הפלייסהולדר EscalationChecker TODO.
  3. מחליפים אותו בהטמעה הבאה:
    class EscalationChecker(BaseAgent):
        """Checks the judge's feedback and escalates (breaks the loop) if it passed."""
    
        async def _run_async_impl(
            self, ctx: InvocationContext
        ) -> AsyncGenerator[Event, None]:
            # Retrieve the feedback saved by the Judge
            feedback = ctx.session.state.get("judge_feedback")
            print(f"[EscalationChecker] Feedback: {feedback}")
    
            # Check for 'pass' status
            is_pass = False
            if isinstance(feedback, dict) and feedback.get("status") == "pass":
                is_pass = True
            # Handle string fallback if JSON parsing failed
            elif isinstance(feedback, str) and '"status": "pass"' in feedback:
                is_pass = True
    
            if is_pass:
                # 'escalate=True' tells the parent LoopAgent to stop looping
                yield Event(author=self.name, actions=EventActions(escalate=True))
            else:
                # Continue the loop
                yield Event(author=self.name)
    
    escalation_checker = EscalationChecker(name="escalation_checker")
    

מושג מרכזי: בקרה על זרימת נתונים באמצעות אירועים

הסוכנים מתקשרים לא רק באמצעות טקסט, אלא גם באמצעות אירועים. על ידי הפקת אירוע עם escalate=True, הסוכן הזה שולח אות אל ההורה שלו (LoopAgent). הסוכן LoopAgent מתוכנת לזהות את האות הזה ולסיים את הלולאה.

9. 🔁 מחזור המחקר

מחקר בלולאה

אנחנו צריכים לולאת משוב: מחקר -> שיפוט -> (כישלון) -> מחקר -> ...

  1. עדיין ב-agents/orchestrator/agent.py.
  2. מוסיפים את ההגדרה research_loop. ממקמים את אחרי הכיתה EscalationChecker והמופע escalation_checker.
    research_loop = LoopAgent(
        name="research_loop",
        description="Iteratively researches and judges until quality standards are met.",
        sub_agents=[researcher, judge, escalation_checker],
        max_iterations=3,
    )
    

מושג מרכזי: LoopAgent

האפשרויות של LoopAgent מתחלפות ברצף sub_agents.

  1. researcher: חיפוש נתונים.
  2. judge: הערכת נתונים.
  3. escalation_checker: קובע אם yield Event(escalate=True). אם מתרחש escalate=True, הלולאה נשברת מוקדם. אחרת, הוא יופעל מחדש אצל החוקר (עד max_iterations).

10. 🔗 צינור עיבוד הנתונים הסופי

Final Pipeline

לבסוף, מחברים את כל החלקים.

  1. עדיין ב-agents/orchestrator/agent.py.
  2. מגדירים את root_agent בתחתית הקובץ. חשוב לוודא שהתג הזה מחליף את הפלייסולדר הקיים root_agent = None.
    root_agent = SequentialAgent(
        name="course_creation_pipeline",
        description="A pipeline that researches a topic and then builds a course from it.",
        sub_agents=[research_loop, content_builder],
    )
    

מושג מפתח: הרכב היררכי

שימו לב ש-research_loop הוא בעצמו סוכן (LoopAgent). אנחנו מתייחסים אליו כמו לכל סוכן משנה אחר ב-SequentialAgent. הקומפוזביליות הזו מאפשרת לכם לפתח לוגיקה מורכבת על ידי הצבת רכיב בתוך רכיב של דפוסים פשוטים (לולאות בתוך רצפים, רצפים בתוך נתבים וכו').

11. 💻 הרצה מקומית

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

ניתוח מעמיק: איך פועל פיתוח מקומי

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

הסקריפט הזה מתחיל uvicorn תהליכים עבור Researcher (יציאה 8001),‏ Judge (8002) ו-Content Builder (8003). הוא מגדיר משתני סביבה כמו RESEARCHER_AGENT_CARD_URL ומעביר אותם ל-Orchestrator (יציאה 8004). כך בדיוק נגדיר את זה בענן בהמשך!

אפליקציה פועלת

  1. מריצים את סקריפט התזמור:
    ./run_local.sh
    
    הפקודה הזו מפעילה 4 תהליכים נפרדים.
  2. כדי לבדוק את זה:
    • אם משתמשים ב-Cloud Shell: לוחצים על הלחצן Web Preview (בפינה השמאלית העליונה של הטרמינל) -> Preview on port 8080 -> Change port ל-8000.
    • אם מריצים באופן מקומי: פותחים את http://localhost:8000 בדפדפן.
    • הנחיה: "צור קורס על ההיסטוריה של הקפה".
    • התבוננות: המארגן יתקשר למשתתף. הפלט עובר לשופט. אם השופט נכשל, הלולאה ממשיכה!
    פתרון בעיות:
    • "שגיאת שרת פנימית" / שגיאות אימות: אם מוצגות שגיאות אימות (למשל, שגיאות שקשורות ל-google-auth), צריך לוודא שהפעלתם את gcloud auth application-default login אם אתם מריצים את הפקודה במחשב מקומי. ב-Cloud Shell, מוודאים שמשתנה הסביבה GOOGLE_CLOUD_PROJECT מוגדר בצורה נכונה.
    • שגיאות בטרמינל: אם הפקודה נכשלת בחלון טרמינל חדש, צריך לייצא מחדש את משתני הסביבה (GOOGLE_CLOUD_PROJECT וכו').
  3. בדיקת סוכנים בבידוד: גם כשהמערכת המלאה פועלת, אפשר לבדוק סוכנים ספציפיים על ידי טירגוט היציאות שלהם ישירות. האפשרות הזו שימושית לניפוי באגים ברכיב ספציפי בלי להפעיל את כל השרשרת.הערה: אלה נקודות קצה של API, לא דפי אינטרנט. אי אפשר לגשת אליהם דרך דפדפן. במקום זאת, אפשר להשתמש ב-curl כדי לוודא שהם פועלים (למשל, על ידי אחזור כרטיס הסוכן שלהם).
    • חוקר בלבד (יציאה 8001):
      • בודקים את הסטטוס (ומאתרים את נקודת הקצה url):
        curl http://localhost:8001/a2a/agent/.well-known/agent-card.json
        
      • שליחת שאילתה (באמצעות פרוטוקול JSON-RPC של A2A):
        curl -X POST http://localhost:8001/a2a/agent \
          -H "Content-Type: application/json" \
          -d '{
            "jsonrpc": "2.0",
            "method": "message/send",
            "id": 1,
            "params": {
              "message": {
                "message_id": "test-1",
                "role": "user",
                "parts": [
                  {
                    "text": "What is the capital of France?",
                    "kind": "text"
                  }
                ]
              }
            }
          }'
        
    • Judge Only (Port 8002):
      • בדיקת הסטטוס:
        curl http://localhost:8002/a2a/agent/.well-known/agent-card.json
        
      • שליחת שאילתה:
        curl -X POST http://localhost:8002/a2a/agent \
          -H "Content-Type: application/json" \
          -d '{
            "jsonrpc": "2.0",
            "method": "message/send",
            "id": 1,
            "params": {
              "message": {
                "message_id": "test-2",
                "role": "user",
                "parts": [
                  {
                    "text": "Topic: Tokyo. Findings: Tokyo is the capital of Japan.",
                    "kind": "text"
                  }
                ]
              }
            }
          }'
        
    • Content Builder Only (Port 8003):
      curl http://localhost:8003/a2a/agent/.well-known/agent-card.json
      
    • Orchestrator (יציאה 8004):
      curl http://localhost:8004/a2a/agent/.well-known/agent-card.json
      

12. 🚀 פריסה ב-Cloud Run

האימות הסופי מתבצע בענן. נפרוס כל סוכן כשירות נפרד.

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

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

  • GOOGLE_CLOUD_PROJECT: מוודא שהנציג משתמש בפרויקט הנכון ב-Google Cloud לרישום ביומן ולקריאות ל-Vertex AI.
  • GOOGLE_GENAI_USE_VERTEXAI: מציין ל-framework של הסוכן (ADK) להשתמש ב-Vertex AI להסקת מסקנות מהמודל במקום להפעיל ישירות את Gemini API.
  • [AGENT]_AGENT_CARD_URL: זה קריטי ל-Orchestrator. הוא מציין ל-Orchestrator איפה נמצאים הסוכנים המרוחקים. אם מגדירים את כתובת ה-URL של Cloud Run (במיוחד את הנתיב של כרטיס הסוכן), המארגן יכול לגלות את החוקר, השופט ויוצר התוכן ולתקשר איתם באינטרנט.
  1. פריסת סוכני המשנה (במקביל): כדי לחסוך זמן, נבצע פריסה של החוקר, השופט ובונה התוכן בו-זמנית.פותחים שלוש כרטיסיות חדשות במסוף. בכל כרטיסייה חדשה, מריצים את הפקודה הבאה כדי להגדיר את הסביבה:
    cd ~/prai-roadshow-lab-1-starter
    source .env
    
    כרטיסייה 1: מריצים את הפריסה של Researcher:
    gcloud run deploy researcher \
      --source agents/researcher/ \
      --region us-central1 \
      --allow-unauthenticated \
      --labels dev-tutorial=prod-ready-1 \
      --set-env-vars GOOGLE_CLOUD_PROJECT=$GOOGLE_CLOUD_PROJECT \
      --set-env-vars GOOGLE_GENAI_USE_VERTEXAI="true"
    
    כרטיסייה 2: מריצים את הפריסה של Judge:
    gcloud run deploy judge \
      --source agents/judge/ \
      --region us-central1 \
      --allow-unauthenticated \
      --labels dev-tutorial=prod-ready-1 \
      --set-env-vars GOOGLE_CLOUD_PROJECT=$GOOGLE_CLOUD_PROJECT \
      --set-env-vars GOOGLE_GENAI_USE_VERTEXAI="true"
    
    כרטיסייה 3: מריצים את הפריסה של הכלי ליצירת תוכן:
    gcloud run deploy content-builder \
      --source agents/content_builder/ \
      --region us-central1 \
      --allow-unauthenticated \
      --labels dev-tutorial=prod-ready-1 \
      --set-env-vars GOOGLE_CLOUD_PROJECT=$GOOGLE_CLOUD_PROJECT \
      --set-env-vars GOOGLE_GENAI_USE_VERTEXAI="true"
    
  2. תיעוד כתובות ה-URL: אחרי שכל שלושת הפריסות מסתיימות, חוזרים אל הטרמינל המקורי (שבו תבצעו את הפריסה של Orchestrator). מריצים את הפקודות הבאות כדי לתעד את כתובות ה-URL של השירות:
    RESEARCHER_URL=$(gcloud run services describe researcher --region us-central1 --format='value(status.url)')
    JUDGE_URL=$(gcloud run services describe judge --region us-central1 --format='value(status.url)')
    CONTENT_BUILDER_URL=$(gcloud run services describe content-builder --region us-central1 --format='value(status.url)')
    
    echo "Researcher: $RESEARCHER_URL"
    echo "Judge: $JUDGE_URL"
    echo "Content Builder: $CONTENT_BUILDER_URL"
    
  3. פריסת ה-Orchestrator: משתמשים במשתני הסביבה שתועדו כדי להגדיר את ה-Orchestrator.
    gcloud run deploy orchestrator \
      --source agents/orchestrator/ \
      --region us-central1 \
      --allow-unauthenticated \
      --labels dev-tutorial=prod-ready-1 \
      --set-env-vars RESEARCHER_AGENT_CARD_URL=$RESEARCHER_URL/a2a/agent/.well-known/agent-card.json \
      --set-env-vars JUDGE_AGENT_CARD_URL=$JUDGE_URL/a2a/agent/.well-known/agent-card.json \
      --set-env-vars CONTENT_BUILDER_AGENT_CARD_URL=$CONTENT_BUILDER_URL/a2a/agent/.well-known/agent-card.json \
      --set-env-vars GOOGLE_CLOUD_PROJECT=$GOOGLE_CLOUD_PROJECT \
      --set-env-vars GOOGLE_GENAI_USE_VERTEXAI="true"
    
    מצלמים את כתובת ה-URL:
    ORCHESTRATOR_URL=$(gcloud run services describe orchestrator --region us-central1 --format='value(status.url)')
    echo $ORCHESTRATOR_URL
    
  4. פריסת ממשק הקצה:
    gcloud run deploy course-creator \
        --source app \
        --region us-central1 \
        --allow-unauthenticated \
        --labels dev-tutorial=prod-ready-1 \
        --set-env-vars AGENT_SERVER_URL=$ORCHESTRATOR_URL \
        --set-env-vars GOOGLE_CLOUD_PROJECT=$GOOGLE_CLOUD_PROJECT
    
  5. בדיקת פריסה מרחוק: פותחים את כתובת ה-URL של Orchestrator שנפרס. הפתרון פועל עכשיו כולו בענן, ומנצל את התשתית בלי שרת (serverless) של Google כדי להרחיב את השימוש בסוכנים!הערה: כל המיקרו-שירותים וכתובות ה-URL שלהם מופיעים בממשק Cloud Run.

13. סיכום

מעולה! יצרתם ופרסתם בהצלחה מערכת מבוזרת מרובת סוכנים שמוכנה לייצור.

מה השגנו

  • פירוק משימה מורכבת: במקום הנחיה אחת גדולה, פיצלנו את העבודה לתפקידים מיוחדים (חוקר, שופט, יוצר תוכן).
  • הטמענו בקרת איכות: השתמשנו בLoopAgent ובJudge מובנה כדי להבטיח שרק מידע באיכות גבוהה יגיע לשלב הסופי.
  • מיועד לשימוש בסביבת ייצור: באמצעות פרוטוקול Agent-to-Agent (A2A) ו-Cloud Run, יצרנו מערכת שבה כל סוכן הוא מיקרו-שירות עצמאי שניתן להתאמה לעומס (scaling). השיטה הזו אמינה הרבה יותר מאשר הפעלה של הכול בסקריפט Python יחיד.
  • תזמור: השתמשנו ב-SequentialAgent וב-LoopAgent כדי להגדיר דפוסי זרימת בקרה ברורים.

השלבים הבאים

עכשיו שיש לכם את הבסיס, אתם יכולים להרחיב את המערכת הזו:

  • הוספת כלים נוספים: נותנים לחוקר גישה למסמכים פנימיים או לממשקי API.
  • שיפור השופט: מוסיפים קריטריונים ספציפיים יותר או אפילו שלב של "התערבות אנושית".
  • החלפת מודלים: אפשר לנסות להשתמש במודלים שונים לסוכנים שונים (למשל, מודל מהיר יותר לשופט ומודל חזק יותר לכותב התוכן).

עכשיו אתם מוכנים לבנות תהליכי עבודה מורכבים ואמינים מבוססי-סוכנים ב-Google Cloud.