Técnicas prácticas de observabilidad para la aplicación de IA generativa en Go

1. Descripción general

Las aplicaciones de IA generativa requieren observabilidad como cualquier otra. ¿Se requieren técnicas de observabilidad especiales para la IA generativa?

En este lab, crearás una aplicación de IA generativa simple. Implementarlo en Cloud Run Además, instálalo con funciones de registro y supervisión esenciales mediante los productos y servicios de observabilidad de Google Cloud.

Qué aprenderás

  • Escribe una aplicación que use Vertex AI con el editor de Cloud Shell
  • Almacena el código de tu aplicación en GitHub
  • Usa gcloud CLI para implementar el código fuente de tu aplicación en Cloud Run
  • Agrega capacidades de supervisión y registro a tu aplicación de IA generativa
  • Usa métricas basadas en registros
  • Implementa el registro y la supervisión con el SDK de Open Telemetry
  • Obtén estadísticas sobre el manejo de datos de IA responsable

2. Requisitos previos

Si aún no tienes una Cuenta de Google, debes crear una nueva.

3. Configura el proyecto

  1. Accede a la consola de Google Cloud con tu Cuenta de Google.
  2. Crea un proyecto nuevo o elige reutilizar uno existente. Anota el ID del proyecto que acabas de crear o seleccionar.
  3. Habilita la facturación para el proyecto.
    • Completar este lab debería costar menos de USD 5 en costos de facturación.
    • Puedes seguir los pasos que se indican al final de este lab para borrar recursos y evitar cargos adicionales.
    • Los usuarios nuevos son aptos para la prueba gratuita de USD 300.
  4. Confirma que la facturación esté habilitada en Mis proyectos en Facturación de Cloud
    • Si tu proyecto nuevo dice Billing is disabled en la columna Billing account, haz lo siguiente:
      1. Haz clic en los tres puntos de la columna Actions.
      2. Haz clic en Cambiar la facturación.
      3. Selecciona la cuenta de facturación que quieres usar.
    • Si asistes a un evento en vivo, es probable que la cuenta se llame Cuenta de facturación de prueba de Google Cloud Platform.

4. Prepara el editor de Cloud Shell

  1. Navega al editor de Cloud Shell. Si se te muestra el siguiente mensaje en el que se solicita que autorices a Cloud Shell a llamar a gcloud con tus credenciales, haz clic en Autorizar para continuar.
    Haz clic para autorizar Cloud Shell
  2. Abre la ventana de la terminal
    1. Haz clic en el menú de opciones Ícono de menú de opciones.
    2. Haz clic en Terminal.
    3. Haz clic en Terminal nueva
      Cómo abrir una terminal nueva en el editor de Cloud Shell.
  3. En la terminal, configura el ID de tu proyecto:
    gcloud config set project [PROJECT_ID]
    
    Reemplaza [PROJECT_ID] por el ID de tu proyecto. Por ejemplo, si el ID de tu proyecto es lab-example-project, el comando será el siguiente:
    gcloud config set project lab-project-id-example
    
    Si aparece el siguiente mensaje que indica que gcloud solicita tus credenciales a la API de GCPI, haz clic en Autorizar para continuar.
    Haz clic para autorizar Cloud Shell
    Si la ejecución se realiza correctamente, deberías ver el siguiente mensaje:
    Updated property [core/project].
    
    Si ves un WARNING y se te pregunta Do you want to continue (Y/N)?, es probable que hayas ingresado el ID del proyecto de forma incorrecta. Presiona N, presiona Enter y, luego, intenta volver a ejecutar el comando gcloud config set project cuando encuentres el ID de proyecto correcto.
  4. (Opcional) Si tienes problemas para encontrar el ID del proyecto, ejecuta el siguiente comando para ver el ID de todos tus proyectos ordenados por hora de creación en orden descendente:
    gcloud projects list \
         --format='value(projectId,createTime)' \
         --sort-by=~createTime
    

5. Habilita las APIs de Google

En la terminal, habilita las APIs de Google que se requieren para este lab:

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

Este comando tardará un tiempo en completarse. Finalmente, se mostrará un mensaje de éxito similar a este:

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

Si recibes un mensaje de error que comienza con ERROR: (gcloud.services.enable) HttpError accessing y contiene detalles de error como los que se indican a continuación, vuelve a intentar el comando después de una demora de 1 a 2 minutos.

"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. Crea una aplicación de Go de IA generativa

En este paso, escribirás un código de la aplicación simple basada en solicitudes que usa el modelo de Gemini para mostrar 10 datos divertidos sobre un animal de tu elección. Sigue estos pasos para crear el código de la aplicación.

  1. En la terminal, crea el directorio codelab-o11y:
    mkdir ~/codelab-o11y
    
  2. Cambia el directorio actual a codelab-o11y:
    cd ~/codelab-o11y
    
  3. Inicializa los módulos de Go:
    go mod init codelab
    
  4. Instala el SDK de Vertex AI para Go:
    go get cloud.google.com/go/vertexai/genai
    
  5. Instala la biblioteca de metadatos para Go para obtener el ID del proyecto actual:
    go get cloud.google.com/go/compute/metadata
    
  6. Crea un archivo setup.go y ábrelo en el editor de Cloud Shell:
    cloudshell edit setup.go
    
    : Se usará para alojar el código de inicialización. Aparecerá un nuevo archivo vacío con el nombre setup.go en la ventana del editor.
  7. Copia el siguiente código y pégalo en el archivo setup.go abierto:
    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. Regresa a la ventana de la terminal y ejecuta el siguiente comando para crear y abrir un archivo main.go en el Editor de Cloud Shell:
    cloudshell edit main.go
    
    Ahora debería aparecer un archivo vacío en la ventana del editor sobre la terminal. Tu pantalla se verá de la siguiente manera:
    Cómo mostrar el editor de Cloud Shell después de comenzar a editar main.go
  9. Copia el siguiente código y pégalo en el archivo main.go abierto:
    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)
        }
    }
    
    Después de unos segundos, el editor de Cloud Shell guardará tu código automáticamente.

Implementa el código de la aplicación de IA generativa en Cloud Run

  1. En la ventana de la terminal, ejecuta el comando para implementar el código fuente de la aplicación en Cloud Run.
    gcloud run deploy codelab-o11y-service \
         --source="${HOME}/codelab-o11y/" \
         --region=us-central1 \
         --allow-unauthenticated
    
    Si ves el mensaje que aparece a continuación, se te informará que el comando creará un repositorio nuevo. Haz clic en 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)?
    
    El proceso de implementación puede tardar unos minutos. Una vez que se complete el proceso de implementación, verás un resultado como el siguiente:
    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. Copia la URL del servicio de Cloud Run que se muestra en una pestaña o ventana separada del navegador. Como alternativa, ejecuta el siguiente comando en la terminal para imprimir la URL del servicio y haz clic en la URL que se muestra mientras mantienes presionada la tecla Ctrl para abrirla:
    gcloud run services list \
         --format='value(URL)' \
         --filter='SERVICE:"codelab-o11y-service"'
    
    Cuando se abra la URL, es posible que recibas un error 500 o veas el siguiente mensaje:
    Sorry, this is just a placeholder...
    
    Significa que los servicios no terminaron su implementación. Espera unos momentos y actualiza la página. Al final, verás un texto que comienza con Datos curiosos sobre perros y contiene 10 datos curiosos sobre perros.

Intenta interactuar con la aplicación para obtener datos interesantes sobre diferentes animales. Para hacerlo, agrega el parámetro animal a la URL, como ?animal=[ANIMAL], en la que [ANIMAL] es el nombre de un animal. Por ejemplo, agrega ?animal=cat para obtener 10 datos curiosos sobre los gatos o ?animal=sea turtle para obtener 10 datos curiosos sobre las tortugas marinas.

7. Cómo auditar tus llamadas a la API de Vertex

La auditoría de las llamadas a la API de Google proporciona respuestas a preguntas como "¿Quién llama a una API en particular, dónde y cuándo?". La auditoría es importante cuando solucionas problemas en tu aplicación, investigas el consumo de recursos o realizas un análisis forense de software.

Los registros de auditoría te permiten hacer un seguimiento de las actividades administrativas y del sistema, así como registrar llamadas a operaciones de API de “lectura de datos” y “escritura de datos”. Para auditar las solicitudes de Vertex AI para generar contenido, debes habilitar los registros de auditoría de "Lectura de datos" en la consola de Cloud.

  1. Haz clic en el siguiente botón para abrir la página Registros de auditoría en la consola de Cloud.

  2. Asegúrate de que la página tenga seleccionado el proyecto que creaste para este lab. El proyecto seleccionado se muestra en la esquina superior izquierda de la página, justo en el menú de opciones:
    Menú desplegable del proyecto de la consola de Google Cloud
    Si es necesario, selecciona el proyecto correcto en el cuadro combinado.
  3. En la tabla Configuración de los registros de auditoría de acceso a los datos, en la columna Servicio, busca el servicio Vertex AI API y selecciónalo marcando la casilla de verificación que se encuentra a la izquierda del nombre del servicio.
    Selecciona la API de Vertex AI.
  4. En el panel de información de la derecha, selecciona el tipo de auditoría "Lectura de datos".
    Verifica los registros de lectura de datos
  5. Haz clic en Guardar.

Para generar registros de auditoría, abre la URL del servicio. Actualiza la página mientras cambias el valor del parámetro ?animal= para obtener resultados diferentes.

Explora los registros de auditoría

  1. Haz clic en el siguiente botón para abrir la página Explorador de registros en la consola de Cloud:

  2. Pega el siguiente filtro en el panel Consulta.
    LOG_ID("cloudaudit.googleapis.com%2Fdata_access") AND
    protoPayload.serviceName="aiplatform.googleapis.com"
    
    El panel Consulta es un editor que se encuentra cerca de la parte superior de la página Explorador de registros:
    Consulta los registros de auditoría
  3. Haz clic en Ejecutar consulta.
  4. Selecciona una de las entradas del registro de auditoría y expande los campos para inspeccionar la información capturada en el registro.
    Puedes ver detalles sobre la llamada a la API de Vertex, incluido el método y el modelo que se usó. También puedes ver la identidad del llamador y los permisos que autorizaron la llamada.

8. Registra las interacciones con la IA generativa

No encontrarás parámetros de solicitud ni datos de respuesta de la API en los registros de auditoría. Sin embargo, esta información puede ser importante para solucionar problemas de análisis de aplicaciones y flujos de trabajo. En este paso, completamos esta brecha agregando el registro de la aplicación. El registro usa el paquete estándar log/slog de Go para escribir registros estructurados. El paquete log/slog no sabe escribir registros en Google Cloud. Admite la escritura en la salida estándar. Sin embargo, Cloud Run cuenta con funciones que capturan información impresa en el resultado estándar y la transfieren a Cloud Logging automáticamente. Para capturar correctamente los registros estructurados, el registro impreso debe tener el formato adecuado. Sigue las instrucciones que se indican a continuación para agregar capacidades de registro estructurado a nuestra aplicación Go.

  1. Regresa a la ventana (o pestaña) de "Cloud Shell" en tu navegador.
  2. En la terminal, vuelve a abrir setup.go:
    cloudshell edit ~/codelab-o11y/setup.go
    
  3. Reemplaza el código por la versión que configura el registro. Para reemplazar el código, borra el contenido del archivo y, luego, copia el siguiente código y pégalo en el editor:
    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. Regresa a la terminal y vuelve a abrir main.go:
    cloudshell edit ~/codelab-o11y/main.go
    
  5. Reemplaza el código de la aplicación por la versión que registra la interacción con el modelo. Para reemplazar el código, borra el contenido del archivo y, luego, copia el siguiente código y pégalo en el editor:
    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)
        }
    }
    

El registro se configura para imprimir registros en stdout, donde el agente de registro de Cloud Run los recopila y transfiere de forma asíncrona a Cloud Logging. La función main() se modifica para configurar el registro estructurado estándar de Go para usar el esquema JSON que sigue los lineamientos de formato estructurado. Todas sus sentencias return se reemplazan por el código que escribe registros de errores antes de salir. La función Handler() se instrumenta para escribir un registro estructurado cuando recibe la respuesta de la llamada a la API de Vertex AI. El registro captura el parámetro de animal de la solicitud y la instrucción y la respuesta del modelo.

Después de unos segundos, el editor de Cloud Shell guarda los cambios automáticamente.

Implementa el código de la aplicación de IA generativa en Cloud Run

  1. En la ventana de la terminal, ejecuta el comando para implementar el código fuente de la aplicación en Cloud Run.
    gcloud run deploy codelab-o11y-service \
         --source="${HOME}/codelab-o11y/" \
         --region=us-central1 \
         --allow-unauthenticated
    
    Si ves el mensaje que aparece a continuación, se te informará que el comando creará un repositorio nuevo. Haz clic en 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)?
    
    El proceso de implementación puede tardar unos minutos. Una vez que se complete el proceso de implementación, verás un resultado como el siguiente:
    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. Copia la URL del servicio de Cloud Run que se muestra en una pestaña o ventana separada del navegador. Como alternativa, ejecuta el siguiente comando en la terminal para imprimir la URL del servicio y haz clic en la URL que se muestra mientras mantienes presionada la tecla Ctrl para abrirla:
    gcloud run services list \
         --format='value(URL)' \
         --filter='SERVICE:"codelab-o11y-service"'
    
    Cuando se abra la URL, es posible que recibas un error 500 o veas el siguiente mensaje:
    Sorry, this is just a placeholder...
    
    Significa que los servicios no terminaron su implementación. Espera unos momentos y actualiza la página. Al final, verás un texto que comienza con Datos curiosos sobre perros y contiene 10 datos curiosos sobre perros.

Para generar registros de la aplicación, abre la URL del servicio. Actualiza la página mientras cambias el valor del parámetro ?animal= para obtener resultados diferentes.
Para ver los registros de la aplicación, haz lo siguiente:

  1. Haz clic en el siguiente botón para abrir la página del Explorador de registros en la consola de Cloud:

  2. Pega el siguiente filtro en el panel Consulta (#2 en la interfaz del Explorador de registros):
    LOG_ID("run.googleapis.com%2Fstdout") AND
    severity=DEBUG
    
  3. Haz clic en Ejecutar consulta.

El resultado de la consulta muestra registros con la instrucción y la respuesta de Vertex AI, incluidas las calificaciones de seguridad.

9. Cuenta las interacciones con la IA generativa

Cloud Run escribe métricas administradas que se pueden usar para supervisar los servicios implementados. Las métricas de supervisión administradas por el usuario proporcionan más control sobre los datos y la frecuencia de actualización de las métricas. Para implementar esa métrica, debes escribir un código que recopile datos y los escriba en Cloud Monitoring. Consulta el siguiente paso (opcional) para saber cómo implementarlo con el SDK de OpenTelemetry.

En este paso, se muestra una alternativa para implementar la métrica del usuario en el código: las métricas basadas en registros. Las métricas basadas en registros te permiten generar métricas de supervisión a partir de las entradas de registro que tu aplicación escribe en Cloud Logging. Usaremos los registros de la aplicación que implementamos en el paso anterior para definir una métrica basada en registros del contador de tipo. La métrica contará la cantidad de llamadas correctas a la API de Vertex.

  1. Observa la ventana del Explorador de registros que usamos en el paso anterior. En el panel Búsqueda, busca el menú desplegable Acciones y haz clic en él para abrirlo. Consulta la captura de pantalla que aparece a continuación para encontrar el menú:
    Barra de herramientas de resultados de la consulta con el menú desplegable Acciones
  2. En el menú que se abre, selecciona Crear métrica para abrir el panel Crear métrica basada en registros.
  3. Sigue estos pasos para configurar una nueva métrica de contador en el panel Crear métrica basada en registros:
    1. Para el Tipo de métrica, selecciona Contador.
    2. Configura los siguientes campos en la sección Detalles:
      • Nombre de la métrica de registro: Establece el nombre como model_interaction_count. Se aplican algunas restricciones de denominación. Para obtener más detalles, consulta Solución de problemas.
      • Descripción: ingresa una descripción para la métrica. Por ejemplo, Number of log entries capturing successful call to model inference.
      • Unidades: Deja este campo en blanco o inserta el número 1.
    3. Deja los valores en la sección Selección de filtro. Ten en cuenta que el campo Build filter tiene el mismo filtro que usamos para ver los registros de la aplicación.
    4. (Opcional) Agrega una etiqueta que ayude a registrar la cantidad de llamadas de cada animal. NOTA: Esta etiqueta tiene el potencial de aumentar en gran medida la cantidad de la métrica y no se recomienda su uso en producción:
      1. Haz clic en Agregar etiqueta.
      2. Configura los siguientes campos en la sección Etiquetas:
        • Nombre de la etiqueta: Establece el nombre como animal.
        • Descripción: Ingresa la descripción de la etiqueta. Por ejemplo, Animal parameter
        • Tipo de etiqueta: Selecciona STRING.
        • Nombre del campo: Escribe jsonPayload.animal.
        • Expresión regular: Déjalo en blanco.
      3. Haga clic en Listo
    5. Haz clic en Crear métrica a fin de crear la métrica.

También puedes crear una métrica basada en registros desde la página Métricas basadas en registros con el comando de CLI gcloud logging metrics create o con el recurso de Terraform google_logging_metric.

Para generar datos de métricas, abre la URL del servicio. Actualiza la página abierta varias veces para realizar varias llamadas al modelo. Al igual que antes, intenta usar diferentes animales en el parámetro.

Ingresa la consulta de PromQL para buscar los datos de métricas basados en registros. Para ingresar una consulta de PromQL, haz lo siguiente:

  1. Haz clic en el siguiente botón para abrir la página Explorador de métricas en la consola de Cloud:

  2. En la barra de herramientas del panel del compilador de consultas, selecciona el botón cuyo nombre sea < > MQL o < > PromQL. Consulta la siguiente imagen para ver la ubicación del botón.
    Ubicación del botón MQL en el Explorador de métricas
  3. Verifica que PromQL esté seleccionado en el botón de activación Lenguaje. El botón de activación de lenguaje se encuentra en la misma barra de herramientas que te permite dar formato a tu consulta.
  4. Ingresa tu consulta en el editor Consultas:
    sum(rate(logging_googleapis_com:user_model_interaction_count{monitored_resource="cloud_run_revision"}[${__interval}]))
    
    Para obtener más información sobre el uso de PromQL, consulta PromQL en Cloud Monitoring.
  5. Haga clic en Ejecutar consulta. Verás un gráfico de líneas similar a esta captura de pantalla:
    Cómo mostrar las métricas consultadas

    Ten en cuenta que, cuando el botón de activación Ejecutar automáticamente está habilitado, no se muestra el botón Ejecutar consulta.

10. Usa Open Telemetry para la supervisión y el seguimiento (opcional)

Como se mencionó en el paso anterior, es posible implementar métricas con el SDK de OpenTelemetry (Otel). Se recomienda usar OTel en arquitecturas de microservicios. En este paso, se describe lo siguiente:

  • Cómo inicializar componentes de OTel para admitir el seguimiento y la supervisión de la aplicación
  • Cómo propagar la configuración de OTel con metadatos de recursos del entorno de Cloud Run
  • Instrumenta la aplicación de Flask con capacidades de seguimiento automático
  • Implementa una métrica de contador para supervisar una cantidad de llamadas de modelo exitosas.
  • Correlaciona el seguimiento con los registros de la aplicación

La arquitectura recomendada para los servicios a nivel del producto es usar el colector de OTel para recopilar y transferir todos los datos de observabilidad de uno o más servicios. El código de este paso no usa el recopilador para simplificar. En su lugar, usa exportaciones de OTel que escriben datos directamente en Google Cloud.

Configura componentes de OTel para el seguimiento y la supervisión de métricas

  1. Regresa a la ventana (o pestaña) de "Cloud Shell" en tu navegador.
  2. En la terminal, vuelve a abrir setup.go:
    cloudshell edit ~/codelab-o11y/setup.go
    
  3. Reemplaza el código por la versión que inicializa el seguimiento y la recopilación de métricas de OpenTelemetry. Para reemplazar el código, borra el contenido del archivo y, luego, copia el siguiente código y pégalo en el editor:
    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. Regresa a la terminal y ejecuta el siguiente comando para actualizar las definiciones de módulos de Go en el archivo go.mod:
    go mod tidy
    
  5. Regresa a la terminal y vuelve a abrir main.go:
    cloudshell edit ~/codelab-o11y/main.go
    
  6. Reemplaza el código actual por la versión que instrumenta el seguimiento de HTTP y escribe la métrica de rendimiento. Para reemplazar el código, borra el contenido del archivo y, luego, copia el siguiente código y pégalo en el editor:
    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)
        }
    }
    

La aplicación ahora usa el SDK de OpenTelemetry para instrumentar la ejecución de código con el seguimiento y para implementar el recuento de una cantidad de ejecuciones correctas como una métrica. Se modifica el método main() para configurar los exportadores de OpenTelemetry para que los seguimientos y las métricas escriban directamente en Google Cloud Tracing y Monitoring. También realiza configuraciones adicionales para propagar los seguimientos y las métricas recopilados con metadatos relacionados con el entorno de Cloud Run. La función Handler() se actualiza para incrementar el contador de métricas cada vez que la llamada a la API de Vertex AI muestra resultados válidos.

Después de unos segundos, el editor de Cloud Shell guarda los cambios automáticamente.

Implementa el código de la aplicación de IA generativa en Cloud Run

  1. En la ventana de la terminal, ejecuta el comando para implementar el código fuente de la aplicación en Cloud Run.
    gcloud run deploy codelab-o11y-service \
         --source="${HOME}/codelab-o11y/" \
         --region=us-central1 \
         --allow-unauthenticated
    
    Si ves el mensaje que aparece a continuación, se te informará que el comando creará un repositorio nuevo. Haz clic en 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)?
    
    El proceso de implementación puede tardar unos minutos. Una vez que se complete el proceso de implementación, verás un resultado como el siguiente:
    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. Copia la URL del servicio de Cloud Run que se muestra en una pestaña o ventana separada del navegador. Como alternativa, ejecuta el siguiente comando en la terminal para imprimir la URL del servicio y haz clic en la URL que se muestra mientras mantienes presionada la tecla Ctrl para abrirla:
    gcloud run services list \
         --format='value(URL)' \
         --filter='SERVICE:"codelab-o11y-service"'
    
    Cuando se abra la URL, es posible que recibas un error 500 o veas el siguiente mensaje:
    Sorry, this is just a placeholder...
    
    Significa que los servicios no terminaron su implementación. Espera unos momentos y actualiza la página. Al final, verás un texto que comienza con Datos curiosos sobre perros y contiene 10 datos curiosos sobre perros.

Para generar datos de telemetría, abre la URL del servicio. Actualiza la página mientras cambias el valor del parámetro ?animal= para obtener resultados diferentes.

Explora los seguimientos de la aplicación

  1. Haz clic en el siguiente botón para abrir la página del explorador de seguimiento en la consola de Cloud:

  2. Selecciona uno de los registros más recientes. Deberías ver 5 o 6 tramos que se ven como en la siguiente captura de pantalla.
    Vista del intervalo de la app en el explorador de seguimiento
  3. Busca el intervalo que registra la llamada al controlador de eventos (el método fun_facts). Será el último intervalo con el nombre /.
  4. En el panel Detalles de seguimiento, selecciona Registros y eventos. Verás los registros de la aplicación que se correlacionan con este intervalo en particular. La correlación se detecta con los IDs de seguimiento y de intervalo en el seguimiento y en el registro. Deberías ver el registro de la aplicación que escribió la instrucción y la respuesta de la API de Vertex.

Explora la métrica de contador

  1. Haz clic en el siguiente botón para abrir la página Explorador de métricas en la consola de Cloud:

  2. En la barra de herramientas del panel del compilador de consultas, selecciona el botón cuyo nombre sea < > MQL o < > PromQL. Consulta la siguiente imagen para ver la ubicación del botón.
    Ubicación del botón MQL en el Explorador de métricas
  3. Verifica que PromQL esté seleccionado en el botón de activación Lenguaje. El botón de activación de lenguaje se encuentra en la misma barra de herramientas que te permite dar formato a tu consulta.
  4. Ingresa tu consulta en el editor Consultas:
    sum(rate(workload_googleapis_com:model_call_counter{monitored_resource="generic_task"}[${__interval}]))
    
  5. Haz clic en Ejecutar consulta.Cuando el botón de activación Ejecutar automáticamente está habilitado, no se muestra el botón Ejecutar consulta.

11. Información sensible ofuscada de los registros (opcional)

En el paso 10, registramos información sobre la interacción de la aplicación con el modelo de Gemini. Esta información incluía el nombre del animal, la instrucción real y la respuesta del modelo. Si bien almacenar esta información en el registro debería ser seguro, no es necesariamente cierto en muchos otros casos. La instrucción puede incluir información personal o sensible que el usuario no desea que se almacene. Para abordar este problema, puedes ofuscar los datos sensibles que se escriben en Cloud Logging. Para minimizar las modificaciones de código, se recomienda la siguiente solución.

  1. Crea un tema de Pub/Sub para almacenar las entradas de registro entrantes
  2. Crea un receptor de registros que redireccione los registros transferidos al tema de Pub/Sub.
  3. Crea una canalización de Dataflow que modifique los registros redireccionados al tema de Pub/Sub siguiendo estos pasos:
    1. Lee una entrada de registro del tema de Pub/Sub
    2. Inspecciona la carga útil de la entrada en busca de información sensible con la API de inspección de DLP
    3. Oculta la información sensible en la carga útil con uno de los métodos de ocultación de DLP.
    4. Escribe la entrada de registro ofuscada en Cloud Logging
  4. Implementa la canalización

12. (Opcional) Limpieza

Para evitar el riesgo de incurrir en cargos por los recursos y las APIs que se usaron en el codelab, se recomienda realizar la limpieza después de terminar el lab. La manera más fácil de eliminar la facturación es borrar el proyecto que creaste para el codelab.

  1. Para borrar el proyecto, ejecuta el comando delete project en la terminal:
    PROJECT_ID=$(gcloud config get-value project)
    gcloud projects delete ${PROJECT_ID} --quiet
    
    Si borras tu proyecto de Cloud, se detendrá la facturación de todos los recursos y las APIs que se usen en ese proyecto. Deberías ver este mensaje, en el que PROJECT_ID será el ID de tu proyecto:
    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. (Opcional) Si recibes un error, consulta el paso 5 para encontrar el ID del proyecto que usaste durante el lab. Reemplázalo por el comando en la primera instrucción. Por ejemplo, si el ID de tu proyecto es lab-example-project, el comando será el siguiente:
    gcloud projects delete lab-project-id-example --quiet
    

13. Felicitaciones

En este lab, creaste una aplicación de IA generativa que usa el modelo de Gemini para hacer predicciones. Y también instrumentamos la aplicación con capacidades esenciales de supervisión y registro. Implementaste la aplicación y los cambios del código fuente en Cloud Run. Luego, usarás los productos de Google Cloud Observability para hacer un seguimiento del rendimiento de la aplicación, de modo que puedas asegurarte de su confiabilidad.

Si te interesa participar en un estudio de investigación de experiencia del usuario (UX) para mejorar los productos con los que trabajaste hoy, regístrate aquí.

Estas son algunas opciones para continuar con tu aprendizaje: