Praktyczne techniki obserwowalności w przypadku aplikacji generatywnej AI w języku Go

1. Omówienie

Aplikacje korzystające z generatywnej AI wymagają możliwości obserwacji jak każda inna aplikacja. Czy w przypadku generatywnej AI wymagane są specjalne techniki obserwowalności?

W tym laboratorium utworzysz prostą aplikację wykorzystującą generatywną AI. Wdróż go w Cloud Run. Dodaj do niego niezbędne funkcje monitorowania i logowania, korzystając z usług i produktów dostrzegalności Google Cloud.

Czego się nauczysz

  • Dodawanie funkcji monitorowania i logowania do aplikacji z generatywną AI
  • Korzystanie ze wskaźników opartych na logach
  • Implementacja logowania i monitorowania za pomocą pakietu Open Telemetry SDK
  • Informacje o odpowiedzialnym przetwarzaniu danych przez AI

2. Wymagania wstępne

Jeśli nie masz jeszcze konta Google, utwórz nowe konto.

3. Konfigurowanie projektu

  1. Zaloguj się w konsoli Google Cloud za pomocą konta Google.
  2. Utwórz nowy projekt lub użyj istniejącego. Zapisz identyfikator projektu, który został właśnie utworzony lub wybrany.
  3. Włącz płatności w projekcie.
  4. Sprawdź, czy płatności są włączone w sekcji Moje projekty w Rozliczeniach usługi Google Cloud.
    • Jeśli w kolumnie Billing account nowego projektu widać wartość Billing is disabled:
      1. Kliknij 3 kropki w kolumnie Actions.
      2. Kliknij Zmień rozliczenia.
      3. Wybierz konto rozliczeniowe, którego chcesz użyć.
    • Jeśli uczestniczysz w wydarzeniu na żywo, konto będzie prawdopodobnie nazywać się Próbne konto rozliczeniowe Google Cloud Platform.

4. Przygotowanie edytora Cloud Shell

  1. Otwórz edytor Cloud Shell. Jeśli pojawi się to pytanie o autoryzację Cloud Shell do wywoływania gcloud za pomocą Twoich danych logowania, kliknij Autoryzuj, aby kontynuować.
    Kliknij, aby autoryzować Cloud Shell
  2. Otwórz okno terminala
    1. Kliknij menu z 3 kreskami Ikona menu z 3 kreskami
    2. Kliknij Terminal.
    3. Kliknij Nowy terminal
      Otwieranie nowego terminala w edytorze Cloud Shell.
  3. W terminalu skonfiguruj identyfikator projektu:
    gcloud config set project [PROJECT_ID]
    
    Zastąp [PROJECT_ID] identyfikatorem projektu. Jeśli np. identyfikator projektu to lab-example-project, polecenie będzie wyglądać tak:
    gcloud config set project lab-project-id-example
    
    Jeśli pojawi się komunikat, że gcloud prosi o Twoje dane logowania do interfejsu GCPI API, kliknij Autoryzuj, aby kontynuować.
    Kliknij, aby autoryzować Cloud Shell
    Po pomyślnym wykonaniu tej czynności powinien wyświetlić się komunikat:
    Updated property [core/project].
    
    Jeśli widzisz WARNING i pojawia się pytanie Do you want to continue (Y/N)?, prawdopodobnie nieprawidłowo wpisano identyfikator projektu. Gdy znajdziesz prawidłowy identyfikator projektu, naciśnij N, a potem Enter i spróbuj ponownie uruchomić polecenie gcloud config set project.
  4. (Opcjonalnie) Jeśli masz problem ze znalezieniem identyfikatora projektu, uruchom to polecenie, aby wyświetlić identyfikatory wszystkich swoich projektów posortowane według czasu utworzenia w kolejności malejącej:
    gcloud projects list \
         --format='value(projectId,createTime)' \
         --sort-by=~createTime
    

5. Włączanie interfejsów API Google

W terminalu włącz interfejsy Google API wymagane w tym laboratorium:

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

Wykonanie tego polecenia może trochę potrwać. W efekcie wyświetli się komunikat o udanym przeprowadzeniu operacji podobny do tego:

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

Jeśli pojawi się komunikat o błędzie rozpoczynający się od ERROR: (gcloud.services.enable) HttpError accessing i zawierający informacje o błędzie podobne do tych poniżej, powtórz polecenie po 1–2 minutach.

"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. Tworzenie aplikacji generatywnej AI w Go

W tym kroku napiszesz kod prostej aplikacji działającej na podstawie żądań, która korzysta z modelu Gemini, aby wyświetlać 10 ciekawych faktów o wybranym przez Ciebie zwierzęciu. Aby utworzyć kod aplikacji, wykonaj te czynności.

  1. Utwórz w terminalu katalog codelab-o11y:
    mkdir ~/codelab-o11y
    
  2. Zmień bieżący katalog na codelab-o11y:
    cd ~/codelab-o11y
    
  3. Inicjowanie modułów Go:
    go mod init codelab
    
  4. Zainstaluj pakiet Vertex AI SDK for Go:
    go get cloud.google.com/go/vertexai/genai
    
  5. Zainstaluj bibliotekę metadanych dla Go, aby uzyskać bieżący identyfikator projektu:
    go get cloud.google.com/go/compute/metadata
    
  6. Utwórz plik setup.go i otwórz go w edytorze Cloud Shell:
    cloudshell edit setup.go
    
    Będzie on używany do hostowania kodu inicjowania. W oknie edytora pojawi się nowy pusty plik o nazwie setup.go.
  7. Skopiuj ten kod i wklej go do otwartego pliku setup.go:
    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. Wróć do okna terminala i uruchom to polecenie, aby utworzyć i otworzyć plik main.go w edytorze Cloud Shell:
    cloudshell edit main.go
    
    W oknie edytora nad terminalem powinien pojawić się pusty plik. Ekran powinien wyglądać tak:
    Wyświetlanie edytora Cloud Shell po rozpoczęciu edytowania pliku main.go
  9. Skopiuj ten kod i wklej go do otwartego pliku main.go:
    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)
        }
    }
    
    Po kilku sekundach edytor Cloud Shell automatycznie zapisze kod.

Wdrażanie kodu aplikacji generatywnej AI w Cloud Run

  1. W oknie terminala uruchom polecenie, aby wdrożyć kod źródłowy aplikacji do Cloud Run.
    gcloud run deploy codelab-o11y-service \
         --source="${HOME}/codelab-o11y/" \
         --region=us-central1 \
         --allow-unauthenticated
    
    Jeśli zobaczysz taki komunikat, oznacza to, że polecenie utworzy nowe repozytorium. Kliknij 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)?
    
    Proces wdrażania może potrwać kilka minut. Po zakończeniu procesu wdrażania zobaczysz dane wyjściowe podobne do tych:
    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. Skopiuj wyświetlony adres URL usługi Cloud Run na osobną kartę lub do osobnego okna w przeglądarce. Możesz też uruchomić to polecenie w terminalu, aby wydrukować adres URL usługi, a potem kliknąć wyświetlony adres URL, przytrzymując klawisz Ctrl, aby go otworzyć:
    gcloud run services list \
         --format='value(URL)' \
         --filter='SERVICE:"codelab-o11y-service"'
    
    Po otwarciu adresu URL może pojawić się błąd 500 lub komunikat:
    Sorry, this is just a placeholder...
    
    Oznacza to, że usługi nie zostały wdrożone. Zaczekaj chwilę i odśwież stronę. Na końcu zobaczysz tekst zaczynający się od słów Ciekawostki o psach i zawierający 10 ciekawostek o tych zwierzętach.

Spróbuj wejść w interakcję z aplikacją, aby dowiedzieć się ciekawych faktów o różnych zwierzętach. Aby to zrobić, dodaj parametr animal do adresu URL, np. ?animal=[ANIMAL], gdzie [ANIMAL] to nazwa zwierzęcia. Na przykład dodaj ?animal=cat, aby uzyskać 10 ciekawostek o kotach, lub ?animal=sea turtle, aby uzyskać 10 ciekawostek o żółwiach morskich.

7. Sprawdzanie wywołań interfejsu Vertex API

Audyt wywołań interfejsu Google API pozwala uzyskać odpowiedzi na pytania w rodzaju „kto, gdzie i kiedy wywołał dany interfejs API?”. Weryfikacja jest ważna podczas rozwiązywania problemów z aplikacją, badania wykorzystania zasobów lub przeprowadzania analizy kryminalistycznej oprogramowania.

Logi kontrolne umożliwiają śledzenie działań administracyjnych i systemowych, a także rejestrowanie wywołań operacji interfejsu API „odczytywanie danych” i „zapisywanie danych”. Aby sprawdzić żądania Vertex AI dotyczące generowania treści, musisz włączyć logi kontrolne „Dane odczytywane” w konsoli Cloud.

  1. Kliknij przycisk poniżej, aby otworzyć stronę Dzienniki kontrolne w konsoli Google Cloud.

  2. Sprawdź, czy na stronie wybrany jest projekt utworzony na potrzeby tego laboratorium. Wybrany projekt jest widoczny w lewym górnym rogu strony obok menu hamburgera:
    Menu projektu w konsoli Google Cloud
    W razie potrzeby wybierz odpowiedni projekt z menu.
  3. W tabeli Konfiguracja logów kontrolnych dostępu do danych w kolumnie Usługa znajdź usługę Vertex AI API i kliknij pole wyboru po lewej stronie jej nazwy.
    Wybierz interfejs Vertex AI API
  4. W panelu informacyjnym po prawej stronie wybierz typ audytu „Odczyt danych”.
    Sprawdzanie dzienników odczytu danych
  5. Kliknij Zapisz.

Aby wygenerować dzienniki kontrolne, otwórz adres URL usługi. Aby uzyskać inne wyniki, odśwież stronę, zmieniając wartość parametru ?animal=.

Poznawanie dzienników kontrolnych

  1. Kliknij przycisk poniżej, aby otworzyć stronę Eksplorator logów w konsoli Google Cloud:

  2. Wklej ten filtr w panelu Zapytanie.
    LOG_ID("cloudaudit.googleapis.com%2Fdata_access") AND
    protoPayload.serviceName="aiplatform.googleapis.com"
    
    Panel Zapytanie to edytor znajdujący się u góry strony Eksplorator logów:
    Zapytania do dzienników kontrolnych
  3. Kliknij Uruchom zapytanie.
  4. Wybierz jeden z wpisów w dzienniku kontrolnym i rozwiń pola, aby sprawdzić informacje zarejestrowane w dzienniku.
    Możesz zobaczyć szczegóły wywołania interfejsu Vertex API, w tym użytą metodę i model. Możesz też zobaczyć tożsamość wywołującego i uprawnienia autoryzujące wywołanie.

8. Rejestrowanie interakcji z generatywną AI

W dziennikach kontrolnych nie ma parametrów żądania ani danych odpowiedzi interfejsu API. Te informacje mogą jednak być ważne w przypadku problemów z analizą aplikacji i przepływu pracy. W tym kroku wypełniamy tę lukę, dodając rejestrowanie aplikacji. Do zapisywania ustrukturyzowanych logów używany jest standardowy pakiet Go log/slog. Pakiet log/slog nie wie, jak zapisać logi w Google Cloud. Obsługuje zapisywanie na standardowe wyjście. Cloud Run zawiera jednak funkcje, które umożliwiają automatyczne przechwytywanie informacji drukowanych na standardowym wyjściu i przetwarzanie ich w Cloud Logging. Aby poprawnie rejestrować dzienniki uporządkowane, należy odpowiednio sformatować wydrukowany dziennik. Aby dodać do aplikacji Go obsługę uporządkowanego logowania, wykonaj podane niżej instrukcje.

  1. Wróć do okna (lub karty) Cloud Shell w przeglądarce.
  2. W terminalu ponownie otwórz setup.go:
    cloudshell edit ~/codelab-o11y/setup.go
    
  3. Zastąp kod wersją, która konfiguruje rejestrowanie. Aby zastąpić kod, usuń zawartość pliku, a potem skopiuj i wklej podany niżej kod do edytora:
    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. Wróć do terminala i ponownie otwórz main.go:
    cloudshell edit ~/codelab-o11y/main.go
    
  5. Zastąp kod aplikacji wersją, która rejestruje interakcje z modelem. Aby zastąpić kod, usuń zawartość pliku, a potem skopiuj i wklej podany niżej kod do edytora:
    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)
        }
    }
    

Logowanie jest skonfigurowane tak, aby drukować logi do pliku stdout, gdzie są one zbierane przez agenta logowania Cloud Run i asynchronicznie przesyłane do Cloud Logging. Funkcja main() została zmodyfikowana, aby skonfigurować standardowy log strukturyzowany Go do korzystania ze schematu JSON zgodnego z wytycznymi dotyczącymi formatowania strukturyzowanego. Wszystkie instrukcje return są zastępowane przez kod, który przed zakończeniem działania programu zapisuje dzienniki błędów. Funkcja Handler() jest instrumentowana w celu zapisywania ustrukturyzowanego dziennika po otrzymaniu odpowiedzi z wywołania interfejsu Vertex AI API. Log zawiera parametr zwierzęcia z żądania oraz prompt i odpowiedź modelu.

Po kilku sekundach edytor Cloud Shell automatycznie zapisuje zmiany.

Wdrażanie kodu aplikacji generatywnej AI w Cloud Run

  1. W oknie terminala uruchom polecenie, aby wdrożyć kod źródłowy aplikacji do Cloud Run.
    gcloud run deploy codelab-o11y-service \
         --source="${HOME}/codelab-o11y/" \
         --region=us-central1 \
         --allow-unauthenticated
    
    Jeśli zobaczysz taki komunikat, oznacza to, że polecenie utworzy nowe repozytorium. Kliknij 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)?
    
    Proces wdrażania może potrwać kilka minut. Po zakończeniu procesu wdrażania zobaczysz dane wyjściowe podobne do tych:
    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. Skopiuj wyświetlony adres URL usługi Cloud Run na osobną kartę lub do osobnego okna w przeglądarce. Możesz też uruchomić to polecenie w terminalu, aby wydrukować adres URL usługi, a potem kliknąć wyświetlony adres URL, przytrzymując klawisz Ctrl, aby go otworzyć:
    gcloud run services list \
         --format='value(URL)' \
         --filter='SERVICE:"codelab-o11y-service"'
    
    Po otwarciu adresu URL może pojawić się błąd 500 lub komunikat:
    Sorry, this is just a placeholder...
    
    Oznacza to, że usługi nie zostały wdrożone. Zaczekaj chwilę i odśwież stronę. Na końcu zobaczysz tekst zaczynający się od słów Ciekawostki o psach i zawierający 10 ciekawostek o tych zwierzętach.

Aby wygenerować dzienniki aplikacji, otwórz adres URL usługi. Aby uzyskać inne wyniki, odśwież stronę, zmieniając wartość parametru ?animal=.
Aby wyświetlić logi aplikacji:

  1. Kliknij przycisk poniżej, aby otworzyć stronę Eksplorator logów w konsoli Cloud:

  2. Wklej ten filtr w panelu Zapytanie (pozycja 2 w interfejsie Eksploratora logów):
    LOG_ID("run.googleapis.com%2Fstdout") AND
    severity=DEBUG
    
  3. Kliknij Uruchom zapytanie.

Wynik zapytania zawiera logi z promptem i odpowiedzią Vertex AI, w tym oceny bezpieczeństwa.

9. Liczenie interakcji z generatywną AI

Cloud Run zapisuje dane zarządzane, których można używać do monitorowania wdrożonych usług. Dane monitorowania zarządzane przez użytkowników zapewniają większą kontrolę nad danymi i częstotliwością ich aktualizacji. Wdrożenie takiej metryki wymaga napisania kodu, który będzie zbierać dane i zapisywać je w Cloud Monitoring. Aby dowiedzieć się, jak zaimplementować tę funkcję za pomocą pakietu SDK OpenTelemetry, przejdź do następnego (opcjonalnego) kroku.

Ten krok pokazuje alternatywę dla implementowania danych o użytkownikach w kodzie – dane oparte na logach. Wskaźniki oparte na logach umożliwiają generowanie danych monitorowania na podstawie wpisów logów zapisywanych przez aplikację w Cloud Logging. Do zdefiniowania danych typu licznik na podstawie dzienników aplikacji użyjemy dzienników aplikacji zaimplementowanych w poprzednim kroku. Dane zliczają liczbę udanych wywołań interfejsu Vertex API.

  1. Spójrz na okno Eksploratora logów, którego użyliśmy w poprzednim kroku. W panelu Zapytanie odszukaj menu Działania i kliknij je, aby je otworzyć. Menu znajdziesz na zrzucie ekranu poniżej:
    Pasek narzędzi wyników zapytania z menu Działania
  2. W otwartym menu kliknij Utwórz wskaźnik, aby otworzyć panel Utwórz wskaźnik oparty na logach.
  3. Aby skonfigurować nowy licznik w panelu Tworzenie danych opartych na logach:
    1. Ustaw Typ wskaźnika: wybierz Licznik.
    2. W sekcji Szczegóły ustaw te pola:
    3. Pozostaw wartości w sekcji Wybór filtra. Pamiętaj, że pole Filtrowanie kompilacji zawiera ten sam filtr, którego użyliśmy do wyświetlenia dzienników aplikacji.
    4. (Opcjonalnie) Dodaj etykietę, która pomoże zliczać liczbę połączeń dla każdego zwierzęcia. UWAGA: ta etykieta może znacznie zwiększyć liczebność danych i nie jest zalecana do użytku w produkcji:
      1. Kliknij Dodaj etykietę.
      2. W sekcji Etykiety ustaw te pola:
        • Nazwa etykiety: ustaw nazwę na animal.
        • Opis: wpisz opis etykiety. Na przykład: Animal parameter.
        • Typ etykiety: kliknij STRING.
        • Nazwa pola: wpisz jsonPayload.animal.
        • Wyrażenie regularne: pozostaw puste.
      3. Kliknij przycisk Gotowe.
    5. Aby utworzyć dane, kliknij Utwórz dane.

Możesz też utworzyć wskaźnik oparty na logach na stronie Wskaźniki oparte na logach, używając gcloud logging metrics create polecenia wiersza poleceń lub google_logging_metric zasobu Terraform.

Aby wygenerować dane statystyczne, otwórz adres URL usługi. Odśwież otwartą stronę kilka razy, aby wykonać kilka wywołań modelu. Podobnie jak wcześniej, spróbuj użyć w tym parametrze różnych zwierząt.

Wpisz zapytanie PromQL, aby wyszukać dane wskaźnika opartego na logach. Aby wpisać zapytanie PromQL:

  1. Kliknij przycisk poniżej, aby otworzyć stronę Eksplorator danych w konsoli Google Cloud:

  2. Na pasku narzędzi w panelu kreatora zapytań kliknij przycisk o nazwie < > MQL lub < > PromQL. Lokalizacja przycisku – patrz zdjęcie poniżej.
    Lokalizacja przycisku MQL w narzędziu Metrics Explorer
  3. Sprawdź, czy w opcji Język wybrana jest opcja PromQL. Przełącznik języka znajduje się na tej samej belce narzędzi, która umożliwia formatowanie zapytania.
  4. Wpisz zapytanie w edytorze Zapytania:
    sum(rate(logging_googleapis_com:user_model_interaction_count{monitored_resource="cloud_run_revision"}[${__interval}]))
    
    Więcej informacji o używaniu PromQL znajdziesz w artykule PromQL w Cloud Monitoring.
  5. Kliknij Uruchom zapytanie. Zobaczysz wykres liniowy podobny do tego:
    Wyświetlanie danych zapytań

    Pamiętaj, że gdy włączony jest przełącznik Automatyczne uruchamianie, przycisk Uruchom zapytanie nie jest widoczny.

10. (Opcjonalnie) Używanie OpenTelemetry do monitorowania i śledzenia

Jak wspomnieliśmy w poprzednim kroku, dane można wdrażać za pomocą pakietu OpenTelemetry (Otel) SDK. Zalecane jest używanie usługi OTel w architekturach mikroserwisów. Ten krok obejmuje:

  • Inicjowanie komponentów OTel w celu obsługi śledzenia i monitorowania aplikacji
  • Wypełnianie konfiguracji OTel za pomocą metadanych zasobów środowiska Cloud Run
  • Instrumentowanie aplikacji Flask z automatycznymi funkcjami śledzenia
  • Wdrożenie licznika do monitorowania liczby udanych wywołań modelu
  • Korelacja śledzenia z logami aplikacji

Zalecana architektura usług na poziomie usługi polega na użyciu kolektora Otel do zbierania i przetwarzania wszystkich danych dotyczących możliwości obserwacji w przypadku co najmniej jednej usługi. Ze względu na prostotę kod na tym etapie nie korzysta z kolekcjonera. Zamiast tego używa eksportów OTel, które zapisują dane bezpośrednio w Google Cloud.

Konfigurowanie komponentów usługi OTel na potrzeby monitorowania śledzenia i danych

  1. Wróć do okna (lub karty) Cloud Shell w przeglądarce.
  2. W terminalu ponownie otwórz setup.go:
    cloudshell edit ~/codelab-o11y/setup.go
    
  3. Zastąp kod wersją, która inicjuje śledzenie i zbieranie danych OpenTelemetry. Aby zastąpić kod, usuń zawartość pliku, a potem skopiuj i wklej podany niżej kod do edytora:
    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. Wróć do terminala i uruchom to polecenie, aby zaktualizować definicje modułów Go w pliku go.mod:
    go mod tidy
    
  5. Wróć do terminala i ponownie otwórz main.go:
    cloudshell edit ~/codelab-o11y/main.go
    
  6. Zastąp obecny kod wersją, która umożliwia rejestrowanie śledzenia HTTP i zapisywanie danych o skuteczności. Aby zastąpić kod, usuń zawartość pliku, a potem skopiuj i wklej podany niżej kod do edytora:
    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)
        }
    }
    

Aplikacja korzysta teraz z pakietu SDK OpenTelemetry, aby śledzić wykonywanie kodu za pomocą śledzenia i implementować zliczanie liczby udanych jego wykonań jako dane. Metoda main() została zmodyfikowana, aby skonfigurować eksportery OpenTelemetry w celu zapisywania śladów i danych bezpośrednio w Google Cloud Tracing i Monitoring. Wykonuje też dodatkowe konfiguracje, aby wypełnić zebrane ślady i dane metadanymi związanymi ze środowiskiem Cloud Run. Funkcja Handler() jest aktualizowana, aby zwiększać licznik danych za każdym razem, gdy wywołanie interfejsu Vertex AI API zwraca prawidłowe wyniki.

Po kilku sekundach edytor Cloud Shell automatycznie zapisuje zmiany.

Wdrażanie kodu aplikacji generatywnej AI w Cloud Run

  1. W oknie terminala uruchom polecenie, aby wdrożyć kod źródłowy aplikacji do Cloud Run.
    gcloud run deploy codelab-o11y-service \
         --source="${HOME}/codelab-o11y/" \
         --region=us-central1 \
         --allow-unauthenticated
    
    Jeśli zobaczysz taki komunikat, oznacza to, że polecenie utworzy nowe repozytorium. Kliknij 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)?
    
    Proces wdrażania może potrwać kilka minut. Po zakończeniu procesu wdrażania zobaczysz dane wyjściowe podobne do tych:
    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. Skopiuj wyświetlony adres URL usługi Cloud Run na osobną kartę lub do osobnego okna w przeglądarce. Możesz też uruchomić to polecenie w terminalu, aby wydrukować adres URL usługi, a potem kliknąć wyświetlony adres URL, przytrzymując klawisz Ctrl, aby go otworzyć:
    gcloud run services list \
         --format='value(URL)' \
         --filter='SERVICE:"codelab-o11y-service"'
    
    Po otwarciu adresu URL może pojawić się błąd 500 lub komunikat:
    Sorry, this is just a placeholder...
    
    Oznacza to, że usługi nie zostały wdrożone. Zaczekaj chwilę i odśwież stronę. Na końcu zobaczysz tekst zaczynający się od słów Ciekawostki o psach i zawierający 10 ciekawostek o tych zwierzętach.

Aby wygenerować dane telemetryczne, otwórz adres URL usługi. Aby uzyskać inne wyniki, odśwież stronę, zmieniając wartość parametru ?animal=.

Analiza śladów aplikacji

  1. Kliknij przycisk poniżej, aby otworzyć stronę Eksplorator logów czasu w konsoli Cloud:

  2. Wybierz jeden z najnowszych śladów. Powinieneś/powinnaś zobaczyć 5 lub 6 elementów, które wyglądają jak na zrzucie ekranu poniżej.
    Przebieg aplikacji w Eksploratorze śledzonych zasobów
  3. Znajdź blok kodu, który śledzi wywołanie do metody obsługi zdarzenia (metody fun_facts). Będzie to ostatni element o nazwie /.
  4. W panelu Szczegóły śledzenia kliknij Logi i zdarzenia. Zobaczysz logi aplikacji powiązane z tym konkretnym spanem. Korelacja jest wykrywana na podstawie identyfikatorów śledzenia i spanu w logu czasu. Powinieneś zobaczyć log aplikacji, który zawiera prompt i odpowiedź interfejsu Vertex API.

Wskaźnik licznika

  1. Kliknij przycisk poniżej, aby otworzyć stronę Eksplorator danych w konsoli Google Cloud:

  2. Na pasku narzędzi w panelu kreatora zapytań kliknij przycisk o nazwie < > MQL lub < > PromQL. Lokalizacja przycisku – patrz obraz poniżej.
    Lokalizacja przycisku MQL w narzędziu Metrics Explorer
  3. Sprawdź, czy w opcji Język wybrana jest opcja PromQL. Przełącznik języka znajduje się na tej samej belce narzędzi, która umożliwia formatowanie zapytania.
  4. Wpisz zapytanie w edytorze Zapytania:
    sum(rate(workload_googleapis_com:model_call_counter{monitored_resource="generic_task"}[${__interval}]))
    
  5. Kliknij Uruchom zapytanie.Gdy włączony jest przełącznik Automatyczne uruchamianie, przycisk Uruchom zapytanie nie jest widoczny.

11. (Opcjonalnie) Zaciemnione informacje poufne z logów

W kroku 10 odnotowaliśmy informacje o interakcji aplikacji z modelem Gemini. Te informacje obejmowały nazwę zwierzęcia, prompt i odpowiedź modelu. Przechowywanie tych informacji w dzienniku powinno być bezpieczne, ale niekoniecznie w wielu innych przypadkach. Prośba może zawierać dane osobowe lub inne informacje poufne, których użytkownik nie chce przechowywać. Aby temu zapobiec, możesz zafałszować dane wrażliwe zapisywane w Cloud Logging. Aby zminimalizować modyfikacje kodu, zalecamy zastosowanie tego rozwiązania.

  1. Tworzenie tematu Pub/Sub do przechowywania przychodzących wpisów w logach
  2. Utwórz ujście logów, które przekierowuje pozyskane logi do tematu Pub/Sub.
  3. Utwórz potok Dataflow, który modyfikuje dzienniki przekierowywane do tematu Pub/Sub w ten sposób:
    1. Czytanie wpisu w logu z tematu PubSub
    2. Sprawdzanie danych wejściowych rekordu pod kątem informacji poufnych za pomocą interfejsu DLP inspection API
    3. Usuń informacje poufne z ładunku za pomocą jednej z metod zapobiegania utracie danych.
    4. Zapisywanie zafałszowanego wpisu w pliku logów w Cloud Logging
  4. Wdrażanie potoku

12. (Opcjonalnie) Czyszczenie

Aby uniknąć opłat za zasoby i interfejsy API używane w tym laboratorium, zalecamy ich usunięcie po zakończeniu pracy. Najprostszym sposobem na uniknięcie płatności jest usunięcie projektu utworzonego na potrzeby tego samouczka.

  1. Aby usunąć projekt, uruchom w terminalu polecenie usuwania projektu:
    PROJECT_ID=$(gcloud config get-value project)
    gcloud projects delete ${PROJECT_ID} --quiet
    
    Usunięcie projektu Cloud powoduje zaprzestanie naliczania opłat za wszystkie zasoby i interfejsy API używane w tym projekcie. Powinien pojawić się komunikat, w którym PROJECT_ID będzie identyfikatorem projektu:
    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. (Opcjonalnie) Jeśli pojawi się błąd, w kroku 5 sprawdź, jak znaleźć identyfikator projektu użyty w tym module. Zastąp nim polecenie w pierwszej instrukcji. Jeśli np. identyfikator projektu to lab-example-project, polecenie będzie wyglądać tak:
    gcloud projects delete lab-project-id-example --quiet
    

13. Gratulacje

W tym module udało Ci się utworzyć aplikację generatywnej AI, która do prognozowania korzysta z modela Gemini. W aplikacji zostały też zaimplementowane podstawowe funkcje monitorowania i logowania. Wdrożysz aplikację i zmiany z kodu źródłowego w Cloud Run. Następnie możesz używać usług Google Cloud Observability do śledzenia wydajności aplikacji, aby mieć pewność, że jest ona niezawodna.

Jeśli chcesz wziąć udział w badaniu wrażeń użytkowników (UX), aby ulepszyć usługi, z których korzystasz obecnie, zarejestruj się tutaj.

Oto kilka opcji dalszej nauki: