Techniques d'observabilité pratiques pour les applications d'IA générative en Go

1. Présentation

Les applications d'IA générative nécessitent une observabilité comme toute autre application. Des techniques d'observabilité spéciales sont-elles requises pour l'IA générative ?

Dans cet atelier, vous allez créer une application d'IA générative simple. Déployez-la sur Cloud Run. et l'instrumenter avec des fonctionnalités de surveillance et de journalisation essentielles à l'aide des services et produits d'observabilité Google Cloud.

Objectifs de l'atelier

  • Écrire une application qui utilise Vertex AI avec l'éditeur Cloud Shell
  • Stocker le code de votre application dans GitHub
  • Utiliser la CLI gcloud pour déployer le code source de votre application sur Cloud Run
  • Ajouter des fonctionnalités de surveillance et de journalisation à votre application d'IA générative
  • Utiliser des métriques basées sur les journaux
  • Implémenter la journalisation et la surveillance avec le SDK Open Telemetry
  • Obtenir des insights sur la gestion des données dans l'IA responsable

2. Prérequis

Si vous ne possédez pas encore de compte Google, vous devez en créer un.

3. Configuration du projet

  1. Connectez-vous à la console Google Cloud avec votre compte Google.
  2. Créez un projet ou choisissez de réutiliser un projet existant. Notez l'ID du projet que vous venez de créer ou de sélectionner.
  3. Activez la facturation pour le projet.
    • La réalisation de cet atelier devrait vous coûter moins de 5 $.
    • Vous pouvez suivre les étapes à la fin de cet atelier pour supprimer les ressources et éviter que des frais supplémentaires ne vous soient facturés.
    • Les nouveaux utilisateurs peuvent bénéficier d'un essai sans frais pour bénéficier d'un crédit de 300$.
  4. Vérifiez que la facturation est activée dans Mes projets dans la facturation Cloud.
    • Si la colonne Billing account indique Billing is disabled pour votre nouveau projet:
      1. Cliquez sur les trois points dans la colonne Actions.
      2. Cliquez sur Modifier la facturation.
      3. Sélectionnez le compte de facturation que vous souhaitez utiliser.
    • Si vous participez à un événement en direct, le compte est probablement nommé Compte de facturation d'essai de Google Cloud Platform.

4. Préparer l'éditeur Cloud Shell

  1. Accédez à l'éditeur Cloud Shell. Si le message suivant s'affiche, vous demandant d'autoriser Cloud Shell à appeler gcloud avec vos identifiants, cliquez sur Autoriser pour continuer.
    Cliquez pour autoriser Cloud Shell
  2. Ouvrir une fenêtre de terminal
    1. Cliquez sur le menu hamburger Icône du menu hamburger.
    2. Cliquez sur Terminal
    3. Cliquez sur Nouveau terminal
      Ouvrir un nouveau terminal dans l'éditeur Cloud Shell.
  3. Dans le terminal, configurez votre ID de projet:
    gcloud config set project [PROJECT_ID]
    
    Remplacez [PROJECT_ID] par l'ID de votre projet. Par exemple, si l'ID de votre projet est lab-example-project, la commande est la suivante:
    gcloud config set project lab-project-id-example
    
    Si le message suivant s'affiche, indiquant que gcloud demande vos identifiants à l'API GCPI, cliquez sur Autoriser pour continuer.
    Cliquez pour autoriser Cloud Shell
    Une fois l'exécution terminée, le message suivant s'affiche:
    Updated property [core/project].
    
    Si un WARNING s'affiche et que vous êtes invité à saisir Do you want to continue (Y/N)?, vous avez probablement saisi l'ID de projet de manière incorrecte. Appuyez sur N, puis sur Enter, puis essayez d'exécuter à nouveau la commande gcloud config set project une fois que vous avez trouvé l'ID de projet correct.
  4. (Facultatif) Si vous ne parvenez pas à trouver l'ID de projet, exécutez la commande suivante pour afficher l'ID de tous vos projets triés par date de création, dans l'ordre décroissant:
    gcloud projects list \
         --format='value(projectId,createTime)' \
         --sort-by=~createTime
    

5. Activer les API Google

Dans le terminal, activez les API Google requises pour cet atelier:

gcloud services enable \
     run.googleapis.com \
     cloudbuild.googleapis.com \
     aiplatform.googleapis.com \
     logging.googleapis.com \
     monitoring.googleapis.com \
     cloudtrace.googleapis.com

L'exécution de cette commande peut prendre un certain temps. Un message semblable à celui-ci s'affiche pour vous indiquer que l'opération s'est correctement déroulée:

Operation "operations/acf.p2-73d90d00-47ee-447a-b600" finished successfully.

Si vous recevez un message d'erreur commençant par ERROR: (gcloud.services.enable) HttpError accessing et contenant des détails d'erreur comme ci-dessous, réessayez la commande après un délai de 1 à 2 minutes.

"error": {
  "code": 429,
  "message": "Quota exceeded for quota metric 'Mutate requests' and limit 'Mutate requests per minute' of service 'serviceusage.googleapis.com' ...",
  "status": "RESOURCE_EXHAUSTED",
  ...
}

6. Créer une application Go d'IA générative

