Creare agenti di enti di beneficenza affidabili con Google ADK e AP2

1. Creare fiducia per favorire la generosità

banner

Il momento dell'ispirazione

Lo smartphone vibra. Vedi un articolo di notizie su un programma di alfabetizzazione di successo che aiuta i bambini delle comunità svantaggiate a imparare a leggere. Senti un forte desiderio di contribuire. Apri il browser e cerchi "donazioni per programmi di alfabetizzazione per bambini".

ricerca google

Vengono visualizzati centinaia di risultati.

Fai clic sul primo link. Il sito web ha un aspetto professionale. Scorri verso il basso fino ai dati finanziari. "Costi amministrativi: 28%." Metti in pausa. Solo 72 centesimi di ogni dollaro donato finanzieranno effettivamente il programma. Va bene? Non sei sicuro.

Prova con un'altra organizzazione. Non ne hai mai sentito parlare. Sono legittimi? Una rapida ricerca ti porta in un vicolo cieco. Trovi un thread di Reddit di due anni fa in cui un utente afferma: "È una truffa, la mia donazione non è mai arrivata a destinazione". Un altro li difende con passione: "Sono sul campo e fanno un lavoro vero!" L'ambiguità è paralizzante.

Trenta minuti dopo, ti trovi nel bel mezzo di un labirinto di recensioni contrastanti, valutazioni di efficienza e registri dell'IRS e non hai ancora effettuato la donazione. L'iniziale slancio di generosità è stato sostituito dalla difficoltà della ricerca. La scheda rimane aperta per alcuni giorni, un piccolo promemoria di una buona intenzione, finché non la chiudi.

Non si tratta di un fallimento personale, ma di un errore di sistema

Questa esperienza è universale. Il desiderio di donare è abbondante, ma la procedura è piena di ostacoli che causano esitazione e dubbi:

  • Frizioni nella ricerca: ogni ente di beneficenza richiede una propria indagine.
  • Verifica dell'affidabilità:è difficile distinguere le organizzazioni altamente efficaci da quelle inefficienti o addirittura da vere e proprie frodi.
  • Paralisi decisionale:un numero eccessivo di scelte porta a una perdita di interesse causata dall'indecisione.
  • Perdita di slancio:la motivazione emotiva a donare svanisce man mano che il carico logistico aumenta.

Questo attrito ha un costo reale e sorprendente. Le donazioni individuali negli Stati Uniti sono enormi: secondo Giving USA 2024, i donatori individuali hanno donato circa 374 miliardi di dollari solo nel 2023. Tuttavia, le ricerche dimostrano che gli ostacoli alla donazione, tra cui i costi di ricerca, l'attrito psicologico e i vincoli di tempo, riducono significativamente l'importo che raggiunge le cause di beneficenza. Studi che coinvolgono milioni di donatori hanno rilevato che anche piccoli attriti nel processo di donazione online impediscono alle persone di realizzare le loro intenzioni di beneficenza.

Si tratta di miliardi di dollari in donazioni previste che non raggiungono mai le cause che ne hanno bisogno.

La visione

Immagina un'esperienza diversa. Invece di una sessione di ricerca di 30 minuti, ti basterà dire:

"Voglio donare 50 $a un programma di alfabetizzazione per bambini. Trovami un ente di beneficenza efficiente, verificato e con un ottimo punteggio."

In pochi secondi, ricevi una risposta che ti rassicura:

scheda dei risultati di beneficenza

Questa è la promessa di un agente AI per le donazioni. Ma per realizzare questa visione, dobbiamo risolvere una sfida fondamentale: quando un agente di AI autonomo gestisce denaro, la fiducia non è facoltativa, ma è l'intera base.

  • Come possiamo dimostrare ciò che un utente ha autorizzato?
  • Chi è responsabile in caso di errore?
  • Come facciamo a dare a donatori, enti benefici e circuiti di pagamento la fiducia necessaria per partecipare?

La tua missione di oggi

In questo workshop, creerai questo agente affidabile combinando due potenti tecnologie:

Google Agent Development Kit (ADK)

Agent Payments Protocol (AP2)

Role

La fabbrica per la creazione di agenti AI di livello di produzione

Il progetto di architettura per la fiducia nelle transazioni AI

Cosa offre

• Framework per l'orchestrazione multi-agente
• Funzionalità di sicurezza integrate come la conferma degli strumenti
• Osservabilità e tracciamento pronti per la produzione
• Interfaccia Python semplice per comportamenti complessi degli agenti

• Limiti di sicurezza basati sui ruoli
• Credenziali digitali verificabili (mandati)
• Prova crittografica del consenso
• Audit trail completi per la responsabilità

Scopri di più

Documentazione ADK

Protocollo AP2

Cosa creerai

architettura

Al termine di questo workshop, avrai creato:

Un sistema multi-agente con ruoli specializzati:

  • Un agente di Shopping che trova enti di beneficenza verificati
  • Un agente del commerciante che crea offerte di donazione vincolanti
  • Un fornitore di credenziali che elabora i pagamenti in modo sicuro
  • Un orchestratore che coordina l'intero flusso

Tre tipi di credenziali verificabili:

  • IntentMandate: "Find me an education charity" (trovami un ente di beneficenza per l'istruzione)
  • CartMandate: "50 $ a Room to Read, firmato dal commerciante"
  • PaymentMandate: "Process via simulated payment" (Elabora tramite pagamento simulato)

Sicurezza a ogni livello:

  • Confini di attendibilità basati sui ruoli
  • Consenso esplicito dell'utente

Un audit trail completo:

  • Ogni decisione è tracciabile
  • Ogni consenso registrato
  • Ogni passaggio visibile

🔒 Importante: questo è un ambiente di apprendimento sicuro

Pronto a creare fiducia?

Nel prossimo modulo, configureremo l'ambiente di sviluppo e creeremo il tuo primo agente AI. Scoprirai rapidamente perché gli agenti semplici non sono affidabili e trascorrerai il resto del workshop imparando a risolvere il problema.

Iniziamo a comprendere il problema in prima persona.

2. Preparazione del workspace

The Foundation for Trustworthy Agents

Prima di poter creare il nostro AI Giving Agent, dobbiamo preparare un ambiente di sviluppo pulito, coerente e configurato correttamente. Questo modulo è un passaggio mirato per garantire che tutti gli strumenti e i servizi necessari siano disponibili.

Il completamento di questa configurazione ti consente di concentrarti interamente sul lavoro entusiasmante di creazione della logica dell'agente nei moduli futuri, senza preoccuparti di problemi di configurazione.

Accedere a Cloud Shell

Innanzitutto, apri Cloud Shell, un terminale basato su browser con Google Cloud SDK e altri strumenti essenziali preinstallati.

Hai bisogno di crediti Google Cloud?

Fai clic su Attiva Cloud Shell nella parte superiore della console Google Cloud (è l'icona del terminale nella barra di navigazione in alto a destra).

cloud shell

Trova l'ID progetto Google Cloud:

  • Apri la console Google Cloud: https://console.cloud.google.com
  • Seleziona il progetto che vuoi utilizzare per questo workshop dal menu a discesa dei progetti nella parte superiore della pagina.
  • L'ID progetto viene visualizzato nella scheda informativa del progetto nella dashboard ID progetto

Una volta aperto Cloud Shell, verifica di aver eseguito l'autenticazione:

# Check that you are logged in
gcloud auth list

Dovresti vedere il tuo account elencato come (ACTIVE).

Configurare il progetto

Ora configuriamo il progetto Google Cloud e abilitiamo le API necessarie.

Impostare l'ID progetto

# Set your project using the auto-detected environment variable in Cloud Shell
gcloud config set project $GOOGLE_CLOUD_PROJECT

# Verify the project has been set
echo "Your active Google Cloud project is: $(gcloud config get-value project)"

Abilita le API richieste

I tuoi agenti devono accedere a diversi servizi Google Cloud:

gcloud services enable \
    aiplatform.googleapis.com \
    secretmanager.googleapis.com \
    cloudtrace.googleapis.com

Potrebbero essere necessari un paio di minuti. Visualizzerai:

Operation "operations/..." finished successfully.

Cosa forniscono queste API:

  • aiplatform.googleapis.com: accesso ai modelli Gemini per il ragionamento degli agenti
  • secretmanager.googleapis.com: spazio di archiviazione sicuro per le chiavi API (best practice per la produzione)
  • cloudtrace.googleapis.com: osservabilità per la nostra traccia di responsabilità

Clona il codice iniziale

Recupera il repository del workshop con tutto il codice e le risorse del modello:

git clone https://github.com/ayoisio/adk-ap2-charity-agents
cd adk-ap2-charity-agents
git checkout codelab

Verifichiamo cosa abbiamo:

ls -la

Dovresti vedere:

  • charity_advisor/: dove creeremo i nostri agenti e strumenti
  • scripts/ - Script di supporto per test e verifica
  • deploy.sh - Helper script for deployment
  • setup.py - Helper script for module installation
  • .env.template - File delle variabili di ambiente

Configura l'ambiente Python

Ora creeremo un ambiente Python isolato per il nostro progetto.

Crea e attiva l'ambiente virtuale

# Create the virtual environment
python3 -m venv venv

# Activate it
source venv/bin/activate

Verifica: ora il prompt dovrebbe mostrare il prefisso (venv).

Installa le dipendenze

pip install -r charity_advisor/requirements.txt
pip install -e .

Viene installato:

  • google-adk: il framework dell'Agent Development Kit
  • google-cloud-aiplatform: integrazione di Vertex AI e Gemini
  • ap2: SDK Agent Payments Protocol (da GitHub)
  • python-dotenv: gestione delle variabili di ambiente

Il flag -e ti consente di importare moduli adk_ap2_charity_agents da qualsiasi posizione.

Configura file di ambiente

Crea la configurazione dal modello:

# Copy the template
cp .env.template .env

# Get your current Project ID
PROJECT_ID=$(gcloud config get-value project)

# Replace the placeholder with your actual project ID
sed -i "s/your-project-id/$PROJECT_ID/g" .env

# Verify the replacement worked
grep GOOGLE_CLOUD_PROJECT .env

Dovresti vedere:

GOOGLE_CLOUD_PROJECT=your-actual-project-id

Verifica

Esegui lo script di verifica per assicurarti che tutto sia configurato correttamente:

python scripts/verify_setup.py

Dovresti vedere tutti i segni di spunta verdi:

======================================================================
SETUP VERIFICATION
======================================================================

✓ Python version: 3.11.x
✓ google-adk: 1.17.0
✓ google-cloud-aiplatform: 1.111.0+
✓ ap2: 0.1.0
✓ python-dotenv: 1.0.0+
✓ .env file found and contains project ID
✓ Google Cloud project configured: your-project-id

✓ Mock charity database found
✓ Agent templates ready
✓ All directories present

======================================================================
✓ Setup complete! You are ready to build trustworthy agents.
======================================================================

Risoluzione dei problemi

Passaggi successivi

Il tuo ambiente è ora completamente preparato. Sei ora in grado di eseguire le seguenti attività:

  • ✅ Progetto Google Cloud configurato
  • ✅ API richieste abilitate
  • ✅ Librerie ADK e AP2 installate
  • ✅ Codice modello pronto per la modifica

Nel modulo successivo, creerai il tuo primo agente AI in poche righe di codice e scoprirai perché gli agenti semplici non sono affidabili quando gestiscono transazioni finanziarie.

3. Il tuo primo agente e la scoperta del divario di fiducia

banner

Dall'idea all'interazione

Nel modulo precedente, abbiamo preparato il nostro ambiente di sviluppo. Ora inizia il lavoro entusiasmante. Creeremo ed eseguiremo il nostro primo agente, gli daremo la sua prima funzionalità e, così facendo, scopriremo le sfide fondamentali che dobbiamo risolvere per renderlo davvero affidabile.

Questo modulo è la tua immagine "prima": il momento che rivela perché la creazione di agenti affidabili richiede più che dare a un LLM l'accesso agli strumenti.

Passaggio 1: esamina l'agente iniziale

Innanzitutto, diamo un'occhiata al modello del nostro primo agente. Contiene una struttura di base con segnaposto che completeremo nei passaggi successivi.

👉 Apri il file

charity_advisor/simple_agent/agent.py

nell'editor.

Visualizzerai:

"""
A simple agent that can research charities using Google Search.
"""

# MODULE_3_STEP_2_IMPORT_COMPONENTS


simple_agent = Agent(
    name="SimpleAgent",
    model="gemini-2.5-flash",
    
    # MODULE_3_STEP_3_WRITE_INSTRUCTION
    instruction="""""",
    
    # MODULE_3_STEP_4_ADD_TOOLS
    tools=[]
)

Nota che i commenti segnaposto seguono un pattern: MODULE_3_STEP_X_DESCRIPTION. Sostituiremo questi marcatori per creare progressivamente il nostro agente.

Passaggio 2: importa i componenti richiesti

Prima di poter creare un'istanza della classe Agent o utilizzare lo strumento google_search, dobbiamo importarli nel nostro file.

👉 Trova:

# MODULE_3_STEP_2_IMPORT_COMPONENTS

👉 Sostituisci quella singola riga con:

from google.adk.agents import Agent
from google.adk.tools import google_search

Ora la classe Agent e lo strumento google_search sono disponibili nel nostro file.

Passaggio 3: scrivi l'istruzione dell'agente

L'istruzione è la "descrizione del lavoro" dell'agente: indica al modello LLM quando e come utilizzare i suoi strumenti. Scriviamone una che guidi il nostro agente a cercare informazioni sulle organizzazioni non profit.

👉 Trova:

# MODULE_3_STEP_3_WRITE_INSTRUCTION
instruction="""""",

👉 Sostituisci queste due righe con:

instruction="""You are a helpful research assistant. When a user asks you to find information about charities,
use the google_search tool to find the most relevant and up-to-date results from the web.
Synthesize the search results into a helpful summary.""",

Passaggio 4: aggiungi lo strumento di ricerca

Un agente senza strumenti è solo un conversatore. Diamo al nostro agente la sua prima funzionalità: la possibilità di cercare sul web.

👉 Trova:

# MODULE_3_STEP_4_ADD_TOOLS
tools=[]

👉 Sostituisci queste due righe con:

tools=[google_search]

Passaggio 5: verifica l'agente completo

Prima di eseguire il test, assicuriamoci che tutti i pezzi siano al loro posto.

👉 Il tuo profilo completo

charity_advisor/simple_agent/agent.py

Il file ora dovrebbe avere esattamente questo aspetto:

"""
A simple agent that can research charities using Google Search.
"""

from google.adk.agents import Agent
from google.adk.tools import google_search


simple_agent = Agent(
    name="SimpleAgent",
    model="gemini-2.5-flash",
    instruction="""You are a helpful research assistant. When a user asks you to find information about charities,
use the google_search tool to find the most relevant and up-to-date results from the web.
Synthesize the search results into a helpful summary.""",
    tools=[google_search]
)

Passaggio 6: prova l'agente, mettendo in evidenza i problemi di attendibilità

Ora che l'agente è completamente configurato, testiamolo e analizziamo il suo comportamento. È qui che scopriamo perché gli agenti semplici non sono affidabili quando prendono decisioni finanziarie.

Test 1: il problema della scoperta

👉 Nel terminale Cloud Shell, esegui questo comando:

adk run charity_advisor/simple_agent

Dovresti visualizzare un output simile al seguente:

INFO:google.adk.agents:Loading agent from charity_advisor/simple_agent
INFO:google.adk.agents:Agent 'SimpleAgent' ready

[user]:

Il prompt [user]: è ora in attesa del tuo input.

👉 Al prompt [user], digita:

Can you find me a verified, highly-rated charity for children's literacy?

👉 Premi Invio e osserva la risposta.

Dopo qualche istante, l'agente sintetizzerà i risultati di ricerca in una risposta come questa:

In base a una ricerca sul web, alcune organizzazioni di beneficenza ben considerate per l'alfabetizzazione dei bambini sembrano essere Reading Is Fundamental e Room to Read. Fonti come Charity Navigator e GuideStar sono spesso consigliate per verificare il loro stato e le loro valutazioni. Ho anche trovato diverse discussioni online, anche su forum come Reddit, in cui gli utenti condividono esperienze personali con vari programmi di alfabetizzazione locali più piccoli.

Analizziamo la situazione. L'agente ha risolto il nostro problema?

No. Ha replicato perfettamente l'esperienza umana descritta nel Modulo 1. Ha automatizzato con successo il processo di "fare una ricerca su Google" e ci ha restituito il problema della "paralisi da analisi".

Ciò mette in evidenza il primo problema di fiducia: la mancanza di dati autorevoli.

L'agente sta cercando sul web aperto, il che significa che:

  • ✅ Ha trovato risultati rapidamente (esperienza utente migliorata)
  • ❌ Mescola organizzazioni con valutazioni elevate con discussioni su Reddit (fonti inaffidabili)
  • ❌ Non è in grado di distinguere tra enti di beneficenza verificati e potenziali frodi (nessuna verifica)
  • ❌ Ci chiede di verificare le informazioni che ha appena fornito (scaricando la responsabilità)

Test 2: il problema di esecuzione

Ora passiamo al secondo test, quello cruciale. Nel prompt [user]:, prova a completare la donazione:

Okay, please donate $50 to Room to Read for me.

L'agente risponderà scusandosi e ammettendo la sua limitazione:

Mi risulta che vorresti fare una donazione, il che è meraviglioso. Tuttavia, sono un assistente di ricerca e non ho la possibilità di gestire transazioni finanziarie o elaborare pagamenti. Per fare una donazione, devi visitare direttamente il sito web ufficiale di Room to Read.

Questo è il secondo momento "Aha!", altrettanto importante.

Non solo non ci si può fidare dell'agente per trovare l'ente di beneficenza giusto, ma non ci si può ancora fidare che esegua l'azione di donazione.

👉 Premi

Ctrl+C

per uscire al termine del test.

Visualizzazione dei due intervalli

problema di attendibilità

Cosa hai appena imparato

In questo modulo, hai creato e configurato correttamente il tuo primo agente AI. In questo modo, hai scoperto le due sfide fondamentali per la creazione di un sistema affidabile.

Concetti fondamentali padroneggiati

The Agent Class:

  • Componente di base di ADK
  • Combina il ragionamento (cervello) degli LLM con gli strumenti (mani)
  • Configurato con modello, istruzioni e strumenti

Struttura basata sulle cartelle:

  • Ogni agente si trova in una cartella separata
  • ADK cerca agent_folder/agent.py
  • Corri con adk run agent_folder

Elenco degli strumenti:

  • Definisce le funzionalità dell'agente
  • Il modello LLM decide quando e come utilizzare gli strumenti
  • Può contenere più strumenti per azioni diverse

Il prompt di istruzioni:

  • Guida il comportamento dell'agente come una descrizione del lavoro
  • Specifica ruolo, trigger, azioni e formato di output
  • Fondamentale per un utilizzo affidabile dello strumento

Il problema della fiducia:

  • Lacuna nella scoperta: fonti non verificate, qualità mista
  • Lacuna di esecuzione: nessuna funzionalità sicura, nessun consenso, nessun audit trail

Passaggi successivi

Nel modulo successivo inizieremo a creare la soluzione implementando l'architettura basata sui ruoli di AP2.

Creiamo il primo agente e vediamo la separazione dei ruoli in azione.

4. Creazione dell'agente Shopping - Rilevamento basato sui ruoli

banner

Principi di base di affidabilità: separazione dei ruoli

Nell'ultimo modulo, hai scoperto che un agente semplice e generico non funziona per due motivi: non può fornire una scoperta affidabile e non può eseguire transazioni sicure. Ora inizieremo a risolvere questi problemi implementando il primo principio del protocollo di pagamento degli agenti: l'architettura basata sui ruoli.

Prima di scrivere qualsiasi codice, cerchiamo di capire perché questo principio è importante.

Principio AP2: separazione dei ruoli

Il problema degli agenti "Fai tutto"

Immagina di assumere una persona che sia il tuo consulente finanziario, contabile e intermediario di investimento. Conveniente? Sì. Sicuro? Assolutamente no. Avrebbe:

  • I tuoi obiettivi di investimento (ruolo di consulente)
  • Accesso ai tuoi account (ruolo di contabile)
  • Autorizzazione a spostare il denaro (ruolo di intermediario)

Se questa persona viene compromessa o commette un errore, tutto è a rischio.

Soluzione di AP2: un agente, un lavoro

AP2 applica il principio di separazione delle competenze per creare confini di trust:

architettura

Perché è importante:

  • Raggio di esplosione limitato: se l'agente Shopping viene compromesso, l'autore dell'attacco non può accedere alle credenziali di pagamento
  • Privacy: il fornitore delle credenziali non vede mai la tua conversazione di acquisto
  • Conformità: è più facile soddisfare i requisiti PCI-DSS quando i dati di pagamento sono isolati
  • Responsabilità: responsabilità chiara per ogni passaggio

Modalità di comunicazione degli agenti: lo stato come blocco note condiviso

Poiché gli agenti non possono accedere direttamente ai dati degli altri, comunicano tramite uno stato condiviso. Consideralo come una lavagna su cui tutti gli agenti possono scrivere e leggere:

# Shopping Agent writes:
state["intent_mandate"] = {
    "natural_language_description": "Donate $50 to Room to Read",
    "merchants": ["Room to Read"],
    "intent_expiry": "2024-11-07T15:32:16Z",
    "amount": 50.0
}

# Merchant Agent reads:
intent = state["intent_mandate"]
charity_name = intent["merchants"][0]
amount = intent["amount"]
# Creates CartMandate based on IntentMandate...

# Credentials Provider reads:
cart_mandate = state["cart_mandate"]
# Processes payment...

In questo modo manteniamo i limiti di fiducia consentendo la collaborazione.

Il nostro primo agente: l'agente per lo shopping

La responsabilità dell'agente per lo shopping è semplice e mirata:

  1. Utilizza lo strumento find_charities per eseguire query nel nostro database attendibile
  2. Presentare le opzioni all'utente
  3. Utilizza lo strumento save_user_choice per creare un IntentMandate e salvarlo nello stato
  4. Trasferimento all'agente successivo (il commerciante)

È tutto. Nessuna gestione dei pagamenti, nessuna creazione di carrelli: solo rilevamento e trasferimento.

Vediamo come fare passo dopo passo.

Passaggio 1: aggiungi Input Validation Helper

Quando crei strumenti di produzione, la convalida dell'input è fondamentale. Creiamo una funzione helper che convalidi i dati dell'ente di beneficenza prima di salvarli nello stato.

👉 Apri

charity_advisor/tools/charity_tools.py

In alto vedrai la funzione find_charities (già completata). Scorri verso il basso per trovare:

# MODULE_4_STEP_1_ADD_VALIDATION_HELPER

👉 Sostituisci quella singola riga con:

def _validate_charity_data(charity_name: str, charity_ein: str, amount: float) -> tuple[bool, str]:
    """
    Validates charity selection data before saving to state.
    
    This helper function performs basic validation to ensure data quality
    before it gets passed to other agents in the pipeline.
    
    Args:
        charity_name: Name of the selected charity
        charity_ein: Employer Identification Number (should be format: XX-XXXXXXX)
        amount: Donation amount in USD
        
    Returns:
        (is_valid, error_message): Tuple where is_valid is True if all checks pass,
                                    and error_message contains details if validation fails
    """
    # Validate charity name
    if not charity_name or not charity_name.strip():
        return False, "Charity name cannot be empty"
    
    # Validate EIN format (should be XX-XXXXXXX)
    if not charity_ein or len(charity_ein) != 10 or charity_ein[2] != '-':
        return False, f"Invalid EIN format: {charity_ein}. Expected format: XX-XXXXXXX"
    
    # Validate amount
    if amount <= 0:
        return False, f"Donation amount must be positive, got: ${amount}"
    
    if amount > 1_000_000:
        return False, f"Donation amount exceeds maximum of $1,000,000: ${amount}"
    
    # All checks passed
    return True, ""

Passaggio 2: aggiungi lo strumento di creazione di IntentMandate

Ora creiamo l'helper che crea la struttura AP2 IntentMandate. Si tratta di una delle tre credenziali verificabili in AP2.

👉 Nello stesso file, trova:

# MODULE_4_STEP_2_ADD_INTENTMANDATE_CREATION_HELPER

👉 Sostituisci quella singola riga con:

def _create_intent_mandate(charity_name: str, charity_ein: str, amount: float) -> dict:
    """
    Creates an IntentMandate - AP2's verifiable credential for user intent.
    
    This function uses the official Pydantic model from the `ap2` package
    to create a validated IntentMandate object before converting it to a dictionary.
    
    Args:
        charity_name: Name of the selected charity
        charity_ein: Employer Identification Number
        amount: Donation amount in USD
        
    Returns:
        Dictionary containing the IntentMandate structure per AP2 specification
    """
    from datetime import datetime, timedelta, timezone
    from ap2.types.mandate import IntentMandate
    
    # Set the expiry for the intent
    expiry = datetime.now(timezone.utc) + timedelta(hours=1)
    
    # Step 1: Instantiate the Pydantic model with official AP2 fields
    intent_mandate_model = IntentMandate(
        user_cart_confirmation_required=True,
        natural_language_description=f"Donate ${amount:.2f} to {charity_name}",
        merchants=[charity_name],
        skus=None,
        requires_refundability=False,
        intent_expiry=expiry.isoformat()
    )
    
    # Step 2: Convert the validated model to a dictionary for state storage
    intent_mandate_dict = intent_mandate_model.model_dump()
    
    # Step 3: Add the codelab's custom fields to the dictionary
    timestamp = datetime.now(timezone.utc)
    intent_mandate_dict.update({
        "timestamp": timestamp.isoformat(),
        "intent_id": f"intent_{charity_ein.replace('-', '')}_{int(timestamp.timestamp())}",
        "charity_ein": charity_ein,
        "amount": amount,
        "currency": "USD"
    })
    
    return intent_mandate_dict

Passaggio 3: crea lo strumento di trasferimento dello stato con IntentMandate

Ora creiamo lo strumento che crea IntentMandate e lo salva nello stato.

👉 Nello stesso file, scorri verso il basso fino alla sezione

save_user_choice

funzione . Trova:

# MODULE_4_STEP_3_COMPLETE_SAVE_TOOL

👉 Sostituisci quella singola riga con:

    # Validate inputs before creating IntentMandate
    is_valid, error_message = _validate_charity_data(charity_name, charity_ein, amount)
    if not is_valid:
        logger.error(f"Validation failed: {error_message}")
        return {"status": "error", "message": error_message}
    
    # Create AP2 IntentMandate using our updated helper function
    intent_mandate = _create_intent_mandate(charity_name, charity_ein, amount)
    
    # Write the IntentMandate to shared state for the next agent
    tool_context.state["intent_mandate"] = intent_mandate
    
    logger.info(f"Successfully created IntentMandate and saved to state")
    logger.info(f"Intent ID: {intent_mandate['intent_id']}")
    logger.info(f"Intent expires: {intent_mandate['intent_expiry']}")
    
    # Return success confirmation
    return {
        "status": "success",
        "message": f"Created IntentMandate: ${amount:.2f} donation to {charity_name} (EIN: {charity_ein})",
        "intent_id": intent_mandate["intent_id"],
        "expiry": intent_mandate["intent_expiry"]
    }

Passaggio 4: aggiungi lo strumento di formattazione della visualizzazione

Prima di creare l'agente, aggiungiamo un altro helper che formatti i dati dell'ente di beneficenza per una visualizzazione intuitiva.

👉 Scorri per trovare:

# MODULE_4_STEP_4_ADD_FORMATTING_HELPER

👉 Sostituisci quella singola riga con:

def _format_charity_display(charity: dict) -> str:
    """
    Formats a charity dictionary into a user-friendly display string.
    
    This helper function demonstrates how to transform structured data
    into readable text for the user.
    
    Args:
        charity: Dictionary containing charity data (name, ein, mission, rating, efficiency)
        
    Returns:
        Formatted string suitable for display to the user
    """
    name = charity.get('name', 'Unknown')
    ein = charity.get('ein', 'N/A')
    mission = charity.get('mission', 'No mission statement available')
    rating = charity.get('rating', 0.0)
    efficiency = charity.get('efficiency', 0.0)
    
    # Format efficiency as percentage
    efficiency_pct = int(efficiency * 100)
    
    # Build formatted string
    display = f"""
**{name}** (EIN: {ein})
⭐ Rating: {rating}/5.0
💰 Efficiency: {efficiency_pct}% of funds go to programs
📋 Mission: {mission}
    """.strip()
    
    return display

Passaggio 5: crea l'agente Shopping - importa i componenti

Ora che i nostri strumenti sono completi e robusti, creiamo l'agente che li utilizzerà.

👉 Apri

charity_advisor/shopping_agent/agent.py

Vedrai un modello con commenti segnaposto. Vediamo come fare passo dopo passo.

👉 Trova:

# MODULE_4_STEP_5_IMPORT_COMPONENTS

👉 Sostituisci quella singola riga con:

from google.adk.agents import Agent
from google.adk.tools import FunctionTool
from charity_advisor.tools.charity_tools import find_charities, save_user_choice

Passaggio 6: scrivi le istruzioni per l'agente

L'istruzione è il punto in cui definiamo la descrizione del lavoro e il flusso di lavoro dell'agente. Questo è fondamentale: un'istruzione scritta male porta a un comportamento inaffidabile.

👉 Trova:

# MODULE_4_STEP_6_WRITE_INSTRUCTION
instruction="""""",

👉 Sostituisci queste due righe con:

    instruction="""You are a research specialist helping users find verified charities.

Your workflow:

1. When the user describes what cause they want to support (e.g., "education", "health", "environment"),
   use the find_charities tool to search our vetted database.

2. Present the results clearly. The tool returns formatted charity information that you should
   show to the user.

3. When the user selects a charity and specifies an amount, use the save_user_choice tool
   to create an IntentMandate and record their decision. You MUST call save_user_choice with:
   - charity_name: The exact name of the chosen charity
   - charity_ein: The EIN of the chosen charity  
   - amount: The donation amount in dollars (as a number, not a string)

4. After successfully saving, inform the user:
   - That you've created an IntentMandate (mention the intent ID if provided)
   - When the intent expires
   - That you're passing their request to the secure payment processor

IMPORTANT BOUNDARIES:
- Your ONLY job is discovery and creating the IntentMandate
- You do NOT process payments
- You do NOT see the user's payment methods
- You do NOT create cart offers (that's the Merchant Agent's job)
- After calling save_user_choice, your work is done

WHAT IS AN INTENTMANDATE:
An IntentMandate is a structured record of what the user wants to do. It includes:
- Natural language description ("Donate $50 to Room to Read")
- Which merchants can fulfill it
- When the intent expires
- Whether user confirmation is required

This is the first of three verifiable credentials in our secure payment system.

If the user asks you to do anything related to payment processing, politely explain that
you don't have that capability and that their request will be handled by the appropriate
specialist agent.""",

Passaggio 7: aggiungi gli strumenti all'agente

Ora concediamo all'agente l'accesso a entrambi gli strumenti.

👉 Trova:

# MODULE_4_STEP_7_ADD_TOOLS

👉 Sostituisci queste due righe con:

    tools=[
        FunctionTool(func=find_charities),
        FunctionTool(func=save_user_choice)
    ]

Passaggio 8: verifica l'agente completo

Verifichiamo che tutto sia cablato correttamente.

👉 Il tuo profilo completo

charity_advisor/shopping_agent/agent.py

Ora dovrebbe avere questo aspetto:

"""
Shopping Agent - Finds charities from a trusted database and saves the user's choice.
This agent acts as our specialized "Research Analyst."
"""

from google.adk.agents import Agent
from google.adk.tools import FunctionTool
from charity_advisor.tools.charity_tools import find_charities, save_user_choice


shopping_agent = Agent(
    name="ShoppingAgent",
    model="gemini-2.5-pro",
    description="Finds and recommends vetted charities from a trusted database, then creates an IntentMandate capturing the user's donation intent.",
    instruction="""You are a research specialist helping users find verified charities.

Your workflow:

1. When the user describes what cause they want to support (e.g., "education", "health", "environment"),
   use the find_charities tool to search our vetted database.

2. Present the results clearly. The tool returns formatted charity information that you should
   show to the user.

3. When the user selects a charity and specifies an amount, use the save_user_choice tool
   to create an IntentMandate and record their decision. You MUST call save_user_choice with:
   - charity_name: The exact name of the chosen charity
   - charity_ein: The EIN of the chosen charity  
   - amount: The donation amount in dollars (as a number, not a string)

4. After successfully saving, inform the user:
   - That you've created an IntentMandate (mention the intent ID if provided)
   - When the intent expires
   - That you're passing their request to the secure payment processor

IMPORTANT BOUNDARIES:
- Your ONLY job is discovery and creating the IntentMandate
- You do NOT process payments
- You do NOT see the user's payment methods
- You do NOT create cart offers (that's the Merchant Agent's job)
- After calling save_user_choice, your work is done

WHAT IS AN INTENTMANDATE:
An IntentMandate is a structured record of what the user wants to do. It includes:
- Natural language description ("Donate $50 to Room to Read")
- Which merchants can fulfill it
- When the intent expires
- Whether user confirmation is required

This is the first of three verifiable credentials in our secure payment system.

If the user asks you to do anything related to payment processing, politely explain that
you don't have that capability and that their request will be handled by the appropriate
specialist agent.""",
    tools=[
        FunctionTool(func=find_charities),
        FunctionTool(func=save_user_choice)
    ]
)

Perfetto. Hai creato un agente conforme ad AP2 e di qualità di produzione con:

  • Convalida dell'input
  • Creazione corretta di IntentMandate utilizzando i modelli Pydantic AP2
  • Output formattato
  • Definisci confini chiari per i ruoli
  • Istruzioni dettagliate
  • Gestione degli errori

Passaggio 9: prova l'agente Shopping

Verifichiamo che il nostro agente funzioni correttamente, crei IntentMandates appropriati e rispetti i suoi limiti.

👉 Nel terminale Cloud Shell, esegui:

adk run charity_advisor/shopping_agent

Viene visualizzato il prompt [user]:.

Test 1: Discovery con Trusted Database

👉 Tipo:

I want to donate to an education charity. What are my options?

Dopo qualche istante, l'agente fornirà una risposta. Grazie al nostro _format_charity_display, i risultati sono formattati in modo ottimale:

Nel nostro database ho trovato tre enti di beneficenza per l'istruzione verificati:

Room to Read (EIN: 77-0479905)
⭐ Valutazione: 4,9/5,0
💰 Efficienza: l'88% dei fondi viene destinato ai programmi
📋 Missione: trasforma la vita di milioni di bambini nelle comunità a basso reddito concentrandosi sull'alfabetizzazione e sull'uguaglianza di genere nell'istruzione.

Teach For America (EIN: 13-3541913)
⭐ Valutazione: 4,7/5,0
💰 Efficienza: l'81% dei fondi viene destinato ai programmi
📋 Missione: si impegna a espandere le opportunità di istruzione per i bambini che vivono in condizioni di difficoltà.

Tech Education Alliance (EIN: 45-2345678)
⭐ Valutazione: 4,8/5,0
💰 Efficienza: il 92% dei fondi viene destinato ai programmi
📋 Missione: porta l'educazione informatica nelle scuole svantaggiate.

Quale ente di beneficenza vuoi sostenere e quanto vuoi donare?

Confrontalo con l'agente semplice del modulo 3, che ti ha fornito risultati di Google non verificati. Questa è la differenza tra rilevamento non attendibile e attendibile.

Test 2: registra la scelta dell'utente e crea IntentMandate

👉 Tipo:

I'll donate $50 to Room to Read.

Dovresti vedere l'agente chiamare lo strumento save_user_choice con i parametri corretti. Grazie alla creazione di IntentMandate utilizzando i modelli AP2, vedrai una risposta più strutturata:

Perfetto! Ho creato un IntentMandate per la tua donazione:

Dettagli dell'intent:

  • ID intento: intent_774795905_1730927536
  • Importo: 50 $ a Room to Read (EIN: 77-0479905)
  • Scade: 2024-11-07T15:32:16Z (tra 1 ora)

Questo IntentMandate acquisisce la tua intenzione di donare e include vincoli per garantire che la transazione venga elaborata in modo sicuro. Ora inoltro la richiesta al nostro sistema di elaborazione dei pagamenti sicuro per creare un'offerta formale e completare la donazione.

Dietro le quinte, sono successe diverse cose:

  1. _validate_charity_data() ha verificato gli input
  2. _create_intent_mandate() ha utilizzato il modello Pydantic AP2 per creare la struttura:
   # Created and validated via Pydantic
   intent_mandate_model = IntentMandate(
       user_cart_confirmation_required=True,
       natural_language_description="Donate $50.00 to Room to Read",
       merchants=["Room to Read"],
       skus=None,
       requires_refundability=False,
       intent_expiry="2024-11-07T15:32:16Z"
   )
   
   # Converted to dict and extended with custom fields
   intent_mandate_dict = intent_mandate_model.model_dump()
   intent_mandate_dict.update({
       "charity_ein": "77-0479905",
       "amount": 50.0,
       "currency": "USD"
   })
  1. Lo strumento ha scritto SOLO IntentMandate nello stato condiviso:
   state["intent_mandate"] = intent_mandate_dict
  1. Un messaggio di successo con i dettagli dell'intent è stato restituito al LLM
  2. Il modello LLM ha composto il messaggio utente finale

Questo IntentMandate è ora pronto per essere ritirato dall'agente del commerciante (modulo 5). Il commerciante estrarrà il nome e l'importo dell'ente di beneficenza da IntentMandate e verificherà che non sia scaduto.

Test 3: convalida in azione

Verifichiamo che l'helper di convalida rilevi input errati.

👉 Tipo:

I'll donate -$25 to Room to Read.

L'agente deve rilevare l'importo non valido:

Ho riscontrato un problema: l'importo della donazione deve essere positivo.Ho ricevuto: -25, 0 €

Specifica un importo della donazione positivo e creerò l'IntentMandate per te.

Questo è un esempio di programmazione difensiva in azione. Il nostro helper _validate_charity_data ha impedito l'inserimento di dati non validi nel sistema e la creazione di un IntentMandate non valido.

Test 4: verifica del limite di attendibilità

👉 Prova a chiedere all'agente di elaborare il pagamento:

Now process my credit card payment.

L'agente deve rifiutare, rispettando i limiti del suo ruolo:

Non ho la possibilità di elaborare i pagamenti, non fa parte del mio ruolo. Il mio compito è aiutarti a trovare enti di beneficenza verificati e a creare l'IntentMandate che acquisisce la tua intenzione di donare.

Il tuo IntentMandate è già stato creato e inviato al nostro elaboratore di pagamenti sicuro. L'agente del commerciante creerà un'offerta formale (CartMandate), quindi il fornitore di credenziali gestirà il pagamento effettivo con il tuo consenso esplicito.

Questo è il limite di attendibilità in azione. L'agente sa di non essere autorizzato a gestire i dati di pagamento e le sue istruzioni lo guidano esplicitamente a spiegarlo agli utenti, insegnando loro anche il concetto di IntentMandate.

👉 Premi

Ctrl+C

per uscire al termine del test.

Cosa hai appena creato

Hai implementato correttamente il primo elemento dell'architettura AP2 con la creazione corretta di IntentMandate utilizzando i modelli Pydantic di AP2.

Concetti fondamentali padroneggiati

Architettura basata sui ruoli:

  • Ogni agente ha un compito chiaramente definito
  • Gli agenti comunicano tramite uno stato condiviso, non tramite accesso diretto
  • I limiti di trust riducono l'impatto della compromissione

IntentMandate (AP2 Credential #1):

  • Creato utilizzando i modelli Pydantic AP2 ufficiali per la convalida
  • Acquisizione strutturata dell'intenzione dell'utente
  • Include la scadenza per la sicurezza (impedisce gli attacchi di replay)
  • Specifica i vincoli (commercianti, rimborsabilità, conferma)
  • Descrizione in linguaggio naturale per gli esseri umani
  • Leggibile automaticamente per gli agenti
  • Modello convalidato prima della conversione in dizionario

Stato come ricordo condiviso:

  • tool_context.state è il "blocco note" a cui possono accedere tutti gli agenti
  • Scrittura nello stato = rendere disponibili le credenziali verificabili
  • Lettura dallo stato = utilizzo e convalida delle credenziali
  • Gli agenti downstream estraggono ciò di cui hanno bisogno dalle credenziali

FunctionTool:

  • Converte le funzioni Python in strumenti richiamabili da LLM
  • Si basa su docstring e suggerimenti sui tipi per la comprensione dell'LLM
  • Gestisce automaticamente la chiamata
  • Componibilità degli strumenti: strumenti piccoli e mirati > strumenti monolitici

Istruzioni per l'agente:

  • Guida passo passo al flusso di lavoro
  • Limiti espliciti ("NON...")
  • Specifiche dei parametri per evitare errori
  • Definizioni tecniche (che cos'è IntentMandate)
  • Gestione dei casi limite (cosa dire quando…)

Passaggi successivi

Nel modulo successivo, creeremo l'agente commerciante per ricevere l'IntentMandate e creare la seconda credenziale verificabile: CartMandate.

L'agente Shopping ha creato un IntentMandate che acquisisce l'intenzione dell'utente con scadenza. Ora abbiamo bisogno di un agente che legga questa credenziale, ne convalidi la scadenza e crei un'offerta formale firmata che reciti: "Io, il commerciante, onorerò questo prezzo e consegnerò questi beni".

Creiamo l'agente Merchant e vediamo la seconda credenziale AP2 in azione.

5. Creazione dell'agente commerciante - Offerte vincolanti e CartMandate

banner

Dalla scoperta all'impegno

Nel modulo precedente, hai creato l'agente Shopping, uno specialista che trova enti di beneficenza verificati e crea un IntentMandate che acquisisce l'intenzione dell'utente. Ora abbiamo bisogno di un agente che riceva questa Intenzione di mandato e crei un'offerta formale e vincolante.

È qui che entra in gioco il secondo principio chiave di AP2: credenziali verificabili tramite CartMandate.

Principio AP2: CartMandate e offerte vincolanti

Perché abbiamo bisogno di un ruolo commerciante

Nel modulo 4, l'agente Shopping ha creato un IntentMandate e l'ha salvato nello stato:

state["intent_mandate"] = {
    "natural_language_description": "Donate $50 to Room to Read",
    "merchants": ["Room to Read"],
    "amount": 50.0,
    "intent_expiry": "2024-11-07T15:32:16Z"
}

Ma questa è solo l'intenzione dell'utente. Prima di poter elaborare qualsiasi pagamento, abbiamo bisogno di:

  • Una struttura dell'offerta formale che i sistemi di pagamento comprendono
  • Prova che il commerciante rispetterà questo prezzo
  • Un impegno vincolante che non può essere modificato a metà transazione
  • Convalida che l'intent non sia scaduto

Questo è il lavoro dell'agente del mercante.

Che cos'è un CartMandate?

Un CartMandate è il termine utilizzato da AP2 per indicare un "carrello degli acquisti digitale" che funge da offerta vincolante. È strutturato in base allo standard PaymentRequest di W3C, il che significa che:

  • Gli elaboratori dei pagamenti di tutto il mondo riconoscono il formato
  • Contiene tutti i dettagli delle transazioni in modo standardizzato
  • Può essere firmato crittograficamente per dimostrarne l'autenticità

Consideralo come un preventivo scritto di un appaltatore:

  • ❌ Verbale: "Sì, posso fare questo lavoro per circa 50 dollari"
  • ✅ Preventivo scritto: costi dettagliati, totale, firma, data

Il preventivo scritto è vincolante. CartMandate è l'equivalente digitale.

intenzione di acquisto

La struttura di un CartMandate

Un CartMandate in AP2 ha una struttura nidificata specifica:

cart_mandate = {
    "contents": {  # ← AP2 wrapper
        "id": "cart_xyz123",
        "cart_expiry": "2024-11-07T15:47:16Z",
        "merchant_name": "Room to Read",
        "user_cart_confirmation_required": False,
        
        "payment_request": {  # ← W3C PaymentRequest nested inside
            "method_data": [...],
            "details": {...},
            "options": {...}
        }
    },
    "merchant_authorization": "SIG_a3f7b2c8"  # ← Merchant signature
}

Tre componenti principali:

1. contents: il wrapper del carrello contenente:

  • ID carrello e scadenza
  • Nome del commerciante
  • W3C PaymentRequest

2. payment_request (all'interno dei contenuti) - Che cosa viene acquistato:

  • method_data: Tipi di pagamento accettati
  • Dettagli: articoli e totale
  • opzioni: Spedizione, requisiti per i dati del pagatore

3. merchant_authorization: firma crittografica

Firme del commerciante: prova dell'impegno

La firma del commerciante è fondamentale. Dimostra:

  • Questa offerta proviene da un commerciante autorizzato
  • Il commerciante si impegna a rispettare questo prezzo esatto
  • L'offerta non è stata manomessa dalla sua creazione

In produzione, si tratta di una firma crittografica che utilizza PKI (Public Key Infrastructure) o JWT (JSON Web Tokens). Per il nostro workshop didattico, simuleremo questa situazione con un hash SHA-256.

# Production (real signature):
signature = sign_with_private_key(cart_data, merchant_private_key)

# Workshop (simulated signature):
cart_hash = hashlib.sha256(cart_json.encode()).hexdigest()
signature = f"SIG_{cart_hash[:16]}"

La nostra missione: creare l'agente commerciante

L'agente del commerciante:

  1. Read the IntentMandate from state (what Shopping Agent wrote)
  2. Verifica che l'intent non sia scaduto
  3. Estrai il nome dell'ente di beneficenza, l'importo e altri dettagli.
  4. Crea una struttura PaymentRequest conforme al W3C utilizzando i modelli Pydantic AP2
  5. Avvolgilo nel CartMandate di AP2 con scadenza
  6. Aggiungere una firma commerciante simulata
  7. Scrivi CartMandate per indicare lo stato del provider di credenziali (modulo successivo)

Vediamo come fare passo dopo passo.

Passaggio 1: aggiungi Expiry Validation Helper

Innanzitutto, configuriamo il file degli strumenti correlati al commerciante e aggiungiamo un helper per convalidare la scadenza dell'intento di addebito.

👉 Apri

charity_advisor/tools/merchant_tools.py

Aggiungiamo la convalida della scadenza:

👉 Trova:

# MODULE_5_STEP_1_ADD_EXPIRY_VALIDATION_HELPER

👉 Sostituisci quella singola riga con:

def _validate_intent_expiry(intent_expiry_str: str) -> tuple[bool, str]:
    """
    Validates that the IntentMandate hasn't expired.
    
    This is a critical security check - expired intents should not be processed.
    
    Args:
        intent_expiry_str: The ISO 8601 timestamp string from the IntentMandate.
        
    Returns:
        (is_valid, error_message): Tuple indicating if intent is still valid.
    """
    try:
        # The .replace('Z', '+00:00') is for compatibility with older Python versions
        expiry_time = datetime.fromisoformat(intent_expiry_str.replace('Z', '+00:00'))
        now = datetime.now(timezone.utc)
        
        if expiry_time < now:
            return False, f"IntentMandate expired at {intent_expiry_str}"
        
        time_remaining = expiry_time - now
        logger.info(f"IntentMandate valid. Expires in {time_remaining.total_seconds():.0f} seconds")
        
        return True, ""
        
    except (ValueError, TypeError) as e:
        return False, f"Invalid intent_expiry format: {e}"

Passaggio 2: aggiungi lo strumento di generazione della firma

Ora creiamo un helper che generi la firma simulata del commerciante.

👉 Trova:

# MODULE_5_STEP_2_ADD_SIGNATURE_HELPER

👉 Sostituisci quella singola riga con:

def _generate_merchant_signature(cart_contents: CartContents) -> str:
    """
    Generates a simulated merchant signature for the CartMandate contents.
    
    In production, this would use PKI or JWT with the merchant's private key.
    For this codelab, we use a SHA-256 hash of the sorted JSON representation.
    
    Args:
        cart_contents: The Pydantic model of the cart contents to sign.
        
    Returns:
        Simulated signature string (format: "SIG_" + first 16 chars of hash).
    """
    # Step 1: Dump the Pydantic model to a dictionary. The `mode='json'` argument
    # ensures that complex types like datetimes are serialized correctly.
    cart_contents_dict = cart_contents.model_dump(mode='json')
    
    # Step 2: Use the standard json library to create a stable, sorted JSON string.
    # separators=(',', ':') removes whitespace for a compact and canonical representation.
    cart_json = json.dumps(cart_contents_dict, sort_keys=True, separators=(',', ':'))
    
    # Step 3: Generate SHA-256 hash.
    cart_hash = hashlib.sha256(cart_json.encode('utf-8')).hexdigest()
    
    # Step 4: Create signature in a recognizable format.
    signature = f"SIG_{cart_hash[:16]}"
    
    logger.info(f"Generated merchant signature: {signature}")
    return signature

Passaggio 3A: crea la firma e la configurazione dello strumento

Ora iniziamo a creare lo strumento principale. Lo creeremo in modo incrementale in quattro passaggi secondari. Innanzitutto, la firma della funzione e la configurazione iniziale.

👉 Trova:

# MODULE_5_STEP_3A_CREATE_TOOL_SIGNATURE

👉 Sostituisci quella singola riga con:

async def create_cart_mandate(tool_context: Any) -> Dict[str, Any]:
    """
    Creates a W3C PaymentRequest-compliant CartMandate from the IntentMandate.
    
    This tool reads the IntentMandate from shared state, validates it, and
    creates a formal, signed offer using the official AP2 Pydantic models.
    
    Returns:
        Dictionary containing status and the created CartMandate.
    """
    logger.info("Tool called: Creating CartMandate from IntentMandate")
    
    # MODULE_5_STEP_3B_ADD_VALIDATION_LOGIC

Passaggio 3B: aggiungi la logica di convalida

Ora aggiungiamo la logica per leggere e convalidare l'IntentMandate utilizzando i modelli Pydantic AP2 ed estraiamo i dati di cui abbiamo bisogno.

👉 Trova:

# MODULE_5_STEP_3B_ADD_VALIDATION_LOGIC

👉 Sostituisci quella singola riga con:

    # 1. Read IntentMandate dictionary from state
    intent_mandate_dict = tool_context.state.get("intent_mandate")
    if not intent_mandate_dict:
        logger.error("No IntentMandate found in state")
        return {
            "status": "error",
            "message": "No IntentMandate found. Shopping Agent must create intent first."
        }
    
    # 2. Parse dictionary into a validated Pydantic model
    try:
        intent_mandate_model = IntentMandate.model_validate(intent_mandate_dict)
    except Exception as e:
        logger.error(f"Could not validate IntentMandate structure: {e}")
        return {"status": "error", "message": f"Invalid IntentMandate structure: {e}"}
    
    # 3. Validate that the intent hasn't expired (CRITICAL security check)
    is_valid, error_message = _validate_intent_expiry(intent_mandate_model.intent_expiry)
    if not is_valid:
        logger.error(f"IntentMandate validation failed: {error_message}")
        return {"status": "error", "message": error_message}
    
    # 4. Extract data. Safely access standard fields from the model, and
    # custom fields (like 'amount') from the original dictionary.
    charity_name = intent_mandate_model.merchants[0] if intent_mandate_model.merchants else "Unknown Charity"
    amount = intent_mandate_dict.get("amount", 0.0)
    
    # MODULE_5_STEP_3C_CREATE_CARTMANDATE_STRUCTURE

Passaggio 3C: crea la struttura CartMandate

Ora creiamo la struttura PaymentRequest conforme a W3C e la inseriamo in AP2 CartMandate utilizzando i modelli Pydantic.

👉 Trova:

# MODULE_5_STEP_3C_CREATE_CARTMANDATE_STRUCTURE

👉 Sostituisci quella singola riga con:

    # 5. Build the nested Pydantic models for the CartMandate
    timestamp = datetime.now(timezone.utc)
    cart_id = f"cart_{hashlib.sha256(f'{charity_name}{timestamp.isoformat()}'.encode()).hexdigest()[:12]}"
    cart_expiry = timestamp + timedelta(minutes=15)
    
    payment_request_model = PaymentRequest(
        method_data=[PaymentMethodData(
            supported_methods="CARD",
            data={"supported_networks": ["visa", "mastercard", "amex"], "supported_types": ["debit", "credit"]}
        )],
        details=PaymentDetailsInit(
            id=f"order_{cart_id}",
            display_items=[PaymentItem(
                label=f"Donation to {charity_name}",
                amount=PaymentCurrencyAmount(currency="USD", value=amount)  # Pydantic v2 handles float -> str conversion
            )],
            total=PaymentItem(
                label="Total Donation",
                amount=PaymentCurrencyAmount(currency="USD", value=amount)
            )
        ),
        options=PaymentOptions(request_shipping=False)
    )
    
    cart_contents_model = CartContents(
        id=cart_id,
        cart_expiry=cart_expiry.isoformat(),
        merchant_name=charity_name,
        user_cart_confirmation_required=False,
        payment_request=payment_request_model
    )
    
    # MODULE_5_STEP_3D_ADD_SIGNATURE_AND_SAVE

Passaggio 3D: aggiungi la firma e salva nello stato

Infine, firmiamo CartMandate utilizzando il nostro modello Pydantic e salviamolo nello stato per l'agente successivo.

👉 Trova:

# MODULE_5_STEP_3D_ADD_SIGNATURE_AND_SAVE

👉 Sostituisci quella singola riga con:

    # 6. Generate signature from the validated Pydantic model
    signature = _generate_merchant_signature(cart_contents_model)
    
    # 7. Create the final CartMandate model, now including the signature
    cart_mandate_model = CartMandate(
        contents=cart_contents_model,
        merchant_authorization=signature
    )
    
    # 8. Convert the final model to a dictionary for state storage and add the custom timestamp
    cart_mandate_dict = cart_mandate_model.model_dump(mode='json')
    cart_mandate_dict["timestamp"] = timestamp.isoformat()
    
    # 9. Write the final dictionary to state
    tool_context.state["cart_mandate"] = cart_mandate_dict
    
    logger.info(f"CartMandate created successfully: {cart_id}")
    
    return {
        "status": "success",
        "message": f"Created signed CartMandate {cart_id} for ${amount:.2f} donation to {charity_name}",
        "cart_id": cart_id,
        "cart_expiry": cart_expiry.isoformat(),
        "signature": signature
    }

Passaggio 4: crea l'agente commerciante - importa i componenti

Ora creiamo l'agente che utilizzerà questo strumento.

👉 Apri

charity_advisor/merchant_agent/agent.py

Vedrai un modello con segnaposto. Iniziamo importando ciò che ci serve.

👉 Trova:

# MODULE_5_STEP_4_IMPORT_COMPONENTS

👉 Sostituisci quella singola riga con:

from google.adk.agents import Agent
from google.adk.tools import FunctionTool
from charity_advisor.tools.merchant_tools import create_cart_mandate

Passaggio 5: scrivi le istruzioni per l'agente Merchant

Ora scriviamo l'istruzione che indica all'agente quando e come utilizzare il suo strumento.

👉 Trova:

# MODULE_5_STEP_5_WRITE_INSTRUCTION
instruction="""""",

👉 Sostituisci queste due righe con:

    instruction="""You are a merchant specialist responsible for creating formal, signed offers (CartMandates).

Your workflow:

1. Read the IntentMandate from shared state.
   The IntentMandate was created by the Shopping Agent and contains:
   - merchants: List of merchant names
   - amount: Donation amount
   - charity_ein: Tax ID
   - intent_expiry: When the intent expires

2. Use the create_cart_mandate tool to create a W3C PaymentRequest-compliant CartMandate.
   This tool will:
   - Validate the IntentMandate hasn't expired (CRITICAL security check)
   - Extract the charity name and amount from the IntentMandate
   - Create a structured offer with payment methods, transaction details, and merchant info
   - Generate a merchant signature to prove authenticity
   - Save the CartMandate to state for the payment processor

3. After creating the CartMandate, inform the user:
   - That you've created a formal, signed offer
   - The cart ID
   - When the cart expires (15 minutes)
   - That you're passing it to the secure payment processor

IMPORTANT BOUNDARIES:
- Your ONLY job is creating signed CartMandates from valid IntentMandates
- You do NOT process payments
- You do NOT see the user's payment methods or credentials
- You do NOT interact with payment networks
- You MUST validate that the IntentMandate hasn't expired before creating a cart
- After calling create_cart_mandate, your work is done

WHAT IS A CARTMANDATE:
A CartMandate is a binding commitment that says:
"I, the merchant, commit to accepting $X for this charity donation, and I prove it with my signature."

This commitment is structured using the W3C PaymentRequest standard and includes:
- Payment methods accepted (card, bank transfer)
- Transaction details (amount, charity name)
- Cart expiry (15 minutes from creation)
- Merchant signature (proof of commitment)

This is the second of three verifiable credentials in our secure payment system.""",

Passaggio 6: aggiungi gli strumenti all'agente Merchant

👉 Trova:

# MODULE_5_STEP_6_ADD_TOOLS
tools=[],

👉 Sostituisci queste due righe con:

    tools=[
        FunctionTool(func=create_cart_mandate)
    ],

Passaggio 7: verifica l'agente commerciante completo

Verifichiamo che tutto sia cablato correttamente.

👉 Il tuo profilo completo

charity_advisor/merchant_agent/agent.py

Ora dovrebbe avere questo aspetto:

"""
Merchant Agent - Creates W3C-compliant CartMandates with merchant signatures.
This agent acts as our "Contract Creator."
"""

from google.adk.agents import Agent
from google.adk.tools import FunctionTool
from charity_advisor.tools.merchant_tools import create_cart_mandate


merchant_agent = Agent(
    name="MerchantAgent",
    model="gemini-2.5-flash",
    description="Creates formal, signed CartMandates for charity donations following W3C PaymentRequest standards.",
    tools=[
        FunctionTool(func=create_cart_mandate)
    ],
    instruction="""You are a merchant specialist responsible for creating formal, signed offers (CartMandates).

Your workflow:

1. Read the IntentMandate from shared state.
   The IntentMandate was created by the Shopping Agent and contains:
   - merchants: List of merchant names
   - amount: Donation amount
   - charity_ein: Tax ID
   - intent_expiry: When the intent expires

2. Use the create_cart_mandate tool to create a W3C PaymentRequest-compliant CartMandate.
   This tool will:
   - Validate the IntentMandate hasn't expired (CRITICAL security check)
   - Extract the charity name and amount from the IntentMandate
   - Create a structured offer with payment methods, transaction details, and merchant info
   - Generate a merchant signature to prove authenticity
   - Save the CartMandate to state for the payment processor

3. After creating the CartMandate, inform the user:
   - That you've created a formal, signed offer
   - The cart ID
   - When the cart expires (15 minutes)
   - That you're passing it to the secure payment processor

IMPORTANT BOUNDARIES:
- Your ONLY job is creating signed CartMandates from valid IntentMandates
- You do NOT process payments
- You do NOT see the user's payment methods or credentials
- You do NOT interact with payment networks
- You MUST validate that the IntentMandate hasn't expired before creating a cart
- After calling create_cart_mandate, your work is done

WHAT IS A CARTMANDATE:
A CartMandate is a binding commitment that says:
"I, the merchant, commit to accepting $X for this charity donation, and I prove it with my signature."

This commitment is structured using the W3C PaymentRequest standard and includes:
- Payment methods accepted (card, bank transfer)
- Transaction details (amount, charity name)
- Cart expiry (15 minutes from creation)
- Merchant signature (proof of commitment)

This is the second of three verifiable credentials in our secure payment system."""
)

Checkpoint: ora hai un agente commerciante completo con la corretta creazione di AP2 CartMandate utilizzando i modelli Pydantic.

Passaggio 8: testa l'agente commerciante

Ora verifichiamo che il nostro agente crei correttamente CartMandates con le firme e con la convalida della scadenza.

Configurazione del test: esegui lo script di test

👉 Nel terminale Cloud Shell, esegui:

python scripts/test_merchant.py

Output previsto:

======================================================================
MERCHANT AGENT TEST
======================================================================

Simulated IntentMandate from Shopping Agent:
  charity: Room to Read
  amount: $50.00
  expiry: 2024-11-07T16:32:16Z

----------------------------------------------------------------------
Merchant Agent Response:
----------------------------------------------------------------------
Perfect! I've received your IntentMandate and created a formal, signed offer (CartMandate) for your donation.

**CartMandate Details:**
- **Cart ID**: cart_3b4c5d6e7f8a
- **Donation Amount**: $50.00 to Room to Read
- **Payment Methods Accepted**: Credit/debit cards (Visa, Mastercard, Amex) or bank transfer
- **Cart Expires**: 2024-11-07T15:47:16Z (in 15 minutes)
- **Merchant Signature**: SIG_a3f7b2c8d9e1f4a2

This signed CartMandate proves my commitment to accept this donation amount. I'm now passing this to the secure payment processor to complete your transaction.

======================================================================
CARTMANDATE CREATED:
======================================================================
  ID: cart_3b4c5d6e7f8a
  Amount: 50.00
  Merchant: Room to Read
  Expires: 2024-11-07T15:47:16Z
  Signature: SIG_a3f7b2c8d9e1f4a2
======================================================================

Test 2: verifica la conformità al W3C

Verifichiamo che la nostra struttura CartMandate sia pienamente conforme agli standard AP2 e W3C PaymentRequest.

👉 Esegui lo script di convalida:

python scripts/validate_cartmandate.py

Output previsto:

======================================================================
AP2 & W3C PAYMENTREQUEST VALIDATION
======================================================================
✅ CartMandate is AP2 and W3C PaymentRequest compliant

Structure validation passed:
  ✓ AP2 'contents' wrapper present
  ✓ AP2 'merchant_authorization' signature present
  ✓ cart_expiry present
  ✓ payment_request nested inside contents
  ✓ method_data present and valid
  ✓ details.total.amount present with currency and value
  ✓ All required W3C PaymentRequest fields present
======================================================================

Cosa hai appena creato

Hai implementato correttamente CartMandate di AP2 utilizzando i modelli Pydantic per la struttura corretta, la convalida della scadenza e le firme del commerciante.

Concetti fondamentali padroneggiati

CartMandate (AP2 Credential #2):

  • Creato utilizzando i modelli Pydantic AP2 ufficiali
  • Struttura AP2 con wrapper dei contenuti
  • W3C PaymentRequest nidificata all'interno
  • Scadenza del carrello (inferiore all'intenzione)
  • Firma del commerciante per l'impegno vincolante
  • La convalida del modello garantisce la conformità alle specifiche

Convalida della scadenza:

  • Reading IntentMandate from state
  • Convalida della struttura con IntentMandate.model_validate()
  • Analisi dei timestamp ISO 8601
  • Confronto con l'ora attuale
  • Funzionalità di sicurezza che impedisce l'elaborazione obsoleta

Firma del commerciante:

  • Dimostra autenticità e impegno
  • Generato dal modello Pydantic convalidato
  • Utilizza model_dump(mode='json') per la rappresentazione canonica
  • Simulato con SHA-256 per l'istruzione
  • La produzione utilizza PKI/JWT
  • Segni del modello di contenuti, non dizionari

W3C PaymentRequest:

  • Creato utilizzando il modello Pydantic PaymentRequest di AP2
  • Standard di settore per i dati di pagamento
  • Nidificato all'interno della struttura AP2
  • Contiene method_data, dettagli, opzioni
  • Consente l'interoperabilità

Credential Chain con modelli:

  • Shopping → IntentMandate (convalidato)
  • Merchant reads IntentMandate → CartMandate (entrambi i modelli convalidati)
  • Il provider di credenziali leggerà CartMandate → PaymentMandate
  • Ogni passaggio convalida le credenziali precedenti utilizzando Pydantic

Sviluppo basato su modelli:

  • Convalida dell'input tramite model_validate()
  • Costruzione type-safe
  • Serializzazione automatica tramite model_dump()
  • Pattern pronti per la produzione

Passaggi successivi

Nel prossimo modulo creeremo il fornitore di credenziali per elaborare i pagamenti in modo sicuro.

L'agente del commerciante ha creato un'offerta vincolante con scadenza utilizzando i modelli AP2. Ora abbiamo bisogno di un agente che legga CartMandate, ottenga il consenso dell'utente ed esegua il pagamento.

Creiamo il provider di credenziali e completiamo la catena di credenziali AP2.

6. Creazione del fornitore di credenziali - Esecuzione di pagamenti sicuri

banner

Dall'offerta vincolante all'esecuzione del pagamento

Nel modulo 5, hai creato l'agente commerciante, uno specialista che legge i mandati di intento, verifica che non siano scaduti e crea mandati del carrello vincolanti con le firme del commerciante. Ora abbiamo bisogno di un agente che riceva il CartMandate ed esegua il pagamento effettivo.

È qui che entra in gioco il terzo e ultimo principio di AP2: l'esecuzione sicura dei pagamenti tramite PaymentMandate.

Principio AP2: mandato di pagamento ed esecuzione del pagamento

Perché abbiamo bisogno di un ruolo di fornitore di credenziali

Nel modulo 5, l'agente commerciante ha creato un CartMandate e l'ha salvato nello stato:

state["cart_mandate"] = {
    "contents": {
        "id": "cart_abc123",
        "cart_expiry": "2025-11-07:15:47:16Z",
        "payment_request": {
            "details": {
                "total": {
                    "amount": {"currency": "USD", "value": "50.00"}
                }
            }
        }
    },
    "merchant_authorization": "SIG_a3f7b2c8"
}

Ma questa è solo un'offerta vincolante. Prima che il pagamento possa essere eseguito, abbiamo bisogno di:

  • Convalida che il carrello non sia scaduto
  • Consenso dell'utente a procedere con il pagamento
  • Una credenziale che autorizza l'esecuzione del pagamento
  • Elaborazione effettiva dei pagamenti (o simulazione per il nostro workshop)

Questo è il compito del fornitore di credenziali.

Che cos'è un PaymentMandate?

Un PaymentMandate è il termine di AP2 per l'autorizzazione finale che consente l'esecuzione del pagamento. È la terza e ultima credenziale verificabile nella catena AP2.

Pensa alle tre credenziali come a una procedura di firma di un contratto:

  • IntentMandate: "Mi interessa acquistare questo prodotto" (lettera di intenti)
  • CartMandate: "Io, il commerciante, offro di vendere a questo prezzo" (preventivo scritto)
  • PaymentMandate: "Ti autorizzo ad addebitare il mio metodo di pagamento" (contratto firmato)

Il pagamento può essere eseguito solo dopo che sono presenti tutte e tre le credenziali.

catena di credenziali completa

Struttura di un PaymentMandate

Un PaymentMandate in AP2 ha una struttura specifica:

payment_mandate = {
    "payment_mandate_contents": {  # ← AP2 wrapper
        "payment_mandate_id": "payment_xyz123",
        "payment_details_id": "cart_abc123",  # Links to CartMandate
        "user_consent": True,
        "consent_timestamp": "2025-11-07T15:48:00Z",
        "amount": {
            "currency": "USD",
            "value": "50.00"
        },
        "merchant_name": "Room to Read"
    },
    "agent_present": True,  # Human-in-the-loop flow
    "timestamp": "2025-11-07T15:48:00Z"
}

Componenti chiave:

1. payment_mandate_contents: il wrapper di autorizzazione contenente:

  • payment_mandate_id: identificatore univoco
  • payment_details_id: rimanda a CartMandate
  • user_consent: indica se l'utente ha approvato
  • importo: importo del pagamento (estratto da CartMandate)

2. agent_present: indica se si tratta di un flusso human-in-the-loop

3. timestamp: data e ora in cui è stata creata l'autorizzazione

La nostra missione: creare il provider di credenziali

Il provider di credenziali:

  1. Leggi il CartMandate dallo stato (ciò che ha scritto l'agente del commerciante)
  2. Verifica che il carrello non sia scaduto utilizzando i modelli Pydantic AP2
  3. Estrai i dettagli di pagamento dalla struttura nidificata
  4. Crea un PaymentMandate con il consenso dell'utente utilizzando i modelli AP2
  5. Simula l'elaborazione del pagamento (in produzione, chiamerebbe l'API di pagamento reale)
  6. Scrivi PaymentMandate e il risultato del pagamento nello stato

Vediamo come fare passo dopo passo.

Passaggio 1: aggiungi l'helper per la convalida della scadenza del carrello

Innanzitutto, creiamo un helper che convalidi che CartMandate non sia scaduto, proprio come abbiamo convalidato la scadenza di IntentMandate nel modulo 5.

👉 Apri

charity_advisor/tools/payment_tools.py

Aggiungiamo la convalida della scadenza:

👉 Trova:

# MODULE_6_STEP_1_ADD_CART_EXPIRY_VALIDATION_HELPER

👉 Sostituisci quella singola riga con:

def _validate_cart_expiry(cart: CartMandate) -> tuple[bool, str]:
    """
    Validates that the CartMandate hasn't expired.
    
    This is a critical security check - expired carts should not be processed.
    
    Args:
        cart: The Pydantic CartMandate model to validate.
        
    Returns:
        (is_valid, error_message): Tuple indicating if cart is still valid.
    """
    try:
        expiry_str = cart.contents.cart_expiry
        expiry_time = datetime.fromisoformat(expiry_str.replace('Z', '+00:00'))
        now = datetime.now(timezone.utc)
        
        if expiry_time < now:
            return False, f"CartMandate expired at {expiry_str}"
        
        time_remaining = expiry_time - now
        logger.info(f"CartMandate valid. Expires in {time_remaining.total_seconds():.0f} seconds")
        
        return True, ""
        
    except (ValueError, TypeError, AttributeError) as e:
        return False, f"Invalid cart_expiry format or structure: {e}"

Passaggio 2: aggiungi PaymentMandate Creation Helper

Ora creiamo un helper che crea la struttura PaymentMandate utilizzando i modelli Pydantic AP2 ufficiali.

👉 Trova:

# MODULE_6_STEP_2_ADD_PAYMENT_MANDATE_CREATION_HELPER

👉 Sostituisci quella singola riga con:

def _create_payment_mandate(cart: CartMandate, consent_granted: bool) -> dict:
    """
    Creates a PaymentMandate using the official AP2 Pydantic models.
    
    It links to the CartMandate and includes user consent status.
    
    Args:
        cart: The validated Pydantic CartMandate model being processed.
        consent_granted: Whether the user has consented to the payment.
        
    Returns:
        A dictionary representation of the final, validated PaymentMandate.
    """
    timestamp = datetime.now(timezone.utc)
    
    # Safely extract details from the validated CartMandate model
    cart_id = cart.contents.id
    merchant_name = cart.contents.merchant_name
    total_item = cart.contents.payment_request.details.total
    
    # Create the nested PaymentResponse model for the mandate
    payment_response_model = PaymentResponse(
        request_id=cart_id,
        method_name="CARD",  # As per the simulated flow
        details={"token": "simulated_payment_token_12345"}
    )
    
    # Create the PaymentMandateContents model
    payment_mandate_contents_model = PaymentMandateContents(
        payment_mandate_id=f"payment_{hashlib.sha256(f'{cart_id}{timestamp.isoformat()}'.encode()).hexdigest()[:12]}",
        payment_details_id=cart_id,
        payment_details_total=total_item,
        payment_response=payment_response_model,
        merchant_agent=merchant_name,
        timestamp=timestamp.isoformat()
    )
    
    # Create the top-level PaymentMandate model
    # In a real system, a user signature would be added to this model
    payment_mandate_model = PaymentMandate(
        payment_mandate_contents=payment_mandate_contents_model
    )
    
    # Convert the final Pydantic model to a dictionary for state storage
    final_dict = payment_mandate_model.model_dump(mode='json')
    
    # Add any custom/non-standard fields required by the codelab's logic to the dictionary
    # The spec does not have these fields, but your original code did. We add them
    # back to ensure compatibility with later steps.
    final_dict['payment_mandate_contents']['user_consent'] = consent_granted
    final_dict['payment_mandate_contents']['consent_timestamp'] = timestamp.isoformat() if consent_granted else None
    final_dict['agent_present'] = True
    
    return final_dict

Passaggio 3A: crea la firma e la configurazione dello strumento

Ora iniziamo a creare lo strumento principale in modo incrementale. Innanzitutto, la firma della funzione e la configurazione iniziale.

👉 Trova:

# MODULE_6_STEP_3A_CREATE_TOOL_SIGNATURE

👉 Sostituisci quella singola riga con:

async def create_payment_mandate(tool_context: Any) -> Dict[str, Any]:
    """
    Creates a PaymentMandate and simulates payment processing using Pydantic models.
    
    This tool now reads the CartMandate from state, parses it into a validated model,
    and creates a spec-compliant PaymentMandate.
    """
    logger.info("Tool called: Creating PaymentMandate and processing payment")
    
    # MODULE_6_STEP_3B_VALIDATE_CARTMANDATE

Passaggio 3B: convalida CartMandate

Ora aggiungiamo la logica per leggere, convalidare CartMandate utilizzando i modelli Pydantic AP2 e controllare la scadenza.

👉 Trova:

# MODULE_6_STEP_3B_VALIDATE_CARTMANDATE

👉 Sostituisci quella singola riga con:

    # 1. Read CartMandate dictionary from state
    cart_mandate_dict = tool_context.state.get("cart_mandate")
    if not cart_mandate_dict:
        logger.error("No CartMandate found in state")
        return { "status": "error", "message": "No CartMandate found. Merchant Agent must create cart first." }
    
    # 2. Parse dictionary into a validated Pydantic model
    try:
        cart_model = CartMandate.model_validate(cart_mandate_dict)
    except Exception as e:
        logger.error(f"Could not validate CartMandate structure: {e}")
        return {"status": "error", "message": f"Invalid CartMandate structure: {e}"}
    
    # 3. Validate that the cart hasn't expired using the Pydantic model
    is_valid, error_message = _validate_cart_expiry(cart_model)
    if not is_valid:
        logger.error(f"CartMandate validation failed: {error_message}")
        return {"status": "error", "message": error_message}
    
    # MODULE_6_STEP_3C_EXTRACT_PAYMENT_DETAILS

Passaggio 3C: estrai i dettagli di pagamento dalla struttura nidificata

Ora esploriamo il modello CartMandate convalidato per estrarre i dati di pagamento di cui abbiamo bisogno.

👉 Trova:

# MODULE_6_STEP_3C_EXTRACT_PAYMENT_DETAILS

👉 Sostituisci quella singola riga con:

    # 4. Safely extract data from the validated model
    cart_id = cart_model.contents.id
    merchant_name = cart_model.contents.merchant_name
    amount_value = cart_model.contents.payment_request.details.total.amount.value
    currency = cart_model.contents.payment_request.details.total.amount.currency
    consent_granted = True  # Assume consent for this codelab flow
    
    # MODULE_6_STEP_3D_CREATE_PAYMENTMANDATE_AND_SIMULATE

Passaggio 3D: crea PaymentMandate e simula il pagamento

Infine, creiamo PaymentMandate utilizzando il nostro helper basato su Pydantic, simuliamo l'elaborazione dei pagamenti e salviamo tutto nello stato.

👉 Trova:

# MODULE_6_STEP_3D_CREATE_PAYMENTMANDATE_AND_SIMULATE

👉 Sostituisci quella singola riga con:

    # 5. Create the spec-compliant PaymentMandate using the validated CartMandate model
    payment_mandate_dict = _create_payment_mandate(cart_model, consent_granted)
    
    # 6. Simulate payment processing
    transaction_id = f"txn_{hashlib.sha256(f'{cart_id}{datetime.now(timezone.utc).isoformat()}'.encode()).hexdigest()[:16]}"
    payment_result = {
        "transaction_id": transaction_id,
        "status": "completed",
        "amount": amount_value,
        "currency": currency,
        "merchant": merchant_name,
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "simulation": True
    }
    
    # 7. Write the compliant PaymentMandate dictionary and result to state
    tool_context.state["payment_mandate"] = payment_mandate_dict
    tool_context.state["payment_result"] = payment_result
    
    logger.info(f"Payment processed successfully: {transaction_id}")
    
    return {
        "status": "success",
        "message": f"Payment of {currency} {amount_value:.2f} to {merchant_name} processed successfully",
        "transaction_id": transaction_id,
        "payment_mandate_id": payment_mandate_dict["payment_mandate_contents"]["payment_mandate_id"]
    }

Passaggio 4: crea l'agente del fornitore di credenziali - Importa componenti

Ora creiamo l'agente che utilizza questo strumento.

👉 Apri

charity_advisor/credentials_provider/agent.py

Vedrai un modello con segnaposto. Iniziamo importando ciò che ci serve.

👉 Trova:

# MODULE_6_STEP_4_IMPORT_COMPONENTS

👉 Sostituisci quella singola riga con:

from google.adk.agents import Agent
from google.adk.tools import FunctionTool
from charity_advisor.tools.payment_tools import create_payment_mandate

Passaggio 5: scrivi l'istruzione del provider di credenziali

Ora scriviamo l'istruzione che guida l'agente.

👉 Trova:

# MODULE_6_STEP_5_WRITE_INSTRUCTION
instruction="""""",

👉 Sostituisci queste due righe con:

    instruction="""You are a payment specialist responsible for securely processing payments with user consent.

Your workflow:

1. Read the CartMandate from shared state.
   The CartMandate was created by the Merchant Agent and has this structure:
   - contents: AP2 wrapper containing:
     - id: Cart identifier
     - cart_expiry: When the cart expires
     - merchant_name: Who is receiving payment
     - payment_request: W3C PaymentRequest with transaction details
   - merchant_authorization: Merchant's signature

2. Extract payment details from the nested structure:
   - Navigate: cart_mandate["contents"]["payment_request"]["details"]["total"]["amount"]
   - This gives you the currency and value

3. **IMPORTANT - Two-Turn Conversational Confirmation Pattern:**
   Before calling create_payment_mandate, you MUST:
   - Present the payment details clearly to the user
   - Ask explicitly: "I'm ready to process a payment of $X to [Charity Name]. Do you want to proceed with this donation?"
   - WAIT for the user's explicit confirmation (e.g., "yes", "proceed", "confirm")
   - ONLY call create_payment_mandate AFTER receiving explicit confirmation
   - If user says "no" or "cancel", DO NOT call the tool

4. After user confirms, use the create_payment_mandate tool to:
   - Validate the CartMandate hasn't expired (CRITICAL security check)
   - Create a PaymentMandate (the third AP2 credential)
   - Simulate payment processing
   - Record the transaction result

5. After processing, inform the user:
   - That payment was processed successfully (this is a simulation)
   - The transaction ID
   - The amount and merchant
   - That this completes the three-agent AP2 credential chain

IMPORTANT BOUNDARIES:
- Your ONLY job is creating PaymentMandates and processing payments
- You do NOT discover charities (that's Shopping Agent's job)
- You do NOT create offers (that's Merchant Agent's job)
- You MUST validate that the CartMandate hasn't expired before processing
- You MUST get explicit user confirmation before calling create_payment_mandate
- In production, this consent mechanism would be even more robust

WHAT IS A PAYMENTMANDATE:
A PaymentMandate is the final credential that authorizes payment execution. It:
- Links to the CartMandate (proving the merchant's offer)
- Records user consent
- Contains payment details extracted from the CartMandate
- Enables the actual payment transaction

This is the third and final verifiable credential in our secure payment system.

THE COMPLETE AP2 CREDENTIAL CHAIN:
1. Shopping Agent creates IntentMandate (user's intent)
2. Merchant Agent reads IntentMandate, creates CartMandate (merchant's binding offer)
3. You read CartMandate, get user confirmation, create PaymentMandate (authorized payment execution)

Each credential:
- Has an expiry time (security feature)
- Links to the previous credential
- Is validated before the next step
- Creates an auditable chain of trust""",

Passaggio 6: aggiungi Strumenti al fornitore di credenziali

👉 Trova:

# MODULE_6_STEP_6_ADD_TOOLS
tools=[],

👉 Sostituisci queste due righe con:

    tools=[
        FunctionTool(func=create_payment_mandate)
    ],

Passaggio 7: verifica il provider di credenziali completo

Verifichiamo che tutto sia cablato correttamente.

👉 Il tuo profilo completo

charity_advisor/credentials_provider/agent.py

Ora dovrebbe avere questo aspetto:

"""
Credentials Provider Agent - Handles payment processing with user consent.
This agent acts as our "Payment Processor."
"""

from google.adk.agents import Agent
from google.adk.tools import FunctionTool
from charity_advisor.tools.payment_tools import create_payment_mandate


credentials_provider = Agent(
    name="CredentialsProvider",
    model="gemini-2.5-flash",
    description="Securely processes payments by creating PaymentMandates and executing transactions with user consent.",
    tools=[
        FunctionTool(func=create_payment_mandate)
    ],
    instruction="""You are a payment specialist responsible for securely processing payments with user consent.

Your workflow:

1. Read the CartMandate from shared state.
   The CartMandate was created by the Merchant Agent and has this structure:
   - contents: AP2 wrapper containing:
     - id: Cart identifier
     - cart_expiry: When the cart expires
     - merchant_name: Who is receiving payment
     - payment_request: W3C PaymentRequest with transaction details
   - merchant_authorization: Merchant's signature

2. Extract payment details from the nested structure:
   - Navigate: cart_mandate["contents"]["payment_request"]["details"]["total"]["amount"]
   - This gives you the currency and value

3. **IMPORTANT - Two-Turn Conversational Confirmation Pattern:**
   Before calling create_payment_mandate, you MUST:
   - Present the payment details clearly to the user
   - Ask explicitly: "I'm ready to process a payment of $X to [Charity Name]. Do you want to proceed with this donation?"
   - WAIT for the user's explicit confirmation (e.g., "yes", "proceed", "confirm")
   - ONLY call create_payment_mandate AFTER receiving explicit confirmation
   - If user says "no" or "cancel", DO NOT call the tool

4. After user confirms, use the create_payment_mandate tool to:
   - Validate the CartMandate hasn't expired (CRITICAL security check)
   - Create a PaymentMandate (the third AP2 credential)
   - Simulate payment processing
   - Record the transaction result

5. After processing, inform the user:
   - That payment was processed successfully (this is a simulation)
   - The transaction ID
   - The amount and merchant
   - That this completes the three-agent AP2 credential chain

IMPORTANT BOUNDARIES:
- Your ONLY job is creating PaymentMandates and processing payments
- You do NOT discover charities (that's Shopping Agent's job)
- You do NOT create offers (that's Merchant Agent's job)
- You MUST validate that the CartMandate hasn't expired before processing
- You MUST get explicit user confirmation before calling create_payment_mandate
- In production, this consent mechanism would be even more robust

WHAT IS A PAYMENTMANDATE:
A PaymentMandate is the final credential that authorizes payment execution. It:
- Links to the CartMandate (proving the merchant's offer)
- Records user consent
- Contains payment details extracted from the CartMandate
- Enables the actual payment transaction

This is the third and final verifiable credential in our secure payment system.

THE COMPLETE AP2 CREDENTIAL CHAIN:
1. Shopping Agent creates IntentMandate (user's intent)
2. Merchant Agent reads IntentMandate, creates CartMandate (merchant's binding offer)
3. You read CartMandate, get user confirmation, create PaymentMandate (authorized payment execution)

Each credential:
- Has an expiry time (security feature)
- Links to the previous credential
- Is validated before the next step
- Creates an auditable chain of trust"""
)

Checkpoint: ora hai un fornitore di credenziali completo con la lettura corretta di CartMandate e la creazione di PaymentMandate utilizzando i modelli Pydantic AP2.

Passaggio 8: prova il provider di credenziali

Ora verifichiamo che il nostro agente elabori correttamente i pagamenti e completi la catena di credenziali.

👉 Nel terminale Cloud Shell, esegui:

python scripts/test_credentials_provider.py

Output previsto:

======================================================================
CREDENTIALS PROVIDER TEST (MOCK - NO CONFIRMATION)
======================================================================

Simulated CartMandate from Merchant Agent:
  - Cart ID: cart_test123
  - Merchant: Room to Read
  - Amount: $50.00
  - Expires: 2025-11-07T15:47:16Z
  - Signature: SIG_test_signature

Calling Credentials Provider to process payment...
======================================================================
INFO:charity_advisor.tools.payment_tools:Tool called: Creating PaymentMandate and processing payment
INFO:charity_advisor.tools.payment_tools:CartMandate valid. Expires in 900 seconds
INFO:charity_advisor.tools.payment_tools:Payment processed successfully: txn_a3f7b2c8d9e1f4a2

======================================================================
CREDENTIALS PROVIDER RESPONSE:
======================================================================
I've successfully processed your payment. Here are the details:

**Payment Completed** (Simulated)
- Transaction ID: txn_a3f7b2c8d9e1f4a2
- Amount: USD 50.00
- Merchant: Room to Read
- Status: Completed

This completes the three-agent AP2 credential chain:
1.  Shopping Agent created IntentMandate (your intent)
2.  Merchant Agent created CartMandate (binding offer)
3.  Credentials Provider created PaymentMandate (payment authorization)

Your donation has been processed securely through our verifiable credential system.

======================================================================
PAYMENTMANDATE CREATED:
======================================================================
  Payment Mandate ID: payment_3b4c5d6e7f8a
  Linked to Cart: cart_test123
  User Consent: True
  Amount: USD 50.00
  Merchant: Room to Read
  Agent Present: True
======================================================================

======================================================================
PAYMENT RESULT:
======================================================================
  Transaction ID: txn_a3f7b2c8d9e1f4a2
  Status: completed
  Amount: USD 50.00
  Merchant: Room to Read
  Simulation: True
======================================================================

Passaggio 9: testa la pipeline completa con tre agenti

Ora proviamo tutti e tre gli agenti insieme.

👉 Esegui il test della pipeline completa:

python scripts/test_full_pipeline.py

Output previsto:

======================================================================
THREE-AGENT PIPELINE TEST (AP2 CREDENTIAL CHAIN)
======================================================================

[1/3] SHOPPING AGENT - Finding charity and creating IntentMandate...
----------------------------------------------------------------------
✓ IntentMandate created
  - Intent ID: intent_774799058_1730927536
  - Description: Donate $75.00 to Room to Read
  - Merchant: Room to Read
  - Amount: $75.0
  - Expires: 2025-11-07T16:32:16Z

[2/3] MERCHANT AGENT - Reading IntentMandate and creating CartMandate...
----------------------------------------------------------------------
✓ CartMandate created
  - ID: cart_3b4c5d6e7f8a
  - Expires: 2025-11-07T15:47:16Z
  - Signature: SIG_a3f7b2c8d9e1f4a2

[3/3] CREDENTIALS PROVIDER - Creating PaymentMandate and processing...
----------------------------------------------------------------------
NOTE: In the web UI, this would show a confirmation dialog
      For this test, consent is automatically granted
✓ Payment processed (SIMULATED)
  - Transaction ID: txn_a3f7b2c8d9e1f4a2
  - Amount: $75.0
  - Status: completed

======================================================================
COMPLETE AP2 CREDENTIAL CHAIN
======================================================================

✓ Credential 1: IntentMandate (User's Intent)
  - Intent ID: intent_774799058_1730927536
  - Description: Donate $75.00 to Room to Read
  - Expiry: 2025-11-07T16:32:16Z

✓ Credential 2: CartMandate (Merchant's Offer)
  - Cart ID: cart_3b4c5d6e7f8a
  - Cart Expiry: 2025-11-07T15:47:16Z
  - Merchant Signature: SIG_a3f7b2c8d9e1f4a2

✓ Credential 3: PaymentMandate (Payment Execution)
  - Payment Mandate ID: payment_3b4c5d6e7f8a
  - Linked to Cart: cart_3b4c5d6e7f8a
  - Agent Present: True

✓ Transaction Result:
  - Transaction ID: txn_a3f7b2c8d9e1f4a2
  - Simulation: True

======================================================================
✅ COMPLETE PIPELINE TEST PASSED
======================================================================

Questa è la catena completa di credenziali AP2 in azione.

Ogni agente:

  1. Legge una credenziale dallo stato
  2. Lo convalida utilizzando i modelli Pydantic (struttura + controllo della scadenza)
  3. Crea la credenziale successiva utilizzando i modelli AP2
  4. Scrive allo stato per l'agente successivo

Cosa hai appena creato

Hai completato correttamente la catena di credenziali a tre agenti AP2 con la corretta convalida della struttura utilizzando i modelli Pydantic e la simulazione di pagamento.

Concetti fondamentali padroneggiati

PaymentMandate (AP2 Credential #3):

  • Creato utilizzando i modelli Pydantic AP2 ufficiali
  • Credenziale finale che autorizza l'esecuzione del pagamento
  • Link a CartMandate tramite payment_details_id
  • Registra il consenso dell'utente e il timestamp
  • Contiene l'importo del pagamento estratto da CartMandate
  • Include il flag agent_present per human-in-the-loop
  • La convalida del modello garantisce la conformità alle specifiche

Lettura da CartMandate:

  • Convalida la struttura con CartMandate.model_validate()
  • Accesso agli attributi con controllo del tipo: cart_model.contents.payment_request.details.total.amount
  • Informazioni sul wrapper AP2 e sulla separazione standard W3C
  • Estrai in modo sicuro merchant_name, amount e currency dal modello
  • Pydantic rileva automaticamente gli errori di struttura

Convalida scadenza carrello:

  • Accetta il modello Pydantic CartMandate convalidato
  • Legge da cart.contents.cart_expiry (accesso agli attributi)
  • Funzionalità di sicurezza che impedisce l'elaborazione di carrelli obsoleti
  • Durata più breve (15 minuti) rispetto all'intento (1 ora)

Simulazione di pagamento:

  • Simulazione didattica dell'elaboratore dei pagamenti reale
  • Genera l'ID transazione
  • Registra payment_result nello stato
  • Chiaramente contrassegnato come simulazione (flag simulation: True)

Completa la catena AP2 con i modelli:

  • Tre agenti, tre credenziali, tre convalide Pydantic
  • Ogni agente convalida la struttura delle credenziali precedenti utilizzando i modelli
  • Ogni credenziale è collegata alla precedente per la traccia di controllo
  • I trasferimenti basati sullo stato mantengono la separazione dei ruoli
  • Sicurezza dei tipi in tutta la catena

Sviluppo basato su modelli:

  • Convalida dell'input tramite model_validate()
  • Costruzione type-safe con modelli nidificati
  • Serializzazione automatica tramite model_dump(mode='json')
  • Pattern pronti per la produzione fin dall'inizio

Passaggi successivi

Nel modulo successivo, creeremo l'agente orchestratore che coordina tutti e tre gli agenti specializzati.

Hai creato tre potenti agenti specializzati utilizzando i modelli Pydantic AP2. Ora creiamo il direttore d'orchestra che li coordina in un'esperienza di donazione senza interruzioni.

Creiamo l'agente di orchestrazione e vediamo il sistema completo in azione.

7. Orchestrazione: unire tutti gli elementi

pipeline sequenziale

Dagli specialisti a un'esperienza senza interruzioni

Nei moduli precedenti, hai creato tre agenti specializzati:

  • Agente Shopping: trova enti benefici, crea IntentMandate
  • Agente commerciante: crea CartMandate da IntentMandate
  • Fornitore di credenziali: crea PaymentMandate, elabora il pagamento

Questi agenti rientrano naturalmente in due fasi:

  • Fase 1 (Shopping): conversazione in più turni per trovare e selezionare l'ente di beneficenza
  • Fase 2 (elaborazione): esecuzione atomica della creazione dell'offerta e del pagamento

Ma al momento dovresti orchestrare manualmente queste fasi.

È qui che brillano i pattern di orchestrazione di ADK.

Principio AP2: l'orchestrazione applica i limiti di trust

Perché l'orchestrazione è importante per la sicurezza

L'orchestrazione non riguarda solo la comodità, ma anche l'applicazione di limiti di attendibilità tramite l'architettura.

Senza orchestrazione:

# User could accidentally skip steps or reorder them
shopping_agent.run("Find charity")
# Oops, forgot to create CartMandate!
credentials_provider.run("Process payment")  # No offer to validate!

Con orchestrazione:

# Pipeline enforces correct order
donation_processing_pipeline = SequentialAgent(
    sub_agents=[
        merchant_agent,      # Must run first
        credentials_provider # Must run second
    ]
)
# Steps ALWAYS run in order, no skipping allowed

La pipeline sequenziale garantisce:

  • ✅ IntentMandate creato prima di CartMandate
  • ✅ CartMandate creato prima dell'elaborazione del pagamento
  • ✅ Ogni agente viene eseguito nel proprio contesto isolato
  • ✅ Lo stato avanza nella catena di credenziali

La nostra missione: creare il sistema completo

Creeremo due livelli:

Livello 1: la pipeline di elaborazione (SequentialAgent)

  • Cavi insieme Commerciante → Credenziali
  • Viene eseguito automaticamente in sequenza dopo la selezione dell'ente di beneficenza
  • Esecuzione atomica dell'offerta e del pagamento

Livello 2: l'orchestratore principale (agente rivolto agli utenti)

  • Personalità amichevole
  • Delegati a shopping_agent per la selezione dell'ente di beneficenza
  • Delega alla pipeline di elaborazione dopo la creazione di IntentMandate
  • Gestisce le transizioni di conversazione e di fase

Questo approccio a due livelli corrisponde al flusso naturale:

  • Fase di acquisto: conversazione a più turni (l'utente naviga, fa domande, decide)
  • Fase di elaborazione: esecuzione atomica (offerta → pagamento)

Creiamo entrambi.

Passaggio 1: importa i componenti di orchestrazione

Innanzitutto, configuriamo il file di orchestrazione con le importazioni necessarie.

👉 Apri

charity_advisor/agent.py

Iniziamo con le importazioni:

👉 Trova:

# MODULE_7_STEP_1_IMPORT_COMPONENTS

👉 Sostituisci quella singola riga con:

from google.adk.agents import Agent, SequentialAgent
from charity_advisor.shopping_agent.agent import shopping_agent
from charity_advisor.merchant_agent.agent import merchant_agent
from charity_advisor.credentials_provider.agent import credentials_provider

Passaggio 2: crea la pipeline di elaborazione

Ora creiamo la pipeline che esegue la creazione dell'offerta e l'elaborazione dei pagamenti in modo atomico.

👉 Trova:

# MODULE_7_STEP_2_CREATE_SEQUENTIAL_PIPELINE

👉 Sostituisci queste due righe con:

# Create the donation processing pipeline
# This runs Merchant → Credentials in sequence AFTER charity is selected
donation_processing_pipeline = SequentialAgent(
    name="DonationProcessingPipeline",
    description="Creates signed offer and processes payment after charity is selected",
    sub_agents=[
        merchant_agent,
        credentials_provider
    ]
)

Passaggio 3A: crea la configurazione dell'agente principale

Ora creiamo l'agente rivolto agli utenti che orchestra entrambe le fasi. Lo faremo in tre parti: configurazione (3A), istruzioni (3B) e subagenti (3C).

👉 Trova:

# MODULE_7_STEP_3A_CREATE_ROOT_AGENT_SETUP

👉 Sostituisci quella singola riga con:

# Create the root orchestrator agent
# This is what users interact with directly
root_agent = Agent(
    name="CharityAdvisor",
    model="gemini-2.5-pro",
    description="A friendly charity giving assistant that helps users donate to verified organizations.",
    # MODULE_7_STEP_3B_WRITE_ROOT_AGENT_INSTRUCTION

Passaggio 3B: scrivi l'istruzione dell'agente principale

Ora aggiungiamo l'istruzione che guida il comportamento del consulente dell'ente di beneficenza in entrambe le fasi.

👉 Trova:

# MODULE_7_STEP_3B_WRITE_ROOT_AGENT_INSTRUCTION

👉 Sostituisci quella singola riga con:

    instruction="""You are a helpful and friendly charity giving advisor.

Your workflow has TWO distinct phases:

PHASE 1: CHARITY SELECTION (delegate to shopping_agent)
When a user expresses interest in donating:
1. Delegate to shopping_agent immediately
2. The shopping_agent will:
   - Search for charities matching their cause
   - Present verified options with ratings
   - Engage in conversation (user may ask questions, change their mind)
   - Wait for user to select a specific charity and amount
   - Create an IntentMandate when user decides
3. Wait for shopping_agent to complete

You'll know Phase 1 is complete when shopping_agent's response includes:
- "IntentMandate created" or "Intent ID: intent_xxx" 
- Charity name and donation amount

PHASE 2: PAYMENT PROCESSING (delegate to DonationProcessingPipeline)
After shopping_agent completes:
1. Acknowledge the user's selection naturally:
   "Perfect! Let me process your $X donation to [Charity]..."
2. Delegate to DonationProcessingPipeline
3. The pipeline will automatically:
   - Create signed cart offer (MerchantAgent)
   - Get consent and process payment (CredentialsProvider)
4. After pipeline completes, summarize the transaction

CRITICAL RULES:
- Phase 1 may take multiple conversation turns (this is normal)
- Only proceed to Phase 2 after IntentMandate exists
- Don't rush the user during charity selection
- Don't ask user to "proceed" between phases - transition automatically

EXAMPLE FLOW:
User: "I want to donate to education"
You: [delegate to shopping_agent]
Shopping: "Here are 3 education charities..." [waits]
User: "Tell me more about the first one"
Shopping: "Room to Read focuses on..." [waits]
User: "Great, I'll donate $50 to Room to Read"
Shopping: "IntentMandate created (ID: intent_123)..."
You: "Perfect! Processing your $50 donation to Room to Read..." [delegate to DonationProcessingPipeline]
Pipeline: [creates offer, gets consent, processes payment]
You: "Done! Your donation was processed successfully. Transaction ID: txn_456"

Your personality:
- Warm and encouraging
- Patient during charity selection
- Clear about educational nature
- Smooth transitions between phases""",
# MODULE_7_STEP_3C_ADD_ROOT_AGENT_SUBAGENTS

Passaggio 3C: aggiungi i subagenti

Infine, concediamo all'advisor dell'ente di beneficenza l'accesso sia all'agente di shopping sia alla pipeline di elaborazione e chiudiamo la definizione dell'agente.

👉 Trova:

# MODULE_7_STEP_3C_ADD_ROOT_AGENT_SUBAGENTS

👉 Sostituisci quella singola riga con:

    sub_agents=[
        shopping_agent,
        donation_processing_pipeline
    ]
)

Passaggio 4: verifica il sistema completo

Verifichiamo che l'orchestrazione sia cablata correttamente.

👉 Il tuo profilo completo

charity_advisor/agent.py

Ora dovrebbe avere questo aspetto:

"""
Main orchestration: The donation processing pipeline and root orchestrator agent.
"""

from google.adk.agents import Agent, SequentialAgent
from charity_advisor.shopping_agent.agent import shopping_agent
from charity_advisor.merchant_agent.agent import merchant_agent
from charity_advisor.credentials_provider.agent import credentials_provider

# Create the donation processing pipeline
# This runs Merchant → Credentials in sequence AFTER charity is selected
donation_processing_pipeline = SequentialAgent(
    name="DonationProcessingPipeline",
    description="Creates signed offer and processes payment after charity is selected",
    sub_agents=[
        merchant_agent,
        credentials_provider
    ]
)

# Create the root orchestrator agent
# This is what users interact with directly
root_agent = Agent(
    name="CharityAdvisor",
    model="gemini-2.5-flash",
    description="A friendly charity giving assistant that helps users donate to verified organizations.",
    instruction="""You are a helpful and friendly charity giving advisor.

Your workflow has TWO distinct phases:

PHASE 1: CHARITY SELECTION (delegate to shopping_agent)
When a user expresses interest in donating:
1. Delegate to shopping_agent immediately
2. The shopping_agent will:
   - Search for charities matching their cause
   - Present verified options with ratings
   - Engage in conversation (user may ask questions, change their mind)
   - Wait for user to select a specific charity and amount
   - Create an IntentMandate when user decides
3. Wait for shopping_agent to complete

You'll know Phase 1 is complete when shopping_agent's response includes:
- "IntentMandate created" or "Intent ID: intent_xxx" 
- Charity name and donation amount

PHASE 2: PAYMENT PROCESSING (delegate to DonationProcessingPipeline)
After shopping_agent completes:
1. Acknowledge the user's selection naturally:
   "Perfect! Let me process your $X donation to [Charity]..."
2. Delegate to DonationProcessingPipeline
3. The pipeline will automatically:
   - Create signed cart offer (MerchantAgent)
   - Get consent and process payment (CredentialsProvider)
4. After pipeline completes, summarize the transaction

CRITICAL RULES:
- Phase 1 may take multiple conversation turns (this is normal)
- Only proceed to Phase 2 after IntentMandate exists
- Don't rush the user during charity selection
- Don't ask user to "proceed" between phases - transition automatically

EXAMPLE FLOW:
User: "I want to donate to education"
You: [delegate to shopping_agent]
Shopping: "Here are 3 education charities..." [waits]
User: "Tell me more about the first one"
Shopping: "Room to Read focuses on..." [waits]
User: "Great, I'll donate $50 to Room to Read"
Shopping: "IntentMandate created (ID: intent_123)..."
You: "Perfect! Processing your $50 donation to Room to Read..." [delegate to DonationProcessingPipeline]
Pipeline: [creates offer, gets consent, processes payment]
You: "Done! Your donation was processed successfully. Transaction ID: txn_456"

Your personality:
- Warm and encouraging
- Patient during charity selection
- Clear about educational nature
- Smooth transitions between phases""",
    sub_agents=[
        shopping_agent,
        donation_processing_pipeline
    ]
)

(Facoltativo) Passaggio 5: rafforza la sicurezza con i callback di convalida (vai al passaggio 7)

callback

SequentialAgent garantisce l'ordine di esecuzione, ma cosa succede se:

  • L'agente Shopping non riesce a creare IntentMandate
  • Un'ora dopo l'acquisto (l'intenzione scade)
  • Lo stato viene danneggiato o cancellato
  • Qualcuno tenta di chiamare direttamente il commerciante, bypassando Shopping

I callback aggiungono l'applicazione dell'architettura: convalidano i prerequisiti prima che un agente inizi la chiamata LLM. Si tratta di una difesa in profondità: gli strumenti convalidano durante l'esecuzione, i callback convalidano prima dell'esecuzione.

Aggiungiamo i callback di convalida ai nostri agenti Merchant e Credentials Provider.

Passaggio 5A: aggiungi la convalida del commerciante - Importa i tipi di callback

Innanzitutto, aggiungiamo le importazioni necessarie per i callback.

👉 Apri

charity_advisor/merchant_agent/agent.py

All'inizio del file, dopo le importazioni esistenti, aggiungi:

from typing import Optional
from datetime import datetime, timezone
from google.adk.agents.callback_context import CallbackContext
from google.genai.types import Content, Part
import logging

logger = logging.getLogger(__name__)

Passaggio 5B: crea la funzione di convalida dell'intent

Ora creiamo una funzione di callback che convalida l'IntentMandate prima dell'esecuzione di Merchant Agent.

👉 In

charity_advisor/merchant_agent/agent.py

, aggiungi questa funzione PRIMA di

merchant_agent = Agent(...)

definizione:

def validate_intent_before_merchant(
    callback_context: CallbackContext,
) -> Optional[Content]:
    """
    Validates IntentMandate exists and hasn't expired before Merchant runs.
    
    This callback enforces that the Shopping Agent completed successfully
    before the Merchant Agent attempts to create a CartMandate.
    
    Returns:
        None: Allow Merchant Agent to proceed normally
        Content: Skip Merchant Agent and return error to user
    """
    state = callback_context.state
    
    # Check credential exists
    if "intent_mandate" not in state:
        logger.error("❌ IntentMandate missing - Shopping Agent may have failed")
        return Content(parts=[Part(text=(
            "Error: Cannot create cart. User intent was not properly recorded. "
            "Please restart the donation process."
        ))])
    
    intent_mandate = state["intent_mandate"]
    
    # Validate expiry (critical security check)
    try:
        expiry_time = datetime.fromisoformat(
            intent_mandate["intent_expiry"].replace('Z', '+00:00')
        )
        now = datetime.now(timezone.utc)
        
        if expiry_time < now:
            logger.error(f"❌ IntentMandate expired at {intent_mandate['intent_expiry']}")
            return Content(parts=[Part(text=(
                "Error: Your donation intent has expired. "
                "Please select a charity again to restart."
            ))])
        
        time_remaining = expiry_time - now
        logger.info(f"✓ IntentMandate validated. Expires in {time_remaining.total_seconds():.0f}s")
        
    except (KeyError, ValueError) as e:
        logger.error(f"❌ Invalid IntentMandate structure: {e}")
        return Content(parts=[Part(text=(
            "Error: Invalid intent data. Please restart the donation."
        ))])
    
    # All checks passed - allow Merchant Agent to proceed
    logger.info(f"✓ Prerequisites met for Merchant Agent: {intent_mandate['intent_id']}")
    return None

Passaggio 5C: allega il richiamo all'agente del commerciante

Ora colleghiamo il callback all'agente.

👉 In

charity_advisor/merchant_agent/agent.py

, modifica il

merchant_agent = Agent(...)

definizione:

Trova questa riga nella definizione dell'agente:

merchant_agent = Agent(
    name="MerchantAgent",
    model="gemini-2.5-flash",
    description="Creates formal, signed CartMandates for charity donations following W3C PaymentRequest standards.",

Aggiungi questa riga subito dopo

description

line:

    before_agent_callback=validate_intent_before_merchant,

La definizione dell'agente ora dovrebbe essere simile a questa:

merchant_agent = Agent(
    name="MerchantAgent",
    model="gemini-2.5-flash",
    description="Creates formal, signed CartMandates for charity donations following W3C PaymentRequest standards.",
    before_agent_callback=validate_intent_before_merchant,
    tools=[
        FunctionTool(func=create_cart_mandate)
    ],
    instruction="""..."""
)

Passaggio 6: aggiungi la convalida del fornitore di credenziali (facoltativo, vai al passaggio 7)

Stesso pattern: aggiungiamo la convalida per il passaggio del pagamento.

Passaggio 6A: importa i tipi di richiamata

👉 Apri

charity_advisor/credentials_provider/agent.py

All'inizio del file, dopo le importazioni esistenti, aggiungi:

from typing import Optional
from datetime import datetime, timezone
from google.adk.agents.callback_context import CallbackContext
from google.genai.types import Content, Part
import logging

logger = logging.getLogger(__name__)

Passaggio 6B: crea la funzione di convalida del carrello

👉 In

charity_advisor/credentials_provider/agent.py

, aggiungi questa funzione PRIMA di

credentials_provider = Agent(...)

definizione:

def validate_cart_before_payment(
    callback_context: CallbackContext,
) -> Optional[Content]:
    """
    Validates CartMandate exists and hasn't expired before payment processing.
    
    This callback enforces that the Merchant Agent completed successfully
    before the Credentials Provider attempts to process payment.
    
    Returns:
        None: Allow Credentials Provider to proceed
        Content: Skip payment processing and return error
    """
    state = callback_context.state
    
    # Check credential exists
    if "cart_mandate" not in state:
        logger.error("❌ CartMandate missing - Merchant Agent may have failed")
        return Content(parts=[Part(text=(
            "Error: Cannot process payment. Cart was not properly created. "
            "Please restart the donation process."
        ))])
    
    cart_mandate = state["cart_mandate"]
    
    # Validate AP2 structure
    if "contents" not in cart_mandate:
        logger.error("❌ CartMandate missing AP2 contents wrapper")
        return Content(parts=[Part(text=(
            "Error: Invalid cart structure. Please restart."
        ))])
    
    # Validate expiry
    try:
        contents = cart_mandate["contents"]
        expiry_time = datetime.fromisoformat(
            contents["cart_expiry"].replace('Z', '+00:00')
        )
        now = datetime.now(timezone.utc)
        
        if expiry_time < now:
            logger.error(f"❌ CartMandate expired at {contents['cart_expiry']}")
            return Content(parts=[Part(text=(
                "Error: Your cart has expired (15 minute limit). "
                "Please restart the donation to get a fresh offer."
            ))])
        
        time_remaining = expiry_time - now
        logger.info(f"✓ CartMandate validated. Expires in {time_remaining.total_seconds():.0f}s")
        
    except (KeyError, ValueError) as e:
        logger.error(f"❌ Invalid CartMandate structure: {e}")
        return Content(parts=[Part(text=(
            "Error: Invalid cart data. Please restart the donation."
        ))])
    
    # All checks passed - allow payment processing
    logger.info(f"✓ Prerequisites met for Credentials Provider: {contents['id']}")
    return None

Passaggio 6C: collega il callback al provider di credenziali

👉 In

charity_advisor/credentials_provider/agent.py

, modifica il

credentials_provider = Agent(...)

definizione:

Trova questa riga nella definizione dell'agente:

credentials_provider = Agent(
    name="CredentialsProvider",
    model="gemini-2.5-flash",
    description="Securely processes payments by creating PaymentMandates and executing transactions with user consent.",

Aggiungi questa riga subito dopo

description

line:

    before_agent_callback=validate_cart_before_payment,

La definizione dell'agente ora dovrebbe essere simile a questa:

credentials_provider = Agent(
    name="CredentialsProvider",
    model="gemini-2.5-flash",
    description="Securely processes payments by creating PaymentMandates and executing transactions with user consent.",
    before_agent_callback=validate_cart_before_payment,
    tools=[
        FunctionTool(func=create_payment_mandate)
    ],
    instruction="""..."""
)

Passaggio 7: esegui il test con la GUI web dell'ADK

Ora testiamo il sistema protetto completo con i callback di convalida attivi.

👉 Nel terminale Cloud Shell, esegui:

adk web

Dovresti visualizzare un output simile al seguente:

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

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

👉 Poi, per accedere alla GUI web dell'ADK dal browser:

Dall'icona Anteprima web (a forma di occhio o quadrato con una freccia) nella barra degli strumenti di Cloud Shell (di solito in alto a destra), seleziona Cambia porta. Nella finestra popup, imposta la porta su 8000 e fai clic su "Cambia e visualizza anteprima". Cloud Shell aprirà una nuova scheda del browser che mostra la GUI web dell'ADK.

webpreview

👉 Seleziona il tuo agente dal menu a discesa:

Nell'interfaccia utente web dell'ADK, in alto vedrai un menu a discesa. Seleziona charity_advisor dall'elenco.

agent-select

Vedrai l'interfaccia web dell'ADK con:

  • Riquadro della chat: a sinistra, per la conversazione
  • Pannello Trace: sul lato destro, per l'osservabilità (lo utilizzeremo nel modulo 9)

Test 1: completa il flusso di donazione (caso normale)

👉 Nell'interfaccia di chat, digita:

I want to donate to an education charity

Guarda l'intero flusso:

agente web shopping ADK

adk web donation processing pipeline

Cosa sta succedendo (visibile nel riquadro della traccia a destra):

1. Advisor delegates to ShoppingAgent:

  • ShoppingAgent cerca enti di beneficenza per l'istruzione
  • Mostra tre opzioni verificate con i dettagli

2. Interagisci con ShoppingAgent (potrebbero essere necessari più turni):

User: "Tell me more about Room to Read"
Shopping: [explains mission and impact]
User: "I'll donate $50 to Room to Read"

3. ShoppingAgent crea IntentMandate:

  • Crea e firma l'intent
  • Restituisce la conferma con l'ID intent.

4. L'advisor passa alla fase di elaborazione:

Perfetto! Elaborazione della tua donazione di 50 $a Room to Read in corso…

5. DonationProcessingPipeline activates:

  • Il callback del commerciante convalida IntentMandate (✓ superato) ← NOVITÀ
  • L'agente del commerciante crea CartMandate con firma
  • Il callback delle credenziali convalida CartMandate (✓ superato) ← NOVITÀ
  • Il fornitore di credenziali prepara il pagamento

6. Elaborazione dei pagamenti:

  • Il provider di credenziali crea PaymentMandate
  • Simula l'elaborazione dei pagamenti
  • ID transazione di reso

7. Advisor summarizes:

Perfetto! La tua donazione è stata elaborata correttamente. 🎉

Details:

  • Importo: 50 €
  • Organizzazione non profit: Room to Read (EIN: 77-0479905)
  • ID transazione: txn_a3f7b2c8d9e1f4a2

Test 2: verifica gli errori di intercettazione dei callback (test avanzato facoltativo)

Vuoi vedere i callback in azione che rilevano gli errori? Dovresti corrompere manualmente lo stato (debug avanzato), ma in produzione i callback intercetterebbero:

  • Lo strumento Shopping Agent non funziona → Blocchi di callback del commerciante: "Errore: impossibile creare il carrello…"
  • Pass di 2 ore → Blocchi di callback del commerciante: "Errore: l'intent è scaduto..."
  • Il carrello scade → Callback delle credenziali blocca: "Errore: il carrello è scaduto (limite di 15 minuti)..."

Questi casi limite ora sono applicati a livello di architettura dai callback di convalida.

Cosa hai appena creato

Hai orchestrato correttamente tre agenti specializzati in un sistema fluido e affidabile con convalida dell'architettura.

Passaggi successivi

Ora hai completato la parte tecnica principale della creazione di agenti affidabili:

Hai creato un sistema completo e affidabile che applica localmente la catena di credenziali. Ora rendiamolo accessibile agli utenti reali tramite l'implementazione della produzione e attiviamo la traccia di responsabilità che rende possibile il modulo 9.

Eseguiamo il deployment dell'agente rafforzato su Google Cloud.

8. Deployment

banner

Il tuo sistema di donazioni affidabile è ora completo di tre agenti specializzati che lavorano a livello locale:

ma viene eseguito solo sulla macchina di sviluppo. Per rendere questo sistema utile agli utenti reali e per acquisire le tracce di responsabilità che dimostrano l'affidabilità, devi implementarlo in produzione.

Questo modulo ti guida nel deployment dell'agente su Google Cloud con l'osservabilità abilitata fin dal primo giorno. Il flag --trace_to_cloud che utilizzerai durante il deployment è ciò che rende possibile la traccia di responsabilità nel modulo 9.

Informazioni sulle opzioni di deployment

L'ADK supporta più target di deployment. Ognuno ha caratteristiche diverse per complessità, gestione delle sessioni, scalabilità e costi:

Fattore

Attività locali (adk web)

Motore agente

Cloud Run

complessità

Minimo

Bassa

Media

Persistenza della sessione

Solo in memoria (perso al riavvio)

Gestito da Vertex AI (automatico)

Cloud SQL (PostgreSQL) o in memoria

Infrastruttura

Nessuno (solo macchina di sviluppo)

Completamente gestito

Container + database facoltativo

Avvio a freddo

N/D

100-500ms

100-2000ms

Scalabilità

Singola istanza

Automatico

Automatico (a zero)

Modello di costo

Senza costi (elaborazione locale)

Basato sul calcolo

Basata sulle richieste + livello senza costi

UI Support

Sì (integrato)

No (solo API)

Sì (tramite il flag --with_ui)

Configurazione dell'osservabilità

Visualizzatore tracce locale

Automatico con --trace_to_cloud

Richiede il flag --trace_to_cloud

Ideale per

Sviluppo e test

Agenti di produzione

Agenti di produzione

Consiglio:per questo sistema di donazioni affidabile, ti consigliamo di utilizzare Agent Engine come deployment di produzione principale perché fornisce:

  • Infrastruttura completamente gestita (nessun container da gestire)
  • Persistenza della sessione integrata tramite VertexAiSessionService
  • Scalabilità automatica senza avvii a freddo
  • Deployment semplificato (non sono richieste conoscenze di Docker)
  • Integrazione immediata di Cloud Trace

Opzione aggiuntiva: Google Kubernetes Engine (GKE)

Per gli utenti avanzati che richiedono il controllo a livello di Kubernetes, il networking personalizzato o l'orchestrazione multi-servizio, è disponibile il deployment GKE. Questa opzione offre la massima flessibilità, ma richiede maggiore esperienza operativa (gestione dei cluster, manifest, service account).

Il deployment di GKE non è trattato in questo codelab, ma è documentato in dettaglio nella guida al deployment di ADK GKE.

Prerequisiti

1. Configurazione del progetto Google Cloud

Devi avere un progetto Google Cloud con la fatturazione abilitata. Se non ne hai uno:

  1. Crea un progetto: Google Cloud Console
  2. Abilita fatturazione: Abilita fatturazione
  3. Prendi nota dell'ID progetto (non del nome o del numero del progetto).

2. Riautenticazione (facoltativa)

Autenticati con Google Cloud:

gcloud auth application-default login
gcloud config set project YOUR_PROJECT_ID

Sostituisci YOUR_PROJECT_ID con l'ID del tuo progetto Google Cloud effettivo.

Verifica l'autenticazione:

gcloud config get-value project
# Should output: YOUR_PROJECT_ID

3. Variabili di ambiente

Utilizza questi comandi per compilare automaticamente il file .env:

# Get your current Project ID
PROJECT_ID=$(gcloud config get-value project)
STAGING_BUCKET_VALUE="gs://${PROJECT_ID}-staging"
ENV_FILE=".env"

# Check if STAGING_BUCKET is already set in the .env file
if grep -q "^STAGING_BUCKET=" "${ENV_FILE}"; then
  # If it exists, replace the line
  # The sed command finds the line starting with STAGING_BUCKET= and replaces the entire line.
  # Using | as a delimiter to avoid issues with slashes in the bucket name.
  sed -i "s|^STAGING_BUCKET=.*|STAGING_BUCKET=${STAGING_BUCKET_VALUE}|" "${ENV_FILE}"
  echo "Updated STAGING_BUCKET in ${ENV_FILE}"
else
  # If it doesn't exist, add it to the end of the file
  echo "STAGING_BUCKET=${STAGING_BUCKET_VALUE}" >> "${ENV_FILE}"
  echo "Added STAGING_BUCKET to ${ENV_FILE}"
fi

# Verify it was added or updated correctly
echo "Current STAGING_BUCKET setting:"
grep "^STAGING_BUCKET=" "${ENV_FILE}"

Dovresti vedere:

STAGING_BUCKET=gs://your-actual-project-id-staging

Note importanti:

  • Sostituisci YOUR_PROJECT_ID con l'ID progetto effettivo (o utilizza i comandi riportati sopra).
  • Per GOOGLE_CLOUD_LOCATION, utilizza una regione supportata.
  • Il bucket di staging verrà creato automaticamente se non esiste quando esegui lo script di deployment

4. Abilita le API richieste

Il processo di deployment richiede l'abilitazione di diverse API Google Cloud. Esegui questo comando per abilitarle:

gcloud services enable \
    aiplatform.googleapis.com \
    storage.googleapis.com \
    cloudbuild.googleapis.com \
    cloudtrace.googleapis.com \
    compute.googleapis.com

Questo comando attiva:

  • API AI Platform: per i modelli Agent Engine e Vertex AI
  • API Cloud Storage: per il bucket di staging
  • API Cloud Build: per la creazione di container (Cloud Run)
  • API Cloud Trace: per le tracce di osservabilità e responsabilità
  • API Compute Engine: per la gestione dei service account

Passaggio 1: comprendi l'infrastruttura di deployment

Il progetto include uno script di deployment unificato (deploy.sh) che gestisce tutte le modalità di deployment.

👉 (Facoltativo) Rivedi lo script di deployment:

cat deploy.sh

Lo script fornisce tre modalità di deployment:

  • ./deploy.sh local - Esegui localmente con l'archiviazione in memoria
  • ./deploy.sh agent-engine - Esegui il deployment su Vertex AI Agent Engine (consigliato)
  • ./deploy.sh cloud-run - Esegui il deployment in Cloud Run con UI facoltativa

Come funziona dietro le quinte:

Per il deployment di Agent Engine, lo script esegue:

adk deploy agent_engine \
  --project=$GOOGLE_CLOUD_PROJECT \
  --region=$GOOGLE_CLOUD_LOCATION \
  --staging_bucket=$STAGING_BUCKET \
  --display_name="Charity Advisor" \
  --trace_to_cloud \
  charity_advisor

Per il deployment di Cloud Run, esegue:

adk deploy cloud_run \
  --project=$GOOGLE_CLOUD_PROJECT \
  --region=$GOOGLE_CLOUD_LOCATION \
  --service_name="charity-advisor" \
  --app_name="charity_advisor" \
  --with_ui \
  --trace_to_cloud \
  charity_advisor

Il flag --trace_to_cloud è fondamentale per entrambi i tipi di deployment: consente l'integrazione di Cloud Trace per la traccia di responsabilità che esplorerai nel modulo 9.

Passaggio 2: prepara il wrapper del motore dell'agente

Agent Engine richiede un punto di ingresso specifico che esegue il wrapping dell'agente per il runtime gestito. Questo file è stato creato per te.

👉 Rivedi

charity_advisor/agent_engine_app.py

:

"""Agent Engine application wrapper.

This file prepares the Charity Advisor agent for deployment to Vertex AI Agent Engine.
"""

from vertexai import agent_engines
from .agent import root_agent

# Wrap the agent in an AdkApp object for Agent Engine deployment
app = agent_engines.AdkApp(
    agent=root_agent,
    enable_tracing=True,  # Enables Cloud Trace integration automatically
)

Perché è necessario questo file:

  • Agent Engine richiede che l'agente sia racchiuso in un oggetto AdkApp
  • Il parametro enable_tracing=True attiva automaticamente l'integrazione di Cloud Trace
  • Questo wrapper viene a cui fa riferimento la CLI ADK durante il deployment
  • Configura VertexAiSessionService per la persistenza automatica della sessione

Agent Engine è il deployment di produzione consigliato per il tuo sistema di donazioni affidabile, perché fornisce un'infrastruttura completamente gestita con persistenza della sessione integrata.

Esegui il deployment

Dalla radice del progetto:

chmod +x deploy.sh
./deploy.sh agent-engine

Fasi di deployment

Guarda lo script eseguire queste fasi:

Phase 1: API Enablement
   aiplatform.googleapis.com
   storage.googleapis.com
   cloudbuild.googleapis.com
   cloudtrace.googleapis.com
   compute.googleapis.com

Phase 2: IAM Setup
   Getting project number
   Granting Storage Object Admin
   Granting Vertex AI User
   Granting Cloud Trace Agent

Phase 3: Staging Bucket
   Creating gs://your-project-id-staging (if needed)
   Setting permissions

Phase 4: Validation
   Checking agent.py exists
   Verifying root_agent defined
   Checking agent_engine_app.py exists
   Validating requirements.txt

Phase 5: Build & Deploy
   Packaging agent code
   Uploading to staging bucket
   Creating Agent Engine instance
   Configuring session persistence
   Setting up Cloud Trace integration
   Running health checks

Questo processo richiede 5-10 minuti, in quanto pacchettizza l'agente ed esegue il deployment nell'infrastruttura Vertex AI.

Salvare l'ID motore dell'agente

Al termine del deployment:

✅ Agent Engine created successfully!

   Agent Engine ID: 7917477678498709504
   Resource Name: projects/123456789/locations/us-central1/reasoningEngines/7917477678498709504
   Endpoint: https://us-central1-aiplatform.googleapis.com/v1/...

   ⚠️  IMPORTANT: Save the Agent Engine ID from the output above
   Add it to your .env file as:
   AGENT_ENGINE_ID=7917477678498709504

   This ID is required for:
   - Testing the deployed agent
   - Updating the deployment later
   - Accessing logs and traces

Aggiorna immediatamente il file .env:

echo "AGENT_ENGINE_ID=7917477678498709504" >> .env

Che cosa è stato eseguito il deployment

Il deployment di Agent Engine ora include:

Tutti e tre gli agenti (Shopping, Merchant, Credenziali) in esecuzione nel runtime gestito
Logica completa della catena di credenziali (Intent → Carrello → Mandati di pagamento)
Meccanismo di consenso dell'utente con flusso di lavoro di conferma
Persistenza automatica della sessione tramite VertexAiSessionService
Infrastruttura di scalabilità automatica gestita da Google
Integrazione di Cloud Trace per una visibilità completa

Passaggio 4: testa l'agente di cui è stato eseguito il deployment

Aggiornamento dell'ambiente

Verifica che il tuo .env includa l'ID motore dell'agente:

AGENT_ENGINE_ID=7917477678498709504  # From deployment output
GOOGLE_CLOUD_PROJECT=your-project-id
GOOGLE_CLOUD_LOCATION=us-central1
STAGING_BUCKET=gs://your-project-id-staging

Esegui lo script di test

Il progetto include uno script di test specifico per le implementazioni di Agent Engine.

👉 Esegui il test:

python scripts/test_deployed_agent.py

Risultato previsto

Testing Agent Engine deployment...
Project: your-project-id
Location: us-central1
Agent Engine ID: 7917477678498709504
Endpoint: https://us-central1-aiplatform.googleapis.com/v1/...

Creating session...
✓ Session created: 4857885913439920384

Sending donation query...
✓ Response received:
  Event 1: I'll help you donate $50 to a children's education charity...
  Event 2: Here are some highly-rated children's education charities...
  Event 3: Which charity would you like to support?...

✅ Test completed successfully!

Session ID: 4857885913439920384

This donation generated a trace in Cloud Trace.
View it in Module 9: Observability

To view traces:
https://console.cloud.google.com/traces/list?project=your-project-id

Elenco di controllo per la verifica

Dopo il test, verifica:

✅ L'agente risponde alle query
✅ Tutti e tre gli agenti vengono eseguiti in sequenza (Shopping → Commerciante → Credenziali)
✅ Il meccanismo di consenso si attiva (richiesta di conferma)
✅ La sessione persiste tra le richieste
✅ Nessun errore di autenticazione
✅ Nessun timeout di connessione

Se riscontri errori:

  • Controlla che le variabili di ambiente siano impostate correttamente
  • Verifica che le API siano abilitate: gcloud services list --enabled
  • Controllare i log di Agent Engine nella console Vertex AI
  • Verifica che il file agent_engine_app.py esista nella cartella charity_advisor

(Facoltativo) Passaggio 5: esegui il deployment in Cloud Run

Anche se Agent Engine è consigliato per un deployment di produzione semplificato, Cloud Run offre un maggiore controllo e supporta la GUI web dell'ADK. Questa sezione è facoltativa.

Quando utilizzare Cloud Run

Scegli Cloud Run se hai bisogno di:

  • L'interfaccia utente web dell'ADK per l'interazione utente
  • Controllo completo sull'ambiente container
  • Configurazioni personalizzate del database
  • Integrazione con i servizi Cloud Run esistenti

Esegui il deployment

chmod +x deploy.sh
./deploy.sh cloud-run

Quali sono le differenze?

Lo script:

  • Crea un container Docker con il codice dell'agente
  • Crea un database Cloud SQL PostgreSQL (se necessario)
  • Configurare la connessione al database
  • Esegui il deployment con la UI web dell'ADK abilitata

Il deployment richiede 10-15 minuti a causa del provisioning di Cloud SQL.

Gestione delle sessioni:

  • Utilizza DatabaseSessionService anziché VertexAiSessionService
  • Richiede le credenziali del database in .env (o generate automaticamente)
  • Lo stato viene mantenuto nel database PostgreSQL

Supporto dell'interfaccia utente:

  • UI web disponibile all'indirizzo: https://charity-advisor-xyz.a.run.app

Test del deployment di Cloud Run

Se hai eseguito il deployment in Cloud Run con --with_ui, puoi eseguire il test direttamente nel browser:

  1. Vai all'URL del servizio (fornito nell'output del deployment).
  2. Visualizzerai l'interfaccia web dell'ADK. Seleziona il tuo agente dal menu a discesa.
  3. Avvia una donazione di prova:
   I want to donate $50 to a children's education charity
  1. Osserva il flusso di esecuzione:
    • ShoppingAgent trova enti di beneficenza e salva la tua intenzione
    • MerchantAgent crea il mandato per il carrello
    • CredentialsProvider crea il mandato di pagamento e richiede la conferma
    • Dopo la conferma, il pagamento viene elaborato
  2. Verifica che la risposta includa:
    • Consigli sulle organizzazioni non profit
    • Richiesta di conferma
    • Messaggio di riuscita dopo l'approvazione

Risoluzione dei problemi

Problemi comuni

Problema: ERROR: GOOGLE_CLOUD_PROJECT is not set

Soluzione:assicurati che il file .env abbia l'ID progetto corretto:

GOOGLE_CLOUD_PROJECT=your-actual-project-id

Problema: bucket di staging non creato automaticamente

Soluzione: lo script deve creare automaticamente il bucket. In caso contrario, crealo manualmente:

gsutil mb -p $GOOGLE_CLOUD_PROJECT -l $GOOGLE_CLOUD_LOCATION $STAGING_BUCKET

Riepilogo

Hai completato correttamente le seguenti operazioni:

✅ Compreso l'infrastruttura di deployment fornita da deploy.sh
✅ Esaminata la configurazione del wrapper Agent Engine
✅ Eseguito il deployment del sistema di donazioni affidabile in Agent Engine (consigliato)
✅ Attivata l'integrazione di Cloud Trace con --trace_to_cloud
✅ Verificato che l'agente sia accessibile e funzionale
✅ Creata la base per i percorsi di responsabilità nel modulo 9

Nel modulo successivo vedrai esattamente cosa sblocca questo flag: visibilità completa di ogni donazione, ogni momento di consenso e ogni passaggio della catena di credenziali.

9. Osservabilità

banner

traccia del grafico

Nel modulo 1, hai appreso un problema fondamentale: quando un agente AI gestisce denaro, come si fa a dimostrare cosa è successo?

Un utente potrebbe rivendicare:

  • "Non ho mai scelto questa organizzazione benefica!"
  • "Non ho autorizzato questo pagamento!"
  • "Il sistema mi ha addebitato un importo senza il mio consenso."

In un sistema di AI tradizionale a scatola nera, non avresti modo di dimostrare il contrario. Ma il tuo sistema di donazioni affidabile è diverso. Nel modulo 8, hai eseguito il deployment con il flag --trace_to_cloud, il che significa che ogni donazione ora crea una traccia di controllo completa e a prova di manomissione in Cloud Trace.

Questo modulo ti insegna a leggere queste tracce e a utilizzarle come prove. Scoprirai come:

  • Navigare in Esplora tracce di Cloud Trace per trovare le tracce di produzione
  • Leggi la visualizzazione a cascata per comprendere il flusso di esecuzione
  • Trova la catena di credenziali (Intent → Carrello → Mandati di pagamento)
  • Individuare i momenti di consenso con la prova del timestamp
  • Utilizzare le tracce per la risoluzione delle controversie
  • Esportare le tracce per la conformità e gli audit

È questo che distingue i sistemi affidabili da quelli capaci ma opachi: la capacità di dimostrare cosa è successo con precisione forense.

Informazioni su tracce e intervalli

Prima di visualizzare le tracce in Cloud Trace, devi capire cosa stai guardando.

Che cos'è una traccia?

Una traccia è la cronologia completa della gestione di una singola richiesta da parte dell'agente. Acquisisce tutto ciò che accade dall'invio di una query da parte di un utente fino alla ricezione della risposta finale.

Ogni traccia mostra:

  • Durata totale della richiesta
  • Tutte le operazioni eseguite
  • Come si relazionano tra loro le operazioni (relazioni padre-figlio)
  • Quando è iniziata e terminata ogni operazione
  • Stato di riuscita o non riuscita

Per l'agente di beneficenza:una traccia equivale a un flusso di donazione completo da "Voglio fare una donazione" a "Pagamento riuscito".

Che cos'è uno span?

Uno span rappresenta una singola unità di lavoro all'interno di una traccia. Considera gli span come i componenti di base di una traccia.

Tipi di intervallo comuni nel tuo sistema di donazioni:

Tipo di intervallo

Cosa rappresenta

Esempio

agent_run

Esecuzione di un agente

ShoppingAgent.run, MerchantAgent.run

call_llm

Richiesta a un modello linguistico

gemini-2.5-flash richiesta di selezione dell'ente di beneficenza

execute_tool

Esecuzione della funzione dello strumento

find_charities, create_payment_mandate

state_read

Lettura dalla memoria della sessione

Recupero di intent_mandate dallo stato

state_write

Scrittura nella memoria della sessione

Memorizzazione di cart_mandate nello stato

Ogni intervallo contiene:

  • Nome:l'operazione che rappresenta
  • Durata (ora di inizio → ora di fine)
  • Attributi:metadati come input dello strumento, risposte del modello, conteggi dei token
  • Stato: operazione riuscita (OK) o errore (ERROR)
  • Relazioni principale-secondario:quali operazioni hanno attivato quali

Come gli intervalli formano una traccia

Gli intervalli sono nidificati l'uno dentro l'altro per mostrare la causalità:

Root Span: CharityAdvisor.run (entire request)
  └─ Child: DonationPipeline.run (sequential workflow)
      ├─ Child: ShoppingAgent.run
         ├─ Grandchild: call_llm (Gemini processes charity search)
         ├─ Grandchild: execute_tool (find_charities)
         └─ Grandchild: execute_tool (save_user_choice)
      ├─ Child: MerchantAgent.run
         ├─ Grandchild: call_llm (Gemini generates cart)
         └─ Grandchild: execute_tool (create_cart_mandate)
      └─ Child: CredentialsProvider.run
          ├─ Grandchild: call_llm (Gemini processes payment)
          └─ Grandchild: execute_tool (create_payment_mandate) [CONSENT!]

Questa gerarchia mostra esattamente cosa è successo e in quale ordine. Puoi notare che il mandato di pagamento è stato creato dopo il mandato del carrello, che è stato creato dopo che l'utente ha selezionato un ente di beneficenza.

Passaggio 1: accedi a Cloud Trace Explorer

Ora visualizziamo le tracce effettive dell'agente di cui hai eseguito il deployment.

  1. Apri la console Google Cloud: console.cloud.google.com
  2. Seleziona il tuo progetto dal menu a discesa in alto (dovrebbe essere preselezionato se ci hai lavorato)
  3. Vai a Cloud Trace Explorer:

Cosa stai guardando

Trace Explorer mostra un elenco di tutte le tracce del tuo progetto:

Colonna

Informazioni riportate

Richiesta

Metodo HTTP ed endpoint (per le richieste API)

Ora di inizio

Quando è iniziata la richiesta

Latenza

Durata totale della richiesta

Span

Numero di operazioni nella traccia

Ogni riga rappresenta una richiesta completa all'agente di cui è stato eseguito il deployment.

Generare tracce di test (se necessario)

Se non vedi ancora tracce, l'elenco potrebbe essere vuoto perché:

  • Non sono ancora state effettuate richieste all'agente di cui è stato eseguito il deployment
  • Le tracce vengono visualizzate 1-2 minuti dopo una richiesta

Generare una traccia di test:

Se hai eseguito il deployment in Cloud Run con UI, visita l'URL del servizio e completa una donazione nel browser.

Se hai eseguito il deployment in Agent Engine, esegui lo script di test del modulo 8:

python scripts/test_deployed_agent.py

Attendi 1-2 minuti, poi aggiorna la pagina di Cloud Trace Explorer. Ora dovresti vedere le tracce.

Filtra tracce

Utilizza le opzioni di filtro in alto per trovare tracce specifiche:

  • Intervallo di tempo:se necessario, passa da "Ultima ora" a "Ultime 24 ore".
  • Latenza minima / Latenza massima:filtra le richieste lente
  • Filtro richieste: cerca in base a operazioni specifiche (ad es. "DonationPipeline")

Per questo modulo, concentrati sulle tracce con durate più lunghe (> 5 secondi), in quanto rappresentano flussi di donazione completi con l'esecuzione di tutti e tre gli agenti.

Passaggio 2: esamina un flusso di donazione completo

Fai clic su una traccia qualsiasi nell'elenco per aprire la visualizzazione a cascata. È qui che trascorrerai la maggior parte del tempo ad analizzare il comportamento degli agenti.

Informazioni sulla visualizzazione a cascata

La visualizzazione a cascata è un diagramma di Gantt che mostra la sequenza temporale di esecuzione completa:

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
              Timeline (horizontal = time) 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

invocation                           ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ 8.2s
  agent_run: CharityAdvisor          ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ 8.1s
    agent_run: DonationPipeline      ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ 7.9s
      agent_run: ShoppingAgent       ▓▓▓▓▓▓ 2.1s
        call_llm: gemini-2.5-flash   ▓▓▓▓ 1.2s
        execute_tool: find_charities ▓▓ 0.5s
        execute_tool: save_user_choice  0.3s
      agent_run: MerchantAgent       ▓▓▓ 1.8s
        call_llm: gemini-2.5-flash   ▓▓ 0.9s
        execute_tool: create_cart_mandate  0.7s
      agent_run: CredentialsProvider ▓▓▓▓▓▓▓▓ 4.0s
        call_llm: gemini-2.5-flash   ▓▓ 0.8s
        execute_tool: create_payment_mandate ▓▓▓▓▓ 3.0s [CONSENT]

Leggere il grafico

Ogni barra rappresenta un intervallo:

  • Posizione orizzontale:quando è iniziato
  • Durata:quanto tempo è stato necessario
  • Rientro:mostra le relazioni padre-figlio
  • Colore:in genere blu per il normale funzionamento, rosso per gli errori

Osservazioni chiave di questa traccia di esempio:

Durata totale: 8,2 secondi
Esecuzione sequenziale: ShoppingAgent è stato completato prima dell'avvio di MerchantAgent
MerchantAgent è stato completato

prima

CredentialsProvider started
Consent was the longest operation: 3.0 seconds for create_payment_mandate (because it waited for user confirmation)
LLM calls are visible: Each agent made one Gemini request
Tool calls are captured: All six tools executed successfully

Questa visualizzazione mostra immediatamente dove viene trascorso il tempo e in quale ordine vengono eseguite le operazioni.

Fai clic su un intervallo per i dettagli

Fai clic sullo span invocation (lo span radice in alto). Nel riquadro a destra, vedrai gli attributi dettagliati:

{
  "http.method": "POST",
  "http.status_code": 200,
  "http.url": "https://charity-advisor-xyz.a.run.app/api/run",
  "user_id": "test_user_123",
  "session_id": "4857885913439920384",
  "trace_id": "a1b2c3d4e5f6...",
  "span_id": "1234567890abcdef"
}

Questi attributi forniscono il contesto dell'intera richiesta.

Passaggio 3: trova la catena di certificati

Il tuo sistema attendibile utilizza una catena di credenziali per dimostrare l'autorizzazione in ogni passaggio:

IntentMandate (User chose charity)
    ↓
CartMandate (Merchant created cart, signed IntentMandate)
    ↓
PaymentMandate (Payment provider created payment, signed CartMandate)

Troviamo ogni mandato nella traccia.

Trovare l'IntentMandate

Fai clic sullo span execute_tool: save_user_choice (in ShoppingAgent).

Nel riquadro degli attributi vedrai:

{
  "tool.name": "save_user_choice",
  "tool.input.charity_name": "Save the Children",
  "tool.input.amount": 50,
  "tool.output.status": "success",
  "tool.output.intent_mandate": {
    "charity_name": "Save the Children",
    "amount": 50,
    "timestamp": "2024-11-08T15:30:12.345Z",
    "signature": "a3f7b9c1d2e4..."
  }
}

Ciò dimostra che:

  • ✅ L'utente ha selezionato "Save the Children"
  • ✅ L'importo era di 50 $
  • ✅ Scelta registrata alle ore 15:30:12 UTC
  • ✅ La firma è stata generata (in produzione, sarebbe crittografica)

L'IntentMandate è ora nello stato della sessione e disponibile per gli agenti successivi.

Trovare il CartMandate

Fai clic sul tag execute_tool: create_cart_mandate (in MerchantAgent).

Nel riquadro degli attributi:

{
  "tool.name": "create_cart_mandate",
  "tool.input.intent_mandate": {
    "charity_name": "Save the Children",
    "amount": 50,
    "signature": "a3f7b9c1d2e4..."
  },
  "tool.output.status": "success",
  "tool.output.cart_mandate": {
    "cart_id": "cart_7893",
    "intent_signature": "a3f7b9c1d2e4...",
    "cart_signature": "e8f2a9b3c7d1...",
    "timestamp": "2024-11-08T15:30:14.789Z"
  }
}

Ciò dimostra che:

  • ✅ MerchantAgent ha ricevuto l'IntentMandate (l'input lo mostra)
  • ✅ Il carrello è stato creato con l'ID "cart_7893"
  • ✅ La firma del carrello fa riferimento alla firma del mandato di intento (collegamento a catena).
  • ✅ Created at 15:30:14 UTC (2.4 seconds after intent)

CartMandate ora fa riferimento a IntentMandate, formando la catena.

Trovare il PaymentMandate

Fai clic sul tag execute_tool: create_payment_mandate (in CredentialsProvider).

Nel riquadro degli attributi:

{
  "tool.name": "create_payment_mandate",
  "tool.input.cart_mandate": {
    "cart_id": "cart_7893",
    "intent_signature": "a3f7b9c1d2e4...",
    "cart_signature": "e8f2a9b3c7d1..."
  },
  "tool.confirmation_required": true,
  "tool.confirmation_timestamp": "2024-11-08T15:30:17.891Z",
  "tool.user_response": "CONFIRMED",
  "tool.wait_duration_ms": 29168,
  "tool.output.status": "success",
  "tool.output.payment_mandate": {
    "payment_id": "pay_9821",
    "cart_signature": "e8f2a9b3c7d1...",
    "payment_signature": "b4c9e2a7f8d3...",
    "timestamp": "2024-11-08T15:30:47.059Z"
  }
}

Ciò dimostra la catena completa:

  • ✅ CredentialsProvider ha ricevuto CartMandate (l'input lo mostra)
  • ✅ Il pagamento fa riferimento alla firma CartMandate (link a catena).
  • Era necessaria la conferma (confirmation_required: true)
  • Utente confermato alle ore 15:30:17 UTC
  • Il sistema ha atteso 29,2 secondi la decisione dell'utente
  • ✅ Il pagamento è stato creato dopo la conferma (timestamp: 15:30:47)

Visualizzare la catena

La traccia dimostra che la catena di credenziali è stata eseguita correttamente:

15:30:12 UTC  IntentMandate created (signature: a3f7...)
                  
15:30:14 UTC  CartMandate created (references: a3f7...)
                  
15:30:17 UTC  User consent requested
                  
15:30:47 UTC  PaymentMandate created (references: e8f2...)

Ogni mandato fa riferimento alla firma di quello precedente. Si tratta di una prova di manomissione: puoi verificare la catena controllando che le firme corrispondano.

Passaggio 4: analizza le prestazioni e i colli di bottiglia

Cloud Trace non si limita a dimostrare cosa è successo, ma ti mostra dove viene impiegato il tempo in modo che tu possa ottimizzare.

Identificare il percorso critico

Nella visualizzazione a cascata, cerca gli intervalli più lunghi nello stack verticale. Questi rappresentano i colli di bottiglia del rendimento.

Dalla traccia di esempio:

Total: 8.2 seconds

Breakdown:
  - ShoppingAgent:         2.1s (26%)
  - MerchantAgent:         1.8s (22%)
  - CredentialsProvider:   4.0s (49%)   Bottleneck
  - Other overhead:        0.3s (3%)

Informazioni critiche:CredentialsProvider rappresenta il 49% del tempo totale. Perché?

Visualizza in dettaglio lo span CredentialsProvider:

CredentialsProvider: 4.0s
  - call_llm:              0.8s (20%)
  - create_payment_mandate: 3.0s (75%)   User consent wait
  - Other:                 0.2s (5%)

Il ritardo di 3 secondi è previsto ed è un buon segno: l'utente sta riflettendo prima di confermare. Non si tratta di un problema di rendimento, ma di una prova di un consenso ponderato.

Monitoraggio dei costi LLM

Fai clic su un intervallo call_llm per visualizzare l'utilizzo dei token:

{
  "llm.model": "gemini-2.5-flash",
  "llm.usage.prompt_tokens": 487,
  "llm.usage.completion_tokens": 156,
  "llm.usage.total_tokens": 643,
  "llm.response_time_ms": 1243
}

Utilizza questa funzionalità per:

  • Monitorare il costo per richiesta (token × prezzo del modello)
  • Identificare prompt inutilmente lunghi
  • Confrontare il rendimento dei modelli (Flash e Pro)
  • Ottimizzare per la latenza rispetto alla qualità

Esempio di calcolo:

Gemini 2.5 Flash pricing (as of Nov 2024):
  Input:  $0.075 per 1M tokens
  Output: $0.30 per 1M tokens

This request:
  Input:  487 tokens × $0.075 / 1M = $0.000037
  Output: 156 tokens × $0.30 / 1M  = $0.000047
  Total:                            = $0.000084 (~$0.00008)

For 10,000 donations/month:
  10,000 × 3 agents × $0.00008 = $2.40/month in LLM costs

Questa visibilità granulare ti aiuta a prendere decisioni basate sui dati in merito alla selezione del modello.

Confronto tra tracce

Filtra più tracce e confronta le durate:

Trace 1: 8.2s  (with consent wait: 3.0s)
Trace 2: 12.5s (with consent wait: 7.8s)  ← User took longer
Trace 3: 5.1s  (with consent wait: 0.2s)  ← User clicked fast
Trace 4: 6.3s  (with consent wait: 1.5s)

Approfondimento:la maggior parte della variazione deriva dal tempo di decisione dell'utente, non dalle prestazioni del sistema. L'esecuzione dell'agente principale (senza consenso) è coerente a circa 5 secondi.

Indica che il sistema funziona in modo affidabile.

Per i sistemi di produzione, configura gli avvisi per rilevare i problemi prima che gli utenti si lamentino.

Avviso sulle percentuali di errore elevate

Crea un avviso quando più del 5% delle tracce contiene errori:

  1. Vai a Cloud Monitoring.
  2. Fai clic su "Avvisi""Crea criterio".
  3. Configura:
    Resource: Cloud Trace Span
    Metric: Span error count
    Condition: Rate > 5% over 5 minutes
    Notification: Email your-team@example.com
    

Avviso sulla latenza elevata

Crea un avviso quando la latenza p95 supera i 15 secondi:

Resource: Cloud Trace
Metric: Span duration (95th percentile)
Condition: > 15000ms for 5 minutes
Notification: PagerDuty

In questo modo, il peggioramento delle prestazioni viene rilevato prima che influisca sull'esperienza utente.

Crea un avviso se qualsiasi pagamento viene elaborato senza conferma:

Resource: Cloud Trace Span
Filter: tool.name="create_payment_mandate" AND tool.confirmation_required!=true
Condition: Any match
Notification: Critical alert to security team

Si tratta di un rilevatore di violazioni della sicurezza: se si attiva, significa che il meccanismo di consenso presenta gravi problemi.

Cosa hai imparato

Grazie a Cloud Trace, ora sai come:

Naviga in Cloud Trace Explorer per trovare le tracce di produzione
Leggi le visualizzazioni a cascata per vedere il flusso di esecuzione completo
Traccia la catena di credenziali tramite IntentMandate → CartMandate → PaymentMandate ✅ Utilizza le tracce come prova per la risoluzione delle controversie
Analizza le prestazioni per identificare i colli di bottiglia
Monitora i costi degli LLM a un livello granulare

La differenza che fa

Confronta due sistemi che gestiscono lo stesso reclamo "Non ho mai autorizzato questo pagamento":

Sistema senza osservabilità

User: "I never authorized that $50 donation!"
You:  "Our logs show the transaction completed successfully."
User: "But I didn't approve it!"
You:  "The system requires confirmation before processing."
User: "I never saw any confirmation!"
You:  "..." [no way to prove what happened]

Result: Refund issued, trust lost, user never returns.

Sistema con Cloud Trace

User: "I never authorized that $50 donation!"
You:  "Let me pull up the trace from your session..."
      [Shows waterfall with consent span]
You:  "Here's the evidence:
       - 15:30:17 UTC: System asked for confirmation
       - Message shown: 'You are about to donate $50...'
       - 15:30:47 UTC: You clicked 'CONFIRM'
       - Wait time: 29.2 seconds
       
       The system waited almost 30 seconds for your decision.
       Here's the exact timestamp of your confirmation."
       
User: "Oh... I remember now. My mistake. Sorry!"

Result: Trust preserved, no refund needed, user continues using service.

Questo è il potere delle tracce di responsabilità. Passi da "fidati di noi" a "ti mostriamo esattamente cosa è successo".

Passaggi successivi

Ora hai completato la parte tecnica principale della creazione di agenti affidabili:

Moduli 1-6: progettazione di un'architettura affidabile (ruoli, credenziali, consenso)
Modulo 7: orchestrazione di flussi di lavoro complessi (SequentialAgent)
Modulo 8: deployment con osservabilità abilitata
Modulo 9: apprendimento della lettura e dell'utilizzo dei percorsi di responsabilità

L'architettura che hai creato (separazione dei ruoli, catene di credenziali, meccanismi di consenso, osservabilità completa) viene trasferita direttamente ai sistemi di produzione che gestiscono denaro reale, dati reali e conseguenze reali.

10. Il tuo percorso

Cosa hai creato

Hai iniziato questo workshop con una domanda: "Come faccio a creare agenti AI di cui posso fidarmi per gestire i miei soldi?"

Ora hai la risposta.

Dove hai iniziato (Modulo 3):

simple_agent = Agent(
    model="gemini-2.5-flash",
    instruction="Find charities and donate",
    tools=[google_search]
)

Dove ti trovi ora (Modulo 10):

  • ✅ Tre agenti specializzati con separazione dei ruoli
  • ✅ Tre credenziali verificabili (intento → carrello → mandati di pagamento)
  • ✅ Completa la catena di credenziali con la convalida della scadenza a ogni passaggio
  • ✅ Meccanismo di consenso esplicito con prova del timestamp
  • ✅ Deployment in produzione in Agent Engine con osservabilità
  • ✅ Traccia di responsabilità completa in Cloud Trace
  • ✅ Prove forensi per la risoluzione delle controversie

Workshop e produzione: il divario

Il tuo sistema mostra l'architettura e i pattern corretti, ma utilizza semplificazioni didattiche che devono essere aggiornate per denaro reale e utenti reali.

Ecco esattamente cosa è stato semplificato e cosa richiede la produzione:

Componente

Implementazione del workshop

Requisiti di produzione

Signatures

Hash SHA-256 (SIG_abc123) per la dimostrazione

Firme crittografiche reali che utilizzano PKI o JWT con chiavi private

Elaborazione dei pagamenti

Resi simulati (flag simulation: True)

Integrazione con API di pagamento reali (Stripe, PayPal, Square)

Autenticazione utente

Fiducia implicita (non è richiesto l'accesso)

OAuth 2.0, WebAuthn o gestione delle sessioni

Gestione dei secret

Variabili di ambiente nel file .env

Google Secret Manager o Cloud KMS con crittografia

Database delle organizzazioni non profit

File JSON simulato con 9 enti di beneficenza

Integrazione API live (ricerca di organizzazioni esenti da imposte dell'IRS, API Charity Navigator)

Gestione degli errori

Try-catch di base con messaggi di errore

Logica di nuovi tentativi con backoff esponenziale, interruttori di sicurezza, code di messaggi non recapitabili

Test

Verifica manuale tramite script

Suite di test unitari/di integrazione/E2E completa con CI/CD

Persistenza della sessione

In memoria (locale) o automatico (Agent Engine)

Database di produzione con backup e ripristino di emergenza

Limitazione di frequenza

Nessuno (ambiente didattico)

Limiti di frequenza per utente, limitazione basata sull'IP, rilevamento di abusi

Modelli architetturali chiave che hai padroneggiato

I pattern che hai imparato in questo workshop sono pattern di produzione. Non dubitare di loro.

Separazione dei ruoli (principio 1 di AP2)

Ogni agente ha UN compito chiaro e vede SOLO ciò di cui ha bisogno. Se un agente viene compromesso, l'attaccante non può accedere ai dati degli altri agenti. In questo modo, la portata dell'attacco viene limitata.

Sistemi di produzione che utilizzano questo servizio:elaborazione dei pagamenti, flussi di lavoro dei documenti, catene di approvazione, moduli in più passaggi con porte di convalida.

Credenziali verificabili (principio AP2 n. 2)

Ogni credenziale ha una data di scadenza, fa riferimento alla credenziale precedente e richiede la convalida prima del passaggio successivo. In questo modo viene creata una catena di audit antimanomissione.

Valore di produzione:prova completa di cosa è successo, quando e in quale ordine. Essenziale per la risoluzione delle controversie e la conformità normativa.

Timestamp che dimostra che l'utente ha approvato l'azione. Non può essere contestato.

Valore di produzione:requisito legale per le transazioni finanziarie. Protegge sia l'utente che l'azienda.

Orchestrazione sequenziale (pattern ADK)

Impone l'ordine di esecuzione corretto. Impedisce di saltare i passaggi. Garantisce che ogni agente veda l'output dell'agente precedente.

Valore di produzione:ideale per i sistemi human-in-the-loop in cui gli utenti si aspettano risultati immediati. Questo è il pattern giusto per i flussi di donazione, le procedure di pagamento e le catene di approvazione.

Osservabilità completa (OpenTelemetry + Cloud Trace)

Ogni decisione, chiamata di strumenti, momento di consenso e trasferimento delle credenziali acquisito automaticamente.

Valore produttivo:prove forensi per le contestazioni. Dati di ottimizzazione del rendimento. Audit trail di conformità. Esegui il debug dei problemi di produzione con precisione.

Risorse per l'apprendimento continuo

Documentazione ADK:

AP2 e standard correlati:

Servizi Google Cloud:

Esegui la pulizia delle risorse

Per evitare addebiti continui, elimina il deployment:

Agent Engine: segui i passaggi descritti nella documentazione di Agent Engine

Cloud Run (se è stato eseguito il deployment):

gcloud run services delete charity-advisor \
    --region=$GOOGLE_CLOUD_LOCATION

Bucket di archiviazione:

gsutil -m rm -r gs://$GOOGLE_CLOUD_PROJECT-staging
gsutil -m rm -r gs://$GOOGLE_CLOUD_PROJECT-artifacts

Il tuo percorso continua

Hai iniziato con una semplice domanda e hai creato una risposta completa. Hai acquisito le competenze di base per creare agenti AI affidabili. Questi pattern vengono trasferiti a qualsiasi dominio in cui gli agenti AI gestiscono operazioni sensibili: transazioni finanziarie, decisioni sanitarie, documenti legali, operazioni della supply chain.

Il trasferimento dei principi. Il modello di attendibilità funziona.

Ora crea qualcosa di affidabile. ❤️

banner