Way Back Home - Level 1: Pinpoint Location


המשימה

משך: 2 דקות

כותרת

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

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

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

הגיע הזמן לחבר את כל החלקים.

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

‫⚠️ כדי להגיע לרמה הזו, צריך להשלים את רמה 0.

לפני שמתחילים, מוודאים שיש לכם:
- [ ] קובץ config.json בתיקיית הבסיס של הפרויקט עם מזהה המשתתף והקואורדינטות
- [ ] האווטאר שלכם גלוי במפה העולמית
- [ ] המשואה שלכם מוצגת (חלשה) בקואורדינטות שלכם

אם לא השלמתם את רמה 0, כדאי להתחיל משם.


מה תפַתחו

בשלב הזה, תבנו מערכת AI מרובת סוכנים שמנתחת ראיות ממקום ההתרסקות באמצעות עיבוד מקבילי:

אדריכלות


יעדי למידה

קונספט הנושאים שתלמד
מערכות מרובות סוכנים יצירת סוכנים מיוחדים עם אחריות אחת
ParallelAgent הרכבת סוכנים עצמאיים שיפעלו במקביל
before_agent_callback אחזור הגדרות והגדרת מצב לפני הפעלת הסוכן
ToolContext גישה לערכי מצב בפונקציות של כלים
שרתי MCP בהתאמה אישית כלי בנייה עם דפוס חובה (קוד Python ב-Cloud Run)
OneMCP BigQuery התחברות ל-MCP מנוהל של Google כדי לגשת ל-BigQuery
AI מולטימודאלי ניתוח תמונות וסרטונים עם אודיו באמצעות Gemini
Agent Orchestration תיאום בין כמה סוכנים באמצעות מתזמן ראשי
Cloud Deployment פריסת שרת וסוכן MCP ב-Cloud Run
הכנה ל-A2A איך מבנים סוכנים כדי לאפשר תקשורת עתידית בין סוכנים

הביומים של כדור הארץ

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

ביום של כוכב לכת

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

ביום רביע הוכחות גיאולוגיות הוכחות בוטניות הוכחות אסטרונומיות
🧊 CRYO NW (x<50, y≥50) מתאן קפוא, גבישי קרח שרכים קפואים, צמחים קפואים כוכב ענק כחול
🌋 VOLCANIC NE (x≥50, y≥50) מכרות אובסידיאן פריחה של אש, צמחייה עמידה לחום מערכת בינארית של ננס אדום
💜 BIOLUMINESCENT SW (x<50, y<50) קרקע פוספורסנטית פטריות זוהרות, צמחים זוהרים פועם ירוק
🦴 מאובן SE (x≥50, y<50) מרבצי ענבר, מינרלים מסוג ite עצים מאובנים, צמחייה עתיקה שמש צהובה

המשימה שלכם: ליצור סוכני AI שינתחו את הראיות ויסיקו באיזה ביומה אתם נמצאים.

הגדרת הסביבה

משך: 3 דקות

לפני שמפיקים הוכחות, צריך להפעיל את ממשקי ה-API הנדרשים של Google Cloud, כולל OneMCP ל-BigQuery, שמספק גישה מנוהלת ל-MCP ב-BigQuery.

הרצת סקריפט להגדרת הסביבה

‫👈💻 מריצים את סקריפט הגדרת הסביבה:

cd ~/way-back-home/level_1
chmod +x setup/setup_env.sh
./setup/setup_env.sh

הפלט אמור להיראות כך:

================================================================
Level 1: Environment Setup
================================================================
Project: your-project-id

[1/6] Enabling core Google Cloud APIs...
       Vertex AI API enabled
       Cloud Run API enabled
       Cloud Build API enabled
       BigQuery API enabled
       Artifact Registry API enabled
       IAM API enabled

[2/6] Enabling OneMCP BigQuery (Managed MCP)...
       OneMCP BigQuery enabled

[3/6] Setting up service account and IAM permissions...
       Service account 'way-back-home-sa' created
       Vertex AI User role granted
       Cloud Run Invoker role granted
       BigQuery User role granted
       BigQuery Data Viewer role granted
       Storage Object Viewer role granted

[4/6] Configuring Cloud Build IAM for deployments...
       Cloud Build can now deploy services as way-back-home-sa
       Cloud Run Admin role granted to Compute SA

[5/6] Creating Artifact Registry repository...
       Repository 'way-back-home' created

[6/6] Creating environment variables file...
      Found PARTICIPANT_ID in config.json: abc123...
       Created ../set_env.sh

================================================================
 Environment Setup Complete!
================================================================

משתני סביבה של מקור

‫👈💻 מפעילים את משתני הסביבה:

source ~/way-back-home/set_env.sh

יצירת סביבה וירטואלית

‫👈💻 יוצרים ומפעילים את הסביבה הווירטואלית של Python לרמה 1:

cd ~/way-back-home/level_1
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

הגדרת קטלוג הכוכבים

‫👈💻 מגדירים את קטלוג הכוכבים ב-BigQuery:

python setup/setup_star_catalog.py

הפרטים שמוצגים הם:

Setting up star catalog in project: your-project-id
==================================================
✓ Dataset way_back_home already exists
✓ Created table star_catalog
✓ Inserted 12 rows into star_catalog

📊 Star Catalog Summary:
----------------------------------------
  NE (VOLCANIC): 3 stellar patterns
  NW (CRYO): 3 stellar patterns
  SE (FOSSILIZED): 3 stellar patterns
  SW (BIOLUMINESCENT): 3 stellar patterns
----------------------------------------
✓ Star catalog is ready for triangulation queries

==================================================
✅ Star catalog setup complete!

יצירת הוכחות לאתר קריסה

משך: 2 דקות

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

הפעלת מחולל ההוכחות

‫👈💻 בספרייה level_1 (עם הפעלת venv), מריצים את הפקודה:

cd ~/way-back-home/level_1
python generate_evidence.py