À cette étape, vous allez écrire le code de l'application simple basée sur les requêtes qui utilise le modèle Gemini pour afficher 10 faits amusants sur un animal de votre choix. Pour créer le code de l'application, procédez comme suit :

  1. Dans le terminal, créez le répertoire codelab-o11y:
    mkdir ~/codelab-o11y
    
  2. Définissez le répertoire actuel sur codelab-o11y:
    cd ~/codelab-o11y
    
  3. Initialisez les modules Go:
    go mod init codelab
    
  4. Installez le SDK Vertex AI pour Go:
    go get cloud.google.com/go/vertexai/genai
    
  5. Installez la bibliothèque de métadonnées pour Go afin d'obtenir l'ID de projet actuel:
    go get cloud.google.com/go/compute/metadata
    
  6. Créez un fichier setup.go et ouvrez-le dans l'éditeur Cloud Shell:
    cloudshell edit setup.go
    
    Il servira à héberger le code d'initialisation. Un nouveau fichier vide portant le nom setup.go s'affiche dans la fenêtre de l'éditeur.
  7. Copiez le code suivant et collez-le dans le fichier setup.go ouvert:
    package main
    
    import (
        "context"
        "os"
    
        "cloud.google.com/go/compute/metadata"
    )
    
    func projectID(ctx context.Context) (string, error) {
        var projectID = os.Getenv("GOOGLE_CLOUD_PROJECT")
        if projectID == "" {
               return metadata.ProjectIDWithContext(ctx)
        }
        return projectID, nil
    }
    
  8. Revenez à la fenêtre du terminal et exécutez la commande suivante pour créer et ouvrir un fichier main.go dans l'éditeur Cloud Shell:
    cloudshell edit main.go
    
    Un fichier vide devrait maintenant s'afficher dans la fenêtre de l'éditeur au-dessus du terminal. Votre écran ressemblera à ceci:
    Afficher l'éditeur Cloud Shell après avoir commencé à modifier main.go
  9. Copiez le code suivant et collez-le dans le fichier main.go ouvert:
    package main
    
    import (
        "context"
        "fmt"
        "net/http"
        "os"
    
        "cloud.google.com/go/vertexai/genai"
    )
    
    var model *genai.GenerativeModel
    
    func main() {
        ctx := context.Background()
        projectID, err := projectID(ctx)
        if err != nil {
            return
        }
    
        var client *genai.Client
        client, err = genai.NewClient(ctx, projectID, "us-central1")
        if err != nil {
            return
        }
        defer client.Close()
           model = client.GenerativeModel("gemini-1.5-flash-001")
           http.HandleFunc("/", Handler)
           port := os.Getenv("PORT")
        if port == "" {
            port = "8080"
        }
        if err := http.ListenAndServe(":"+port, nil); err != nil {
            return
        }
    }
    
    func Handler(w http.ResponseWriter, r *http.Request) {
        animal := r.URL.Query().Get("animal")
        if animal == "" {
            animal = "dog"
        }
    
        prompt := fmt.Sprintf("Give me 10 fun facts about %s. Return the results as HTML without markdown backticks.", animal)
        resp, err := model.GenerateContent(r.Context(), genai.Text(prompt))
        if err != nil {
            w.WriteHeader(http.StatusTooManyRequests)
            return
        }
    
        if len(resp.Candidates) > 0 && len(resp.Candidates[0].Content.Parts) > 0 {
            htmlContent := resp.Candidates[0].Content.Parts[0]
            w.Header().Set("Content-Type", "text/html; charset=utf-8")
            fmt.Fprint(w, htmlContent)
        }
    }
    
    Au bout de quelques secondes, l'éditeur Cloud Shell enregistre automatiquement votre code.

Déployer le code de l'application d'IA générative sur Cloud Run

  1. Dans la fenêtre du terminal, exécutez la commande pour déployer le code source de l'application sur Cloud Run.
    gcloud run deploy codelab-o11y-service \
         --source="${HOME}/codelab-o11y/" \
         --region=us-central1 \
         --allow-unauthenticated
    
    Si l'invite ci-dessous s'affiche, vous êtes informé que la commande créera un dépôt. Cliquez sur Enter.
    Deploying from source requires an Artifact Registry Docker repository to store built containers.
    A repository named [cloud-run-source-deploy] in region [us-central1] will be created.
    
    Do you want to continue (Y/n)?
    
    Le processus de déploiement peut prendre jusqu'à quelques minutes. Une fois le processus de déploiement terminé, le résultat affiché doit ressembler à l'exemple suivant:
    Service [codelab-o11y-service] revision [codelab-o11y-service-00001-t2q] has been deployed and is serving 100 percent of traffic.
    Service URL: https://codelab-o11y-service-12345678901.us-central1.run.app
    
  2. Copiez l'URL du service Cloud Run affichée dans un onglet ou une fenêtre distincts de votre navigateur. Vous pouvez également exécuter la commande suivante dans le terminal pour imprimer l'URL du service, puis cliquer sur l'URL affichée en maintenant la touche Ctrl enfoncée pour l'ouvrir:
    gcloud run services list \
         --format='value(URL)' \
         --filter='SERVICE:"codelab-o11y-service"'
    
    Lorsque l'URL est ouverte, une erreur 500 ou le message suivant peuvent s'afficher:
    Sorry, this is just a placeholder...
    
    Cela signifie que le déploiement des services n'est pas terminé. Patientez quelques instants, puis actualisez la page. À la fin, un texte commençant par Anecdotes amusantes sur les chiens s'affiche, contenant 10 anecdotes amusantes sur les chiens.

Essayez d'interagir avec l'application pour obtenir des anecdotes sur différents animaux. Pour ce faire, ajoutez le paramètre animal à l'URL, comme ?animal=[ANIMAL], où [ANIMAL] est un nom d'animal. Par exemple, ajoutez ?animal=cat pour obtenir 10 faits amusants sur les chats ou ?animal=sea turtle pour obtenir 10 faits amusants sur les tortues marines.

7. Auditer vos appels d'API Vertex

