Aidemy: creazione di sistemi multi-agente con LangGraph, EDA e IA generativa su Google Cloud

1. Introduzione

Ciao! Quindi, ti piace l'idea degli agenti, piccoli aiutanti che possono fare le cose per te senza che tu debba fare nulla, giusto? Ottimo! Ma, diciamocelo, un agente non sempre è sufficiente, soprattutto quando devi gestire progetti più grandi e complessi. Probabilmente ne avrai bisogno di un intero team. Ed è qui che entrano in gioco i sistemi multi-agente.

Gli agenti, se basati su LLM, offrono un'incredibile flessibilità rispetto alla programmazione rigida tradizionale. Tuttavia, e c'è sempre un però, presentano una serie di sfide complicate. Ed è esattamente ciò che esamineremo in questo workshop.

titolo

Ecco cosa puoi aspettarti di imparare: pensa a questo corso come a un modo per far salire di livello il tuo gioco di agente:

Creazione del tuo primo agente con LangGraph: ci metteremo all'opera per creare il tuo agente utilizzando LangGraph, un framework molto diffuso. Imparerai a creare strumenti che si connettono ai database, a utilizzare l'API Gemini 2 più recente per alcune ricerche su internet e a ottimizzare i prompt e le risposte, in modo che l'agente possa interagire non solo con gli LLM, ma anche con i servizi esistenti. Ti mostreremo anche come funziona la chiamata di funzioni.

Orchestrazione degli agenti, a modo tuo: esploreremo diversi modi per orchestrare gli agenti, da semplici percorsi lineari a scenari multi-percorso più complessi. Pensa a come dirigere il flusso del team di agenti.

Sistemi multi-agente: scoprirai come configurare un sistema in cui i tuoi agenti possono collaborare e portare a termine i lavori insieme, il tutto grazie a un'architettura basata su eventi.

LLM Freedom: utilizza il meglio per il compito. Non ci limitiamo a un solo LLM. Scoprirai come utilizzare più LLM, assegnando loro ruoli diversi per migliorare la capacità di risolvere i problemi utilizzando "modelli di pensiero" interessanti.

Contenuti dinamici? Nessun problema.: Immagina che il tuo agente crei contenuti dinamici personalizzati per ogni utente in tempo reale. Ti mostreremo come.

Passare al cloud con Google Cloud: dimentica di limitarti a fare esperimenti su un notebook. Ti mostreremo come progettare ed eseguire il deployment del tuo sistema multi-agente su Google Cloud in modo che sia pronto per il mondo reale.

Questo progetto sarà un buon esempio di come utilizzare tutte le tecniche di cui abbiamo parlato.

2. Architettura

Fare l'insegnante o lavorare nel settore dell'istruzione può essere molto gratificante, ma, diciamocelo, il carico di lavoro, in particolare tutto il lavoro di preparazione, può essere impegnativo. Inoltre, spesso il personale non è sufficiente e i tutor possono essere costosi. Ecco perché stiamo proponendo un assistente didattico basato sull'IA. Questo strumento può alleggerire il carico degli insegnanti e contribuire a colmare il divario causato dalla carenza di personale e dalla mancanza di tutoraggio a prezzi accessibili.

Il nostro assistente didattico basato sull'IA può creare programmi dettagliati delle lezioni, quiz divertenti, riepiloghi audio facili da seguire e compiti personalizzati. In questo modo, gli insegnanti possono concentrarsi su ciò che sanno fare meglio: entrare in contatto con gli studenti e aiutarli a innamorarsi dell'apprendimento.

Il sistema ha due siti: uno per consentire agli insegnanti di creare i piani di lezione per le settimane successive,

Pianificatore

e un'altra per gli studenti per accedere a quiz, riepiloghi audio e compiti. Portale

Bene, esaminiamo l'architettura alla base del nostro assistente didattico, Aidemy. Come puoi vedere, l'abbiamo suddiviso in diversi componenti chiave, che lavorano tutti insieme per realizzare questo obiettivo.

Architettura

Elementi e tecnologie di architettura principali:

Google Cloud Platform (GCP): elemento centrale dell'intero sistema:

  • Vertex AI: consente di accedere ai modelli LLM di Gemini di Google.
  • Cloud Run: piattaforma serverless per il deployment di agenti e funzioni containerizzati.
  • Cloud SQL: database PostgreSQL per i dati dei curricula.
  • Pub/Sub ed Eventarc: la base dell'architettura basata su eventi, che consente la comunicazione asincrona tra i componenti.
  • Cloud Storage: memorizza i riepiloghi audio e i file dei compiti.
  • Secret Manager: gestisce in modo sicuro le credenziali del database.
  • Artifact Registry: archivia le immagini Docker per gli agenti.
  • Compute Engine: per eseguire il deployment di LLM auto-hosted anziché affidarsi alle soluzioni dei fornitori

LLM: il "cervello" del sistema:

  • Modelli Gemini di Google: (Gemini 1.0 Pro, Gemini 2 Flash, Gemini 2 Flash Thinking, Gemini 1.5-pro) utilizzati per la pianificazione delle lezioni, la generazione di contenuti, la creazione di HTML dinamico, la spiegazione dei quiz e la combinazione dei compiti.
  • DeepSeek: utilizzato per l'attività specializzata di generazione di compiti di apprendimento autonomo

LangChain e LangGraph: framework per lo sviluppo di applicazioni LLM

  • Facilita la creazione di flussi di lavoro complessi con più agenti.
  • Consente l'orchestrazione intelligente degli strumenti (chiamate API, query del database, ricerche web).
  • Implementa un'architettura basata su eventi per la scalabilità e la flessibilità del sistema.

In sostanza, la nostra architettura combina la potenza degli LLM con dati strutturati e comunicazione basata su eventi, il tutto in esecuzione su Google Cloud. In questo modo possiamo creare un assistente didattico scalabile, affidabile ed efficace.

3. Prima di iniziare

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 è attivata per un progetto.

👉Fai clic su Attiva Cloud Shell nella parte superiore della console Google Cloud (è l'icona a forma di terminale nella parte superiore del riquadro Cloud Shell), poi fai clic sul pulsante "Apri editor" (assomiglia a una cartella aperta con una matita). Nella finestra si aprirà l'editor di codice di Cloud Shell. Sul lato sinistro vedrai un esploratore di file.

Cloud Shell

👉Fai clic sul pulsante Accedi a Cloud Code nella barra di stato in basso, come mostrato. Autorizza il plug-in come da istruzioni. Se nella barra di stato viene visualizzato il messaggio Cloud Code - no project (Cloud Code - nessun progetto), selezionalo nel menu a discesa "Seleziona un progetto Google Cloud" e poi seleziona il progetto Google Cloud specifico dall'elenco dei progetti che hai creato.

Accedi al progetto

👉Apri il terminale nell'IDE cloud, Nuovo terminale

👉Nel terminale, verifica di aver già eseguito l'autenticazione e che il progetto sia impostato sul tuo ID progetto utilizzando il seguente comando:

gcloud auth list

👉Esegui:

gcloud config set project <YOUR_PROJECT_ID>

👉Esegui il seguente comando per abilitare le API Google Cloud necessarie:

gcloud services enable compute.googleapis.com  \
                        storage.googleapis.com  \
                        run.googleapis.com  \
                        artifactregistry.googleapis.com  \
                        aiplatform.googleapis.com \
                        eventarc.googleapis.com \
                        sqladmin.googleapis.com \
                        secretmanager.googleapis.com \
                        cloudbuild.googleapis.com \
                        cloudresourcemanager.googleapis.com \
                        cloudfunctions.googleapis.com

L'operazione potrebbe richiedere alcuni minuti.

Abilita Gemini Code Assist nell'IDE Cloud Shell

Fai clic sul pulsante Assistente codice nel riquadro a sinistra, come mostrato, e seleziona un'ultima volta il progetto Google Cloud corretto. Se ti viene chiesto di abilitare l'API Cloud AI Companion, fallo e vai avanti. Dopo aver selezionato il progetto Google Cloud, assicurati di poterlo vedere nel messaggio di stato di Cloud Code nella barra di stato e di aver attivato anche l'assistenza alla programmazione a destra, nella barra di stato, come mostrato di seguito:

Attivare codeassist

Impostazione dell'autorizzazione

👉Configura l'autorizzazione per l'account di servizio

export PROJECT_ID=$(gcloud config get project)
export SERVICE_ACCOUNT_NAME=$(gcloud compute project-info describe --format="value(defaultServiceAccount)")

echo "Here's your SERVICE_ACCOUNT_NAME $SERVICE_ACCOUNT_NAME"

Concedi autorizzazioni 👉Cloud Storage (lettura/scrittura):

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/storage.objectAdmin"

👉Pub/Sub (Pubblica/Ricevi):

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/pubsub.publisher"

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/pubsub.subscriber"

👉Cloud SQL (lettura/scrittura):

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/cloudsql.editor"

👉Eventarc (Ricevi eventi):

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/iam.serviceAccountTokenCreator"

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/eventarc.eventReceiver"


👉Vertex AI (utente):

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/aiplatform.user"

👉Secret Manager (lettura):

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$SERVICE_ACCOUNT_NAME" \
  --role="roles/secretmanager.secretAccessor"

👉Convalida il risultato nella console IAMConsole IAM

4. Creazione del primo agente

Prima di addentrarci in sistemi multi-agente complessi, dobbiamo stabilire un elemento costitutivo fondamentale: un singolo agente funzionale. In questa sezione, daremo i primi passi creando un semplice agente "fornitore di libri". L'agente del fornitore di libri riceve una categoria come input e utilizza un modello LLM di Gemini per generare una rappresentazione JSON del libro all'interno di quella categoria. Poi, li pubblica come endpoint dell'API REST .

Fornitore di servizi di prenotazione

👉In un'altra scheda del browser, apri la console Google Cloud nel browser web e vai a "Cloud Run" nel menu di navigazione (☰). Fai clic sul pulsante "+ ... SCRIVI UNA FUNZIONE".

Crea funzione

👉Successivamente, configureremo le impostazioni di base della funzione Cloud Run:

  • Nome servizio: book-provider
  • Regione: us-central1
  • Runtime: Python 3.12
  • Autenticazione: imposta Allow unauthenticated invocations su Attivata.

👉Lascia invariate le altre impostazioni e fai clic su Crea. Verrà visualizzato l'editor di codice sorgente.

Vedrai i file main.py e requirements.txt precompilati.

main.py conterrà la logica di business della funzione, mentre requirements.txt conterrà i pacchetti necessari.

👉Ora siamo pronti per scrivere del codice. Prima di iniziare, vediamo se Gemini Code Assist può darci un vantaggio. Torna all'editor di Cloud Shell, fai clic sull'icona di Gemini Code Assist e incolla la seguente richiesta nella casella del prompt: Gemini Code Assist

Use the functions_framework library to be deployable as an HTTP function. 
Accept a request with category and number_of_book parameters (either in JSON body or query string). 
Use langchain and gemini to generate the data for book with fields bookname, author, publisher, publishing_date. 
Use pydantic to define a Book model with the fields: bookname (string, description: "Name of the book"), author (string, description: "Name of the author"), publisher (string, description: "Name of the publisher"), and publishing_date (string, description: "Date of publishing"). 
Use langchain and gemini model to generate book data. the output should follow the format defined in Book model. 

The logic should use JsonOutputParser from langchain to enforce output format defined in Book Model. 
Have a function get_recommended_books(category) that internally uses langchain and gemini to return a single book object. 
The main function, exposed as the Cloud Function, should call get_recommended_books() multiple times (based on number_of_book) and return a JSON list of the generated book objects. 
Handle the case where category or number_of_book are missing by returning an error JSON response with a 400 status code. 
return a JSON string representing the recommended books. use os library to retrieve GOOGLE_CLOUD_PROJECT env var. Use ChatVertexAI from langchain for the LLM call

Code Assist genererà quindi una potenziale soluzione, fornendo sia il codice sorgente sia un file di dipendenze requirements.txt.

Ti invitiamo a confrontare il codice generato da Code Assist con la soluzione corretta testata fornita di seguito. In questo modo puoi valutare l'efficacia dello strumento e identificare eventuali discrepanze. Sebbene non sia mai consigliabile fidarsi ciecamente dei modelli LLM, Code Assist può essere un ottimo strumento per la prototipazione rapida e la generazione di strutture di codice iniziali e dovrebbe essere utilizzato per partire con il piede giusto.

Poiché si tratta di un laboratorio, procederemo con il codice verificato fornito di seguito. Tuttavia, non esitare a fare esperimenti con il codice generato da Code Assist in qualsiasi momento per acquisire una conoscenza più approfondita delle sue funzionalità e limitazioni.

👉Torna all'editor di codice sorgente della funzione Cloud Run (nell'altra scheda del browser). Sostituisci attentamente i contenuti esistenti di main.py con il codice fornito di seguito:

import functions_framework
import json
from flask import Flask, jsonify, request
from langchain_google_vertexai import ChatVertexAI
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.prompts import PromptTemplate
from pydantic import BaseModel, Field
import os

class Book(BaseModel):
    bookname: str = Field(description="Name of the book")
    author: str = Field(description="Name of the author")
    publisher: str = Field(description="Name of the publisher")
    publishing_date: str = Field(description="Date of publishing")


project_id = os.environ.get("GOOGLE_CLOUD_PROJECT")  

llm = ChatVertexAI(model_name="gemini-1.0-pro")

def get_recommended_books(category):
    """
    A simple book recommendation function. 

    Args:
        category (str): category

    Returns:
        str: A JSON string representing the recommended books.
    """
    parser = JsonOutputParser(pydantic_object=Book)
    question = f"Generate a random made up book on {category} with bookname, author and publisher and publishing_date"

    prompt = PromptTemplate(
        template="Answer the user query.\n{format_instructions}\n{query}\n",
        input_variables=["query"],
        partial_variables={"format_instructions": parser.get_format_instructions()},
    )
    
    chain = prompt | llm | parser
    response = chain.invoke({"query": question})

    return  json.dumps(response)
    

@functions_framework.http
def recommended(request):
    request_json = request.get_json(silent=True) # Get JSON data
    if request_json and 'category' in request_json and 'number_of_book' in request_json:
        category = request_json['category']
        number_of_book = int(request_json['number_of_book'])
    elif request.args and 'category' in request.args and 'number_of_book' in request.args:
        category = request.args.get('category')
        number_of_book = int(request.args.get('number_of_book'))

    else:
        return jsonify({'error': 'Missing category or number_of_book parameters'}), 400


    recommendations_list = []
    for i in range(number_of_book):
        book_dict = json.loads(get_recommended_books(category))
        print(f"book_dict=======>{book_dict}")
    
        recommendations_list.append(book_dict)

    
    return jsonify(recommendations_list)

👉Sostituisci i contenuti di requirements.txt con quanto segue:

functions-framework==3.*
google-genai==1.0.0
flask==3.1.0
jsonify==0.5
langchain_google_vertexai==2.0.13
langchain_core==0.3.34
pydantic==2.10.5

👉 Imposteremo l'entry point della funzione: recommended

03-02-function-create.png

👉Fai clic su SALVA E Esegui il deployment per eseguire il deployment della funzione. Attendi il completamento del processo di deployment. La console Cloud mostrerà lo stato. L'operazione potrebbe richiedere alcuni minuti.

testo alternativo 👉Una volta eseguito il deployment, torna nell'editor di Cloud Shell ed esegui nel terminale:

export PROJECT_ID=$(gcloud config get project)
export BOOK_PROVIDER_URL=$(gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)")

curl -X POST -H "Content-Type: application/json" -d '{"category": "Science Fiction", "number_of_book": 2}' $BOOK_PROVIDER_URL

Dovresti vedere alcuni dati del libro in formato JSON.

[
  {"author":"Anya Sharma","bookname":"Echoes of the Singularity","publisher":"NovaLight Publishing","publishing_date":"2077-03-15"},
  {"author":"Anya Sharma","bookname":"Echoes of the Quantum Dawn","publisher":"Nova Genesis Publishing","publishing_date":"2077-03-15"}
]

Complimenti! Hai eseguito correttamente il deployment di una funzione Cloud Run. Questo è uno dei servizi che integreremo durante lo sviluppo del nostro agente Aidemy.

5. Strumenti di creazione: collegamento degli agenti ai dati e al servizio RESTFUL

Scarica il progetto Bootstrap Skeleton. Assicurati di essere nell'editor di Cloud Shell. Nell'esecuzione del terminale,

git clone https://github.com/weimeilin79/aidemy-bootstrap.git

Dopo aver eseguito questo comando, verrà creata una nuova cartella denominata aidemy-bootstrap nel tuo ambiente Cloud Shell.

Nel riquadro di esplorazione dell'editor di Cloud Shell (di solito a sinistra), ora dovresti vedere la cartella creata quando hai clonato il repository Git aidemy-bootstrap. Apri la cartella principale del progetto in Explorer. Al suo interno troverai una sottocartella planner, apri anche questa. Esplorazione progetti

Iniziamo a creare gli strumenti che i nostri agenti utilizzeranno per diventare davvero utili. Come sai, gli LLM sono eccellenti nel ragionare e generare testo, ma hanno bisogno di accedere a risorse esterne per eseguire attività reali e fornire informazioni accurate e aggiornate. Pensa a questi strumenti come al "coltellino svizzero " dell'agente, che gli consente di interagire con il mondo.

Quando crei un agente, è facile inserire una serie di dettagli tramite la programmazione a livello di codice. In questo modo si crea un agente non flessibile. Creando e utilizzando strumenti, l'agente ha accesso a sistemi o logiche esterni che offrono i vantaggi sia dell'LLM sia della programmazione tradizionale.

In questa sezione creeremo le basi per l'agente di pianificazione, che gli insegnanti utilizzeranno per generare i programmi delle lezioni. Prima che l'agente inizi a generare un piano, vogliamo stabilire dei limiti fornendo maggiori dettagli sull'argomento e sull'argomento. Creeremo tre strumenti:

  1. Chiamata API REST: interazione con un'API preesistente per recuperare i dati.
  2. Query sul database:recupero di dati strutturati da un database Cloud SQL.
  3. Ricerca Google: accesso a informazioni in tempo reale dal web.

Recuperare consigli sui libri da un'API

Innanzitutto, creiamo uno strumento che recupera i consigli sui libri dall'API book-provider di cui abbiamo eseguito il deployment nella sezione precedente. Questo dimostra come un agente può sfruttare i servizi esistenti.

Consiglia un libro

Nell'editor di Cloud Shell, apri il progetto aidemy-bootstrap che hai clonato nella sezione precedente. 👉 Modifica book.py nella cartella planner e incolla il seguente codice:

def recommend_book(query: str):
    """
    Get a list of recommended book from an API endpoint
    
    Args:
        query: User's request string
    """

    region = get_next_region();
    llm = VertexAI(model_name="gemini-1.5-pro", location=region)

    query = f"""The user is trying to plan a education course, you are the teaching assistant. Help define the category of what the user requested to teach, respond the categroy with no more than two word.

    user request:   {query}
    """
    print(f"-------->{query}")
    response = llm.invoke(query)
    print(f"CATEGORY RESPONSE------------>: {response}")
    
    # call this using python and parse the json back to dict
    category = response.strip()
    
    headers = {"Content-Type": "application/json"}
    data = {"category": category, "number_of_book": 2}

    books = requests.post(BOOK_PROVIDER_URL, headers=headers, json=data)
   
    return books.text

if __name__ == "__main__":
    print(recommend_book("I'm doing a course for my 5th grade student on Math Geometry, I'll need to recommend few books come up with a teach plan, few quizes and also a homework assignment."))

Spiegazione:

  • recommend_book(query: str): questa funzione accetta come input la query di un utente.
  • Interazione con LLM: utilizza l'LLM per estrarre la categoria dalla query. Questo dimostra come puoi utilizzare l'LLM per creare parametri per gli strumenti.
  • Chiamata API: invia una richiesta POST all'API del fornitore di libri, passando la categoria e il numero di libri desiderato.

👉Per testare questa nuova funzione, imposta la variabile di ambiente ed esegui :

cd ~/aidemy-bootstrap/planner/
export BOOK_PROVIDER_URL=$(gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)")

👉Installa le dipendenze ed esegui il codice per assicurarti che funzioni, esegui:

cd ~/aidemy-bootstrap/planner/
python -m venv env
source env/bin/activate
export PROJECT_ID=$(gcloud config get project)
pip install -r requirements.txt
python book.py

Ignora la finestra popup dell'avviso Git.

Dovresti vedere una stringa JSON contenente consigli sui libri recuperati dall'API del fornitore di libri.

[{"author":"Anya Sharma","bookname":"Echoes of the Singularity","publisher":"NovaLight Publishing","publishing_date":"2077-03-15"},{"author":"Anya Sharma","bookname":"Echoes of the Quantum Dawn","publisher":"Nova Genesis Publishing","publishing_date":"2077-03-15"}]

Se vedi questo messaggio, significa che il primo strumento funziona correttamente.

Anziché creare esplicitamente una chiamata API RESTful con parametri specifici, utilizziamo il linguaggio naturale ("Sto frequentando un corso…"). L'agente estrae quindi in modo intelligente i parametri necessari (come la categoria) utilizzando l'NLP, evidenziando in che modo l'agente sfrutta la comprensione del linguaggio naturale per interagire con l'API.

confronta chiamata

👉Rimuovi il seguente codice di test da book.py

if __name__ == "__main__":
    print(recommend_book("I'm doing a course for my 5th grade student on Math Geometry, I'll need to recommend few books come up with a teach plan, few quizes and also a homework assignment."))

Recuperare i dati del curriculum da un database

Successivamente, creeremo uno strumento che recupera i dati strutturati dei programmi da un database PostgreSQL Cloud SQL. In questo modo, l'agente può accedere a una fonte attendibile di informazioni per la pianificazione delle lezioni.

create db

👉Esegui i comandi seguenti nel terminale per creare un'istanza Cloud SQL denominata aidemy . Questa procedura può richiedere del tempo.

gcloud sql instances create aidemy \
    --database-version=POSTGRES_14 \
    --cpu=2 \
    --memory=4GB \
    --region=us-central1 \
    --root-password=1234qwer \
    --storage-size=10GB \
    --storage-auto-increase

👉Successivamente, crea un database denominato aidemy-db nella nuova istanza.

gcloud sql databases create aidemy-db \
    --instance=aidemy

Verifichiamo l'istanza in Cloud SQL nella console Google Cloud. Dovresti vedere un'istanza Cloud SQL denominata aidemy. Fai clic sul nome dell'istanza per visualizzarne i dettagli. Nella pagina dei dettagli dell'istanza Cloud SQL, fai clic su "SQL Studio" nel menu di navigazione a sinistra. Si aprirà una nuova scheda.

Fai clic per connetterti al database. Accedi a SQL Studio

Seleziona aidemy-db come database. Inserisci postgres come utente e 1234qwer come password. accesso a sql studio

👉 Nell'editor di query di SQL Studio, incolla il seguente codice SQL:

CREATE TABLE curriculums (
    id SERIAL PRIMARY KEY,
    year INT,
    subject VARCHAR(255),
    description TEXT
);

-- Inserting detailed curriculum data for different school years and subjects
INSERT INTO curriculums (year, subject, description) VALUES
-- Year 5
(5, 'Mathematics', 'Introduction to fractions, decimals, and percentages, along with foundational geometry and problem-solving techniques.'),
(5, 'English', 'Developing reading comprehension, creative writing, and basic grammar, with a focus on storytelling and poetry.'),
(5, 'Science', 'Exploring basic physics, chemistry, and biology concepts, including forces, materials, and ecosystems.'),
(5, 'Computer Science', 'Basic coding concepts using block-based programming and an introduction to digital literacy.'),

-- Year 6
(6, 'Mathematics', 'Expanding on fractions, ratios, algebraic thinking, and problem-solving strategies.'),
(6, 'English', 'Introduction to persuasive writing, character analysis, and deeper comprehension of literary texts.'),
(6, 'Science', 'Forces and motion, the human body, and introductory chemical reactions with hands-on experiments.'),
(6, 'Computer Science', 'Introduction to algorithms, logical reasoning, and basic text-based programming (Python, Scratch).'),

-- Year 7
(7, 'Mathematics', 'Algebraic expressions, geometry, and introduction to statistics and probability.'),
(7, 'English', 'Analytical reading of classic and modern literature, essay writing, and advanced grammar skills.'),
(7, 'Science', 'Introduction to cells and organisms, chemical reactions, and energy transfer in physics.'),
(7, 'Computer Science', 'Building on programming skills with Python, introduction to web development, and cyber safety.');

Questo codice SQL crea una tabella denominata curriculums e inserisce alcuni dati di esempio. Fai clic su Esegui per eseguire il codice SQL. Dovresti visualizzare un messaggio di conferma che indica che i comandi sono stati eseguiti correttamente.

👉 Espandi l'esplorazione, trova la tabella appena creata e fai clic su Query. Dovrebbe aprirsi una nuova scheda dell'editor con il codice SQL generato per te.

sql studio select table

SELECT * FROM
  "public"."curriculums" LIMIT 1000;

👉Fai clic su Esegui.

La tabella dei risultati dovrebbe mostrare le righe di dati che hai inserito nel passaggio precedente, a conferma che la tabella e i dati sono stati creati correttamente.

Ora che hai creato un database con dati del curriculum di esempio compilati, creeremo uno strumento per recuperarli.

👉In Cloud Code Editor, modifica il file curriculums.py nella cartella aidemy-bootstrap e incolla il seguente codice:

def connect_with_connector() -> sqlalchemy.engine.base.Engine:

    db_user = os.environ["DB_USER"]
    db_pass = os.environ["DB_PASS"]
    db_name = os.environ["DB_NAME"]

    encoded_db_user = os.environ.get("DB_USER")
    print(f"--------------------------->db_user: {db_user!r}")  
    print(f"--------------------------->db_pass: {db_pass!r}") 
    print(f"--------------------------->db_name: {db_name!r}") 

    ip_type = IPTypes.PRIVATE if os.environ.get("PRIVATE_IP") else IPTypes.PUBLIC

    connector = Connector()

    def getconn() -> pg8000.dbapi.Connection:
        conn: pg8000.dbapi.Connection = connector.connect(
            instance_connection_name,
            "pg8000",
            user=db_user,
            password=db_pass,
            db=db_name,
            ip_type=ip_type,
        )
        return conn

    pool = sqlalchemy.create_engine(
        "postgresql+pg8000://",
        creator=getconn,
        pool_size=2,
        max_overflow=2,
        pool_timeout=30,  # 30 seconds
        pool_recycle=1800,  # 30 minutes
    )
    return pool



def init_connection_pool() -> sqlalchemy.engine.base.Engine:
    
    return (
        connect_with_connector()
    )

    raise ValueError(
        "Missing database connection type. Please define one of INSTANCE_HOST, INSTANCE_UNIX_SOCKET, or INSTANCE_CONNECTION_NAME"
    )

def get_curriculum(year: int, subject: str):
    """
    Get school curriculum
    
    Args:
        subject: User's request subject string
        year: User's request year int
    """
    try:
        stmt = sqlalchemy.text(
            "SELECT description FROM curriculums WHERE year = :year AND subject = :subject"
        )

        with db.connect() as conn:
            result = conn.execute(stmt, parameters={"year": year, "subject": subject})
            row = result.fetchone()
        if row:
            return row[0]  
        else:
            return None  

    except Exception as e:
        print(e)
        return None

db = init_connection_pool()

Spiegazione:

  • Variabili di ambiente: il codice recupera le credenziali del database e i dati di connessione dalle variabili di ambiente (di seguito sono riportate ulteriori informazioni in merito).
  • connect_with_connector(): questa funzione utilizza il connettore Cloud SQL per stabilire una connessione sicura al database.
  • get_curriculum(year: int, subject: str): questa funzione prende come input l'anno e l'argomento, esegue query sulla tabella dei curricula e restituisce la descrizione del curriculum corrispondente.

👉Prima di poter eseguire il codice, dobbiamo impostare alcune variabili di ambiente. Nel terminale, esegui:

export PROJECT_ID=$(gcloud config get project)
export INSTANCE_NAME="aidemy"
export REGION="us-central1"
export DB_USER="postgres"
export DB_PASS="1234qwer"
export DB_NAME="aidemy-db"

👉Per eseguire il test, aggiungi il seguente codice alla fine di curriculums.py:

if __name__ == "__main__":
    print(get_curriculum(6, "Mathematics"))

👉Esegui il codice:

cd ~/aidemy-bootstrap/planner/
source env/bin/activate
python curriculums.py

Nella console dovresti vedere la descrizione del programma di matematica per la 6a classe.

Expanding on fractions, ratios, algebraic thinking, and problem-solving strategies.

Se vedi la descrizione del curriculum, lo strumento di database funziona correttamente. Interrompi lo script premendo Ctrl+C.

👉Rimuovi il seguente codice di test da curriculums.py

if __name__ == "__main__":
    print(get_curriculum(6, "Mathematics"))

👉Esci dall'ambiente virtuale, nel terminale esegui:

deactivate

6. Strumenti di creazione: accedi alle informazioni in tempo reale dal web

Infine, creeremo uno strumento che utilizza l'integrazione di Gemini 2 e della Ricerca Google per accedere alle informazioni in tempo reale dal web. In questo modo l'agente può rimanere al passo con le novità e fornire risultati pertinenti.

L'integrazione di Gemini 2 con l'API Ricerca Google migliora le funzionalità degli agenti fornendo risultati di ricerca più accurati e pertinenti al contesto. In questo modo, gli agenti possono accedere a informazioni aggiornate e basare le loro risposte su dati reali, riducendo al minimo le allucinazioni. L'integrazione dell'API migliorata facilita anche le query in linguaggio più naturale, consentendo agli agenti di formulare richieste di ricerca complesse e sfumate.

Cerca

Questa funzione prende come input una query di ricerca, un programma, un argomento e un anno e utilizza l'API Gemini e lo strumento di ricerca Google per recuperare informazioni pertinenti da internet. Se guardi attentamente, vedrai che utilizza l'SDK di IA generativa di Google per eseguire chiamate di funzioni senza utilizzare altri framework.

👉 Modifica search.py nella cartella aidemy-bootstrap e incolla il seguente codice:

model_id = "gemini-2.0-flash-001"

google_search_tool = Tool(
    google_search = GoogleSearch()
)

def search_latest_resource(search_text: str, curriculum: str, subject: str, year: int):
    """
    Get latest information from the internet
    
    Args:
        search_text: User's request category   string
        subject: "User's request subject" string
        year: "User's request year"  integer
    """
    search_text = "%s in the context of year %d and subject %s with following curriculum detail %s " % (search_text, year, subject, curriculum)
    region = get_next_region()
    client = genai.Client(vertexai=True, project=PROJECT_ID, location=region)
    print(f"search_latest_resource text-----> {search_text}")
    response = client.models.generate_content(
        model=model_id,
        contents=search_text,
        config=GenerateContentConfig(
            tools=[google_search_tool],
            response_modalities=["TEXT"],
        )
    )
    print(f"search_latest_resource response-----> {response}")
    return response

if __name__ == "__main__":
  response = search_latest_resource("What are the syllabus for Year 6 Mathematics?", "Expanding on fractions, ratios, algebraic thinking, and problem-solving strategies.", "Mathematics", 6)
  for each in response.candidates[0].content.parts:
    print(each.text)

Spiegazione:

  • Definizione dello strumento - google_search_tool: inserisci l'oggetto GoogleSearch in uno strumento
  • search_latest_resource(search_text: str, subject: str, year: int): questa funzione prende come input una query di ricerca, un argomento e un anno e utilizza l'API Gemini per eseguire una ricerca su Google. Modello Gemini
  • GenerateContentConfig: definisci che ha accesso allo strumento GoogleSearch

Il modello Gemini analizza internamente il parametro search_text e determina se può rispondere direttamente alla domanda o se deve utilizzare lo strumento GoogleSearch. Si tratta di un passaggio fondamentale che si verifica all'interno del processo di ragionamento dell'LLM. Il modello è stato addestrato a riconoscere le situazioni in cui sono necessari strumenti esterni. Se il modello decide di utilizzare lo strumento di ricerca Google, l'SDK di IA generativa di Google gestisce l'invocazione effettiva. L'SDK prende la decisione del modello e i parametri che genera e li invia all'API Ricerca Google. Questa parte è nascosta all'utente nel codice.

Il modello Gemini integra quindi i risultati di ricerca nella sua risposta. Può utilizzare le informazioni per rispondere alla domanda dell'utente, generare un riepilogo o eseguire un'altra attività.

👉Per eseguire il test, esegui il codice:

cd ~/aidemy-bootstrap/planner/
export PROJECT_ID=$(gcloud config get project)
source env/bin/activate
python search.py

Dovresti vedere la risposta dell'API Gemini Search contenente i risultati di ricerca relativi a "Syllabus per matematica, 5° anno". L'output esatto dipenderà dai risultati di ricerca, ma sarà un oggetto JSON con informazioni sulla ricerca.

Se vedi i risultati di ricerca, lo strumento Ricerca Google funziona correttamente. Interrompi lo script premendo Ctrl+C.

👉E rimuovi l'ultima parte del codice.

if __name__ == "__main__":
  response = search_latest_resource("What are the syllabus for Year 6 Mathematics?", "Expanding on fractions, ratios, algebraic thinking, and problem-solving strategies.", "Mathematics", 6)
  for each in response.candidates[0].content.parts:
    print(each.text)

👉Esci dall'ambiente virtuale, nel terminale esegui:

deactivate

Complimenti! Ora hai creato tre potenti strumenti per l'agente di pianificazione: un connettore API, un connettore di database e uno strumento di ricerca Google. Questi strumenti consentiranno all'agente di accedere alle informazioni e alle funzionalità di cui ha bisogno per creare piani di insegnamento efficaci.

7. Orchestrazione con LangGraph

Ora che abbiamo creato i singoli strumenti, è il momento di orchestrarli utilizzando LangGraph. In questo modo potremo creare un agente "planner" più sofisticato che possa decidere in modo intelligente quali strumenti utilizzare e quando, in base alla richiesta dell'utente.

LangGraph è una libreria Python progettata per semplificare la creazione di applicazioni con stato e multi-attore utilizzando modelli linguistici di grandi dimensioni (LLM). Pensalo come a un framework per orchestrare conversazioni e flussi di lavoro complessi che coinvolgono LLM, strumenti e altri agenti.

Concetti chiave:

  • Struttura del grafo:LangGraph rappresenta la logica dell'applicazione come un grafo diretto. Ogni nodo nel grafico rappresenta un passaggio del processo (ad es. una chiamata a un modello LLM, un'invocazione di uno strumento, un controllo condizionale). Gli archi definiscono il flusso di esecuzione tra i nodi.
  • Stato: LangGraph gestisce lo stato dell'applicazione mentre si sposta nel grafico. Questo stato può includere variabili come l'input dell'utente, i risultati delle chiamate allo strumento, gli output intermedi degli LLM e qualsiasi altra informazione che deve essere conservata tra un passaggio e l'altro.
  • Nodi:ogni nodo rappresenta un calcolo o un'interazione. Possono essere:
    • Nodi dello strumento: utilizza uno strumento (ad es. esegui una ricerca web, esegui una query su un database)
    • Nodi funzione:esegui una funzione Python.
  • Edg: collegano i nodi, definendo il flusso di esecuzione. Possono essere:
    • Bordi diretti:un flusso semplice e incondizionato da un nodo all'altro.
    • Bordi condizionali:il flusso dipende dal risultato di un nodo condizionale.

LangGraph

Useremo LangGraph per implementare l'orchestrazione. Modifica il file aidemy.py nella cartella aidemy-bootstrap per definire la logica di LangGraph. 👉Aggiungi il seguente codice alla fine di aidemy.py:

tools = [get_curriculum, search_latest_resource, recommend_book]

def determine_tool(state: MessagesState):
    llm = ChatVertexAI(model_name="gemini-2.0-flash-001", location=get_next_region())
    sys_msg = SystemMessage(
                    content=(
                        f"""You are a helpful teaching assistant that helps gather all needed information. 
                            Your ultimate goal is to create a detailed 3-week teaching plan. 
                            You have access to tools that help you gather information.  
                            Based on the user request, decide which tool(s) are needed. 

                        """
                    )
                )

    llm_with_tools = llm.bind_tools(tools)
    return {"messages": llm_with_tools.invoke([sys_msg] + state["messages"])} 

Questa funzione è responsabile dell'acquisizione dello stato corrente della conversazione, del fornire all'LLM un messaggio di sistema e poi di chiedere all'LLM di generare una risposta. L'LLM può rispondere direttamente all'utente o scegliere di utilizzare uno degli strumenti disponibili.

tools : questo elenco rappresenta l'insieme di strumenti a disposizione dell'agente. Contiene tre funzioni dello strumento che abbiamo definito nei passaggi precedenti: get_curriculum, search_latest_resource e recommend_book. llm.bind_tools(tools): "collega" l'elenco di strumenti all'oggetto llm. La definizione degli strumenti indica all'LLM che questi strumenti sono disponibili e fornisce all'LLM informazioni su come utilizzarli (ad es. i nomi degli strumenti, i parametri accettati e la loro funzione).

Useremo LangGraph per implementare l'orchestrazione. 👉Aggiungi il seguente codice alla fine di aidemy.py:

def prep_class(prep_needs):
   
    builder = StateGraph(MessagesState)
    builder.add_node("determine_tool", determine_tool)
    builder.add_node("tools", ToolNode(tools))
    
    builder.add_edge(START, "determine_tool")
    builder.add_conditional_edges("determine_tool",tools_condition)
    builder.add_edge("tools", "determine_tool")

    
    memory = MemorySaver()
    graph = builder.compile(checkpointer=memory)

    config = {"configurable": {"thread_id": "1"}}
    messages = graph.invoke({"messages": prep_needs},config)
    print(messages)
    for m in messages['messages']:
        m.pretty_print()
    teaching_plan_result = messages["messages"][-1].content  


    return teaching_plan_result

if __name__ == "__main__":
  prep_class("I'm doing a course for  year 5 on subject Mathematics in Geometry, , get school curriculum, and come up with few books recommendation plus  search latest resources on the internet base on the curriculum outcome. And come up with a 3 week teaching plan")

Spiegazione:

  • StateGraph(MessagesState): crea un oggetto StateGraph. Un StateGraph è un concetto fondamentale di LangGraph. Rappresenta il flusso di lavoro dell'agente come un grafico, in cui ogni nodo del grafico rappresenta un passaggio del processo. Puoi considerarlo come la definizione del progetto di come l'agente ragionerà e agirà.
  • Edera condizionale:proveniente dal nodo "determine_tool", l'argomento tools_condition è probabilmente una funzione che determina quale bordo seguire in base all'output della funzione determine_tool. Gli archi condizionali consentono al grafo di ramificarsi in base alla decisione dell'LLM su quale strumento utilizzare (o se rispondere direttamente all'utente). È qui che entra in gioco l'"intelligenza" dell'agente, che può adattare dinamicamente il proprio comportamento in base alla situazione.
  • Anello:aggiunge un bordo al grafico che collega il nodo "tools" al nodo "determine_tool". Viene creato un ciclo nel grafico, che consente all'agente di utilizzare ripetutamente gli strumenti finché non ha raccolto informazioni sufficienti per completare l'attività e fornire una risposta soddisfacente. Questo ciclo è fondamentale per attività complesse che richiedono più passaggi di ragionamento e raccolta di informazioni.

Ora proviamo il nostro agente di pianificazione per vedere come orchestra i diversi strumenti.

Questo codice eseguirà la funzione prep_class con un input utente specifico, simulando una richiesta di creazione di un piano di insegnamento per la matematica della geometria della 5a elementare, utilizzando il curriculum, i consigli sui libri e le risorse internet più recenti.

Se hai chiuso il terminale o le variabili di ambiente non sono più impostate, esegui di nuovo i seguenti comandi

export BOOK_PROVIDER_URL=$(gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)")
export PROJECT_ID=$(gcloud config get project)
export INSTANCE_NAME="aidemy"
export REGION="us-central1"
export DB_USER="postgres"
export DB_PASS="1234qwer"
export DB_NAME="aidemy-db"

👉Esegui il codice:

cd ~/aidemy-bootstrap/planner/
source env/bin/activate
pip install -r requirements.txt
python aidemy.py

Controlla il log nel terminale. Dovresti vedere la prova che l'agente utilizza tutti e tre gli strumenti (richiedere il programma scolastico, ricevere consigli sui libri e cercare le risorse più recenti) prima di fornire il piano di insegnamento finale. Ciò dimostra che l'orchestrazione di LangGraph funziona correttamente e che l'agente utilizza in modo intelligente tutti gli strumenti disponibili per soddisfare la richiesta dell'utente.

================================ Human Message =================================

I'm doing a course for  year 5 on subject Mathematics in Geometry, , get school curriculum, and come up with few books recommendation plus  search latest resources on the internet base on the curriculum outcome. And come up with a 3 week teaching plan
================================== Ai Message ==================================
Tool Calls:
  get_curriculum (xxx)
 Call ID: xxx
  Args:
    year: 5.0
    subject: Mathematics
================================= Tool Message =================================
Name: get_curriculum

Introduction to fractions, decimals, and percentages, along with foundational geometry and problem-solving techniques.
================================== Ai Message ==================================
Tool Calls:
  search_latest_resource (xxxx)
 Call ID: xxxx
  Args:
    year: 5.0
    search_text: Geometry
    curriculum: {"content": "Introduction to fractions, decimals, and percentages, along with foundational geometry and problem-solving techniques."}
    subject: Mathematics
================================= Tool Message =================================
Name: search_latest_resource

candidates=[Candidate(content=Content(parts=[Part(.....) automatic_function_calling_history=[] parsed=None
================================== Ai Message ==================================
Tool Calls:
  recommend_book (93b48189-4d69-4c09-a3bd-4e60cdc5f1c6)
 Call ID: 93b48189-4d69-4c09-a3bd-4e60cdc5f1c6
  Args:
    query: Mathematics Geometry Year 5
================================= Tool Message =================================
Name: recommend_book

[{.....}]

================================== Ai Message ==================================

Based on the curriculum outcome, here is a 3-week teaching plan for year 5 Mathematics Geometry:

**Week 1: Introduction to Shapes and Properties**
.........

Interrompi lo script premendo Ctrl+C.

👉Ora sostituisci il codice di test con un prompt diverso, che richiede l'uso di strumenti diversi.

if __name__ == "__main__":
  prep_class("I'm doing a course for  year 5 on subject Mathematics in Geometry, search latest resources on the internet base on the subject. And come up with a 3 week teaching plan")

Se hai chiuso il terminale o le variabili di ambiente non sono più impostate, esegui di nuovo i seguenti comandi

export BOOK_PROVIDER_URL=$(gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)")
export PROJECT_ID=$(gcloud config get project)
export INSTANCE_NAME="aidemy"
export REGION="us-central1"
export DB_USER="postgres"
export DB_PASS="1234qwer"
export DB_NAME="aidemy-db"

👉Esegui di nuovo il codice:

cd ~/aidemy-bootstrap/planner/
source env/bin/activate
python aidemy.py

Cosa hai notato questa volta? Quali strumenti ha chiamato l\'agente? Dovresti notare che questa volta l'agente chiama solo lo strumento search_latest_resource. Questo perché il prompt non specifica che ha bisogno degli altri due strumenti e il nostro LLM è abbastanza intelligente da non chiamarli.

================================ Human Message =================================

I'm doing a course for  year 5 on subject Mathematics in Geometry, search latest resources on the internet base on the subject. And come up with a 3 week teaching plan
================================== Ai Message ==================================
Tool Calls:
  get_curriculum (xxx)
 Call ID: xxx
  Args:
    year: 5.0
    subject: Mathematics
================================= Tool Message =================================
Name: get_curriculum

Introduction to fractions, decimals, and percentages, along with foundational geometry and problem-solving techniques.
================================== Ai Message ==================================
Tool Calls:
  search_latest_resource (xxx)
 Call ID: xxxx
  Args:
    year: 5.0
    subject: Mathematics
    curriculum: {"content": "Introduction to fractions, decimals, and percentages, along with foundational geometry and problem-solving techniques."}
    search_text: Geometry
================================= Tool Message =================================
Name: search_latest_resource

candidates=[Candidate(content=Content(parts=[Part(.......token_count=40, total_token_count=772) automatic_function_calling_history=[] parsed=None
================================== Ai Message ==================================

Based on the information provided, a 3-week teaching plan for Year 5 Mathematics focusing on Geometry could look like this:

**Week 1:  Introducing 2D Shapes**
........
* Use visuals, manipulatives, and real-world examples to make the learning experience engaging and relevant.

Interrompi lo script premendo Ctrl+C. 👉Rimuovi il codice di test per mantenere pulito il file aidemy.py:

if __name__ == "__main__":
  prep_class("I'm doing a course for  year 5 on subject Mathematics in Geometry, search latest resources on the internet base on the subject. And come up with a 3 week teaching plan")

Ora che la logica dell'agente è stata definita, avvia l'applicazione web Flask. In questo modo, gli insegnanti avranno a disposizione un'interfaccia basata su moduli familiare per interagire con l'agente. Sebbene le interazioni con i chatbot siano comuni con gli LLM, optiamo per un'interfaccia utente di invio del modulo tradizionale, in quanto potrebbe essere più intuitiva per molti insegnanti.

Se hai chiuso il terminale o le variabili di ambiente non sono più impostate, esegui di nuovo i seguenti comandi

export BOOK_PROVIDER_URL=$(gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)")
export PROJECT_ID=$(gcloud config get project)
export INSTANCE_NAME="aidemy"
export REGION="us-central1"
export DB_USER="postgres"
export DB_PASS="1234qwer"
export DB_NAME="aidemy-db"

👉Ora avvia l'interfaccia utente web.

cd ~/aidemy-bootstrap/planner/
source env/bin/activate
python app.py

Cerca i messaggi di avvio nell'output del terminale Cloud Shell. In genere, Flask stampa messaggi che indicano che è in esecuzione e su quale porta.

Running on http://127.0.0.1:8080
Running on http://127.0.0.1:8080
The application needs to keep running to serve requests.

👉Nel menu "Anteprima web", scegli Anteprima sulla porta 8080. Cloud Shell aprirà una nuova scheda o finestra del browser con l'anteprima web della tua applicazione.

Pagina web

Nell'interfaccia dell'applicazione, seleziona 5 per l'anno, seleziona l'argomento Mathematics e inserisci Geometry nella richiesta di componenti aggiuntivi Limite quota

Invece di rimanere in attesa della risposta, passa al terminale di Cloud Editor. Puoi osservare l'avanzamento e eventuali messaggi di output o errore generati dalla funzione nel terminale dell'emulatore. 😁

👉 Interrompi lo script premendo Ctrl+C nel terminale.

👉Esci dall'ambiente virtuale:

deactivate

8. Eseguire il deployment dell'agente di pianificazione nel cloud

Crea ed esegui il push dell'immagine nel registry

Panoramica

👉È ora di eseguire il deployment nel cloud. Nel terminale, crea un repository di artefatti per archiviare l'immagine Docker che stiamo per creare.

gcloud artifacts repositories create agent-repository \
    --repository-format=docker \
    --location=us-central1 \
    --description="My agent repository"

Dovresti vedere Repository creato [agent-repository].

👉Esegui il seguente comando per creare l'immagine Docker.

cd ~/aidemy-bootstrap/planner/
export PROJECT_ID=$(gcloud config get project)
docker build -t gcr.io/${PROJECT_ID}/aidemy-planner .

👉Dobbiamo ricreare il tag dell'immagine in modo che sia ospitata in Artifact Registry anziché in GCR ed eseguire il push dell'immagine taggata in Artifact Registry:

export PROJECT_ID=$(gcloud config get project)
docker tag gcr.io/${PROJECT_ID}/aidemy-planner us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-planner
docker push us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-planner

Al termine del push, puoi verificare che l'immagine sia stata archiviata correttamente in Artifact Registry. Vai ad Artifact Registry nella console Google Cloud. Dovresti trovare l'immagine aidemy-planner nel repository agent-repository. Immagine del pianificatore Aidemy

Protezione delle credenziali del database con Secret Manager

Per gestire e accedere in modo sicuro alle credenziali del database, utilizzeremo Secret Manager di Google Cloud. In questo modo, evitiamo di inserire informazioni sensibili nel codice dell'applicazione e aumentiamo la sicurezza.

👉Creeremo singoli secret per il nome utente, la password e il nome del database. Questo approccio ci consente di gestire ogni credenziale in modo indipendente. Nel terminale, esegui:

gcloud secrets create db-user
printf "postgres" | gcloud secrets versions add db-user --data-file=-

gcloud secrets create db-pass
printf "1234qwer" | gcloud secrets versions add db-pass --data-file=- 

gcloud secrets create db-name
printf "aidemy-db" | gcloud secrets versions add db-name --data-file=-

L'utilizzo di Secret Manager è un passaggio importante per proteggere l'applicazione e prevenire l'esposizione accidentale di credenziali sensibili. Segue le best practice di sicurezza per i deployment cloud.

Esegui il deployment in Cloud Run

Cloud Run è una piattaforma serverless completamente gestita che ti consente di eseguire il deployment di applicazioni containerizzate in modo rapido e semplice. Nasconde completamente la gestione dell'infrastruttura, permettendoti di concentrarti sulla scrittura e sul deployment del codice. Eseguiremo il deployment del nostro pianificatore come servizio Cloud Run.

👉Nella console Google Cloud, vai a "Cloud Run". Fai clic su ESEGUI IL DEPLOYMENT DEL CONTAINER e seleziona SERVIZIO. Configura il servizio Cloud Run:

Cloud Run

  1. Immagine container: fai clic su "Seleziona" nel campo URL. Trova l'URL dell'immagine di cui hai eseguito il push in Artifact Registry (ad es. us-central1-docker.pkg.dev/YOUR_PROJECT_ID/agent-repository/agent-planner/YOUR_IMG).
  2. Nome del servizio: aidemy-planner
  3. Regione: seleziona la regione us-central1.
  4. Autenticazione: ai fini di questo laboratorio, puoi consentire "Consenti chiamate non autenticate". Per la produzione, probabilmente vorrai limitare l'accesso.
  5. Scheda Contenitori (espandi Contenitori, Rete):
    • Scheda Impostazioni:
      • Risorsa
        • memoria : 2 GB
    • Scheda Variabili e secret:
      • Variabili di ambiente:
        • Aggiungi nome: GOOGLE_CLOUD_PROJECT e valore: <YOUR_PROJECT_ID>
        • Aggiungi il nome: BOOK_PROVIDER_URL e il valore: <YOUR_BOOK_PROVIDER_FUNCTION_URL>
      • Secret esposti come variabili di ambiente:
        • Aggiungi nome: DB_USER, secret: seleziona db-user e versione:latest
        • Aggiungi nome: DB_PASS, secret: seleziona db-pass e versione:latest
        • Aggiungi nome: DB_NAME, secret: seleziona db-name e versione:latest

Esegui il seguente comando nel terminale se devi recuperare il tuo URL YOUR_BOOK_PROVIDER_FUNCTION:

gcloud run services describe book-provider --region=us-central1 --project=$PROJECT_ID --format="value(status.url)"

Imposta secret

Lascia l'opzione Altro come predefinita.

👉Fai clic su CREA.

Cloud Run eseguirà il deployment del servizio.

Una volta eseguito il deployment, fai clic sul servizio per accedere alla relativa pagina dei dettagli, dove troverai l'URL di cui è stato eseguito il deployment disponibile in alto.

URL

Nell'interfaccia dell'applicazione, seleziona 7 per l'anno, scegli Mathematics come oggetto e inserisci Algebra nel campo Richiesta di componenti aggiuntivi. In questo modo, l'agente avrà il contesto necessario per generare un piano di lezioni personalizzato.

Complimenti! Hai creato un piano di insegnamento utilizzando il nostro potente agente IA. Ciò dimostra il potenziale degli agenti di ridurre in modo significativo il carico di lavoro e semplificare le attività, migliorando in ultima analisi l'efficienza e semplificando la vita degli insegnanti.

9. Sistemi multi-agente

Ora che abbiamo implementato lo strumento per la creazione del piano di insegnamento, concentriamoci sulla creazione del portale per gli studenti. Questo portale fornirà agli studenti l'accesso a quiz, riepiloghi audio e compiti relativi al loro programma di studi. Dato l'ambito di questa funzionalità, sfrutteremo la potenza dei sistemi multi-agente per creare una soluzione modulare e scalabile.

Come abbiamo discusso in precedenza, invece di fare affidamento su un singolo agente per gestire tutto, un sistema multi-agente ci consente di suddividere il carico di lavoro in attività più piccole e specializzate, ciascuna gestita da un agente dedicato. Questo approccio offre diversi vantaggi chiave:

Modularità e manutenibilità: anziché creare un singolo agente che fa tutto, crea agenti più piccoli e specializzati con responsabilità ben definite. Questa modularità semplifica la comprensione, la manutenzione e il debug del sistema. Quando si verifica un problema, puoi isolarlo in un agente specifico anziché dover esaminare una base di codice enorme.

Scalabilità: la scalabilità di un singolo agente complesso può rappresentare un collo di bottiglia. Con un sistema multi-agente, puoi scalare i singoli agenti in base alle loro esigenze specifiche. Ad esempio, se un agente gestisce un volume elevato di richieste, puoi avviare facilmente altre istanze dell'agente senza influire sul resto del sistema.

Specializzazione del team: pensaci in questo modo: non chiederesti a un ingegnere di creare un'intera applicazione da zero. Invece, puoi mettere insieme un team di esperti, ognuno con competenze in un determinato ambito. Analogamente, un sistema multi-agente ti consente di sfruttare i punti di forza di diversi LLM e strumenti, assegnandoli agli agenti più adatti per attività specifiche.

Sviluppo parallelo: team diversi possono lavorare contemporaneamente su agenti diversi, velocizzando il processo di sviluppo. Poiché gli agenti sono indipendenti, le modifiche apportate a un agente hanno meno probabilità di influire sugli altri agenti.

Architettura basata su eventi

Per consentire una comunicazione e un coordinamento efficaci tra questi agenti, utilizzeremo un'architettura basata sugli eventi. Ciò significa che gli agenti reagiranno agli "eventi" che si verificano all'interno del sistema.

Gli agenti si abbonano a tipi di eventi specifici (ad es. "piano di insegnamento generato", "compito creato"). Quando si verifica un evento, gli agenti pertinenti vengono avvisati e possono reagire di conseguenza. Questo disaccoppiamento favorisce la flessibilità, la scalabilità e la reattività in tempo reale.

Panoramica

Per iniziare, abbiamo bisogno di un modo per trasmettere questi eventi. Per farlo, configureremo un argomento Pub/Sub. Iniziamo creando un argomento denominato piano.

👉Vai a Pub/Sub della console Google Cloud e fai clic sul pulsante "Crea argomento".

👉Configura l'argomento con ID/nome plan e deseleziona Add a default subscription, lascia invariate le altre impostazioni predefinite e fai clic su Crea.

La pagina Pub/Sub viene aggiornata e dovresti vedere l'argomento appena creato elencato nella tabella. Crea argomento

Ora integriamo la funzionalità di pubblicazione di eventi Pub/Sub nel nostro agente pianificatore. Aggiungeremo un nuovo strumento che invia un evento "plan " all'argomento Pub/Sub che abbiamo appena creato. Questo evento segnalerà agli altri agenti del sistema (ad esempio quelli del portale degli studenti) che è disponibile un nuovo piano di insegnamento.

👉Torna a Cloud Code Editor e apri il file app.py nella cartella planner. Aggiungeremo una funzione che pubblica l'evento. Sostituisci:

##ADD SEND PLAN EVENT FUNCTION HERE

con

def send_plan_event(teaching_plan:str):
    """
    Send the teaching event to the topic called plan
    
    Args:
        teaching_plan: teaching plan
    """
    publisher = pubsub_v1.PublisherClient()
    print(f"-------------> Sending event to topic plan: {teaching_plan}")
    topic_path = publisher.topic_path(PROJECT_ID, "plan")

    message_data = {"teaching_plan": teaching_plan} 
    data = json.dumps(message_data).encode("utf-8") 

    future = publisher.publish(topic_path, data)

    return f"Published message ID: {future.result()}"

  • send_plan_event: questa funzione prende come input il piano di insegnamento generato, crea un client publisher Pub/Sub, costruisce il percorso dell'argomento, converte il piano di insegnamento in una stringa JSON e pubblica il messaggio nell'argomento.
  • Elenco di strumenti: la funzione send_plan_event viene aggiunta all'elenco di strumenti e resa disponibile per l'uso da parte dell'agente.

Nella stessa cartella, nel file app.py, 👉aggiorna il prompt per indicare all'agente di inviare l'evento del piano di insegnamento all'argomento Pub/Sub dopo aver generato il piano di insegnamento. Sostituisci

### ADD send_plan_event CALL

con quanto segue:

send_plan_event(teaching_plan)

Aggiungendo lo strumento send_plan_event e modificando il prompt, abbiamo consentito al nostro agente di pianificazione di pubblicare eventi in Pub/Sub, consentendo ad altri componenti del nostro sistema di reagire alla creazione di nuovi piani di insegnamento. Nelle sezioni seguenti avremo un sistema multi-agente funzionale.

10. Favorire il coinvolgimento degli studenti con i quiz on demand

Immagina un ambiente di apprendimento in cui gli studenti hanno accesso a una quantità infinita di quiz personalizzati in base ai loro piani di apprendimento specifici. Questi quiz forniscono un feedback immediato, incluse risposte e spiegazioni, favorendo una comprensione più approfondita del materiale. Questo è il potenziale che intendiamo sfruttare con il nostro portale di quiz basato sull'IA.

Per realizzare questa visione, creeremo un componente per la generazione di quiz in grado di creare domande a scelta multipla in base ai contenuti del piano di insegnamento.

Panoramica

👉Nel riquadro Esplora dell'editor di Cloud Code, vai alla cartella portal. Apri il file quiz.py, copia e incolla il seguente codice alla fine del file.

def generate_quiz_question(file_name: str, difficulty: str, region:str ):
    """Generates a single multiple-choice quiz question using the LLM.
   
    ```json
    {
      "question": "The question itself",
      "options": ["Option A", "Option B", "Option C", "Option D"],
      "answer": "The correct answer letter (A, B, C, or D)"
    }
    ```
    """

    print(f"region: {region}")
    # Connect to resourse needed from Google Cloud
    llm = VertexAI(model_name="gemini-1.5-pro", location=region)


    plan=None
    #load the file using file_name and read content into string call plan
    with open(file_name, 'r') as f:
        plan = f.read()

    parser = JsonOutputParser(pydantic_object=QuizQuestion)


    instruction = f"You'll provide one question with difficulty level of {difficulty}, 4 options as multiple choices and provide the anwsers, the quiz needs to be related to the teaching plan {plan}"

    prompt = PromptTemplate(
        template="Generates a single multiple-choice quiz question\n {format_instructions}\n  {instruction}\n",
        input_variables=["instruction"],
        partial_variables={"format_instructions": parser.get_format_instructions()},
    )
    
    chain = prompt | llm | parser
    response = chain.invoke({"instruction": instruction})

    print(f"{response}")
    return  response


Nell'agente viene creato un parser di output JSON progettato specificamente per comprendere e strutturare l'output dell'LLM. Utilizza il modello QuizQuestion che abbiamo definito in precedenza per garantire che l'output analizzato sia conforme al formato corretto (domanda, opzioni e risposta).

👉Esegui i seguenti comandi nel terminale per configurare un ambiente virtuale, installare le dipendenze e avviare l'agente:

cd ~/aidemy-bootstrap/portal/
python -m venv env
source env/bin/activate
pip install -r requirements.txt
python app.py

Utilizza la funzionalità di anteprima web di Cloud Shell per accedere all'applicazione in esecuzione. Fai clic sul link "Quiz" nella barra di navigazione in alto o nella scheda della pagina di indice. Dovresti vedere tre quiz generati in modo casuale per lo studente. Questi quiz si basano sul piano di insegnamento e dimostrano la potenza del nostro sistema di generazione di quiz basato sull'IA.

Quiz

Per interrompere il processo in esecuzione locale, premi Ctrl+C nel terminale.

Pensiero di Gemini 2 per le spiegazioni

Ok, quindi abbiamo i quiz, un ottimo inizio. Ma cosa succede se gli studenti sbagliano qualcosa? È lì che avviene l'apprendimento reale, giusto? Se riusciamo a spiegare perché la risposta non era corretta e come arrivare a quella giusta, è molto più probabile che la ricordino. Inoltre, aiuta a chiarire eventuali dubbi e a rafforzare la loro fiducia.

Ecco perché stiamo per tirare fuori l'artiglieria pesante: il modello "pensante" di Gemini 2. È come dare all'IA un po' di tempo in più per riflettere prima di spiegare. In questo modo, può fornire un feedback più dettagliato e migliore.

Vogliamo capire se può aiutare gli studenti fornendo assistenza, risposte e spiegazioni dettagliate. Per provarlo, inizieremo con un argomento notoriamente complicato, il calcolo.

Panoramica

👉Innanzitutto, vai all'editor di Cloud Code, in answer.py all'interno della cartella portal sostituisci

def answer_thinking(question, options, user_response, answer, region):
    return ""

con il seguente snippet di codice:

def answer_thinking(question, options, user_response, answer, region):
    try:
        llm = VertexAI(model_name="gemini-2.0-flash-001",location=region)
        
        input_msg = HumanMessage(content=[f"Here the question{question}, here are the available options {options}, this student's answer {user_response}, whereas the correct answer is {answer}"])
        prompt_template = ChatPromptTemplate.from_messages(
            [
                SystemMessage(
                    content=(
                        "You are a helpful teacher trying to teach the student on question, you were given the question and a set of multiple choices "
                        "what's the correct answer. use friendly tone"
                    )
                ),
                input_msg,
            ]
        )

        prompt = prompt_template.format()
        
        response = llm.invoke(prompt)
        print(f"response: {response}")

        return response
    except Exception as e:
        print(f"Error sending message to chatbot: {e}") # Log this error too!
        return f"Unable to process your request at this time. Due to the following reason: {str(e)}"



if __name__ == "__main__":
    question = "Evaluate the limit: lim (x→0) [(sin(5x) - 5x) / x^3]"
    options = ["A) -125/6", "B) -5/3 ", "C) -25/3", "D) -5/6"]
    user_response = "B"
    answer = "A"
    region = "us-central1"
    result = answer_thinking(question, options, user_response, answer, region)

Questa è un'app di apprendimento linguistico molto semplice che inizializza il modello Gemini 2 Flash, a cui chiediamo di agire come un insegnante utile e di fornire spiegazioni

👉Esegui il seguente comando nel terminale:

cd ~/aidemy-bootstrap/portal/
python answer.py

Dovresti visualizzare un output simile all'esempio fornito nelle istruzioni originali. Il modello attuale potrebbe non fornire una spiegazione esaustiva.

Okay, I see the question and the choices. The question is to evaluate the limit:

lim (x0) [(sin(5x) - 5x) / x^3]

You chose option B, which is -5/3, but the correct answer is A, which is -125/6.

It looks like you might have missed a step or made a small error in your calculations. This type of limit often involves using L'Hôpital's Rule or Taylor series expansion. Since we have the form 0/0, L'Hôpital's Rule is a good way to go! You need to apply it multiple times. Alternatively, you can use the Taylor series expansion of sin(x) which is:
sin(x) = x - x^3/3! + x^5/5! - ...
So, sin(5x) = 5x - (5x)^3/3! + (5x)^5/5! - ...
Then,  (sin(5x) - 5x) = - (5x)^3/3! + (5x)^5/5! - ...
Finally, (sin(5x) - 5x) / x^3 = - 5^3/3! + (5^5 * x^2)/5! - ...
Taking the limit as x approaches 0, we get -125/6.

Keep practicing, you'll get there!

Nel file answer.py, sostituisci il nome del modello da gemini-2.0-flash-001 a gemini-2.0-flash-thinking-exp-01-21 nella funzione answer_thinking.

In questo modo, l'LLM ragiona di più, il che lo aiuta a generare spiegazioni migliori. Esegui di nuovo l'operazione.

👉Esegui per testare il nuovo modello di pensiero:

cd ~/aidemy-bootstrap/portal/
source env/bin/activate
python answer.py

Ecco un esempio di risposta del modello di pensiero molto più approfondita e dettagliata, che fornisce una spiegazione passo passo di come risolvere il problema di calcolo. Ciò mette in evidenza la potenza dei modelli "pensanti" nella generazione di spiegazioni di alta qualità. Dovresti visualizzare un output simile al seguente:

Hey there! Let's take a look at this limit problem together. You were asked to evaluate:

lim (x0) [(sin(5x) - 5x) / x^3]

and you picked option B, -5/3, but the correct answer is actually A, -125/6. Let's figure out why!

It's a tricky one because if we directly substitute x=0, we get (sin(0) - 0) / 0^3 = (0 - 0) / 0 = 0/0, which is an indeterminate form. This tells us we need to use a more advanced technique like L'Hopital's Rule or Taylor series expansion.

Let's use the Taylor series expansion for sin(y) around y=0. Do you remember it?  It looks like this:

sin(y) = y - y^3/3! + y^5/5! - ...
where 3! (3 factorial) is 3 × 2 × 1 = 6, 5! is 5 × 4 × 3 × 2 × 1 = 120, and so on.

In our problem, we have sin(5x), so we can substitute y = 5x into the Taylor series:

sin(5x) = (5x) - (5x)^3/3! + (5x)^5/5! - ...
sin(5x) = 5x - (125x^3)/6 + (3125x^5)/120 - ...

Now let's plug this back into our limit expression:

[(sin(5x) - 5x) / x^3] =  [ (5x - (125x^3)/6 + (3125x^5)/120 - ...) - 5x ] / x^3
Notice that the '5x' and '-5x' cancel out!  So we are left with:
= [ - (125x^3)/6 + (3125x^5)/120 - ... ] / x^3
Now, we can divide every term in the numerator by x^3:
= -125/6 + (3125x^2)/120 - ...

Finally, let's take the limit as x approaches 0.  As x gets closer and closer to zero, terms with x^2 and higher powers will become very, very small and approach zero.  So, we are left with:
lim (x0) [ -125/6 + (3125x^2)/120 - ... ] = -125/6

Therefore, the correct answer is indeed **A) -125/6**.

It seems like your answer B, -5/3, might have come from perhaps missing a factor somewhere during calculation or maybe using an incorrect simplification. Double-check your steps when you were trying to solve it!

Don't worry, these limit problems can be a bit tricky sometimes! Keep practicing and you'll get the hang of it.  Let me know if you want to go through another similar example or if you have any more questions! 😊


Now that we have confirmed it works, let's use the portal.

👉RIMUOVI il seguente codice di test da answer.py:

if __name__ == "__main__":
    question = "Evaluate the limit: lim (x→0) [(sin(5x) - 5x) / x^3]"
    options = ["A) -125/6", "B) -5/3 ", "C) -25/3", "D) -5/6"]
    user_response = "B"
    answer = "A"
    region = "us-central1"
    result = answer_thinking(question, options, user_response, answer, region)

👉Esegui i seguenti comandi nel terminale per configurare un ambiente virtuale, installare le dipendenze e avviare l'agente:

cd ~/aidemy-bootstrap/portal/
source env/bin/activate
python app.py

👉Utilizza la funzionalità di anteprima web di Cloud Shell per accedere all'applicazione in esecuzione. Fai clic sul link "Quiz", rispondi a tutti i quiz e assicurati di sbagliare almeno una risposta, poi fai clic su Invia

risposte che richiedono di pensare

Invece di rimanere in attesa della risposta, passa al terminale di Cloud Editor. Puoi osservare l'avanzamento e eventuali messaggi di output o errore generati dalla funzione nel terminale dell'emulatore. 😁

Per interrompere il processo in esecuzione locale, premi Ctrl+C nel terminale.

11. Orchestrazione degli agenti con Eventarc

Finora, il portale degli studenti ha generato quiz in base a un insieme predefinito di piani di insegnamento. È utile, ma significa che il nostro agente di pianificazione e l'agente dei quiz del portale non comunicano tra loro. Ricordi che abbiamo aggiunto la funzionalità che consente all'agente di pianificazione di pubblicare i piani di insegnamento appena generati in un argomento Pub/Sub? Ora è il momento di collegarlo al nostro agente del portale.

Panoramica

Vogliamo che il portale aggiorni automaticamente i contenuti dei quiz ogni volta che viene generato un nuovo piano di insegnamento. A tal fine, creeremo un endpoint nel portale che possa ricevere questi nuovi piani.

👉Nel riquadro Esplora dell'editor di Cloud Code, vai alla cartella portal. Apri il file app.py per modificarlo. Aggiungi il seguente codice tra ## Aggiungi il tuo codice qui:

## Add your code here

@app.route('/new_teaching_plan', methods=['POST'])
def new_teaching_plan():
    try:
       
        # Get data from Pub/Sub message delivered via Eventarc
        envelope = request.get_json()
        if not envelope:
            return jsonify({'error': 'No Pub/Sub message received'}), 400

        if not isinstance(envelope, dict) or 'message' not in envelope:
            return jsonify({'error': 'Invalid Pub/Sub message format'}), 400

        pubsub_message = envelope['message']
        print(f"data: {pubsub_message['data']}")

        data = pubsub_message['data']
        data_str = base64.b64decode(data).decode('utf-8')
        data = json.loads(data_str)

        teaching_plan = data['teaching_plan']

        print(f"File content: {teaching_plan}")

        with open("teaching_plan.txt", "w") as f:
            f.write(teaching_plan)

        print(f"Teaching plan saved to local file: teaching_plan.txt")

        return jsonify({'message': 'File processed successfully'})


    except Exception as e:
        print(f"Error processing file: {e}")
        return jsonify({'error': 'Error processing file'}), 500
## Add your code here

Ricostruzione ed esecuzione del deployment in Cloud Run

OK, dovrai aggiornare e eseguire nuovamente il deployment sia degli agenti di pianificazione che di quelli del portale in Cloud Run. In questo modo, avranno il codice più recente e saranno configurati per comunicare tramite eventi.

Panoramica del deployment

👉Successivamente, ricostruiremo e invieremo l'immagine dell'agente planner, di nuovo nel terminale:

cd ~/aidemy-bootstrap/planner/
export PROJECT_ID=$(gcloud config get project)
docker build -t gcr.io/${PROJECT_ID}/aidemy-planner .
export PROJECT_ID=$(gcloud config get project)
docker tag gcr.io/${PROJECT_ID}/aidemy-planner us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-planner
docker push us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-planner

👉Eseguiamo la stessa operazione, ovvero creiamo ed eseguiamo il push dell'immagine dell'agente portal:

cd ~/aidemy-bootstrap/portal/
export PROJECT_ID=$(gcloud config get project)
docker build -t gcr.io/${PROJECT_ID}/aidemy-portal .
export PROJECT_ID=$(gcloud config get project)
docker tag gcr.io/${PROJECT_ID}/aidemy-portal us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal
docker push us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal

In Artifact Registry, dovresti vedere entrambe le immagini container aidemy-planner e aidemy-portal.

Repository di container

👉Di nuovo nel terminale, esegui questo comando per aggiornare l'immagine Cloud Run per l'agente di pianificazione:

export PROJECT_ID=$(gcloud config get project)
gcloud run services update aidemy-planner \
    --region=us-central1 \
    --image=us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-planner:latest

Dovresti visualizzare un output simile al seguente:

OK Deploying... Done.                                                                                                                                                     
  OK Creating Revision...                                                                                                                                                 
  OK Routing traffic...                                                                                                                                                   
Done.                                                                                                                                                                     
Service [aidemy-planner] revision [aidemy-planner-xxxxx] has been deployed and is serving 100 percent of traffic.
Service URL: https://aidemy-planner-xxx.us-central1.run.app

Prendi nota dell'URL del servizio, ovvero il link all'agente di pianificazione di cui è stato eseguito il deployment.

👉Esegui questo comando per creare l'istanza Cloud Run per l'agente del portale

export PROJECT_ID=$(gcloud config get project)
gcloud run deploy aidemy-portal \
  --image=us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal:latest \
  --region=us-central1 \
  --platform=managed \
  --allow-unauthenticated \
  --memory=2Gi \
  --cpu=2 \
  --set-env-vars=GOOGLE_CLOUD_PROJECT=${PROJECT_ID}

Dovresti visualizzare un output simile al seguente:

Deploying container to Cloud Run service [aidemy-portal] in project [xxxx] region [us-central1]
OK Deploying new service... Done.                                                                                                                                         
  OK Creating Revision...                                                                                                                                                 
  OK Routing traffic...                                                                                                                                                   
  OK Setting IAM Policy...                                                                                                                                                
Done.                                                                                                                                                                     
Service [aidemy-portal] revision [aidemy-portal-xxxx] has been deployed and is serving 100 percent of traffic.
Service URL: https://aidemy-portal-xxxx.us-central1.run.app

Prendi nota dell'URL del servizio, ovvero il link al portale per studenti di cui è stato eseguito il deployment.

Creazione dell'trigger Eventarc

Ma ecco la grande domanda: in che modo questo endpoint riceve una notifica quando è presente un nuovo piano nell'argomento Pub/Sub? È qui che entra in gioco Eventarc.

Eventarc funge da ponte, rimane in ascolto di eventi specifici (ad esempio un nuovo messaggio che arriva nel nostro argomento Pub/Sub) e attiva automaticamente le azioni in risposta. Nel nostro caso, rileverà quando viene pubblicato un nuovo piano di insegnamento e invierà un segnale all'endpoint del nostro portale, comunicando che è ora di aggiornarlo.

Con Eventarc che gestisce la comunicazione basata su eventi, possiamo collegare facilmente l'agente di pianificazione e l'agente del portale, creando un sistema di apprendimento davvero dinamico e reattivo. È come avere un messaggero intelligente che invia automaticamente i piani di lezione più recenti al posto giusto.

👉Nella console, vai a Eventarc.

👉Fai clic sul pulsante "+ CREA TRIGGER".

Configura l'attivatore (nozioni di base):

  • Nome trigger: plan-topic-trigger
  • Tipi di attivatori: origini Google.
  • Fornitore di eventi: Cloud Pub/Sub
  • Tipo di evento: google.cloud.pubsub.topic.v1.messagePublished
  • Regione: us-central1.
  • Argomento Cloud Pub/Sub : seleziona plan
  • CONCEDI all'account di servizio il ruolo roles/iam.serviceAccountTokenCreator
  • Destinazione evento: Cloud Run
  • Servizio Cloud Run: aidemy-portal
  • Percorso dell'URL del servizio: /new_teaching_plan
  • Ignora messaggio (Autorizzazione negata in "locations/me-central2" (oppure la risorsa potrebbe non esistere).)

Fai clic su "Crea".

La pagina degli trigger Eventarc viene aggiornata e ora dovresti vedere l'trigger appena creato elencato nella tabella.

👉Ora accedi al planner e richiedi un nuovo piano di insegnamento. Questa volta prova l'anno 5, l'oggetto science con la richiesta di aggiunta senza atoms

Esegui questo comando nel terminale se dimentichi la posizione dell'agente di pianificazione

gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep planner

Quindi, attendi un minuto o due. Anche questo ritardo è stato introdotto a causa della limitazione di fatturazione di questo lab. In condizioni normali, non dovrebbe esserci alcun ritardo.

Infine, accedi al portale degli studenti. Dovresti vedere che i quiz sono stati aggiornati e ora sono in linea con il nuovo piano di insegnamento che hai appena generato. Ciò dimostra l'integrazione riuscita di Eventarc nel sistema Aidemy.

Esegui questo comando nel terminale se dimentichi la posizione dell'agente del portale

gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep portal

Aidemy-celebrate

Complimenti! Hai creato un sistema multi-agente su Google Cloud, sfruttando l'architettura basata sugli eventi per una maggiore scalabilità e flessibilità. Hai gettato le basi, ma c'è ancora molto altro da esplorare. Per approfondire i vantaggi reali di questa architettura, scopri la potenza dell'API Multimodal Live di Gemini 2 e impara a implementare l'orchestrazione a percorso singolo con LangGraph. Non esitare a continuare con i due capitoli successivi.

12. (FACOLTATIVO) Recap audio con Gemini

Gemini è in grado di comprendere ed elaborare informazioni provenienti da varie fonti, come testo, immagini e persino audio, aprendo una nuova gamma di possibilità per l'apprendimento e la creazione di contenuti. La capacità di Gemini di "vedere", "ascoltare" e "leggere" sblocca davvero esperienze utente creative e coinvolgenti.

Oltre alla creazione di immagini o testo, un altro passaggio importante nell'apprendimento è la sintesi e il riassunto efficaci. Pensaci: quanto spesso ricordi più facilmente il testo orecchiabile di una canzone rispetto a qualcosa che hai letto in un libro di testo? L'audio può essere incredibilmente memorabile. Ecco perché sfrutteremo le funzionalità multimodali di Gemini per generare riepiloghi audio dei nostri piani didattici. In questo modo, offriremo agli studenti un modo pratico e coinvolgente per rivedere il materiale, aumentando potenzialmente la memorizzazione e la comprensione grazie alla potenza dell'apprendimento uditivo.

Panoramica dell&#39;API Live

Abbiamo bisogno di un luogo in cui archiviare i file audio generati. Cloud Storage offre una soluzione scalabile e affidabile.

👉 Vai a Spazio di archiviazione nella console. Fai clic su "Bucket" nel menu a sinistra. Fai clic sul pulsante "+ CREA" in alto.

👉Configura il bucket:

  • nome del bucket: aidemy-recap-<UNIQUE_NAME> IMPORTANTE: assicurati di definire un nome del bucket univoco che inizi con "aidemy-recap-". Questo nome univoco è fondamentale per evitare conflitti di denominazione durante la creazione del bucket Cloud Storage.
  • regione: us-central1.
  • Classe di archiviazione: "Standard". Standard è adatto per i dati a cui si accede di frequente.
  • Controllo dell'accesso: lascia selezionata l'opzione predefinita "Controllo dell'accesso uniforme". In questo modo, viene fornito un controllo degli accessi coerente a livello di bucket.
  • Opzioni avanzate: per questo laboratorio, in genere sono sufficienti le impostazioni predefinite. Fai clic sul pulsante CREA per creare il bucket.

Potresti visualizzare un popup sulla prevenzione dell'accesso pubblico. Lascia la casella selezionata e fai clic su Confirm.

Ora vedrai il bucket appena creato nell'elenco Bucket. Ricorda il nome del bucket, ti servirà più avanti.

👉Nel terminale di Cloud Code Editor, esegui i seguenti comandi per concedere al service account l'accesso al bucket:

export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
export SERVICE_ACCOUNT_NAME=$(gcloud compute project-info describe --format="value(defaultServiceAccount)")
gcloud storage buckets add-iam-policy-binding gs://$COURSE_BUCKET_NAME \
    --member "serviceAccount:$SERVICE_ACCOUNT_NAME" \
    --role "roles/storage.objectViewer"

gcloud storage buckets add-iam-policy-binding gs://$COURSE_BUCKET_NAME \
    --member "serviceAccount:$SERVICE_ACCOUNT_NAME" \
    --role "roles/storage.objectCreator"

👉In Cloud Code Editor, apri audio.py all'interno della cartella course. Incolla il seguente codice alla fine del file:

config = LiveConnectConfig(
    response_modalities=["AUDIO"],
    speech_config=SpeechConfig(
        voice_config=VoiceConfig(
            prebuilt_voice_config=PrebuiltVoiceConfig(
                voice_name="Charon",
            )
        )
    ),
)

async def process_weeks(teaching_plan: str):
    region = "us-west1" #To workaround onRamp qouta limits
    client = genai.Client(vertexai=True, project=PROJECT_ID, location=region)
    
    clientAudio = genai.Client(vertexai=True, project=PROJECT_ID, location="us-central1")
    async with clientAudio.aio.live.connect(
        model=MODEL_ID,
        config=config,
    ) as session:
        for week in range(1, 4):  
            response = client.models.generate_content(
                model="gemini-1.0-pro",
                contents=f"Given the following teaching plan: {teaching_plan}, Extrace content plan for week {week}. And return just the plan, nothingh else  " # Clarified prompt
            )

            prompt = f"""
                Assume you are the instructor.  
                Prepare a concise and engaging recap of the key concepts and topics covered. 
                This recap should be suitable for generating a short audio summary for students. 
                Focus on the most important learnings and takeaways, and frame it as a direct address to the students.  
                Avoid overly formal language and aim for a conversational tone, tell a few jokes. 
                
                Teaching plan: {response.text} """
            print(f"prompt --->{prompt}")

            await session.send(input=prompt, end_of_turn=True)
            with open(f"temp_audio_week_{week}.raw", "wb") as temp_file:
                async for message in session.receive():
                    if message.server_content.model_turn:
                        for part in message.server_content.model_turn.parts:
                            if part.inline_data:
                                temp_file.write(part.inline_data.data)
                            
            data, samplerate = sf.read(f"temp_audio_week_{week}.raw", channels=1, samplerate=24000, subtype='PCM_16', format='RAW')
            sf.write(f"course-week-{week}.wav", data, samplerate)
        
            storage_client = storage.Client()
            bucket = storage_client.bucket(BUCKET_NAME)
            blob = bucket.blob(f"course-week-{week}.wav")  # Or give it a more descriptive name
            blob.upload_from_filename(f"course-week-{week}.wav")
            print(f"Audio saved to GCS: gs://{BUCKET_NAME}/course-week-{week}.wav")
    await session.close()

 
def breakup_sessions(teaching_plan: str):
    asyncio.run(process_weeks(teaching_plan))
  • Connessione in streaming: innanzitutto viene stabilita una connessione persistente con l'endpoint dell'API Live. A differenza di una chiamata API standard in cui invii una richiesta e ricevi una risposta, questa connessione rimane aperta per uno scambio continuo di dati.
  • Configurazione multimodale: utilizza la configurazione per specificare il tipo di output che vuoi (in questo caso, audio) e puoi anche specificare i parametri che vuoi utilizzare (ad es.selezione della voce, codifica audio).
  • Elaborazione asincrona: questa API funziona in modo asincrono, il che significa che non blocca il thread principale in attesa del completamento della generazione dell'audio. Elaborando i dati in tempo reale e inviando l'output in blocchi, offre un'esperienza quasi istantanea.

Ora la domanda chiave è: quando deve essere eseguita questa procedura di generazione dell'audio? Idealmente, vogliamo che i Recap audio siano disponibili non appena viene creato un nuovo piano di insegnamento. Poiché abbiamo già implementato un'architettura basata su eventi pubblicando il piano di insegnamento in un argomento Pub/Sub, possiamo semplicemente iscriverci a quell'argomento.

Tuttavia, non generiamo nuovi piani di insegnamento molto spesso. Non sarebbe efficiente avere un agente in esecuzione costante in attesa di nuovi piani. Ecco perché ha perfettamente senso implementare questa logica di generazione audio come funzione Cloud Run.

Se viene implementato come funzione, rimane inattivo finché non viene pubblicato un nuovo messaggio nell'argomento Pub/Sub. In questo caso, viene attivata automaticamente la funzione, che genera i Recap audio e li archivia nel nostro bucket.

👉Nella cartella course del file main.py, questo file definisce la funzione Cloud Run che verrà attivata quando sarà disponibile un nuovo piano didattico. Riceve il piano e avvia la generazione del Recap audio. Aggiungi lo snippet di codice seguente alla fine del file.

@functions_framework.cloud_event
def process_teaching_plan(cloud_event):
    print(f"CloudEvent received: {cloud_event.data}")
    time.sleep(60)
    try:
        if isinstance(cloud_event.data.get('message', {}).get('data'), str):  # Check for base64 encoding
            data = json.loads(base64.b64decode(cloud_event.data['message']['data']).decode('utf-8'))
            teaching_plan = data.get('teaching_plan') # Get the teaching plan
        elif 'teaching_plan' in cloud_event.data: # No base64
            teaching_plan = cloud_event.data["teaching_plan"]
        else:
            raise KeyError("teaching_plan not found") # Handle error explicitly

        #Load the teaching_plan as string and from cloud event, call audio breakup_sessions
        breakup_sessions(teaching_plan)

        return "Teaching plan processed successfully", 200

    except (json.JSONDecodeError, AttributeError, KeyError) as e:
        print(f"Error decoding CloudEvent data: {e} - Data: {cloud_event.data}")
        return "Error processing event", 500

    except Exception as e:
        print(f"Error processing teaching plan: {e}")
        return "Error processing teaching plan", 500

@functions_framework.cloud_event: questo decoratore contrassegna la funzione come funzione Cloud Run che verrà attivata da CloudEvents.

Test locale

👉Lo eseguiremo in un ambiente virtuale e installeremo le librerie Python necessarie per la funzione Cloud Run.

cd ~/aidemy-bootstrap/courses
export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
python -m venv env
source env/bin/activate
pip install -r requirements.txt

👉L'emulatore di funzioni Cloud Run ci consente di testare la funzione localmente prima di eseguirne il deployment in Google Cloud. Avvia un emulatore locale eseguendo:

functions-framework --target process_teaching_plan --signature-type=cloudevent --source main.py

👉Mentre l'emulatore è in esecuzione, puoi inviare CloudEvents di test all'emulatore per simulare la pubblicazione di un nuovo piano didattico. In un nuovo terminale:

Due terminali

👉Esegui:

  curl -X POST \
  http://localhost:8080/ \
  -H "Content-Type: application/json" \
  -H "ce-id: event-id-01" \
  -H "ce-source: planner-agent" \
  -H "ce-specversion: 1.0" \
  -H "ce-type: google.cloud.pubsub.topic.v1.messagePublished" \
  -d '{
    "message": {
      "data": "eyJ0ZWFjaGluZ19wbGFuIjogIldlZWsgMTogMkQgU2hhcGVzIGFuZCBBbmdsZXMgLSBEYXkgMTogUmV2aWV3IG9mIGJhc2ljIDJEIHNoYXBlcyAoc3F1YXJlcywgcmVjdGFuZ2xlcywgdHJpYW5nbGVzLCBjaXJjbGVzKS4gRGF5IDI6IEV4cGxvcmluZyBkaWZmZXJlbnQgdHlwZXMgb2YgdHJpYW5nbGVzIChlcXVpbGF0ZXJhbCwgaXNvc2NlbGVzLCBzY2FsZW5lLCByaWdodC1hbmdsZWQpLiBEYXkgMzogRXhwbG9yaW5nIHF1YWRyaWxhdGVyYWxzIChzcXVhcmUsIHJlY3RhbmdsZSwgcGFyYWxsZWxvZ3JhbSwgcmhvbWJ1cywgdHJhcGV6aXVtKS4gRGF5IDQ6IEludHJvZHVjdGlvbiB0byBhbmdsZXM6IHJpZ2h0IGFuZ2xlcywgYWN1dGUgYW5nbGVzLCBhbmQgb2J0dXNlIGFuZ2xlcy4gRGF5IDU6IE1lYXN1cmluZyBhbmdsZXMgdXNpbmcgYSBwcm90cmFjdG9yLiBXZWVrIDI6IDNEIFNoYXBlcyBhbmQgU3ltbWV0cnkgLSBEYXkgNjogSW50cm9kdWN0aW9uIHRvIDNEIHNoYXBlczogY3ViZXMsIGN1Ym9pZHMsIHNwaGVyZXMsIGN5bGluZGVycywgY29uZXMsIGFuZCBweXJhbWlkcy4gRGF5IDc6IERlc2NyaWJpbmcgM0Qgc2hhcGVzIHVzaW5nIGZhY2VzLCBlZGdlcywgYW5kIHZlcnRpY2VzLiBEYXkgODogUmVsYXRpbmcgMkQgc2hhcGVzIHRvIDNEIHNoYXBlcy4gRGF5IDk6IElkZW50aWZ5aW5nIGxpbmVzIG9mIHN5bW1ldHJ5IGluIDJEIHNoYXBlcy4gRGF5IDEwOiBDb21wbGV0aW5nIHN5bW1ldHJpY2FsIGZpZ3VyZXMuIFdlZWsgMzogUG9zaXRpb24sIERpcmVjdGlvbiwgYW5kIFByb2JsZW0gU29sdmluZyAtIERheSAxMTogRGVzY3JpYmluZyBwb3NpdGlvbiB1c2luZyBjb29yZGluYXRlcyBpbiB0aGUgZmlyc3QgcXVhZHJhbnQuIERheSAxMjogUGxvdHRpbmcgY29vcmRpbmF0ZXMgdG8gZHJhdyBzaGFwZXMuIERheSAxMzogVW5kZXJzdGFuZGluZyB0cmFuc2xhdGlvbiAoc2xpZGluZyBhIHNoYXBlKS4gRGF5IDE0OiBVbmRlcnN0YW5kaW5nIHJlZmxlY3Rpb24gKGZsaXBwaW5nIGEgc2hhcGUpLiBEYXkgMTU6IFByb2JsZW0tc29sdmluZyBhY3Rpdml0aWVzIGludm9sdmluZyBwZXJpbWV0ZXIsIGFyZWEsIGFuZCBtaXNzaW5nIGFuZ2xlcy4ifQ=="
    }
  }'

Invece di rimanere a guardare in attesa della risposta, passa all'altro terminale Cloud Shell. Puoi osservare l'avanzamento e eventuali messaggi di output o errore generati dalla funzione nel terminale dell'emulatore. 😁

Nel secondo terminale dovresti vedere OK.

👉Verificherai i dati nel bucket, vai a Cloud Storage e seleziona la scheda "Bucket", quindi aidemy-recap-xxx

Bucket

👉Nel terminale in cui è in esecuzione l'emulatore, digita ctrl+c per uscire. E chiudi il secondo terminale. Chiudi il secondo terminale ed esegui deactivate per uscire dall'ambiente virtuale.

deactivate

Deployment su Google Cloud

Panoramica del deployment 👉Dopo aver eseguito il test locale, è il momento di eseguire il deployment dell'agente del corso su Google Cloud. Nel terminale, esegui questi comandi:

cd ~/aidemy-bootstrap/courses
export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
gcloud functions deploy courses-agent \
  --region=us-central1 \
  --gen2 \
  --source=. \
  --runtime=python312 \
  --trigger-topic=plan \
  --entry-point=process_teaching_plan \
  --set-env-vars=GOOGLE_CLOUD_PROJECT=${PROJECT_ID},COURSE_BUCKET_NAME=$COURSE_BUCKET_NAME

Verifica il deployment selezionando Cloud Run nella console Google Cloud.Dovresti vedere un nuovo servizio denominato courses-agent.

Elenco Cloud Run

Per controllare la configurazione dell'attivatore, fai clic sul servizio courses-agent per visualizzarne i dettagli. Vai alla scheda "ATTIVATORI".

Dovresti vedere un attivatore configurato per ascoltare i messaggi pubblicati nell'argomento del piano.

Trigger Cloud Run

Infine, vediamo come funziona end-to-end.

👉Successivamente, dobbiamo configurare l'agente del portale in modo che sappia dove trovare i file audio generati. Nel terminale, esegui:

export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
export PROJECT_ID=$(gcloud config get project)
gcloud run services update aidemy-portal \
    --region=us-central1 \
    --set-env-vars=GOOGLE_CLOUD_PROJECT=${PROJECT_ID},COURSE_BUCKET_NAME=$COURSE_BUCKET_NAME

👉Prova a generare un nuovo piano di insegnamento nella pagina dell'agente di pianificazione. L'avvio potrebbe richiedere alcuni minuti, ma non preoccuparti, si tratta di un servizio serverless. Recupera l'URL dell'agente di pianificazione (se non lo hai a portata di mano, esegui questo comando nel terminale):

gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep planner

Dopo aver generato il nuovo piano, attendi 2-3 minuti per la generazione dell'audio. Anche questa operazione richiederà qualche minuto in più a causa della limitazione di fatturazione di questo account di laboratorio.

Puoi monitorare se la funzione courses-agent ha ricevuto il piano di insegnamento controllando la scheda "ATTIVATORI" della funzione. Aggiorna periodicamente la pagina. Dovresti vedere che la funzione è stata invocata. Se la funzione non è stata invocata dopo più di 2 minuti, puoi provare a generare di nuovo il piano di insegnamento. Tuttavia, evita di generare piani ripetutamente in rapida successione, poiché ogni piano generato verrà utilizzato ed elaborato in sequenza dall'agente, creando potenzialmente un backlog.

Attiva l&#39;osservazione

👉Visita il portale e fai clic su "Corsi". Dovresti vedere tre schede, ognuna delle quali mostra un Recap audio. Per trovare l'URL dell'agente del tuo portale:

gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep portal

Fai clic su "Riproduci" su ogni corso per assicurarti che i Recap audio siano in linea con il piano di insegnamento che hai appena generato. Corsi del portale

Esci dall'ambiente virtuale.

deactivate

13. (FACOLTATIVO) Collaborazione basata sui ruoli con Gemini e DeepSeek

Avere più punti di vista è fondamentale, soprattutto quando si creano compiti coinvolgenti e stimolanti. Ora creeremo un sistema multi-agente che sfrutta due modelli diversi con ruoli distinti per generare compiti: uno promuove la collaborazione e l'altro l'autoapprendimento. Utilizzeremo un'architettura "single-shot", in cui il flusso di lavoro segue un percorso fisso.

Generatore di compiti Gemini

Panoramica di Gemini Inizieremo configurando la funzione Gemini per generare compiti incentrati sulla collaborazione. Modifica il file gemini.py nella cartella assignment.

👉Incolla il seguente codice alla fine del file gemini.py:

def gen_assignment_gemini(state):
    region=get_next_region()
    client = genai.Client(vertexai=True, project=PROJECT_ID, location=region)
    print(f"---------------gen_assignment_gemini")
    response = client.models.generate_content(
        model=MODEL_ID, contents=f"""
        You are an instructor 

        Develop engaging and practical assignments for each week, ensuring they align with the teaching plan's objectives and progressively build upon each other.  

        For each week, provide the following:

        * **Week [Number]:** A descriptive title for the assignment (e.g., "Data Exploration Project," "Model Building Exercise").
        * **Learning Objectives Assessed:** List the specific learning objectives from the teaching plan that this assignment assesses.
        * **Description:** A detailed description of the task, including any specific requirements or constraints.  Provide examples or scenarios if applicable.
        * **Deliverables:** Specify what students need to submit (e.g., code, report, presentation).
        * **Estimated Time Commitment:**  The approximate time students should dedicate to completing the assignment.
        * **Assessment Criteria:** Briefly outline how the assignment will be graded (e.g., correctness, completeness, clarity, creativity).

        The assignments should be a mix of individual and collaborative work where appropriate.  Consider different learning styles and provide opportunities for students to apply their knowledge creatively.

        Based on this teaching plan: {state["teaching_plan"]}
        """
    )

    print(f"---------------gen_assignment_gemini answer {response.text}")
    
    state["model_one_assignment"] = response.text
    
    return state


import unittest

class TestGenAssignmentGemini(unittest.TestCase):
    def test_gen_assignment_gemini(self):
        test_teaching_plan = "Week 1: 2D Shapes and Angles - Day 1: Review of basic 2D shapes (squares, rectangles, triangles, circles). Day 2: Exploring different types of triangles (equilateral, isosceles, scalene, right-angled). Day 3: Exploring quadrilaterals (square, rectangle, parallelogram, rhombus, trapezium). Day 4: Introduction to angles: right angles, acute angles, and obtuse angles. Day 5: Measuring angles using a protractor. Week 2: 3D Shapes and Symmetry - Day 6: Introduction to 3D shapes: cubes, cuboids, spheres, cylinders, cones, and pyramids. Day 7: Describing 3D shapes using faces, edges, and vertices. Day 8: Relating 2D shapes to 3D shapes. Day 9: Identifying lines of symmetry in 2D shapes. Day 10: Completing symmetrical figures. Week 3: Position, Direction, and Problem Solving - Day 11: Describing position using coordinates in the first quadrant. Day 12: Plotting coordinates to draw shapes. Day 13: Understanding translation (sliding a shape). Day 14: Understanding reflection (flipping a shape). Day 15: Problem-solving activities involving perimeter, area, and missing angles."
        
        initial_state = {"teaching_plan": test_teaching_plan, "model_one_assignment": "", "model_two_assigmodel_one_assignmentnment": "", "final_assignment": ""}

        updated_state = gen_assignment_gemini(initial_state)

        self.assertIn("model_one_assignment", updated_state)
        self.assertIsNotNone(updated_state["model_one_assignment"])
        self.assertIsInstance(updated_state["model_one_assignment"], str)
        self.assertGreater(len(updated_state["model_one_assignment"]), 0)
        print(updated_state["model_one_assignment"])


if __name__ == '__main__':
    unittest.main()

Utilizza il modello Gemini per generare i compiti.

Siamo pronti a testare l'agente Gemini.

👉Esegui questi comandi nel terminale per configurare l'ambiente:

cd ~/aidemy-bootstrap/assignment
export PROJECT_ID=$(gcloud config get project)
python -m venv env
source env/bin/activate
pip install -r requirements.txt

👉Per testarlo, puoi eseguire:

python gemini.py

Nell'output dovresti vedere un compito con più lavoro di gruppo. Anche il test di assert alla fine mostrerà i risultati.

Here are some engaging and practical assignments for each week, designed to build progressively upon the teaching plan's objectives:

**Week 1: Exploring the World of 2D Shapes**

* **Learning Objectives Assessed:**
    * Identify and name basic 2D shapes (squares, rectangles, triangles, circles).
    * .....

* **Description:**
    * **Shape Scavenger Hunt:** Students will go on a scavenger hunt in their homes or neighborhoods, taking pictures of objects that represent different 2D shapes. They will then create a presentation or poster showcasing their findings, classifying each shape and labeling its properties (e.g., number of sides, angles, etc.). 
    * **Triangle Trivia:** Students will research and create a short quiz or presentation about different types of triangles, focusing on their properties and real-world examples. 
    * **Angle Exploration:** Students will use a protractor to measure various angles in their surroundings, such as corners of furniture, windows, or doors. They will record their measurements and create a chart categorizing the angles as right, acute, or obtuse. 
....

**Week 2: Delving into the World of 3D Shapes and Symmetry**

* **Learning Objectives Assessed:**
    * Identify and name basic 3D shapes.
    * ....

* **Description:**
    * **3D Shape Construction:** Students will work in groups to build 3D shapes using construction paper, cardboard, or other materials. They will then create a presentation showcasing their creations, describing the number of faces, edges, and vertices for each shape. 
    * **Symmetry Exploration:** Students will investigate the concept of symmetry by creating a visual representation of various symmetrical objects (e.g., butterflies, leaves, snowflakes) using drawing or digital tools. They will identify the lines of symmetry and explain their findings. 
    * **Symmetry Puzzles:** Students will be given a half-image of a symmetrical figure and will be asked to complete the other half, demonstrating their understanding of symmetry. This can be done through drawing, cut-out activities, or digital tools.

**Week 3: Navigating Position, Direction, and Problem Solving**

* **Learning Objectives Assessed:**
    * Describe position using coordinates in the first quadrant.
    * ....

* **Description:**
    * **Coordinate Maze:** Students will create a maze using coordinates on a grid paper. They will then provide directions for navigating the maze using a combination of coordinate movements and translation/reflection instructions. 
    * **Shape Transformations:** Students will draw shapes on a grid paper and then apply transformations such as translation and reflection, recording the new coordinates of the transformed shapes. 
    * **Geometry Challenge:** Students will solve real-world problems involving perimeter, area, and angles. For example, they could be asked to calculate the perimeter of a room, the area of a garden, or the missing angle in a triangle. 
....

Interrompi con ctl+c e per ripulire il codice di test. RIMUOVI il seguente codice da gemini.py

import unittest

class TestGenAssignmentGemini(unittest.TestCase):
    def test_gen_assignment_gemini(self):
        test_teaching_plan = "Week 1: 2D Shapes and Angles - Day 1: Review of basic 2D shapes (squares, rectangles, triangles, circles). Day 2: Exploring different types of triangles (equilateral, isosceles, scalene, right-angled). Day 3: Exploring quadrilaterals (square, rectangle, parallelogram, rhombus, trapezium). Day 4: Introduction to angles: right angles, acute angles, and obtuse angles. Day 5: Measuring angles using a protractor. Week 2: 3D Shapes and Symmetry - Day 6: Introduction to 3D shapes: cubes, cuboids, spheres, cylinders, cones, and pyramids. Day 7: Describing 3D shapes using faces, edges, and vertices. Day 8: Relating 2D shapes to 3D shapes. Day 9: Identifying lines of symmetry in 2D shapes. Day 10: Completing symmetrical figures. Week 3: Position, Direction, and Problem Solving - Day 11: Describing position using coordinates in the first quadrant. Day 12: Plotting coordinates to draw shapes. Day 13: Understanding translation (sliding a shape). Day 14: Understanding reflection (flipping a shape). Day 15: Problem-solving activities involving perimeter, area, and missing angles."
        
        initial_state = {"teaching_plan": test_teaching_plan, "model_one_assignment": "", "model_two_assigmodel_one_assignmentnment": "", "final_assignment": ""}

        updated_state = gen_assignment_gemini(initial_state)

        self.assertIn("model_one_assignment", updated_state)
        self.assertIsNotNone(updated_state["model_one_assignment"])
        self.assertIsInstance(updated_state["model_one_assignment"], str)
        self.assertGreater(len(updated_state["model_one_assignment"]), 0)
        print(updated_state["model_one_assignment"])


if __name__ == '__main__':
    unittest.main()

Configurare il generatore di compiti DeepSeek

Sebbene le piattaforme di IA basate su cloud siano pratiche, gli LLM auto-hosting possono essere fondamentali per proteggere la privacy dei dati e garantire la sovranità dei dati. Eseguiremo il deployment del modello DeepSeek più piccolo (1,5 miliardi di parametri) su un'istanza Cloud Compute Engine. Esistono altri modi, ad esempio ospitarlo sulla piattaforma Vertex AI di Google o sulla tua istanza GKE, ma poiché questo è solo un workshop sugli agenti IA e non voglio trattenerti qui per sempre, utilizziamo il modo più semplice. Tuttavia, se ti interessa e vuoi approfondire altre opzioni, dai un'occhiata al file deepseek-vertexai.py nella cartella del compito, dove viene fornito un codice di esempio su come interagire con i modelli di cui è stato eseguito il deployment su VertexAI.

Panoramica di Deepseek

👉Esegui questo comando nel terminale per creare una piattaforma LLM auto-hosted Ollama:

cd ~/aidemy-bootstrap/assignment
gcloud compute instances create ollama-instance \
    --image-family=ubuntu-2204-lts \
    --image-project=ubuntu-os-cloud \
    --machine-type=e2-standard-4 \
    --zone=us-central1-a \
    --metadata-from-file startup-script=startup.sh \
    --boot-disk-size=50GB \
    --tags=ollama \
    --scopes=https://www.googleapis.com/auth/cloud-platform

Per verificare che l'istanza Compute Engine sia in esecuzione:

Vai a Compute Engine > "Istanze VM" nella console Google Cloud. Dovresti vedere ollama-instance elencato con un segno di spunta verde che indica che è in esecuzione. Se non riesci a vederlo, assicurati che la zona sia us-central1. In caso contrario, potresti doverla cercare.

Elenco di Compute Engine

👉Installeremo il modello DeepSeek più piccolo e lo testeremo. Nell'editor di Cloud Shell, in un terminale Nuovo, esegui il seguente comando per accedere all'istanza GCE tramite SSH.

gcloud compute ssh ollama-instance --zone=us-central1-a

Dopo aver stabilito la connessione SSH, potrebbe essere visualizzato il seguente messaggio:

"Vuoi continuare (Y/n)?"

Basta digitare Y(senza distinzione tra maiuscole e minuscole) e premere Invio per continuare.

Successivamente, è possibile che ti venga chiesto di creare una passphrase per la chiave SSH. Se preferisci non utilizzare una passphrase, premi due volte Invio per accettare il valore predefinito (nessuna passphrase).

👉Ora che sei nella macchina virtuale, estrai il modello DeepSeek R1 più piccolo e verifica se funziona.

ollama pull deepseek-r1:1.5b
ollama run deepseek-r1:1.5b "who are you?"

👉Esci dall'istanza GCE e inserisci quanto segue nel terminale SSH:

exit

Chiudi il nuovo terminale e torna a quello originale.

👉Non dimenticare di configurare il criterio di rete in modo che altri servizi possano accedere all'LLM. Se vuoi eseguire questa operazione in produzione, limita l'accesso all'istanza, implementa l'accesso di sicurezza per il servizio o limita l'accesso IP. Esegui:

gcloud compute firewall-rules create allow-ollama-11434 \
    --allow=tcp:11434 \
    --target-tags=ollama \
    --description="Allow access to Ollama on port 11434"

👉Per verificare se il criterio del firewall funziona correttamente, prova a eseguire:

export OLLAMA_HOST=http://$(gcloud compute instances describe ollama-instance --zone=us-central1-a --format='value(networkInterfaces[0].accessConfigs[0].natIP)'):11434
curl -X POST "${OLLAMA_HOST}/api/generate" \
     -H "Content-Type: application/json" \
     -d '{
          "prompt": "Hello, what are you?",
          "model": "deepseek-r1:1.5b",
          "stream": false
        }'

Successivamente, lavoreremo sulla funzione Deepseek nell'agente di assegnazione per generare compiti con l'enfasi sul lavoro individuale.

👉Modifica deepseek.py nella cartella assignment e aggiungi il seguente snippet alla fine

def gen_assignment_deepseek(state):
    print(f"---------------gen_assignment_deepseek")

    template = """
        You are an instructor who favor student to focus on individual work.

        Develop engaging and practical assignments for each week, ensuring they align with the teaching plan's objectives and progressively build upon each other.  

        For each week, provide the following:

        * **Week [Number]:** A descriptive title for the assignment (e.g., "Data Exploration Project," "Model Building Exercise").
        * **Learning Objectives Assessed:** List the specific learning objectives from the teaching plan that this assignment assesses.
        * **Description:** A detailed description of the task, including any specific requirements or constraints.  Provide examples or scenarios if applicable.
        * **Deliverables:** Specify what students need to submit (e.g., code, report, presentation).
        * **Estimated Time Commitment:**  The approximate time students should dedicate to completing the assignment.
        * **Assessment Criteria:** Briefly outline how the assignment will be graded (e.g., correctness, completeness, clarity, creativity).

        The assignments should be a mix of individual and collaborative work where appropriate.  Consider different learning styles and provide opportunities for students to apply their knowledge creatively.

        Based on this teaching plan: {teaching_plan}
        """

    
    prompt = ChatPromptTemplate.from_template(template)

    model = OllamaLLM(model="deepseek-r1:1.5b",
                   base_url=OLLAMA_HOST)

    chain = prompt | model


    response = chain.invoke({"teaching_plan":state["teaching_plan"]})
    state["model_two_assignment"] = response
    
    return state

import unittest

class TestGenAssignmentDeepseek(unittest.TestCase):
    def test_gen_assignment_deepseek(self):
        test_teaching_plan = "Week 1: 2D Shapes and Angles - Day 1: Review of basic 2D shapes (squares, rectangles, triangles, circles). Day 2: Exploring different types of triangles (equilateral, isosceles, scalene, right-angled). Day 3: Exploring quadrilaterals (square, rectangle, parallelogram, rhombus, trapezium). Day 4: Introduction to angles: right angles, acute angles, and obtuse angles. Day 5: Measuring angles using a protractor. Week 2: 3D Shapes and Symmetry - Day 6: Introduction to 3D shapes: cubes, cuboids, spheres, cylinders, cones, and pyramids. Day 7: Describing 3D shapes using faces, edges, and vertices. Day 8: Relating 2D shapes to 3D shapes. Day 9: Identifying lines of symmetry in 2D shapes. Day 10: Completing symmetrical figures. Week 3: Position, Direction, and Problem Solving - Day 11: Describing position using coordinates in the first quadrant. Day 12: Plotting coordinates to draw shapes. Day 13: Understanding translation (sliding a shape). Day 14: Understanding reflection (flipping a shape). Day 15: Problem-solving activities involving perimeter, area, and missing angles."
        
        initial_state = {"teaching_plan": test_teaching_plan, "model_one_assignment": "", "model_two_assignment": "", "final_assignment": ""}

        updated_state = gen_assignment_deepseek(initial_state)

        self.assertIn("model_two_assignment", updated_state)
        self.assertIsNotNone(updated_state["model_two_assignment"])
        self.assertIsInstance(updated_state["model_two_assignment"], str)
        self.assertGreater(len(updated_state["model_two_assignment"]), 0)
        print(updated_state["model_two_assignment"])


if __name__ == '__main__':
    unittest.main()

👉Proviamo eseguendo:

cd ~/aidemy-bootstrap/assignment
source env/bin/activate
export PROJECT_ID=$(gcloud config get project)
export OLLAMA_HOST=http://$(gcloud compute instances describe ollama-instance --zone=us-central1-a --format='value(networkInterfaces[0].accessConfigs[0].natIP)'):11434
python deepseek.py

Dovresti vedere un compito con più lavoro di studio autonomo.

**Assignment Plan for Each Week**

---

### **Week 1: 2D Shapes and Angles**
- **Week Title:** "Exploring 2D Shapes"
Assign students to research and present on various 2D shapes. Include a project where they create models using straws and tape for triangles, draw quadrilaterals with specific measurements, and compare their properties. 

### **Week 2: 3D Shapes and Symmetry**
Assign students to create models or nets for cubes and cuboids. They will also predict how folding these nets form the 3D shapes. Include a project where they identify symmetrical properties using mirrors or folding techniques.

### **Week 3: Position, Direction, and Problem Solving**

Assign students to use mirrors or folding techniques for reflections. Include activities where they measure angles, use a protractor, solve problems involving perimeter/area, and create symmetrical designs.
....

👉Interrompi ctl+c e ripulisci il codice di test. RIMUOVI il seguente codice da deepseek.py

import unittest

class TestGenAssignmentDeepseek(unittest.TestCase):
    def test_gen_assignment_deepseek(self):
        test_teaching_plan = "Week 1: 2D Shapes and Angles - Day 1: Review of basic 2D shapes (squares, rectangles, triangles, circles). Day 2: Exploring different types of triangles (equilateral, isosceles, scalene, right-angled). Day 3: Exploring quadrilaterals (square, rectangle, parallelogram, rhombus, trapezium). Day 4: Introduction to angles: right angles, acute angles, and obtuse angles. Day 5: Measuring angles using a protractor. Week 2: 3D Shapes and Symmetry - Day 6: Introduction to 3D shapes: cubes, cuboids, spheres, cylinders, cones, and pyramids. Day 7: Describing 3D shapes using faces, edges, and vertices. Day 8: Relating 2D shapes to 3D shapes. Day 9: Identifying lines of symmetry in 2D shapes. Day 10: Completing symmetrical figures. Week 3: Position, Direction, and Problem Solving - Day 11: Describing position using coordinates in the first quadrant. Day 12: Plotting coordinates to draw shapes. Day 13: Understanding translation (sliding a shape). Day 14: Understanding reflection (flipping a shape). Day 15: Problem-solving activities involving perimeter, area, and missing angles."
        
        initial_state = {"teaching_plan": test_teaching_plan, "model_one_assignment": "", "model_two_assignment": "", "final_assignment": ""}

        updated_state = gen_assignment_deepseek(initial_state)

        self.assertIn("model_two_assignment", updated_state)
        self.assertIsNotNone(updated_state["model_two_assignment"])
        self.assertIsInstance(updated_state["model_two_assignment"], str)
        self.assertGreater(len(updated_state["model_two_assignment"]), 0)
        print(updated_state["model_two_assignment"])


if __name__ == '__main__':
    unittest.main()

Ora utilizzeremo lo stesso modello Gemini per combinare entrambi i compiti in uno nuovo. Modifica il file gemini.py nella cartella assignment.

👉Incolla il seguente codice alla fine del file gemini.py:

def combine_assignments(state):
    print(f"---------------combine_assignments ")
    region=get_next_region()
    client = genai.Client(vertexai=True, project=PROJECT_ID, location=region)
    response = client.models.generate_content(
        model=MODEL_ID, contents=f"""
        Look at all the proposed assignment so far {state["model_one_assignment"]} and {state["model_two_assignment"]}, combine them and come up with a final assignment for student. 
        """
    )

    state["final_assignment"] = response.text
    
    return state

Per combinare i punti di forza di entrambi i modelli, orchestreremo un flusso di lavoro definito utilizzando LangGraph. Questo flusso di lavoro è costituito da tre passaggi: in primo luogo, il modello Gemini genera un compito incentrato sulla collaborazione; in secondo luogo, il modello DeepSeek genera un compito che enfatizza il lavoro individuale; infine, Gemini sintetizza questi due compiti in un unico compito completo. Poiché predefiniamo la sequenza di passaggi senza il processo decisionale dell'LLM, si tratta di un'orchestrazione a un solo percorso definita dall'utente.

Panoramica della combinazione Langraph

👉Incolla il seguente codice alla fine del file main.py nella cartella assignment:

def create_assignment(teaching_plan: str):
    print(f"create_assignment---->{teaching_plan}")
    builder = StateGraph(State)
    builder.add_node("gen_assignment_gemini", gen_assignment_gemini)
    builder.add_node("gen_assignment_deepseek", gen_assignment_deepseek)
    builder.add_node("combine_assignments", combine_assignments)
    
    builder.add_edge(START, "gen_assignment_gemini")
    builder.add_edge("gen_assignment_gemini", "gen_assignment_deepseek")
    builder.add_edge("gen_assignment_deepseek", "combine_assignments")
    builder.add_edge("combine_assignments", END)

    graph = builder.compile()
    state = graph.invoke({"teaching_plan": teaching_plan})

    return state["final_assignment"]



import unittest

class TestCreatAssignment(unittest.TestCase):
    def test_create_assignment(self):
        test_teaching_plan = "Week 1: 2D Shapes and Angles - Day 1: Review of basic 2D shapes (squares, rectangles, triangles, circles). Day 2: Exploring different types of triangles (equilateral, isosceles, scalene, right-angled). Day 3: Exploring quadrilaterals (square, rectangle, parallelogram, rhombus, trapezium). Day 4: Introduction to angles: right angles, acute angles, and obtuse angles. Day 5: Measuring angles using a protractor. Week 2: 3D Shapes and Symmetry - Day 6: Introduction to 3D shapes: cubes, cuboids, spheres, cylinders, cones, and pyramids. Day 7: Describing 3D shapes using faces, edges, and vertices. Day 8: Relating 2D shapes to 3D shapes. Day 9: Identifying lines of symmetry in 2D shapes. Day 10: Completing symmetrical figures. Week 3: Position, Direction, and Problem Solving - Day 11: Describing position using coordinates in the first quadrant. Day 12: Plotting coordinates to draw shapes. Day 13: Understanding translation (sliding a shape). Day 14: Understanding reflection (flipping a shape). Day 15: Problem-solving activities involving perimeter, area, and missing angles."
        initial_state = {"teaching_plan": test_teaching_plan, "model_one_assignment": "", "model_two_assignment": "", "final_assignment": ""}
        updated_state = create_assignment(initial_state)
        
        print(updated_state)


if __name__ == '__main__':
    unittest.main()

👉Per testare inizialmente la funzione create_assignment e confermare che il flusso di lavoro che combina Gemini e DeepSeek sia funzionale, esegui il seguente comando:

cd ~/aidemy-bootstrap/assignment
source env/bin/activate
pip install -r requirements.txt
python main.py

Dovresti vedere qualcosa che combini entrambi i modelli con la loro prospettiva individuale per lo studio degli studenti e anche per i lavori di gruppo.

**Tasks:**

1. **Clue Collection:** Gather all the clues left by the thieves. These clues will include:
    * Descriptions of shapes and their properties (angles, sides, etc.)
    * Coordinate grids with hidden messages
    * Geometric puzzles requiring transformation (translation, reflection, rotation)
    * Challenges involving area, perimeter, and angle calculations

2. **Clue Analysis:** Decipher each clue using your geometric knowledge. This will involve:
    * Identifying the shape and its properties
    * Plotting coordinates and interpreting patterns on the grid
    * Solving geometric puzzles by applying transformations
    * Calculating area, perimeter, and missing angles 

3. **Case Report:** Create a comprehensive case report outlining your findings. This report should include:
    * A detailed explanation of each clue and its solution
    * Sketches and diagrams to support your explanations
    * A step-by-step account of how you followed the clues to locate the artifact
    * A final conclusion about the thieves and their motives

👉Interrompi ctl+c e ripulisci il codice di test. RIMUOVI il seguente codice da main.py

import unittest

class TestCreatAssignment(unittest.TestCase):
    def test_create_assignment(self):
        test_teaching_plan = "Week 1: 2D Shapes and Angles - Day 1: Review of basic 2D shapes (squares, rectangles, triangles, circles). Day 2: Exploring different types of triangles (equilateral, isosceles, scalene, right-angled). Day 3: Exploring quadrilaterals (square, rectangle, parallelogram, rhombus, trapezium). Day 4: Introduction to angles: right angles, acute angles, and obtuse angles. Day 5: Measuring angles using a protractor. Week 2: 3D Shapes and Symmetry - Day 6: Introduction to 3D shapes: cubes, cuboids, spheres, cylinders, cones, and pyramids. Day 7: Describing 3D shapes using faces, edges, and vertices. Day 8: Relating 2D shapes to 3D shapes. Day 9: Identifying lines of symmetry in 2D shapes. Day 10: Completing symmetrical figures. Week 3: Position, Direction, and Problem Solving - Day 11: Describing position using coordinates in the first quadrant. Day 12: Plotting coordinates to draw shapes. Day 13: Understanding translation (sliding a shape). Day 14: Understanding reflection (flipping a shape). Day 15: Problem-solving activities involving perimeter, area, and missing angles."
        initial_state = {"teaching_plan": test_teaching_plan, "model_one_assignment": "", "model_two_assignment": "", "final_assignment": ""}
        updated_state = create_assignment(initial_state)
        
        print(updated_state)


if __name__ == '__main__':
    unittest.main()

Generate Assignment.png

Per rendere automatica la procedura di generazione dei compiti e adattarla ai nuovi piani di insegnamento, sfrutteremo l'architettura basata su eventi esistente. Il codice seguente definisce una funzione Cloud Run (generate_assignment) che verrà attivata ogni volta che un nuovo piano didattico viene pubblicato nell'argomento Pub/Sub "plan".

👉Aggiungi il seguente codice alla fine di main.py:

@functions_framework.cloud_event
def generate_assignment(cloud_event):
    print(f"CloudEvent received: {cloud_event.data}")

    try:
        if isinstance(cloud_event.data.get('message', {}).get('data'), str): 
            data = json.loads(base64.b64decode(cloud_event.data['message']['data']).decode('utf-8'))
            teaching_plan = data.get('teaching_plan')
        elif 'teaching_plan' in cloud_event.data: 
            teaching_plan = cloud_event.data["teaching_plan"]
        else:
            raise KeyError("teaching_plan not found") 

        assignment = create_assignment(teaching_plan)

        print(f"Assignment---->{assignment}")

        #Store the return assignment into bucket as a text file
        storage_client = storage.Client()
        bucket = storage_client.bucket(ASSIGNMENT_BUCKET)
        file_name = f"assignment-{random.randint(1, 1000)}.txt"
        blob = bucket.blob(file_name)
        blob.upload_from_string(assignment)

        return f"Assignment generated and stored in {ASSIGNMENT_BUCKET}/{file_name}", 200

    except (json.JSONDecodeError, AttributeError, KeyError) as e:
        print(f"Error decoding CloudEvent data: {e} - Data: {cloud_event.data}")
        return "Error processing event", 500

    except Exception as e:
        print(f"Error generate assignment: {e}")
        return "Error generate assignment", 500

Test locale

Prima di eseguire il deployment in Google Cloud, è buona prassi testare la funzione Cloud Run localmente. Ciò consente un'iterazione più rapida e un debug più semplice.

Per prima cosa, crea un bucket Cloud Storage per archiviare i file dei compiti generati e concedi all'account di servizio l'accesso al bucket. Esegui i seguenti comandi nel terminale:

👉IMPORTANTE: assicurati di definire un nome ASSIGNMENT_BUCKET univoco che inizi con "aidemy-assignment-". Questo nome univoco è fondamentale per evitare conflitti di denominazione durante la creazione del bucket Cloud Storage. (Sostituisci <YOUR_NAME> con una parola casuale)

export ASSIGNMENT_BUCKET=aidemy-assignment-<YOUR_NAME> #Name must be unqiue

👉Esegui:

export PROJECT_ID=$(gcloud config get project)
export SERVICE_ACCOUNT_NAME=$(gcloud compute project-info describe --format="value(defaultServiceAccount)")
gsutil mb -p $PROJECT_ID -l us-central1 gs://$ASSIGNMENT_BUCKET

gcloud storage buckets add-iam-policy-binding gs://$ASSIGNMENT_BUCKET \
    --member "serviceAccount:$SERVICE_ACCOUNT_NAME" \
    --role "roles/storage.objectViewer"

gcloud storage buckets add-iam-policy-binding gs://$ASSIGNMENT_BUCKET \
    --member "serviceAccount:$SERVICE_ACCOUNT_NAME" \
    --role "roles/storage.objectCreator"

👉Ora avvia l'emulatore di funzioni Cloud Run:

cd ~/aidemy-bootstrap/assignment
functions-framework --target generate_assignment --signature-type=cloudevent --source main.py

👉Mentre l'emulatore è in esecuzione in un terminale, apri un secondo terminale in Cloud Shell. In questo secondo terminale, invia un CloudEvent di test all'emulatore per simulare la pubblicazione di un nuovo piano didattico:

Due terminali

  curl -X POST \
  http://localhost:8080/ \
  -H "Content-Type: application/json" \
  -H "ce-id: event-id-01" \
  -H "ce-source: planner-agent" \
  -H "ce-specversion: 1.0" \
  -H "ce-type: google.cloud.pubsub.topic.v1.messagePublished" \
  -d '{
    "message": {
      "data": "eyJ0ZWFjaGluZ19wbGFuIjogIldlZWsgMTogMkQgU2hhcGVzIGFuZCBBbmdsZXMgLSBEYXkgMTogUmV2aWV3IG9mIGJhc2ljIDJEIHNoYXBlcyAoc3F1YXJlcywgcmVjdGFuZ2xlcywgdHJpYW5nbGVzLCBjaXJjbGVzKS4gRGF5IDI6IEV4cGxvcmluZyBkaWZmZXJlbnQgdHlwZXMgb2YgdHJpYW5nbGVzIChlcXVpbGF0ZXJhbCwgaXNvc2NlbGVzLCBzY2FsZW5lLCByaWdodC1hbmdsZWQpLiBEYXkgMzogRXhwbG9yaW5nIHF1YWRyaWxhdGVyYWxzIChzcXVhcmUsIHJlY3RhbmdsZSwgcGFyYWxsZWxvZ3JhbSwgcmhvbWJ1cywgdHJhcGV6aXVtKS4gRGF5IDQ6IEludHJvZHVjdGlvbiB0byBhbmdsZXM6IHJpZ2h0IGFuZ2xlcywgYWN1dGUgYW5nbGVzLCBhbmQgb2J0dXNlIGFuZ2xlcy4gRGF5IDU6IE1lYXN1cmluZyBhbmdsZXMgdXNpbmcgYSBwcm90cmFjdG9yLiBXZWVrIDI6IDNEIFNoYXBlcyBhbmQgU3ltbWV0cnkgLSBEYXkgNjogSW50cm9kdWN0aW9uIHRvIDNEIHNoYXBlczogY3ViZXMsIGN1Ym9pZHMsIHNwaGVyZXMsIGN5bGluZGVycywgY29uZXMsIGFuZCBweXJhbWlkcy4gRGF5IDc6IERlc2NyaWJpbmcgM0Qgc2hhcGVzIHVzaW5nIGZhY2VzLCBlZGdlcywgYW5kIHZlcnRpY2VzLiBEYXkgODogUmVsYXRpbmcgMkQgc2hhcGVzIHRvIDNEIHNoYXBlcy4gRGF5IDk6IElkZW50aWZ5aW5nIGxpbmVzIG9mIHN5bW1ldHJ5IGluIDJEIHNoYXBlcy4gRGF5IDEwOiBDb21wbGV0aW5nIHN5bW1ldHJpY2FsIGZpZ3VyZXMuIFdlZWsgMzogUG9zaXRpb24sIERpcmVjdGlvbiwgYW5kIFByb2JsZW0gU29sdmluZyAtIERheSAxMTogRGVzY3JpYmluZyBwb3NpdGlvbiB1c2luZyBjb29yZGluYXRlcyBpbiB0aGUgZmlyc3QgcXVhZHJhbnQuIERheSAxMjogUGxvdHRpbmcgY29vcmRpbmF0ZXMgdG8gZHJhdyBzaGFwZXMuIERheSAxMzogVW5kZXJzdGFuZGluZyB0cmFuc2xhdGlvbiAoc2xpZGluZyBhIHNoYXBlKS4gRGF5IDE0OiBVbmRlcnN0YW5kaW5nIHJlZmxlY3Rpb24gKGZsaXBwaW5nIGEgc2hhcGUpLiBEYXkgMTU6IFByb2JsZW0tc29sdmluZyBhY3Rpdml0aWVzIGludm9sdmluZyBwZXJpbWV0ZXIsIGFyZWEsIGFuZCBtaXNzaW5nIGFuZ2xlcy4ifQ=="
    }
  }'

Invece di rimanere a guardare in attesa della risposta, passa all'altro terminale Cloud Shell. Puoi osservare l'avanzamento e eventuali messaggi di output o errore generati dalla funzione nel terminale dell'emulatore. 😁

Dovrebbe essere restituito OK.

Per verificare che il compito sia stato generato e archiviato correttamente, vai alla console Google Cloud e vai a Storage > "Cloud Storage". Seleziona il bucket aidemy-assignment che hai creato. Nel bucket dovresti vedere un file di testo denominato assignment-{random number}.txt. Fai clic sul file per scaricarlo e verificarne i contenuti. In questo modo viene verificato che un nuovo file contenga la nuova assegnazione appena generata.

12-01-assignment-bucket

👉Nel terminale in cui è in esecuzione l'emulatore, digita ctrl+c per uscire. E chiudi il secondo terminale. 👉 Inoltre, nel terminale in cui è in esecuzione l'emulatore, esci dall'ambiente virtuale.

deactivate

Panoramica del deployment

👉Eseguiremo il deployment dell'agente di assegnazione nel cloud

cd ~/aidemy-bootstrap/assignment
export ASSIGNMENT_BUCKET=$(gcloud storage buckets list --format="value(name)" | grep aidemy-assignment)
export OLLAMA_HOST=http://$(gcloud compute instances describe ollama-instance --zone=us-central1-a --format='value(networkInterfaces[0].accessConfigs[0].natIP)'):11434
export PROJECT_ID=$(gcloud config get project)
gcloud functions deploy assignment-agent \
 --gen2 \
 --timeout=540 \
 --memory=2Gi \
 --cpu=1 \
 --set-env-vars="ASSIGNMENT_BUCKET=${ASSIGNMENT_BUCKET}" \
 --set-env-vars=GOOGLE_CLOUD_PROJECT=${GOOGLE_CLOUD_PROJECT} \
 --set-env-vars=OLLAMA_HOST=${OLLAMA_HOST} \
 --region=us-central1 \
 --runtime=python312 \
 --source=. \
 --entry-point=generate_assignment \
 --trigger-topic=plan 

Verifica il deployment nella console Google Cloud, vai a Cloud Run.Dovresti vedere un nuovo servizio denominato courses-agent. 12-03-function-list

Ora che il flusso di lavoro per la generazione dei compiti è stato implementato, testato e implementato, possiamo passare al passaggio successivo: rendere questi compiti accessibili nel portale degli studenti.

14. (FACOLTATIVO) Collaborazione basata sui ruoli con Gemini e DeepSeek - Continua.

Generazione di siti web dinamici

Per migliorare il portale degli studenti e renderlo più coinvolgente, implementeremo la generazione di HTML dinamico per le pagine dei compiti. L'obiettivo è aggiornare automaticamente il portale con un design accattivante e moderno ogni volta che viene generato un nuovo compito. In questo modo, vengono sfruttate le funzionalità di programmazione dell'LLM per creare un'esperienza utente più dinamica e interessante.

14-01-generate-html

👉Nell'editor di Cloud Shell, modifica il file render.py all'interno della cartella portal, sostituisci

def render_assignment_page():
    return ""

con il seguente snippet di codice:

def render_assignment_page(assignment: str):
    try:
        region=get_next_region()
        llm = VertexAI(model_name="gemini-2.0-flash-001", location=region)
        input_msg = HumanMessage(content=[f"Here the assignment {assignment}"])
        prompt_template = ChatPromptTemplate.from_messages(
            [
                SystemMessage(
                    content=(
                        """
                        As a frontend developer, create HTML to display a student assignment with a creative look and feel. Include the following navigation bar at the top:
                        ```
                        <nav>
                            <a href="/">Home</a>
                            <a href="/quiz">Quizzes</a>
                            <a href="/courses">Courses</a>
                            <a href="/assignment">Assignments</a>
                        </nav>
                        ```
                        Also include these links in the <head> section:
                        ```
                        <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
                        <link rel="preconnect" href="https://fonts.googleapis.com">
                        <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
                        <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet">

                        ```
                        Do not apply inline styles to the navigation bar. 
                        The HTML should display the full assignment content. In its CSS, be creative with the rainbow colors and aesthetic. 
                        Make it creative and pretty
                        The assignment content should be well-structured and easy to read.
                        respond with JUST the html file
                        """
                    )
                ),
                input_msg,
            ]
        )

        prompt = prompt_template.format()
        
        response = llm.invoke(prompt)

        response = response.replace("```html", "")
        response = response.replace("```", "")
        with open("templates/assignment.html", "w") as f:
            f.write(response)


        print(f"response: {response}")

        return response
    except Exception as e:
        print(f"Error sending message to chatbot: {e}") # Log this error too!
        return f"Unable to process your request at this time. Due to the following reason: {str(e)}"

Utilizza il modello Gemini per generare dinamicamente il codice HTML per il compito. Prende come input i contenuti del compito e utilizza un prompt per indicare a Gemini di creare una pagina HTML accattivante con uno stile creativo.

Successivamente, creeremo un endpoint che verrà attivato ogni volta che un nuovo documento viene aggiunto al bucket dei compiti:

👉Nella cartella del portale, modifica il file app.py e aggiungi il seguente codice all'interno di ## Add your code here" comments, DOPO la funzione new_teaching_plan:

## Add your code here

@app.route('/render_assignment', methods=['POST'])
def render_assignment():
    try:
        data = request.get_json()
        file_name = data.get('name')
        bucket_name = data.get('bucket')

        if not file_name or not bucket_name:
            return jsonify({'error': 'Missing file name or bucket name'}), 400

        storage_client = storage.Client()
        bucket = storage_client.bucket(bucket_name)
        blob = bucket.blob(file_name)
        content = blob.download_as_text()

        print(f"File content: {content}")

        render_assignment_page(content)

        return jsonify({'message': 'Assignment rendered successfully'})

    except Exception as e:
        print(f"Error processing file: {e}")
        return jsonify({'error': 'Error processing file'}), 500


## Add your code here

Quando viene attivato, recupera il nome del file e il nome del bucket dai dati della richiesta, scarica i contenuti del compito da Cloud Storage e chiama la funzione render_assignment_page per generare il codice HTML.

👉Eseguiamolo in locale:

cd ~/aidemy-bootstrap/portal
source env/bin/activate
python app.py

👉Nel menu "Anteprima web" nella parte superiore della finestra Cloud Shell, seleziona "Anteprima sulla porta 8080". L'applicazione si aprirà in una nuova scheda del browser. Vai al link Assegnazione nella barra di navigazione. A questo punto dovresti vedere una pagina vuota, un comportamento previsto poiché non abbiamo ancora stabilito il ponte di comunicazione tra l'agente di assegnazione e il portale per compilare dinamicamente i contenuti.

14-02-deployment-overview

👉Per incorporare queste modifiche ed eseguire il deployment del codice aggiornato, ricostruisci e carica l'immagine dell'agente del portale:

cd ~/aidemy-bootstrap/portal/
export PROJECT_ID=$(gcloud config get project)
docker build -t gcr.io/${PROJECT_ID}/aidemy-portal .
export PROJECT_ID=$(gcloud config get project)
docker tag gcr.io/${PROJECT_ID}/aidemy-portal us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal
docker push us-central1-docker.pkg.dev/${PROJECT_ID}/agent-repository/aidemy-portal

👉Dopo aver eseguito il push della nuova immagine, esegui nuovamente il deployment del servizio Cloud Run. Esegui lo script seguente per applicare l'aggiornamento di Cloud Run:

export PROJECT_ID=$(gcloud config get project)
export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
gcloud run services update aidemy-portal \
    --region=us-central1 \
    --set-env-vars=GOOGLE_CLOUD_PROJECT=${PROJECT_ID},COURSE_BUCKET_NAME=$COURSE_BUCKET_NAME

👉 Ora implementeremo un trigger Eventarc che ascolta qualsiasi nuovo oggetto creato (finalizzato) nel bucket dei compiti. Questo attivatore chiamerà automaticamente l'endpoint /render_assignment sul servizio del portale quando viene creato un nuovo file di compito.

export PROJECT_ID=$(gcloud config get project)
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$(gcloud storage service-agent --project $PROJECT_ID)" \
  --role="roles/pubsub.publisher"
export SERVICE_ACCOUNT_NAME=$(gcloud compute project-info describe --format="value(defaultServiceAccount)")
gcloud eventarc triggers create portal-assignment-trigger \
--location=us-central1 \
--service-account=$SERVICE_ACCOUNT_NAME \
--destination-run-service=aidemy-portal \
--destination-run-region=us-central1 \
--destination-run-path="/render_assignment" \
--event-filters="bucket=$ASSIGNMENT_BUCKET" \
--event-filters="type=google.cloud.storage.object.v1.finalized"

Per verificare che l'attivatore sia stato creato correttamente, vai alla pagina Trigger Eventarc nella console Google Cloud. Nella tabella dovresti vedere portal-assignment-trigger. Fai clic sul nome dell'attivatore per visualizzarne i dettagli. Trigger assegnazione

Potrebbero essere necessari fino a 2-3 minuti per l'attivazione del nuovo trigger.

Per vedere la generazione di assegnazioni dinamiche in azione, esegui il seguente comando per trovare l'URL del tuo agente di pianificazione (se non lo hai a portata di mano):

gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep planner

Trova l'URL dell'agente del tuo portale:

gcloud run services list --platform=managed --region=us-central1 --format='value(URL)' | grep portal

Nell'agente di pianificazione, genera un nuovo piano di insegnamento.

13-02-assignment

Dopo alcuni minuti (per consentire il completamento della generazione dell'audio, della generazione dei compiti e del rendering HTML), vai al portale degli studenti.

👉Fai clic sul link "Compito" nella barra di navigazione. Dovresti vedere un compito appena creato con un codice HTML generato dinamicamente. Ogni volta che viene generato un piano di insegnamento, deve essere un compito dinamico.

13-02-assignment

Complimenti per aver completato il sistema multi-agente Aidemy. Hai acquisito esperienza pratica e informazioni preziose su:

  • I vantaggi dei sistemi multi-agente, tra cui modularità, scalabilità, specializzazione e manutenzione semplificata.
  • L'importanza delle architetture basate su eventi per la creazione di applicazioni adattabili e a basso accoppiamento.
  • L'uso strategico degli LLM, l'abbinamento del modello giusto all'attività e l'integrazione con strumenti per un impatto concreto.
  • Best practice di sviluppo cloud-native che utilizzano i servizi Google Cloud per creare soluzioni scalabili e affidabili.
  • L'importanza di prendere in considerazione la privacy dei dati e i modelli di self-hosting come alternativa alle soluzioni dei fornitori.

Ora hai una base solida per creare applicazioni sofisticate basate sull'IA su Google Cloud.

15. Problemi e passaggi successivi

Congratulazioni per aver creato il sistema multi-agente Aidemy. Hai gettato le basi per un'istruzione basata sull'IA. Ora, esaminiamo alcune sfide e potenziali miglioramenti futuri per espandere ulteriormente le sue funzionalità e soddisfare le esigenze del mondo reale:

Apprendimento interattivo con domande e risposte live:

  • Sfida: puoi utilizzare l'API Live di Gemini 2 per creare una funzionalità di domande e risposte in tempo reale per gli studenti? Immagina una classe virtuale in cui gli studenti possono porre domande e ricevere risposte immediate basate sull'IA.

Invio e valutazione automatici dei compiti:

  • Sfida: progetta e implementa un sistema che consenta agli studenti di inviare i compiti in formato digitale e di farli valutare automaticamente dall'IA, con un meccanismo per rilevare e prevenire il plagio. Questa sfida offre un'ottima opportunità per esplorare la Retrieval Augmented Generation (RAG) per migliorare l'accuratezza e l'affidabilità dei processi di valutazione e rilevamento del plagio.

aidemy-climb

16. Esegui la pulizia

Ora che abbiamo creato ed esplorato il nostro sistema multi-agente Aidemy, è il momento di ripulire il nostro ambiente Google Cloud.

  1. Eliminare i servizi Cloud Run
gcloud run services delete aidemy-planner --region=us-central1 --quiet
gcloud run services delete aidemy-portal --region=us-central1 --quiet
gcloud run services delete courses-agent --region=us-central1 --quiet
gcloud run services delete book-provider --region=us-central1 --quiet
gcloud run services delete assignment-agent --region=us-central1 --quiet
  1. Eliminare l'trigger Eventarc
gcloud eventarc triggers delete portal-assignment-trigger --location=us --quiet
gcloud eventarc triggers delete plan-topic-trigger --location=us-central1 --quiet
gcloud eventarc triggers delete portal-assignment-trigger --location=us-central1 --quiet
ASSIGNMENT_AGENT_TRIGGER=$(gcloud eventarc triggers list --project="$PROJECT_ID" --location=us-central1 --filter="name:assignment-agent" --format="value(name)")
COURSES_AGENT_TRIGGER=$(gcloud eventarc triggers list --project="$PROJECT_ID" --location=us-central1 --filter="name:courses-agent" --format="value(name)")
gcloud eventarc triggers delete $ASSIGNMENT_AGENT_TRIGGER --location=us-central1 --quiet
gcloud eventarc triggers delete $COURSES_AGENT_TRIGGER --location=us-central1 --quiet
  1. Elimina argomento Pub/Sub
gcloud pubsub topics delete plan --project="$PROJECT_ID" --quiet
  1. Eliminare l'istanza Cloud SQL
gcloud sql instances delete aidemy --quiet
  1. Eliminare il repository Artifact Registry
gcloud artifacts repositories delete agent-repository --location=us-central1 --quiet
  1. Eliminare i secret di Secret Manager
gcloud secrets delete db-user --quiet
gcloud secrets delete db-pass --quiet
gcloud secrets delete db-name --quiet
  1. Elimina l'istanza Compute Engine (se creata per Deepseek)
gcloud compute instances delete ollama-instance --zone=us-central1-a --quiet
  1. Elimina la regola firewall per l'istanza Deepseek
gcloud compute firewall-rules delete allow-ollama-11434 --quiet
  1. Eliminare i bucket Cloud Storage
export COURSE_BUCKET_NAME=$(gcloud storage buckets list --format="value(name)" | grep aidemy-recap)
export ASSIGNMENT_BUCKET=$(gcloud storage buckets list --format="value(name)" | grep aidemy-assignment)
gsutil rb gs://$COURSE_BUCKET_NAME
gsutil rb gs://$ASSIGNMENT_BUCKET

aidemy-broom