הפלט אמור להיראות כך:

 Welcome back, Explorer_Aria!
  Coordinates: (23, 67)
  Ready to analyze your crash site.

📍 Crash site analysis initiated...
   Generating evidence for your location...

🔬 Generating soil sample...
 Soil sample captured: outputs/soil_sample.png
 Capturing star field...
 Star field captured: outputs/star_field.png
🌿 Recording flora activity...
   (This may take 1-2 minutes for video generation)
   Generating video...
   Generating video...
   Generating video...
 Flora recorded: outputs/flora_recording.mp4

📤 Uploading evidence to Mission Control...
 Config updated with evidence URLs

==================================================
 Evidence generation complete!
==================================================

בדיקת ההוכחות

‫👈 כדאי להקדיש רגע לבדיקת קובצי הראיות שנוצרו בתיקייה outputs/. כל אחד מהם משקף את מאפייני הביום של מיקום ההתרסקות – אבל לא תדעו איזה ביום זה עד שהסוכנים שלכם מבוססי ה-AI ינתחו אותם!

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

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

פיתוח שרת MCP מותאם אישית

משך: 8 דקות

מערכות הניתוח המשולבות של חללית המילוט נשרפו, אבל נתוני החיישנים הגולמיים שרדו את ההתרסקות. תבנו שרת MCP עם FastMCP שמספק כלים לניתוח גיאולוגי ובוטני.

יצירת כלי לניתוח גיאולוגי

הכלי הזה מנתח תמונות של דגימות קרקע כדי לזהות את ההרכב המינרלי שלהן.

‫👉✏️ פותחים את mcp-server/main.py ומחפשים את #REPLACE-GEOLOGICAL-TOOL. מחליפים אותו ב:

GEOLOGICAL_PROMPT = """Analyze this alien soil sample image.

Classify the PRIMARY characteristic (choose exactly one):

1. CRYO - Frozen/icy minerals, crystalline structures, frost patterns,
   blue-white coloration, permafrost indicators

2. VOLCANIC - Volcanic rock, basalt, obsidian, sulfur deposits,
   red-orange minerals, heat-formed crystite structures

3. BIOLUMINESCENT - Glowing particles, phosphorescent minerals,
   organic-mineral hybrids, purple-green luminescence

4. FOSSILIZED - Ancient compressed minerals, amber deposits,
   petrified organic matter, golden-brown stratification

Respond ONLY with valid JSON (no markdown, no explanation):
{
    "biome": "CRYO|VOLCANIC|BIOLUMINESCENT|FOSSILIZED",
    "confidence": 0.0-1.0,
    "minerals_detected": ["mineral1", "mineral2"],
    "description": "Brief description of what you observe"
}
"""


@mcp.tool()
def analyze_geological(
    image_url: Annotated[
        str,
        Field(description="Cloud Storage URL (gs://...) of the soil sample image")
    ]
) -> dict:
    """
    Analyzes a soil sample image to identify mineral composition and classify the planetary biome.
    
    Args:
        image_url: Cloud Storage URL of the soil sample image (gs://bucket/path/image.png)
        
    Returns:
        dict with biome, confidence, minerals_detected, and description
    """
    logger.info(f">>> 🔬 Tool: 'analyze_geological' called for '{image_url}'")
    
    try:
        response = client.models.generate_content(
            model="gemini-2.5-flash",
            contents=[
                GEOLOGICAL_PROMPT,
                genai_types.Part.from_uri(file_uri=image_url, mime_type="image/png")
            ]
        )
        
        result = parse_json_response(response.text)
        logger.info(f"    ✓ Geological analysis complete: {result.get('biome', 'UNKNOWN')}")
        return result
        
    except Exception as e:
        logger.error(f"    ✗ Geological analysis failed: {str(e)}")
        return {"error": str(e), "biome": "UNKNOWN", "confidence": 0.0}

יצירת הכלי לניתוח בוטני

הכלי הזה מנתח הקלטות וידאו של צמחים – כולל טראק האודיו.

‫👉✏️ מחפשים את #REPLACE-BOTANICAL-TOOL ומחליפים אותו ב:

BOTANICAL_PROMPT = """Analyze this alien flora video recording.

Pay attention to BOTH:
1. VISUAL elements: Plant appearance, movement patterns, colors, bioluminescence
2. AUDIO elements: Ambient sounds, rustling, organic noises, frequencies

Classify the PRIMARY biome (choose exactly one):

1. CRYO - Crystalline ice-plants, frost-covered vegetation, 
   crackling/tinkling sounds, slow brittle movements, blue-white flora

2. VOLCANIC - Heat-resistant plants, sulfur-adapted species,
   hissing/bubbling sounds, smoke-filtering vegetation, red-orange flora

3. BIOLUMINESCENT - Glowing plants, pulsing light patterns,
   humming/resonating sounds, reactive to stimuli, purple-green flora

4. FOSSILIZED - Ancient petrified plants, amber-preserved specimens,
   deep resonant sounds, minimal movement, golden-brown flora

Respond ONLY with valid JSON (no markdown, no explanation):
{
    "biome": "CRYO|VOLCANIC|BIOLUMINESCENT|FOSSILIZED",
    "confidence": 0.0-1.0,
    "species_detected": ["species1", "species2"],
    "audio_signatures": ["sound1", "sound2"],
    "description": "Brief description of visual and audio observations"
}
"""


@mcp.tool()
def analyze_botanical(
    video_url: Annotated[
        str,
        Field(description="Cloud Storage URL (gs://...) of the flora video recording")
    ]
) -> dict:
    """
    Analyzes a flora video recording (visual + audio) to identify plant species and classify the biome.
    
    Args:
        video_url: Cloud Storage URL of the flora video (gs://bucket/path/video.mp4)
        
    Returns:
        dict with biome, confidence, species_detected, audio_signatures, and description
    """
    logger.info(f">>> 🌿 Tool: 'analyze_botanical' called for '{video_url}'")
    
    try:
        response = client.models.generate_content(
            model="gemini-2.5-flash",
            contents=[
                BOTANICAL_PROMPT,
                genai_types.Part.from_uri(file_uri=video_url, mime_type="video/mp4")
            ]
        )
        
        result = parse_json_response(response.text)
        logger.info(f"    ✓ Botanical analysis complete: {result.get('biome', 'UNKNOWN')}")
        return result
        
    except Exception as e:
        logger.error(f"    ✗ Botanical analysis failed: {str(e)}")
        return {"error": str(e), "biome": "UNKNOWN", "confidence": 0.0}