L'audit des appels d'API Google permet de répondre à des questions telles que "Qui appelle une API particulière, où et quand ?". L'audit est important lorsque vous dépannez votre application, examinez la consommation de ressources ou effectuez une analyse logicielle.

Les journaux d'audit vous permettent de suivre les activités administratives et système, ainsi que d'enregistrer les appels aux opérations d'API "lecture de données" et "écriture de données". Pour auditer les requêtes Vertex AI visant à générer du contenu, vous devez activer les journaux d'audit "Lecture des données" dans la console Cloud.

  1. Cliquez sur le bouton ci-dessous pour ouvrir la page "Journaux d'audit" dans la console Cloud.

  2. Assurez-vous que le projet que vous avez créé pour cet atelier est sélectionné sur la page. Le projet sélectionné s'affiche en haut à gauche de la page, à droite du menu hamburger:
    Menu déroulant des projets dans la console Google Cloud
    Si nécessaire, sélectionnez le bon projet dans la liste déroulante.
  3. Dans le tableau Configuration des journaux d'audit des accès aux données, dans la colonne "Service", recherchez le service Vertex AI API, puis cochez la case située à gauche de son nom.
    Sélectionnez l'API Vertex AI.
  4. Dans le panneau d'informations à droite, sélectionnez le type d'audit "Lecture des données".
    Vérifier les journaux de lecture des données
  5. Cliquez sur Enregistrer.

Pour générer des journaux d'audit, ouvrez l'URL du service. Actualisez la page en modifiant la valeur du paramètre ?animal= pour obtenir des résultats différents.

Explorer les journaux d'audit

  1. Cliquez sur le bouton ci-dessous pour ouvrir la page de l'explorateur de journaux dans la console Cloud:

  2. Collez le filtre suivant dans le volet "Requête".
    LOG_ID("cloudaudit.googleapis.com%2Fdata_access") AND
    protoPayload.serviceName="aiplatform.googleapis.com"
    
    Le volet "Requête" est un éditeur situé en haut de la page de l'explorateur de journaux:
    Interroger les journaux d'audit
  3. Cliquez sur Exécuter la requête.
  4. Sélectionnez l'une des entrées du journal d'audit et développez les champs pour inspecter les informations capturées dans le journal.
    Vous pouvez consulter des informations sur l'appel de l'API Vertex, y compris la méthode et le modèle utilisés. Vous pouvez également voir l'identité de l'appelant et les autorisations qui ont autorisé l'appel.

8. Journaliser les interactions avec l'IA générative

