1. Introduzione
In questo lab, andrai oltre i semplici chatbot e creerai un sistema multi-agente distribuito.
Anche se un singolo LLM può rispondere alle domande, la complessità del mondo reale spesso richiede ruoli specializzati. Non chiedi al tuo ingegnere backend di progettare la UI e non chiedi al tuo designer di ottimizzare le query del database. Allo stesso modo, possiamo creare agenti AI specializzati che si concentrano su un'attività e si coordinano tra loro per risolvere problemi complessi.
Creerai un sistema di creazione di corsi composto da:
- Agente ricercatore: utilizza google_search per trovare informazioni aggiornate.
- Giudice dell'agente: valuta la qualità e la completezza della ricerca.
- Agente Content Builder: trasforma la ricerca in un corso strutturato.
- Agente orchestratore: gestione del flusso di lavoro e della comunicazione tra questi specialisti.
Obiettivi didattici
- Definisci un agente che utilizza strumenti (ricercatore) in grado di effettuare ricerche sul web.
- Implementa l'output strutturato con Pydantic per il giudice.
- Connettiti ad agenti remoti utilizzando il protocollo A2A (Agent-to-Agent).
- Crea un LoopAgent per creare un ciclo di feedback tra il ricercatore e il giudice.
- Esegui il sistema distribuito localmente utilizzando l'ADK.
- Esegui il deployment del sistema multi-agente in Google Cloud Run.
- Utilizza un modello Gemma su una GPU Cloud Run per l'agente di creazione dei contenuti.
Che cosa ti serve
- Un browser web come Chrome
- Un progetto cloud Google Cloud con la fatturazione abilitata
2. Principi di architettura e orchestrazione
Innanzitutto, vediamo come funzionano insieme questi agenti. Stiamo creando una pipeline di creazione dei corsi.
Progettazione del sistema

Orchestrazione con gli agenti
Gli agenti standard (come il ricercatore) funzionano. Gli agenti orchestratori (come LoopAgent o SequentialAgent) gestiscono altri agenti. Non hanno strumenti propri, il loro "strumento" è la delega.
LoopAgent: si comporta come un ciclowhilenel codice. Esegue una sequenza di agenti ripetutamente finché non viene soddisfatta una condizione (o non viene raggiunto il numero massimo di iterazioni). Utilizziamo questa funzionalità per il ciclo di ricerca:- Ricercatore trova informazioni.
- Judge lo critica.
- Se Judge indica "Fail", EscalationChecker consente al ciclo di continuare.
- Se Judge dice "Pass", EscalationChecker interrompe il ciclo.
SequentialAgent: si comporta come una normale esecuzione di script. Esegue gli agenti uno dopo l'altro. Lo utilizziamo per la pipeline di alto livello:- Innanzitutto, esegui il ciclo di ricerca (finché non termina con dati validi).
- Quindi, esegui Content Builder (per scrivere il corso).
Combinando questi elementi, creiamo un sistema solido in grado di autocorreggersi prima di generare l'output finale.
3. Configurazione
Configurazione del progetto
Crea un progetto Google Cloud
- Nella console Google Cloud, nella pagina di selezione del progetto, seleziona o crea un progetto Google Cloud.
- Verifica che la fatturazione sia attivata per il tuo progetto Cloud. Scopri come verificare se la fatturazione è abilitata per un progetto.
Avvia Cloud Shell
Cloud Shell è un ambiente a riga di comando in esecuzione in Google Cloud che viene precaricato con gli strumenti necessari.
- Fai clic su Attiva Cloud Shell nella parte superiore della console Google Cloud.
- Una volta connesso a Cloud Shell, verifica l'autenticazione:
gcloud auth list - Verifica che il progetto sia configurato:
gcloud config get project - Se il progetto non è impostato come previsto, impostalo:
export PROJECT_ID=<YOUR_PROJECT_ID> gcloud config set project $PROJECT_ID
Configurazione dell'ambiente
- Apri Cloud Shell: fai clic sull'icona Attiva Cloud Shell in alto a destra nella console Google Cloud.
Recuperare il codice iniziale
- Clona il repository iniziale nella tua home directory:sposta nella home directory
Clona solo il codice necessario per questo codelab dalla cartella Google Cloud DevRel Demos.cd ~ Passa alla cartella contenente il codice per questo codelabgit clone --depth 1 --filter=blob:none --sparse https://github.com/GoogleCloudPlatform/devrel-demos.git temp-repo && cd temp-repo && git sparse-checkout set agents/multi-agent-system && cd .. && mv temp-repo/agents/multi-agent-system . && rm -rf temp-repocd multi-agent-system - Abilita le API: esegui il seguente comando per abilitare i servizi Google Cloud necessari:
gcloud services enable \ run.googleapis.com \ artifactregistry.googleapis.com \ cloudbuild.googleapis.com \ aiplatform.googleapis.com \ compute.googleapis.com - Apri questa cartella nell'editor.
cloudshell edit .
Configura l'ambiente
- Configura le variabili di ambiente.Creeremo un file
.envper archiviare queste variabili in modo che tu possa ricaricarle facilmente se la sessione si disconnette.cat <<EOF > .env export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project) export GOOGLE_CLOUD_LOCATION=europe-west4 export GOOGLE_GENAI_USE_VERTEXAI=true EOF - Recupera le variabili di ambiente:
source .env
4. 🕵️ L'agente di ricerca

Il Ricercatore è uno specialista. Il suo unico compito è trovare informazioni. Per farlo, deve accedere a uno strumento: la Ricerca Google.
Perché separare il ruolo di Ricercatore?
Approfondimento:perché non avere un solo agente che faccia tutto?
Gli agenti piccoli e mirati sono più facili da valutare e debuggare. Se la ricerca non è buona, devi modificare il prompt del ricercatore. Se la formattazione del corso non è corretta, devi apportare modifiche al Content Builder. In un prompt monolitico "fai tutto", la correzione di un elemento spesso ne compromette un altro.
- Se lavori in Cloud Shell, esegui questo comando per aprire l'editor di Cloud Shell:
cloudshell workspace . - Apri
agents/researcher/agent.py. - Esamina il seguente codice che definisce l'agente
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
Concetto chiave: utilizzo dello strumento
Nota che passiamo tools=[google_search]. L'ADK gestisce la complessità della descrizione di questo strumento per l'LLM. Quando il modello decide di aver bisogno di informazioni, genera una chiamata di strumento strutturata, l'ADK esegue la funzione Python google_search e restituisce il risultato al modello.
5. ⚖️ L'agente giudice

Il ricercatore lavora sodo, ma gli LLM possono essere pigri. Abbiamo bisogno di un Giudice per esaminare il lavoro. Il giudice accetta la ricerca e restituisce una valutazione strutturata di superamento/non superamento.
Output strutturato
Approfondimento:per automatizzare i flussi di lavoro, abbiamo bisogno di output prevedibili. Una recensione testuale prolissa è difficile da analizzare a livello di programmazione. Applicando uno schema JSON (utilizzando Pydantic), ci assicuriamo che Judge restituisca un valore booleano pass o fail su cui il nostro codice può agire in modo affidabile.
- Apri
agents/judge/agent.py. - Esamina il seguente codice che definisce lo schema
JudgeFeedbacke l'agentejudge.# 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
Concetto chiave: limitare il comportamento dell'agente
Abbiamo impostato disallow_transfer_to_parent=True e disallow_transfer_to_peers=True. In questo modo, il giudice è obbligato a restituire solo il JudgeFeedback strutturato. Non può decidere di "chattare" con l'utente o delegare un altro agente. Ciò lo rende un componente deterministico nel nostro flusso logico.
6. ✍️ L'agente Content Builder

Content Builder è lo strumento di scrittura creativa. Prende la ricerca approvata e la trasforma in un corso. Utilizza un modello Gemma pubblicato da Cloud Run.
Diamo prima un'occhiata al servizio Cloud Run che ospita il modello
- Apri
ollama_backend/Dockerfile - Qui puoi vedere come il Dockerfile utilizza un'immagine Ollama, rimane in ascolto delle richieste sulla porta 8080 e archivia il modello richiesto in una cartella /model.
FROM ollama/ollama:latest # Listen on all interfaces, port 8080 (Cloud Run default) ENV OLLAMA_HOST 0.0.0.0:8080 # Store model weight files in /models ENV OLLAMA_MODELS /models
⚙️ Durante il deployment, configurerai quanto segue:
- GPU: NVIDIA L4 scelta per l'eccellente rapporto prezzo/prestazioni per i carichi di lavoro di inferenza. L4 fornisce 24 GB di memoria GPU e operazioni tensoriali ottimizzate, il che lo rende ideale per modelli con 270 milioni di parametri come Gemma
- Memoria: 16 GB di memoria di sistema per gestire il caricamento dei modelli, le operazioni CUDA e la gestione della memoria di Ollama
- CPU: 8 core per la gestione ottimale di I/O e attività di pre-elaborazione
- Concurrency: 4 richieste per istanza bilanciano la velocità effettiva con la memoria utilizzata della GPU
- Timeout: 600 secondi per il caricamento iniziale del modello e l'avvio del container
Ora esaminiamo l'agente Builder di contenuti che utilizza il modello Gemma.
- Apri
agents/content_builder/agent.py. - Esamina il seguente codice che definisce l'agente
content_builder.
# the `ollama-gemma-gpu` Cloud Run service URL which hosts the Gemma model
target_url = os.environ.get("OLLAMA_API_BASE")
# ... existing code ...
# (Note: We use 'ollama/gemma3:270m' to align with ADK's expected prefix)
gemma_model_name = os.environ.get("GEMMA_MODEL_NAME", "gemma3:270m")
model = LiteLlm(
model=f"ollama_chat/{gemma_model_name}",
api_base=target_url
)
# 5. Define the Agent
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. These will be used for the Table of Contents.
3. Use `###` (H3) for sub-sections within main sections.
4. Use bullet points and clear paragraphs.
5. Maintain a professional but engaging tone.
**Structure Requirements:**
- Begin with a brief Introduction section explaining what the learner will gain.
- Organize content into 3-5 main sections with clear headings.
- Include Key Takeaways at the end as a bulleted summary.
- Keep each section focused and concise.
Ensure the content directly addresses the user's original request.
Do not include any preamble or explanation outside the course content itself.
""",
)
root_agent = content_builder
Concetto chiave: propagazione del contesto
Ti starai chiedendo: "Come fa Content Builder a sapere cosa ha trovato il ricercatore?" Nell'ADK, gli agenti in una pipeline condividono un session.state. In un secondo momento, in Orchestrator configureremo Researcher e Judge in modo che salvino i loro output in questo stato condiviso. Il prompt di Content Builder ha effettivamente accesso a questa cronologia.
7. 🎻 L'organizzatore

L'Orchestratore è il gestore del nostro team multi-agente. A differenza degli agenti specializzati (Ricercatore, Giudice, Generatore di contenuti) che svolgono attività specifiche, il compito dell'orchestratore è coordinare il workflow e garantire che le informazioni fluiscano correttamente tra loro.
🌐 L'architettura: da agente ad agente (A2A)

In questo lab, creeremo un sistema distribuito. Anziché eseguire tutti gli agenti in un unico processo Python, li implementiamo come microservizi indipendenti. In questo modo, ogni agente può scalare in modo indipendente e non funzionare senza arrestare in modo anomalo l'intero sistema.
Per rendere possibile questa operazione, utilizziamo il protocollo Agent-to-Agent (A2A).
Il protocollo A2A
Approfondimento:in un sistema di produzione, gli agenti vengono eseguiti su server diversi (o anche su cloud diversi). Il protocollo A2A crea un modo standard per la scoperta e la comunicazione reciproca tramite HTTP. RemoteA2aAgent è il client ADK per questo protocollo.
- Apri
agents/orchestrator/agent.py. - Esamina il seguente codice che definisce le connessioni.
# ... 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. 🛑 Il controllo dell'escalation
Un ciclo deve avere un modo per fermarsi. Se il giudice dice "Passa", vogliamo uscire immediatamente dal ciclo e passare al Content Builder.
Logica personalizzata con BaseAgent
Approfondimento:non tutti gli agenti utilizzano gli LLM. A volte hai bisogno di una semplice logica Python. BaseAgent ti consente di definire un agente che esegue solo codice. In questo caso, controlliamo lo stato della sessione e utilizziamo EventActions(escalate=True) per segnalare a LoopAgent di interrompersi.
- Ancora in
agents/orchestrator/agent.py. - Rivedi il seguente codice, che esamina il feedback del giudice e procede al passaggio successivo quando è pronto
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")
Concetto chiave: controllo del flusso tramite eventi
Gli agenti comunicano non solo con il testo, ma anche con gli eventi. Generando un evento con escalate=True, questo agente invia un segnale al relativo elemento principale (LoopAgent). LoopAgent è programmato per intercettare questo segnale e terminare il loop.
9. 🔁 Il ciclo di ricerca

Abbiamo bisogno di un ciclo di feedback: Ricerca -> Giudizio -> (Errore) -> Ricerca -> ...
- In
agents/orchestrator/agent.py. - Esamina in che modo il seguente codice definisce la definizione
research_loop.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, )
Concetto chiave: LoopAgent
LoopAgent scorre in ordine le sub_agents.
researcher: trova i dati.judge: valuta i dati.escalation_checker: decide seyield Event(escalate=True). Se si verificaescalate=True, il ciclo si interrompe in anticipo. In caso contrario, riprende dalla ricercatrice (fino amax_iterations).
10. 🔗 La pipeline finale

Riepilogo…
- In
agents/orchestrator/agent.py. - Esamina la definizione di
root_agentin fondo al file.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], )
Concetto chiave: composizione gerarchica
Tieni presente che research_loop è a sua volta un agente (un LoopAgent). Lo trattiamo come qualsiasi altro sub-agente in SequentialAgent. Questa componibilità ti consente di creare logiche complesse nidificando pattern semplici (cicli all'interno di sequenze, sequenze all'interno di router e così via).
11. 🚀 Esegui il deployment in Cloud Run
Eseguiremo il deployment di ogni agente come servizio separato su Cloud Run, incluso un servizio Cloud Run per la UI del creatore del corso e un servizio Cloud Run che utilizza le GPU per il modello Gemma.
Informazioni sulla configurazione del deployment
Quando esegui il deployment degli agenti in Cloud Run, passiamo diverse variabili di ambiente per configurare il loro comportamento e la loro connettività:
GOOGLE_CLOUD_PROJECT: garantisce che l'agente utilizzi il progetto Google Cloud corretto per il logging e le chiamate Vertex AI.GOOGLE_GENAI_USE_VERTEXAI: indica al framework dell'agente (ADK) di utilizzare Vertex AI per l'inferenza del modello anziché chiamare direttamente le API Gemini.[AGENT]_AGENT_CARD_URL: questo è fondamentale per l'orchestratore. Indica all'agente di orchestrazione dove trovare gli agenti remoti. Se imposti questo valore sull'URL Cloud Run di cui è stato eseguito il deployment (in particolare il percorso della scheda dell'agente), consenti a Orchestrator di rilevare e comunicare con Researcher, Judge e Content Builder su internet.
Per eseguire il deployment di tutti gli agenti nei servizi Cloud Run, esegui questo script.
Innanzitutto, assicurati che lo script sia eseguibile.
chmod u+x ~/multi-agent-system/deploy.sh
Nota: l'esecuzione richiederà diversi minuti, poiché il deployment di ogni servizio viene eseguito in sequenza.
~/multi-agent-system/deploy.sh
12. Crea un corso.
Apri il sito web di Course Creator. Il servizio Cloud Run Course Creator è l'ultimo servizio di cui è stato eseguito il deployment dallo script. Puoi identificare l'URL del creatore del corso come https://course-creator-, che deve essere l'ultima riga di output dello script di deployment.
e digita un'idea per un corso, ad esempio "algebra lineare".
Gli agenti inizieranno a lavorare al tuo corso.

13. Elimina
Per evitare che al tuo account Google Cloud vengano addebitati costi relativi alle risorse utilizzate in questo codelab, segui questi passaggi per eliminare i servizi e le immagini container.
1. Elimina i servizi Cloud Run
Il modo più efficiente per liberare spazio è eliminare i servizi di cui hai eseguito il deployment su Cloud Run.
# Delete the main agent and app services
gcloud run services delete researcher content-builder judge orchestrator course-creator \
--region $REGION --quiet
# Delete the GPU backend (Ollama)
gcloud run services delete ollama-gemma-gpu \
--region $OLLAMA_REGION --quiet
2. Elimina le immagini di Artifact Registry
Quando hai utilizzato il flag --source per eseguire il deployment, Google Cloud ha creato un repository in Artifact Registry per archiviare le immagini container. Per rimuovere questi elementi e risparmiare sui costi di archiviazione, elimina il repository:
gcloud artifacts repositories delete cloud-run-source-deploy --location us-east4 --quiet
3. Rimuovere i file locali e l'ambiente
Per mantenere pulito l'ambiente Cloud Shell, rimuovi la cartella del progetto e qualsiasi configurazione locale:
cd ~
rm -rf multi-agent-system
4. (Facoltativo) Elimina il progetto
Se hai creato un progetto solo per questo codelab, puoi assicurarti che non venga addebitato alcun costo chiudendo il progetto stesso tramite la pagina Gestisci risorse.
14. Complimenti!
Hai creato e implementato correttamente un sistema multi-agente distribuito pronto per la produzione.
Cosa hai realizzato
- Decomposizione di un'attività complessa: anziché un unico prompt gigante, abbiamo suddiviso il lavoro in ruoli specializzati (Ricercatore, Giudice, Generatore di contenuti).
- Implementazione del controllo qualità: abbiamo utilizzato un
LoopAgente unJudgestrutturato per garantire che solo le informazioni di alta qualità raggiungano la fase finale. - Creato per la produzione: utilizzando il protocollo Agent-to-Agent (A2A) e Cloud Run, abbiamo creato un sistema in cui ogni agente è un microservizio indipendente e scalabile. Questo approccio è molto più solido rispetto all'esecuzione di tutto in un unico script Python.
- Orchestrazione: abbiamo utilizzato
SequentialAgenteLoopAgentper definire pattern di flusso di controllo chiari. *. GPU Cloud Run: è stato eseguito il deployment di un modello Gemma su una GPU Cloud Run