בדיקת שרת MCP באופן מקומי

‫👈💻 בדיקת שרת ה-MCP:

cd ~/way-back-home/level_1/mcp-server
pip install -r requirements.txt
python main.py

הפרטים שמוצגים הם:

[INFO] Initialized Gemini client for project: your-project-id
[INFO] 🚀 Location Analyzer MCP Server starting on port 8080
[INFO] 📍 MCP endpoint: http://0.0.0.0:8080/mcp
[INFO] 🔧 Tools: analyze_geological, analyze_botanical

שרת fastmcp

שרת FastMCP פועל עכשיו עם תעבורת HTTP. כדי להפסיק, לוחצים על Ctrl+C.

פריסת שרת MCP ב-Cloud Run

‫👈💻 פריסה:

cd ~/way-back-home/level_1/mcp-server
source ~/way-back-home/set_env.sh

gcloud builds submit . \
  --config=cloudbuild.yaml \
  --substitutions=_REGION="$REGION",_REPO_NAME="$REPO_NAME",_SERVICE_ACCOUNT="$SERVICE_ACCOUNT"

שמירת כתובת ה-URL של השירות

‫👉💻 שומרים את כתובת ה-URL של השירות:

export MCP_SERVER_URL=$(gcloud run services describe location-analyzer \
  --region=$REGION --format='value(status.url)')
echo "MCP Server URL: $MCP_SERVER_URL"

# Add to set_env.sh for later use
echo "export MCP_SERVER_URL=\"$MCP_SERVER_URL\"" >> ~/way-back-home/set_env.sh

יצירת נציגים מומחים

משך: 8 דקות

עכשיו תיצרו שלושה סוכנים מומחים, שלכל אחד מהם תהיה אחריות אחת.

יצירת סוכן לניתוח גיאולוגי

‫👉✏️ פותחים את agent/agents/geological_analyst.py ומחפשים את #REPLACE-GEOLOGICAL-AGENT. מחליפים אותו ב:

from google.adk.agents import Agent
from agent.tools.mcp_tools import get_geological_tool

geological_analyst = Agent(
    name="GeologicalAnalyst",
    model="gemini-2.5-flash",
    description="Analyzes soil samples to classify planetary biome based on mineral composition.",
    instruction="""You are a geological specialist analyzing alien soil samples.

## YOUR EVIDENCE TO ANALYZE
Soil sample URL: {soil_url}

## YOUR TASK
1. Call the analyze_geological tool with the soil sample URL above
2. Examine the results for mineral composition and biome indicators
3. Report your findings clearly

The four possible biomes are:
- CRYO: Frozen, icy minerals, blue/white coloring
- VOLCANIC: Magma, obsidian, volcanic rock, red/orange coloring
- BIOLUMINESCENT: Glowing, phosphorescent minerals, purple/green
- FOSSILIZED: Amber, ancient preserved matter, golden/brown

## REPORTING FORMAT
Always report your classification clearly:
"GEOLOGICAL ANALYSIS: [BIOME] (confidence: X%)"

Include a brief description of what you observed in the sample.

## IMPORTANT
- You do NOT synthesize with other evidence
- You do NOT confirm locations
- Just analyze the soil sample and report what you find
- Call the tool immediately with the URL provided above""",
    tools=[get_geological_tool()]
)

יצירת סוכן לניתוח בוטני

‫👉✏️ פותחים את agent/agents/botanical_analyst.py ומחפשים את #REPLACE-BOTANICAL-AGENT. מחליפים אותו ב:

from google.adk.agents import Agent
from agent.tools.mcp_tools import get_botanical_tool

botanical_analyst = Agent(
    name="BotanicalAnalyst",
    model="gemini-2.5-flash",
    description="Analyzes flora recordings to classify planetary biome based on plant life and ambient sounds.",
    instruction="""You are a botanical specialist analyzing alien flora recordings.

## YOUR EVIDENCE TO ANALYZE
Flora recording URL: {flora_url}

## YOUR TASK
1. Call the analyze_botanical tool with the flora recording URL above
2. Pay attention to BOTH visual AND audio elements in the recording
3. Report your findings clearly

The four possible biomes are:
- CRYO: Frost ferns, crystalline plants, cold wind sounds, crackling ice
- VOLCANIC: Fire blooms, heat-resistant flora, crackling/hissing sounds
- BIOLUMINESCENT: Glowing fungi, luminescent plants, ethereal hum, chiming
- FOSSILIZED: Petrified trees, ancient formations, deep resonant sounds

## REPORTING FORMAT
Always report your classification clearly:
"BOTANICAL ANALYSIS: [BIOME] (confidence: X%)"

Include descriptions of what you SAW and what you HEARD.

## IMPORTANT
- You do NOT synthesize with other evidence
- You do NOT confirm locations
- Just analyze the flora recording and report what you find
- Call the tool immediately with the URL provided above""",
    tools=[get_botanical_tool()]
)

יצירת סוכן לניתוח נתונים אסטרונומיים

הסוכן הזה משתמש בגישה שונה עם שני דפוסי כלים:

  1. Local FunctionTool: Gemini Vision to extract star features
  2. OneMCP BigQuery: הרצת שאילתות בקטלוג הכוכבים דרך ה-MCP המנוהל של Google

‫👉✏️ פותחים את agent/agents/astronomical_analyst.py ומחפשים את #REPLACE-ASTRONOMICAL-AGENT. מחליפים אותו ב:

from google.adk.agents import Agent
from agent.tools.star_tools import (
    extract_star_features_tool,
    get_bigquery_mcp_toolset,
)

# Get the BigQuery MCP toolset
bigquery_toolset = get_bigquery_mcp_toolset()

astronomical_analyst = Agent(
    name="AstronomicalAnalyst",
    model="gemini-2.5-flash",
    description="Analyzes star field images and queries the star catalog via OneMCP BigQuery.",
    instruction="""You are an astronomical specialist analyzing alien night skies.

## YOUR EVIDENCE TO ANALYZE
Star field URL: {stars_url}

## YOUR TWO TOOLS

### TOOL 1: extract_star_features (Local Gemini Vision)
Call this FIRST with the star field URL above.
Returns: "primary_star": "...", "nebula_type": "...", "stellar_color": "..."

### TOOL 2: BigQuery MCP (execute_query)
Call this SECOND with the results from Tool 1.
Use this exact SQL query (replace the placeholders with values from Step 1):

SELECT quadrant, biome, primary_star, nebula_type
FROM `{project_id}.way_back_home.star_catalog`
WHERE LOWER(primary_star) = LOWER('PRIMARY_STAR_FROM_STEP_1')
  AND LOWER(nebula_type) = LOWER('NEBULA_TYPE_FROM_STEP_1')
LIMIT 1

## YOUR WORKFLOW
1. Call extract_star_features with: {stars_url}
2. Get the primary_star and nebula_type from the result
3. Call execute_query with the SQL above (replacing placeholders)
4. Report the biome and quadrant from the query result

## BIOME REFERENCE
| Biome | Quadrant | Primary Star | Nebula Type |
|-------|----------|--------------|-------------|
| CRYO | NW | blue_giant | ice_blue |
| VOLCANIC | NE | red_dwarf_binary | fire |
| BIOLUMINESCENT | SW | green_pulsar | purple_magenta |
| FOSSILIZED | SE | yellow_sun | golden |

## REPORTING FORMAT
"ASTRONOMICAL ANALYSIS: [BIOME] in [QUADRANT] quadrant (confidence: X%)"

Include a description of the stellar features you observed.

## IMPORTANT
- You do NOT synthesize with other evidence
- You do NOT confirm locations
- Just analyze the stars and report what you find
- Start by calling extract_star_features with the URL above""",
    tools=[extract_star_features_tool, bigquery_toolset]
)

יצירת חיבורים לכלי MCP

משך: 8 דקות

יוצרים את עטיפות הכלים שמתחברות לשרת ה-MCP שפרסתם.

יצירת חיבור לכלי MCP (MCP בהתאמה אישית)

הפעולה הזו מתחברת לשרת FastMCP המותאם אישית שלכם שנפרס ב-Cloud Run.

‫👉✏️ פותחים את agent/tools/mcp_tools.py ומחפשים את #REPLACE-MCP-TOOL-CONNECTION. מחליפים אותו ב:

import os
import logging

from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset
from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams

logger = logging.getLogger(__name__)

MCP_SERVER_URL = os.environ.get("MCP_SERVER_URL")

_mcp_toolset = None

def get_mcp_toolset():
    """Get the MCPToolset connected to the location-analyzer server."""
    global _mcp_toolset
    
    if _mcp_toolset is not None:
        return _mcp_toolset
    
    if not MCP_SERVER_URL:
        raise ValueError(
            "MCP_SERVER_URL not set. Please run:\n"
            "  export MCP_SERVER_URL='https://location-analyzer-xxx.a.run.app'"
        )
    
    # FastMCP exposes MCP protocol at /mcp endpoint
    mcp_endpoint = f"{MCP_SERVER_URL}/mcp"
    logger.info(f"[MCP Tools] Connecting to: {mcp_endpoint}")
    
    _mcp_toolset = MCPToolset(
        connection_params=StreamableHTTPConnectionParams(
            url=mcp_endpoint,
            timeout=120,  # 2 minutes for Gemini analysis
        )
    )
    
    return _mcp_toolset

def get_geological_tool():
    """Get the geological analysis tool from the MCP server."""
    return get_mcp_toolset()

def get_botanical_tool():
    """Get the botanical analysis tool from the MCP server."""
    return get_mcp_toolset()

יצירת כלי ניתוח כוכבים (OneMCP BigQuery)

בקטע הזה מוצג דפוס MCP מנוהל. במקום לכתוב קוד לקוח משלנו ל-BigQuery, אנחנו מתחברים לשרת BigQuery של OneMCP של Google.

‫👉✏️ פותחים את agent/tools/star_tools.py ומחפשים את #REPLACE-STAR-TOOLS. מחליפים אותו ב:

import os
import json
import logging

from google import genai
from google.genai import types as genai_types
from google.adk.tools import FunctionTool
from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset
from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams
import google.auth
import google.auth.transport.requests

logger = logging.getLogger(__name__)

# =============================================================================
# CONFIGURATION - Environment variables only
# =============================================================================

PROJECT_ID = os.environ.get("GOOGLE_CLOUD_PROJECT", "")

if not PROJECT_ID:
    logger.warning("[Star Tools] GOOGLE_CLOUD_PROJECT not set")

# Initialize Gemini client for star feature extraction
genai_client = genai.Client(
    vertexai=True,
    project=PROJECT_ID or "placeholder",
    location=os.environ.get("GOOGLE_CLOUD_LOCATION", "us-central1")
)

logger.info(f"[Star Tools] Initialized for project: {PROJECT_ID}")

# =============================================================================
# OneMCP BigQuery Connection
# =============================================================================

BIGQUERY_MCP_URL = "https://bigquery.googleapis.com/mcp"

_bigquery_toolset = None

def get_bigquery_mcp_toolset():
    """
    Get the MCPToolset connected to Google's BigQuery MCP server.
    
    This uses OAuth 2.0 authentication with Application Default Credentials.
    The toolset provides access to BigQuery's pre-built MCP tools like:
    - execute_query: Run SQL queries
    - list_datasets: List available datasets
    - get_table_schema: Get table structure
   """
    global _bigquery_toolset
    
    if _bigquery_toolset is not None:
        return _bigquery_toolset
    
    logger.info("[Star Tools] Connecting to OneMCP BigQuery...")
    
    # Get OAuth credentials
    credentials, project_id = google.auth.default(
        scopes=["https://www.googleapis.com/auth/bigquery"]
    )
    
    # Refresh to get a valid token
    credentials.refresh(google.auth.transport.requests.Request())
    oauth_token = credentials.token
    
    # Configure headers for BigQuery MCP
    headers = {
        "Authorization": f"Bearer {oauth_token}",
        "x-goog-user-project": project_id or PROJECT_ID
    }
    
    # Create MCPToolset with StreamableHTTP connection
    _bigquery_toolset = MCPToolset(
        connection_params=StreamableHTTPConnectionParams(
            url=BIGQUERY_MCP_URL,
            headers=headers
        )
    )
    
    logger.info("[Star Tools] Connected to BigQuery MCP")
    return _bigquery_toolset


# =============================================================================
# Local FunctionTool: Star Feature Extraction
# =============================================================================
# This is a LOCAL tool that calls Gemini directly - demonstrating that
# you can mix local FunctionTools with MCP tools in the same agent.

STAR_EXTRACTION_PROMPT = """Analyze this alien night sky image and extract stellar features.

Identify:
1. PRIMARY STAR TYPE: blue_giant, red_dwarf, red_dwarf_binary, green_pulsar, yellow_sun, etc.
2. NEBULA TYPE: ice_blue, fire, purple_magenta, golden, etc.
3. STELLAR COLOR: blue_white, red_orange, green_purple, yellow_gold, etc.

Respond ONLY with valid JSON:
{"primary_star": "...", "nebula_type": "...", "stellar_color": "...", "description": "..."}
"""


def _parse_json_response(text: str) -> dict:
    """Parse JSON from Gemini response, handling markdown formatting."""
    cleaned = text.strip()
    if cleaned.startswith("```json"):
        cleaned = cleaned[7:]
    elif cleaned.startswith("```"):
        cleaned = cleaned[3:]
    if cleaned.endswith("```"):
        cleaned = cleaned[:-3]
    cleaned = cleaned.strip()
    
    try:
        return json.loads(cleaned)
    except json.JSONDecodeError as e:
        logger.error(f"Failed to parse JSON: {e}")
        return {"error": f"Failed to parse response: {str(e)}"}


def extract_star_features(image_url: str) -> dict:
    """
    Extract stellar features from a star field image using Gemini Vision.
    
    This is a LOCAL FunctionTool - we call Gemini directly, not through MCP.
    The agent will use this alongside the BigQuery MCP tools.
    """
    logger.info(f"[Stars] Extracting features from: {image_url}")
    
    response = genai_client.models.generate_content(
        model="gemini-2.5-flash",
        contents=[
            STAR_EXTRACTION_PROMPT,
            genai_types.Part.from_uri(file_uri=image_url, mime_type="image/png")
        ]
    )
    
    result = _parse_json_response(response.text)
    logger.info(f"[Stars] Extracted: primary_star={result.get('primary_star')}")
    return result


# Create the local FunctionTool
extract_star_features_tool = FunctionTool(extract_star_features)

יצירת כלי התזמור

משך: 8 דקות

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

יצירת צוות לניתוח מקביל

קודם, ניצור את פונקציית ה-callback ואת ParallelAgent שמפעיל את המומחים במקביל.

‫👉✏️ פותחים את agent/agent.py ומחפשים את #REPLACE-PARALLEL-CREW. מחליפים אותו ב:

import os
import logging
import httpx

from google.adk.agents import Agent, ParallelAgent
from google.adk.agents.callback_context import CallbackContext

# Import specialist agents
from agent.agents.geological_analyst import geological_analyst
from agent.agents.botanical_analyst import botanical_analyst
from agent.agents.astronomical_analyst import astronomical_analyst

# Import confirmation tool
from agent.tools.confirm_tools import confirm_location_tool

logger = logging.getLogger(__name__)


# =============================================================================
# BEFORE AGENT CALLBACK - Fetches config and sets state
# =============================================================================

async def setup_participant_context(callback_context: CallbackContext) -> None:
    """
    Fetch participant configuration and populate state for all agents.
    
    This callback:
    1. Reads PARTICIPANT_ID and BACKEND_URL from environment
    2. Fetches participant data from the backend API
    3. Sets state values: soil_url, flora_url, stars_url, username, x, y, etc.
    4. Returns None to continue normal agent execution
    """
    participant_id = os.environ.get("PARTICIPANT_ID", "")
    backend_url = os.environ.get("BACKEND_URL", "https://api.waybackhome.dev")
    project_id = os.environ.get("GOOGLE_CLOUD_PROJECT", "")
    
    logger.info(f"[Callback] Setting up context for participant: {participant_id}")
    
    # Set project_id and backend_url in state immediately
    callback_context.state["project_id"] = project_id
    callback_context.state["backend_url"] = backend_url
    callback_context.state["participant_id"] = participant_id
    
    if not participant_id:
        logger.warning("[Callback] No PARTICIPANT_ID set - using placeholder values")
        callback_context.state["username"] = "Explorer"
        callback_context.state["x"] = 0
        callback_context.state["y"] = 0
        callback_context.state["soil_url"] = "Not available - set PARTICIPANT_ID"
        callback_context.state["flora_url"] = "Not available - set PARTICIPANT_ID"
        callback_context.state["stars_url"] = "Not available - set PARTICIPANT_ID"
        return None
    
    # Fetch participant data from backend API
    try:
        url = f"{backend_url}/participants/{participant_id}"
        logger.info(f"[Callback] Fetching from: {url}")
        
        async with httpx.AsyncClient(timeout=30.0) as client:
            response = await client.get(url)
            response.raise_for_status()
            data = response.json()
        
        # Extract evidence URLs
        evidence_urls = data.get("evidence_urls", {})
        
        # Set all state values for sub-agents to access
        callback_context.state["username"] = data.get("username", "Explorer")
        callback_context.state["x"] = data.get("x", 0)
        callback_context.state["y"] = data.get("y", 0)
        callback_context.state["soil_url"] = evidence_urls.get("soil", "Not available")
        callback_context.state["flora_url"] = evidence_urls.get("flora", "Not available")
        callback_context.state["stars_url"] = evidence_urls.get("stars", "Not available")
        
        logger.info(f"[Callback] State populated for {data.get('username')}")
        
    except Exception as e:
        logger.error(f"[Callback] Error fetching participant config: {e}")
        callback_context.state["username"] = "Explorer"
        callback_context.state["x"] = 0
        callback_context.state["y"] = 0
        callback_context.state["soil_url"] = f"Error: {e}"
        callback_context.state["flora_url"] = f"Error: {e}"
        callback_context.state["stars_url"] = f"Error: {e}"
    
    return None


# =============================================================================
# PARALLEL ANALYSIS CREW
# =============================================================================

evidence_analysis_crew = ParallelAgent(
    name="EvidenceAnalysisCrew",
    description="Runs geological, botanical, and astronomical analysis in parallel.",
    sub_agents=[geological_analyst, botanical_analyst, astronomical_analyst]
)

יצירת ה-Root Orchestrator

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

‫👉✏️ באותו קובץ (agent/agent.py), מחפשים את #REPLACE-ROOT-ORCHESTRATOR. מחליפים אותו ב:

# =============================================================================
# ROOT ORCHESTRATOR
# =============================================================================

root_agent = Agent(
    name="MissionAnalysisAI",
    model="gemini-2.5-flash",
    description="Coordinates crash site analysis to confirm explorer location.",
    instruction="""You are the Mission Analysis AI coordinating a rescue operation.

## Explorer Information
- Name: {username}
- Coordinates: ({x}, {y})

## Evidence URLs (automatically provided to specialists via state)
- Soil sample: {soil_url}
- Flora recording: {flora_url}
- Star field: {stars_url}

## Your Workflow

### STEP 1: DELEGATE TO ANALYSIS CREW
Tell the EvidenceAnalysisCrew to analyze all the evidence.
The evidence URLs are already available to the specialists.

### STEP 2: COLLECT RESULTS
Each specialist will report:
- "GEOLOGICAL ANALYSIS: [BIOME] (confidence: X%)"
- "BOTANICAL ANALYSIS: [BIOME] (confidence: X%)"
- "ASTRONOMICAL ANALYSIS: [BIOME] in [QUADRANT] quadrant (confidence: X%)"

### STEP 3: APPLY 2-OF-3 AGREEMENT RULE
- If 2 or 3 specialists agree → that's the answer
- If all 3 disagree → use judgment based on confidence

### STEP 4: CONFIRM LOCATION
Call confirm_location with the determined biome.

## Biome Reference
| Biome | Quadrant | Key Characteristics |
|-------|----------|---------------------|
| CRYO | NW | Frozen, blue, ice crystals |
| VOLCANIC | NE | Magma, red/orange, obsidian |
| BIOLUMINESCENT | SW | Glowing, purple/green |
| FOSSILIZED | SE | Amber, golden, ancient |

## Response Style
Be encouraging and narrative! Celebrate when the beacon activates!
""",
    sub_agents=[evidence_analysis_crew],
    tools=[confirm_location_tool],
    before_agent_callback=setup_participant_context
)

יצירת הכלי לאימות מיקום

הכלי הזה משתמש ב-ToolContext כדי לקרוא ערכי סטטוס שהוגדרו על ידי הקריאה החוזרת.

‫👉✏️ בagent/tools/confirm_tools.py, מחפשים את #REPLACE-CONFIRM-TOOL. מחליפים אותו ב:

import os
import logging
import requests

from google.adk.tools import FunctionTool
from google.adk.tools.tool_context import ToolContext

logger = logging.getLogger(__name__)

BIOME_TO_QUADRANT = {
    "CRYO": "NW",
    "VOLCANIC": "NE",
    "BIOLUMINESCENT": "SW",
    "FOSSILIZED": "SE"
}


def _get_actual_biome(x: int, y: int) -> tuple[str, str]:
    """Determine actual biome and quadrant from coordinates."""
    if x < 50 and y >= 50:
        return "NW", "CRYO"
    elif x >= 50 and y >= 50:
        return "NE", "VOLCANIC"
    elif x < 50 and y < 50:
        return "SW", "BIOLUMINESCENT"
    else:
        return "SE", "FOSSILIZED"


def confirm_location(biome: str, tool_context: ToolContext) -> dict:
    """
    Confirm the explorer's location and activate the rescue beacon.
    
    Uses ToolContext to read state values set by before_agent_callback.
    """
    # Read from state (set by before_agent_callback)
    participant_id = tool_context.state.get("participant_id", "")
    x = tool_context.state.get("x", 0)
    y = tool_context.state.get("y", 0)
    backend_url = tool_context.state.get("backend_url", "https://api.waybackhome.dev")
    
    # Fallback to environment variables
    if not participant_id:
        participant_id = os.environ.get("PARTICIPANT_ID", "")
    if not backend_url:
        backend_url = os.environ.get("BACKEND_URL", "https://api.waybackhome.dev")

    if not participant_id:
        return {"success": False, "message": "❌ No participant ID available."}

    biome_upper = biome.upper().strip()

    if biome_upper not in BIOME_TO_QUADRANT:
        return {"success": False, "message": f"❌ Unknown biome: {biome}"}

    # Get actual biome from coordinates
    actual_quadrant, actual_biome = _get_actual_biome(x, y)

    if biome_upper != actual_biome:
        return {
            "success": False,
            "message": f"❌ Mismatch! Analysis: {biome_upper}, Actual: {actual_biome}"
        }

    quadrant = BIOME_TO_QUADRANT[biome_upper]

    try:
        response = requests.patch(
            f"{backend_url}/participants/{participant_id}/location",
            params={"x": x, "y": y},
            timeout=10
        )
        response.raise_for_status()

        return {
            "success": True,
            "message": f"🔦 BEACON ACTIVATED!\n\nLocation: {biome_upper} in {quadrant}\nCoordinates: ({x}, {y})"
        }

    except requests.exceptions.ConnectionError:
        return {
            "success": True,
            "message": f"🔦 BEACON ACTIVATED! (Local)\n\nLocation: {biome_upper} in {quadrant}",
            "simulated": True
        }

    except Exception as e:
        return {"success": False, "message": f"❌ Failed: {str(e)}"}


confirm_location_tool = FunctionTool(confirm_location)

בדיקה באמצעות ממשק משתמש באינטרנט של ADK

משך: 5 דקות

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

הפעלת שרת האינטרנט של ADK

‫👈💻 מגדירים משתני סביבה ומפעילים את שרת האינטרנט של ADK:

cd ~/way-back-home/level_1
source ~/way-back-home/set_env.sh

# Verify environment is set
echo "PARTICIPANT_ID: $PARTICIPANT_ID"
echo "MCP Server: $MCP_SERVER_URL"

# Start ADK web server
adk web

הפרטים שמוצגים הם:

+-----------------------------------------------------------------------------+
| ADK Web Server started                                                      |
|                                                                             |
| For local testing, access at http://localhost:8000.                         |
+-----------------------------------------------------------------------------+

INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)

גישה לממשק המשתמש באינטרנט

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

סמל של תצוגה מקדימה באינטרנט

‫👈 מגדירים את היציאה ל-8000 ולוחצים על "שינוי ותצוגה מקדימה".

תיבת דו-שיח לשינוי היציאה

‫👈 ממשק האינטרנט של ADK ייפתח. בתפריט הנפתח, בוחרים באפשרות agent.

בחירת נציג

הרצת הניתוח

‫👉 בממשק הצ'אט, מקלידים:

Analyze the evidence from my crash site and confirm my location to activate the beacon.

צפייה במערכת מרובת סוכנים בפעולה:

הדגמה של ADK באינטרנט

  1. התנאי before_agent_callback מופעל ראשון, ומביא את נתוני המשתתפים
  2. הכלי הראשי לניהול תהליכים מקבל את הבקשה שלכם עם נתונים על המצב
  3. EvidenceAnalysisCrew מופעל (ParallelAgent)
  4. שלושה מומחים פועלים במקביל באמצעות תבניות {key}:
    • ‫GeologicalAnalyst → רואה את {soil_url} שנפתר מהמצב
    • ‫BotanicalAnalyst → רואה את {flora_url} כפתרון מהמצב
    • ‫AstronomicalAnalyst → רואה ש-{stars_url} ו-{project_id} סיימו את הטיפול
  5. כלי התזמור הבסיסי מבצע סינתזה (הסכמה של 2 מתוך 3)
  6. confirm_location נקרא עם ToolContext ‏→ ‏ "🔦 BEACON ACTIVATED!"

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

‫👈 בסיום הבדיקה, מקישים על Ctrl+C במסוף כדי לעצור את השרת.

פריסה ב-Cloud Run

משך: 5 דקות

עכשיו פורסים את המערכת מרובת הסוכנים ב-Cloud Run כדי להכין אותה ל-A2A.

פריסת הסוכן

‫👈💻 פריסה ל-Cloud Run באמצעות ADK CLI:

cd ~/way-back-home/level_1
source ~/way-back-home/set_env.sh

adk deploy cloud_run \
  --project=$GOOGLE_CLOUD_PROJECT \
  --region=$REGION \
  --service_name=mission-analysis-ai \
  --with_ui \
  --a2a \
  ./agent

כשמופיעה בקשה Allow unauthenticated invocations to [mission-analysis-ai] (y/N)?, מזינים y כדי לאפשר גישה לכולם.

הפלט אמור להיראות כך:

Building and deploying agent to Cloud Run...
✓ Container built successfully
✓ Deploying to Cloud Run...
✓ Service deployed: https://mission-analysis-ai-abc123-uc.a.run.app

Your agent is now live!

הגדרת משתני סביבה ב-Cloud Run

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

‫👈💻 מגדירים את משתני הסביבה הנדרשים:

gcloud run services update mission-analysis-ai \
  --region=$REGION \
  --set-env-vars="GOOGLE_CLOUD_PROJECT=$GOOGLE_CLOUD_PROJECT,GOOGLE_CLOUD_LOCATION=$REGION,MCP_SERVER_URL=$MCP_SERVER_URL,BACKEND_URL=$BACKEND_URL,PARTICIPANT_ID=$PARTICIPANT_ID,GOOGLE_GENAI_USE_VERTEXAI=True"

שמירת כתובת ה-URL של הסוכן

‫👉💻 קבלת כתובת ה-URL של הפריסה:

export AGENT_URL=$(gcloud run services describe mission-analysis-ai \
  --region=$REGION --format='value(status.url)')
echo "Agent URL: $AGENT_URL"

# Add to set_env.sh
echo "export LEVEL1_AGENT_URL=\"$AGENT_URL\"" >> ~/way-back-home/set_env.sh

אימות הפריסה

‫👈💻 כדי לבדוק את הנציג שפרסתם, פותחים את כתובת ה-URL בדפדפן (הדגל --with_ui פרס את ממשק האינטרנט של ADK) או בודקים באמצעות curl:

curl -X GET "$AGENT_URL/list-apps"

אמורה להופיע תגובה עם רשימה של הסוכנים שלכם.

סיכום

משך: דקה אחת

רשימת משימות לאימות

‫✅ MCP Server
- [ ] Deployed to Cloud Run
- [ ] analyze_geological tool works
- [ ] analyze_botanical tool works

‫✅ Specialist Agents
- [ ] GeologicalAnalyst uses {soil_url} from state
- [ ] BotanicalAnalyst uses {flora_url} from state
- [ ] AstronomicalAnalyst uses {stars_url} and {project_id} from state

‫✅ before_agent_callback
- [ ] מאחזר נתוני משתתפים מ-API של קצה עורפי
- [ ] מגדיר ערכי מצב לכל הסוכנים המשניים
- [ ] פועל עם PARTICIPANT_ID מהסביבה

‫✅ ParallelAgent
‫– [ ] כל שלושת המומחים פועלים בו-זמנית
‫– [ ] המצב משותף באמצעות InvocationContext

‫✅ Root Orchestrator
- [ ] Synthesizes with 2-of-3 agreement
- [ ] confirm_location uses ToolContext for state
- [ ] Beacon activates!

‫✅ Deployment
- [ ] Agent deployed to Cloud Run
- [ ] A2A endpoint accessible

‫✅ World Map
- [ ] Beacon is now BRIGHT (not dim)
- [ ] Biome is displayed on hover


🎉 רמה 1 הושלמה!

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

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

המיקום נמצא

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


פתרון בעיות

‫"MCP_SERVER_URL not set" bash export MCP_SERVER_URL=$(gcloud run services describe location-analyzer \ --region=$REGION --format='value(status.url)')

"PARTICIPANT_ID not set" bash source ~/way-back-home/set_env.sh echo $PARTICIPANT_ID

"לא נמצאה טבלה ב-BigQuery" bash python setup/setup_star_catalog.py

"מומחים מבקשים כתובות URL" המשמעות היא שתבניות {key} לא פועלות. בודקים: - האם before_agent_callback מוגדר בסוכן הבסיסי? – האם הערכים של מצב הגדרת ה-callback נכונים? – האם סוכני משנה משתמשים ב-{soil_url} (לא ב-f-strings)?

"כל שלושת הניתוחים לא מסכימים" יצירת הוכחות מחדש: python generate_evidence.py

'הסוכן לא מגיב ב-ADK web' - בודקים שהיציאה 8000 נכונה - מוודאים שהוגדרו MCP_SERVER_URL ו-PARTICIPANT_ID - בודקים אם יש הודעות שגיאה במסוף


סיכום אדריכלי

רכיב סוג דוגמת קוד מטרה
setup_participant_context התקשרות חזרה before_agent_callback מאחזרים את ההגדרה, מגדירים את המצב
GeologicalAnalyst סוכן תבנית {soil_url} סיווג אדמה
BotanicalAnalyst סוכן תבנית {flora_url} סיווג של צמחים
AstronomicalAnalyst סוכן {stars_url}, {project_id} טריאנגולציה של כוכבים
confirm_location כלי גישה למצב ToolContext הפעלת משואת ה-Bluetooth
EvidenceAnalysisCrew ParallelAgent הרכב סוכני משנה הפעלת מומחים במקביל
MissionAnalysisAI נציג (שורש) כלי תזמור + קריאה חוזרת (callback) תיאום + סינתזה
location-analyzer שרת FastMCP ‫MCP בהתאמה אישית ניתוח גיאולוגי + בוטני
bigquery.googleapis.com/mcp OneMCP Managed MCP גישה ל-BigQuery

מושגים מרכזיים שנלמדו

‫✅ before_agent_callback: אחזור הגדרות לפני הפעלת הסוכן
‫✅ {key} State Templating: גישה לערכי מצב בהוראות לסוכן
‫✅ ToolContext: גישה לערכי מצב בפונקציות של כלי
‫✅ State Sharing: מצב האב זמין אוטומטית לסוכני המשנה באמצעות InvocationContext
‫✅ Multi-Agent Architecture: סוכנים ייעודיים עם אחריות יחידה
‫✅ ParallelAgent: ביצוע מקביל של משימות עצמאיות
‫✅ Custom MCP Server: שרת MCP משלכם ב-Cloud Run
‫✅ OneMCP BigQuery: תבנית MCP מנוהלת לגישה למסד נתונים
‫✅ Cloud Deployment: פריסה ללא שמירת מצב באמצעות משתני סביבה
‫✅ A2A Preparation: הסוכן מוכן לתקשורת בין סוכנים


למי שלא משחקים: יישומים בעולם האמיתי

התכונה 'איתור המיקום שלך' מייצגת ניתוח מקביל של מומחים עם קונצנזוס – הפעלה של כמה ניתוחי AI מיוחדים בו-זמנית וסינתוז של התוצאות.

אפליקציות ארגוניות

תרחיש לדוגמה מומחים מקבילים כלל סינתזה
אבחון רפואי מנתח תמונות, מנתח תסמינים, מנתח מעבדה סף ביטחון של 2 מתוך 3
זיהוי הונאות מנתח עסקאות, מנתח התנהגות, מנתח רשת כל סימון אחד = בדיקה
עיבוד מסמכים סוכן OCR, סוכן סיווג, סוכן חילוץ נדרשת הסכמה של כולם
בקרת איכות בודק חזותי, מנתח חיישנים, בודק מפרטים כרטיס 2 מתוך 3

תובנות חשובות לגבי הארכיטקטורה

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

  2. {key} State Templating: הצהרתי, נקי ואידיומטי. אין f-strings, אין ייבוא, אין מניפולציה של sys.path.

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

  4. ParallelAgent למשימות עצמאיות: כשניתוחים לא תלויים זה בזה, כדאי להריץ אותם במקביל כדי לחסוך זמן.

  5. שני דפוסי MCP: מותאם אישית (בנייה עצמית) לעומת OneMCP (אירוח של Google). שניהם משתמשים ב-StreamableHTTP.

  6. פריסה ללא שמירת מצב: אותו קוד פועל באופן מקומי ובפריסה. משתני סביבה + backend API = אין קובצי הגדרות אישיות במאגרי מידע.


מה השלב הבא?

רמה 2: עיבוד אותות SOS →

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