Vous ne trouvez pas de paramètres de requête d'API ni de données de réponse dans les journaux d'audit. Toutefois, ces informations peuvent être importantes pour résoudre les problèmes d'analyse des applications et des workflows. À cette étape, nous allons combler cette lacune en ajoutant la journalisation de l'application. La journalisation utilise le package Go log/slog standard pour écrire des journaux structurés. Le package log/slog ne sait pas écrire de journaux dans Google Cloud. Il permet d'écrire sur la sortie standard. Toutefois, Cloud Run capture les informations imprimées dans la sortie standard et les ingère automatiquement dans Cloud Logging. Pour capturer correctement les journaux structurés, le journal imprimé doit être mis en forme en conséquence. Suivez les instructions ci-dessous pour ajouter des fonctionnalités de journalisation structurée à notre application Go.

  1. Revenez à la fenêtre (ou à l'onglet) "Cloud Shell" dans votre navigateur.
  2. Dans le terminal, rouvrez setup.go:
    cloudshell edit ~/codelab-o11y/setup.go
    
  3. Remplacez le code par la version qui configure la journalisation. Pour remplacer le code, supprimez le contenu du fichier, puis copiez le code ci-dessous et collez-le dans l'éditeur:
    package main
    
    import (
    	"context"
    	"os"
    	"log/slog"
    	"cloud.google.com/go/compute/metadata"
    )
    
    func projectID(ctx context.Context) (string, error) {
        var projectID = os.Getenv("GOOGLE_CLOUD_PROJECT")
        if projectID == "" {
               return metadata.ProjectIDWithContext(ctx)
        }
        return projectID, nil
    }
    
    func setupLogging() {
        opts := &slog.HandlerOptions{
            Level: slog.LevelDebug,
            ReplaceAttr: func(group []string, a slog.Attr) slog.Attr {
                switch a.Key {
                case slog.LevelKey:
                    a.Key = "severity"
                    if level := a.Value.Any().(slog.Level); level == slog.LevelWarn {
                        a.Value = slog.StringValue("WARNING")
                    }
                case slog.MessageKey:
                    a.Key = "message"
                case slog.TimeKey:
                    a.Key = "timestamp"
                }
                return a
            },
        }
        jsonHandler := slog.NewJSONHandler(os.Stdout, opts)
        slog.SetDefault(slog.New(jsonHandler))
    }
    
  4. Revenez au terminal et rouvrez main.go:
    cloudshell edit ~/codelab-o11y/main.go
    
  5. Remplacez le code de l'application par la version qui consigne l'interaction avec le modèle. Pour remplacer le code, supprimez le contenu du fichier, puis copiez le code ci-dessous et collez-le dans l'éditeur:
    package main
    
    import (
        "context"
        "fmt"
        "net/http"
        "os"
    
        "encoding/json"
        "log/slog"
    
        "cloud.google.com/go/vertexai/genai"
    )
    
    var model *genai.GenerativeModel
    
    func main() {
        ctx := context.Background()
        projectID, err := projectID(ctx)
        if err != nil {
            return
        }
    
        setupLogging()
    
        var client *genai.Client
        client, err = genai.NewClient(ctx, projectID, "us-central1")
        if err != nil {
            slog.ErrorContext(ctx, "Failed to marshal response to JSON", slog.Any("error", err))
            os.Exit(1)
        }
        defer client.Close()
        model = client.GenerativeModel("gemini-1.5-flash-001")
        http.HandleFunc("/", Handler)
        port := os.Getenv("PORT")
        if port == "" {
            port = "8080"
        }
        if err := http.ListenAndServe(":"+port, nil); err != nil {
            slog.ErrorContext(ctx, "Failed to start the server", slog.Any("error", err))
            os.Exit(1)
        }
    }
    
    func Handler(w http.ResponseWriter, r *http.Request) {
        animal := r.URL.Query().Get("animal")
        if animal == "" {
            animal = "dog"
        }
    
        prompt := fmt.Sprintf("Give me 10 fun facts about %s. Return the results as HTML without markdown backticks.", animal)
        resp, err := model.GenerateContent(r.Context(), genai.Text(prompt))
        if err != nil {
            w.WriteHeader(http.StatusTooManyRequests)
            return
        }
    
        jsonBytes, err := json.Marshal(resp)
        if err != nil {
            slog.Error("Failed to marshal response to JSON", slog.Any("error", err))
        } else {
            slog.DebugContext(r.Context(), "content is generated", slog.String("animal", animal),
                slog.String("prompt", prompt), slog.String("response", string(jsonBytes)))
        }
    
        if len(resp.Candidates) > 0 && len(resp.Candidates[0].Content.Parts) > 0 {
            htmlContent := resp.Candidates[0].Content.Parts[0]
            w.Header().Set("Content-Type", "text/html; charset=utf-8")
            fmt.Fprint(w, htmlContent)
        }
    }
    

La journalisation est configurée pour imprimer les journaux dans stdout, où ils sont collectés par l'agent de journalisation Cloud Run et ingérés de manière asynchrone dans Cloud Logging. La fonction main() est modifiée pour configurer le journal structuré standard Go afin qu'il utilise un schéma JSON qui suit les consignes de mise en forme structurée. Toutes ses instructions return sont remplacées par le code qui écrit les journaux d'erreurs avant la sortie. La fonction Handler() est instrumentée pour écrire un journal structuré lorsqu'elle reçoit la réponse de l'appel d'API Vertex AI. Le journal enregistre le paramètre animal de la requête, ainsi que l'invite et la réponse du modèle.

Au bout de quelques secondes, l'éditeur Cloud Shell enregistre automatiquement vos modifications.

Déployer le code de l'application d'IA générative sur Cloud Run

  1. Dans la fenêtre du terminal, exécutez la commande pour déployer le code source de l'application sur Cloud Run.
    gcloud run deploy codelab-o11y-service \
         --source="${HOME}/codelab-o11y/" \
         --region=us-central1 \
         --allow-unauthenticated
    
    Si l'invite ci-dessous s'affiche, vous êtes informé que la commande créera un dépôt. Cliquez sur Enter.
    Deploying from source requires an Artifact Registry Docker repository to store built containers.
    A repository named [cloud-run-source-deploy] in region [us-central1] will be created.
    
    Do you want to continue (Y/n)?
    
    Le processus de déploiement peut prendre jusqu'à quelques minutes. Une fois le processus de déploiement terminé, le résultat affiché doit ressembler à l'exemple suivant:
    Service [codelab-o11y-service] revision [codelab-o11y-service-00001-t2q] has been deployed and is serving 100 percent of traffic.
    Service URL: https://codelab-o11y-service-12345678901.us-central1.run.app
    
  2. Copiez l'URL du service Cloud Run affichée dans un onglet ou une fenêtre distincts de votre navigateur. Vous pouvez également exécuter la commande suivante dans le terminal pour imprimer l'URL du service, puis cliquer sur l'URL affichée en maintenant la touche Ctrl enfoncée pour l'ouvrir:
    gcloud run services list \
         --format='value(URL)' \
         --filter='SERVICE:"codelab-o11y-service"'
    
    Lorsque l'URL est ouverte, une erreur 500 ou le message suivant peuvent s'afficher:
    Sorry, this is just a placeholder...
    
    Cela signifie que le déploiement des services n'est pas terminé. Patientez quelques instants, puis actualisez la page. À la fin, un texte commençant par Anecdotes amusantes sur les chiens s'affiche, contenant 10 anecdotes amusantes sur les chiens.

Pour générer des journaux d'application, ouvrez l'URL du service. Actualisez la page en modifiant la valeur du paramètre ?animal= pour obtenir des résultats différents.
Pour afficher les journaux de l'application, procédez comme suit:

  1. Cliquez sur le bouton ci-dessous pour ouvrir la page de l'explorateur de journaux dans la console Cloud:

  2. Collez le filtre suivant dans le volet "Requête" (numéro 2 dans l'interface de l'explorateur de journaux):
    LOG_ID("run.googleapis.com%2Fstdout") AND
    severity=DEBUG
    
  3. Cliquez sur Exécuter la requête.

Le résultat de la requête affiche les journaux avec la requête et la réponse Vertex AI, y compris les évaluations de sécurité.

9. Compter les interactions avec l'IA générative

Cloud Run écrit des métriques gérées qui peuvent être utilisées pour surveiller les services déployés. Les métriques de surveillance gérées par l'utilisateur offrent un meilleur contrôle des données et de la fréquence de mise à jour des métriques. Pour implémenter une telle métrique, vous devez écrire un code qui collecte les données et les écrit dans Cloud Monitoring. Consultez l'étape suivante (facultative) pour découvrir comment l'implémenter à l'aide du SDK OpenTelemetry.

Cette étape présente une alternative à l'implémentation de la métrique utilisateur dans le code : les métriques basées sur les journaux. Les métriques basées sur les journaux vous permettent de générer des métriques de surveillance à partir des entrées de journal que votre application écrit dans Cloud Logging. Nous allons utiliser les journaux d'application que nous avons implémentés à l'étape précédente pour définir une métrique basée sur les journaux de type compteur. La métrique comptabilise le nombre d'appels réussis à l'API Vertex.

  1. Examinez la fenêtre de l'explorateur de journaux que nous avons utilisée à l'étape précédente. Dans le volet "Requête", recherchez le menu déroulant Actions, puis cliquez dessus pour l'ouvrir. Pour trouver le menu, consultez la capture d'écran ci-dessous:
    Barre d'outils des résultats de requête avec le menu déroulant "Actions"
  2. Dans le menu qui s'ouvre, sélectionnez Créer une métrique pour ouvrir le panneau Créer une métrique basée sur les journaux.
  3. Pour configurer une métrique de compteur dans le panneau Créer une métrique basée sur les journaux, procédez comme suit:
    1. Définissez le Type de métrique : sélectionnez Compteur.
    2. Définissez les champs suivants dans la section Détails:
      • Nom de la métrique de journal: définissez le nom sur model_interaction_count. Certaines restrictions en termes de dénomination s'appliquent. Pour en savoir plus, consultez la section Dépannage des restrictions de dénomination.
      • Description : saisissez une description de la métrique. Par exemple, Number of log entries capturing successful call to model inference..
      • Unités: laissez ce champ vide ou insérez le chiffre 1.
    3. Laissez les valeurs dans la section Sélection du filtre. Notez que le champ Créer un filtre contient le même filtre que celui que nous avons utilisé pour afficher les journaux d'application.
    4. (Facultatif) Ajoutez un libellé qui permet de comptabiliser le nombre d'appels pour chaque animal. REMARQUE: ce libellé peut considérablement augmenter la cardinalité de la métrique et n'est pas recommandé pour une utilisation en production:
      1. Cliquez sur Ajouter une étiquette.
      2. Définissez les champs suivants dans la section Libellés:
        • Nom du libellé: définissez le nom sur animal.
        • Description: saisissez la description du libellé. Exemple :Animal parameter
        • Type de libellé: sélectionnez STRING.
        • Nom du champ: saisissez jsonPayload.animal.
        • Expression régulière: laissez ce champ vide.
      3. Cliquez sur Terminé.
    5. Cliquez sur Créer une métrique pour créer la métrique.

Vous pouvez également créer une métrique basée sur les journaux à partir de la page Métriques basées sur les journaux, à l'aide de la commande CLI gcloud logging metrics create ou avec la ressource Terraform google_logging_metric.

Pour générer des données de métriques, ouvrez l'URL du service. Actualisez la page ouverte plusieurs fois pour effectuer plusieurs appels au modèle. Comme précédemment, essayez d'utiliser différents animaux dans le paramètre.

Saisissez la requête PromQL pour rechercher les données de métrique basées sur les journaux. Pour saisir une requête PromQL, procédez comme suit:

  1. Cliquez sur le bouton ci-dessous pour ouvrir la page "Explorateur de métriques" dans la console Cloud:

  2. Dans la barre d'outils du volet de création de requêtes, sélectionnez le bouton nommé < > MQL ou < > PromQL. Consultez l'image ci-dessous pour connaître l'emplacement du bouton.
    Emplacement du bouton &quot;MQL&quot; dans l&#39;explorateur de métriques
  3. Vérifiez que PromQL est sélectionné dans le bouton d'activation Langage. Le bouton de langage se trouve dans la barre d'outils qui vous permet de mettre en forme votre requête.
  4. Saisissez votre requête dans l'éditeur Requêtes:
    sum(rate(logging_googleapis_com:user_model_interaction_count{monitored_resource="cloud_run_revision"}[${__interval}]))
    
    Pour en savoir plus sur l'utilisation de PromQL, consultez PromQL dans Cloud Monitoring.
  5. Cliquez sur Exécuter la requête. Un graphique linéaire semblable à celui-ci s'affiche:
    Afficher les métriques interrogées

    Notez que lorsque l'option Exécution automatique est activée, le bouton Exécuter la requête ne s'affiche pas.

10. (Facultatif) Utiliser Open Telemetry pour la surveillance et le traçage

Comme indiqué à l'étape précédente, il est possible d'implémenter des métriques à l'aide du SDK OpenTelemetry (Otel). L'utilisation d'OTel sur les architectures de microservices est une pratique recommandée. Cette étape décrit les éléments suivants:

  • Initialisation des composants OTel pour prendre en charge le traçage et la surveillance de l'application
  • Renseigner la configuration OTel avec les métadonnées de ressources de l'environnement Cloud Run
  • Instrumenter une application Flask avec des fonctionnalités de traçage automatique
  • Implémenter une métrique de compteur pour surveiller le nombre d'appels de modèle réussis
  • Corréler le traçage avec les journaux d'application

L'architecture recommandée pour les services au niveau du produit consiste à utiliser le collecteur OTel pour collecter et ingérer toutes les données d'observabilité d'un ou de plusieurs services. Par souci de simplicité, le code de cette étape n'utilise pas le collecteur. Il utilise plutôt des exportations OTel qui écrivent des données directement dans Google Cloud.

Configurer les composants OTel pour le traçage et la surveillance des métriques

  1. Revenez à la fenêtre (ou à l'onglet) "Cloud Shell" dans votre navigateur.
  2. Dans le terminal, rouvrez setup.go:
    cloudshell edit ~/codelab-o11y/setup.go
    
  3. Remplacez le code par la version qui initialise le traçage et la collecte de métriques OpenTelemetry. Pour remplacer le code, supprimez le contenu du fichier, puis copiez le code ci-dessous et collez-le dans l'éditeur:
    package main
    
    import (
        "context"
        "errors"
        "fmt"
        "net/http"
        "os"
    
        "log/slog"
    
        "go.opentelemetry.io/contrib/detectors/gcp"
        "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
        "go.opentelemetry.io/contrib/propagators/autoprop"
        "go.opentelemetry.io/otel"
        sdkmetric "go.opentelemetry.io/otel/sdk/metric"
        "go.opentelemetry.io/otel/sdk/resource"
        sdktrace "go.opentelemetry.io/otel/sdk/trace"
        semconv "go.opentelemetry.io/otel/semconv/v1.27.0"
        "go.opentelemetry.io/otel/trace"
    
        cloudmetric "github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric"
        cloudtrace "github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace"
    
        "cloud.google.com/go/compute/metadata"
    )
    
    var (
        projID string
    )
    
    func projectID(ctx context.Context) (string, error) {
        var projectID = os.Getenv("GOOGLE_CLOUD_PROJECT")
        if projectID == "" {
            return metadata.ProjectIDWithContext(ctx)
        }
        return projectID, nil
    }
    
    func setupLogging() {
        opts := &slog.HandlerOptions{
            Level: slog.LevelDebug,
            ReplaceAttr: func(group []string, a slog.Attr) slog.Attr {
                switch a.Key {
                case slog.LevelKey:
                    a.Key = "severity"
                    if level := a.Value.Any().(slog.Level); level == slog.LevelWarn {
                        a.Value = slog.StringValue("WARNING")
                    }
                case slog.MessageKey:
                    a.Key = "message"
                case slog.TimeKey:
                    a.Key = "timestamp"
                }
                return a
            },
        }
        jsonHandler := slog.NewJSONHandler(os.Stdout, opts)
        instrumentedHandler := handlerWithSpanContext(jsonHandler)
        slog.SetDefault(slog.New(instrumentedHandler))
    }
    
    type spanContextLogHandler struct {
        slog.Handler
    }
    
    func handlerWithSpanContext(handler slog.Handler) *spanContextLogHandler {
        return &spanContextLogHandler{Handler: handler}
    }
    
    func (t *spanContextLogHandler) Handle(ctx context.Context, record slog.Record) error {
        if s := trace.SpanContextFromContext(ctx); s.IsValid() {
            trace := fmt.Sprintf("projects/%s/traces/%s", projID, s.TraceID())
            record.AddAttrs(
                slog.Any("logging.googleapis.com/trace", trace),
            )
            record.AddAttrs(
                slog.Any("logging.googleapis.com/spanId", s.SpanID()),
            )
            record.AddAttrs(
                slog.Bool("logging.googleapis.com/trace_sampled", s.TraceFlags().IsSampled()),
            )
        }
        return t.Handler.Handle(ctx, record)
    }
    
    func setupTelemetry(ctx context.Context) (shutdown func(context.Context) error, err error) {
        var shutdownFuncs []func(context.Context) error
        shutdown = func(ctx context.Context) error {
            var err error
            for _, fn := range shutdownFuncs {
                err = errors.Join(err, fn(ctx))
            }
            shutdownFuncs = nil
            return err
        }
    
        projID, err = projectID(ctx)
        if err != nil {
            err = errors.Join(err, shutdown(ctx))
            return
        }
    
        res, err2 := resource.New(
            ctx,
            resource.WithDetectors(gcp.NewDetector()),
            resource.WithTelemetrySDK(),
            resource.WithAttributes(semconv.ServiceNameKey.String(os.Getenv("K_SERVICE"))),
        )
        if err2 != nil {
            err = errors.Join(err2, shutdown(ctx))
            return
        }
    
        otel.SetTextMapPropagator(autoprop.NewTextMapPropagator())
    
        texporter, err2 := cloudtrace.New(cloudtrace.WithProjectID(projID))
        if err2 != nil {
            err = errors.Join(err2, shutdown(ctx))
            return
        }
        tp := sdktrace.NewTracerProvider(
            sdktrace.WithSampler(sdktrace.AlwaysSample()),
            sdktrace.WithResource(res),
            sdktrace.WithBatcher(texporter))
        shutdownFuncs = append(shutdownFuncs, tp.Shutdown)
        otel.SetTracerProvider(tp)
    
        mexporter, err2 := cloudmetric.New(cloudmetric.WithProjectID(projID))
        if err2 != nil {
            err = errors.Join(err2, shutdown(ctx))
            return
        }
        mp := sdkmetric.NewMeterProvider(
            sdkmetric.WithReader(sdkmetric.NewPeriodicReader(mexporter)),
            sdkmetric.WithResource(res),
        )
        shutdownFuncs = append(shutdownFuncs, mp.Shutdown)
        otel.SetMeterProvider(mp)
    
        return shutdown, nil
    }
    
    func registerHttpHandler(route string, handleFn http.HandlerFunc) {
        instrumentedHandler := otelhttp.NewHandler(otelhttp.WithRouteTag(route, handleFn), route)
        http.Handle(route, instrumentedHandler)
    }
    
  4. Revenez dans le terminal et exécutez la commande suivante pour mettre à jour les définitions de module Go dans le fichier go.mod:
    go mod tidy
    
  5. Revenez au terminal et rouvrez main.go:
    cloudshell edit ~/codelab-o11y/main.go
    
  6. Remplacez le code actuel par la version qui instrumente le traçage HTTP et écrit la métrique de performances. Pour remplacer le code, supprimez le contenu du fichier, puis copiez le code ci-dessous et collez-le dans l'éditeur:
    package main
    
    import (
        "context"
        "errors"
        "fmt"
        "net/http"
        "os"
    
        "encoding/json"
        "log/slog"
    
        "cloud.google.com/go/vertexai/genai"
    
        "go.opentelemetry.io/otel"
        "go.opentelemetry.io/otel/attribute"
        "go.opentelemetry.io/otel/metric"
    )
    
    var model *genai.GenerativeModel
    var counter metric.Int64Counter
    
    const scopeName = "genai-o11y/go/workshop/example"
    
    func main() {
        ctx := context.Background()
        projectID, err := projectID(ctx)
        if err != nil {
            return
        }
    
        setupLogging()
        shutdown, err := setupTelemetry(ctx)
        if err != nil {
            slog.ErrorContext(ctx, "error setting up OpenTelemetry", slog.Any("error", err))
            os.Exit(1)
        }
        meter := otel.Meter(scopeName)
        counter, err = meter.Int64Counter("model_call_counter")
        if err != nil {
            slog.ErrorContext(ctx, "error setting up OpenTelemetry", slog.Any("error", err))
            os.Exit(1)
        }
    
        var client *genai.Client
        client, err = genai.NewClient(ctx, projectID, "us-central1")
        if err != nil {
            slog.ErrorContext(ctx, "Failed to marshal response to JSON", slog.Any("error", err))
            os.Exit(1)
        }
        defer client.Close()
        model = client.GenerativeModel("gemini-1.5-flash-001")
    
        registerHttpHandler("/", Handler)
    
        port := os.Getenv("PORT")
        if port == "" {
            port = "8080"
        }
    
        if err = errors.Join(http.ListenAndServe(":"+port, nil), shutdown(ctx)); err != nil {
            slog.ErrorContext(ctx, "Failed to start the server", slog.Any("error", err))
            os.Exit(1)
        }
    }
    
    func Handler(w http.ResponseWriter, r *http.Request) {
        animal := r.URL.Query().Get("animal")
        if animal == "" {
            animal = "dog"
        }
    
        prompt := fmt.Sprintf("Give me 10 fun facts about %s. Return the results as HTML without markdown backticks.", animal)
        resp, err := model.GenerateContent(r.Context(), genai.Text(prompt))
        if err != nil {
            w.WriteHeader(http.StatusTooManyRequests)
            return
        }
        jsonBytes, err := json.Marshal(resp)
        if err != nil {
            slog.ErrorContext(r.Context(), "Failed to marshal response to JSON", slog.Any("error", err))
        } else {
            slog.DebugContext(r.Context(), "content is generated", slog.String("animal", animal),
                slog.String("prompt", prompt), slog.String("response", string(jsonBytes)))
        }
        if len(resp.Candidates) > 0 && len(resp.Candidates[0].Content.Parts) > 0 {
            clabels := []attribute.KeyValue{attribute.Key("animal").String(animal)}
            counter.Add(r.Context(), 1, metric.WithAttributes(clabels...))
            htmlContent := resp.Candidates[0].Content.Parts[0]
            w.Header().Set("Content-Type", "text/html; charset=utf-8")
            fmt.Fprint(w, htmlContent)
        }
    }
    

L'application utilise désormais le SDK OpenTelemetry pour instrumenter l'exécution du code avec le traçage et pour implémenter le comptage d'un nombre d'exécutions réussies en tant que métrique. La méthode main() est modifiée pour configurer les exportateurs OpenTelemetry afin que les traces et les métriques soient écrites directement dans Google Cloud Tracing et Monitoring. Il effectue également des configurations supplémentaires pour renseigner les traces et les métriques collectées avec des métadonnées liées à l'environnement Cloud Run. La fonction Handler() est mise à jour pour incrémenter le compteur de métrique chaque fois que l'appel de l'API Vertex AI renvoie des résultats valides.

Au bout de quelques secondes, l'éditeur Cloud Shell enregistre automatiquement vos modifications.

Déployer le code de l'application d'IA générative sur Cloud Run

  1. Dans la fenêtre du terminal, exécutez la commande pour déployer le code source de l'application sur Cloud Run.
    gcloud run deploy codelab-o11y-service \
         --source="${HOME}/codelab-o11y/" \
         --region=us-central1 \
         --allow-unauthenticated
    
    Si l'invite ci-dessous s'affiche, vous êtes informé que la commande créera un dépôt. Cliquez sur Enter.
    Deploying from source requires an Artifact Registry Docker repository to store built containers.
    A repository named [cloud-run-source-deploy] in region [us-central1] will be created.
    
    Do you want to continue (Y/n)?
    
    Le processus de déploiement peut prendre jusqu'à quelques minutes. Une fois le processus de déploiement terminé, le résultat affiché doit ressembler à l'exemple suivant:
    Service [codelab-o11y-service] revision [codelab-o11y-service-00001-t2q] has been deployed and is serving 100 percent of traffic.
    Service URL: https://codelab-o11y-service-12345678901.us-central1.run.app
    
  2. Copiez l'URL du service Cloud Run affichée dans un onglet ou une fenêtre distincts de votre navigateur. Vous pouvez également exécuter la commande suivante dans le terminal pour imprimer l'URL du service, puis cliquer sur l'URL affichée en maintenant la touche Ctrl enfoncée pour l'ouvrir:
    gcloud run services list \
         --format='value(URL)' \
         --filter='SERVICE:"codelab-o11y-service"'
    
    Lorsque l'URL est ouverte, une erreur 500 ou le message suivant peuvent s'afficher:
    Sorry, this is just a placeholder...
    
    Cela signifie que le déploiement des services n'est pas terminé. Patientez quelques instants, puis actualisez la page. À la fin, un texte commençant par Anecdotes amusantes sur les chiens s'affiche, contenant 10 anecdotes amusantes sur les chiens.

Pour générer des données de télémétrie, ouvrez l'URL du service. Actualisez la page en modifiant la valeur du paramètre ?animal= pour obtenir des résultats différents.

Explorer les traces d'application

  1. Cliquez sur le bouton ci-dessous pour ouvrir la page de l'explorateur Trace dans la console Cloud:

  2. Sélectionnez l'une des traces les plus récentes. Vous devriez voir cinq ou six segments qui ressemblent à la capture d'écran ci-dessous.
    Vue de la période de l&#39;application dans l&#39;explorateur de traces
  3. Recherchez le délai qui suit l'appel du gestionnaire d'événements (la méthode fun_facts). Il s'agit de la dernière span avec le nom /.
  4. Dans le volet Détails de la trace, sélectionnez Journaux et événements. Les journaux d'application correspondant à ce segment s'affichent. La corrélation est détectée à l'aide des ID de trace et de délai dans la trace et dans le journal. Vous devriez voir le journal de l'application qui a écrit l'invite et la réponse de l'API Vertex.

Explorer la métrique de compteur

  1. Cliquez sur le bouton ci-dessous pour ouvrir la page "Explorateur de métriques" dans la console Cloud:

  2. Dans la barre d'outils du volet de création de requêtes, sélectionnez le bouton nommé < > MQL ou < > PromQL. Consultez l'image ci-dessous pour connaître l'emplacement du bouton.
    Emplacement du bouton &quot;MQL&quot; dans l&#39;explorateur de métriques
  3. Vérifiez que PromQL est sélectionné dans le bouton d'activation Langage. Le bouton de langage se trouve dans la barre d'outils qui vous permet de mettre en forme votre requête.
  4. Saisissez votre requête dans l'éditeur Requêtes:
    sum(rate(workload_googleapis_com:model_call_counter{monitored_resource="generic_task"}[${__interval}]))
    
  5. Cliquez sur Exécuter la requête.Lorsque l'option Exécution automatique est activée, le bouton Exécuter la requête ne s'affiche pas.

11. (Facultatif) Informations sensibles masquées dans les journaux

À l'étape 10, nous avons enregistré des informations sur l'interaction de l'application avec le modèle Gemini. Ces informations incluaient le nom de l'animal, la requête réelle et la réponse du modèle. Bien que le stockage de ces informations dans le journal soit sécurisé, ce n'est pas nécessairement vrai pour de nombreux autres scénarios. L'invite peut inclure des informations personnelles ou sensibles que l'utilisateur ne souhaite pas stocker. Pour y remédier, vous pouvez obscurcir les données sensibles écrites dans Cloud Logging. Pour réduire les modifications de code, nous vous recommandons la solution suivante.

  1. Créer un sujet Pub/Sub pour stocker les entrées de journal entrantes
  2. Créez un récepteur de journaux qui redirige les journaux ingérés vers un sujet Pub/Sub.
  3. Créez un pipeline Dataflow qui modifie les journaux redirigés vers un sujet Pub/Sub en procédant comme suit:
    1. Lire une entrée de journal à partir du sujet Pub/Sub
    2. Inspecter la charge utile de l'entrée pour détecter les informations sensibles à l'aide de l'API d'inspection DLP
    3. Masquer les informations sensibles dans la charge utile à l'aide de l'une des méthodes de masquage DLP
    4. Écrire l'entrée de journal masquée dans Cloud Logging
  4. Déployer le pipeline

12. (Facultatif) Effectuer un nettoyage

Pour éviter que les ressources et les API utilisées dans l'atelier de programmation ne soient facturées sur votre compte, nous vous recommandons de nettoyer l'environnement une fois l'atelier terminé. Le moyen le plus simple d'empêcher la facturation est de supprimer le projet que vous avez créé pour l'atelier de programmation.

  1. Pour supprimer le projet, exécutez la commande de suppression du projet dans le terminal:
    PROJECT_ID=$(gcloud config get-value project)
    gcloud projects delete ${PROJECT_ID} --quiet
    
    La suppression de votre projet Cloud arrête la facturation de toutes les ressources et API utilisées dans ce projet. Le message suivant doit s'afficher, où PROJECT_ID correspond à l'ID de votre projet:
    Deleted [https://cloudresourcemanager.googleapis.com/v1/projects/PROJECT_ID].
    
    You can undo this operation for a limited period by running the command below.
        $ gcloud projects undelete PROJECT_ID
    
    See https://cloud.google.com/resource-manager/docs/creating-managing-projects for information on shutting down projects.
    
  2. (Facultatif) Si un message d'erreur s'affiche, consultez l'étape 5 pour trouver l'ID de projet que vous avez utilisé pendant l'atelier. Remplacez-la par la commande de la première instruction. Par exemple, si l'ID de votre projet est lab-example-project, la commande est la suivante:
    gcloud projects delete lab-project-id-example --quiet
    

13. Félicitations

Dans cet atelier, vous avez créé une application d'IA générative qui utilise le modèle Gemini pour effectuer des prédictions. et instrumenté l'application avec des fonctionnalités de surveillance et de journalisation de base. Vous avez déployé l'application et les modifications du code source vers Cloud Run. Vous utiliserez ensuite les produits Google Cloud Observability pour suivre les performances de l'application afin de vous assurer de sa fiabilité.

Si vous souhaitez participer à une étude de recherche sur l'expérience utilisateur (UX) afin d'améliorer les produits que vous avez utilisés aujourd'hui, inscrivez-vous ici.

Voici quelques options pour poursuivre votre apprentissage: