Praktische Methoden zur Beobachtbarkeit von Anwendungen mit generativer KI in Java

1. Übersicht

Generative KI-Anwendungen erfordern wie alle anderen Anwendungen Beobachtbarkeit. Sind für generative KI spezielle Verfahren zur Observabilität erforderlich?

In diesem Lab erstellen Sie eine einfache Gen AI-Anwendung. Stellen Sie sie in Cloud Run bereit. Außerdem können Sie sie mit wichtigen Monitoring- und Logging-Funktionen mithilfe der Google Cloud-Dienste und -Produkte für die Beobachtbarkeit ausstatten.

Lerninhalte

  • Anwendung mit Vertex AI mit dem Cloud Shell-Editor schreiben
  • Anwendungscode in GitHub speichern
  • Quellcode Ihrer Anwendung mit der gcloud CLI in Cloud Run bereitstellen
  • Ihrer Anwendung mit generativer KI Monitoring- und Logging-Funktionen hinzufügen
  • Logbasierte Messwerte verwenden
  • Logging und Monitoring mit dem OpenTelemetry SDK implementieren
  • Informationen zur verantwortungsbewussten Datenverarbeitung bei KI

2. Vorbereitung

Wenn Sie noch kein Google-Konto haben, müssen Sie ein neues Konto erstellen.

3. Projekt einrichten

  1. Melden Sie sich mit Ihrem Google-Konto in der Google Cloud Console an.
  2. Erstellen Sie ein neues Projekt oder verwenden Sie ein vorhandenes. Notieren Sie sich die Projekt-ID des Projekts, das Sie gerade erstellt oder ausgewählt haben.
  3. Aktivieren Sie die Abrechnung für das Projekt.
    • Die Durchführung dieses Labs sollte weniger als 5 $in Rechnung stellen.
    • Sie können die Schritte am Ende dieses Labs ausführen, um Ressourcen zu löschen und weitere Kosten zu vermeiden.
    • Neue Nutzer können das kostenlose Testabo mit einem Guthaben von 300$ nutzen.
  4. Prüfen Sie, ob die Abrechnung unter Meine Projekte in der Cloud-Abrechnung aktiviert ist.
    • Wenn in der Spalte Billing account für Ihr neues Projekt Billing is disabled angezeigt wird:
      1. Klicken Sie in der Spalte Actions auf das Dreipunkt-Menü.
      2. Klicken Sie auf Abrechnung ändern.
      3. Wählen Sie das Rechnungskonto aus, das Sie verwenden möchten.
    • Wenn Sie an einer Live-Veranstaltung teilnehmen, lautet der Name des Kontos wahrscheinlich Google Cloud Platform-Testrechnungskonto.

4. Cloud Shell-Editor vorbereiten

  1. Rufen Sie den Cloud Shell-Editor auf. Wenn Sie die folgende Aufforderung sehen, Cloud Shell zu autorisieren, gcloud mit Ihren Anmeldedaten aufzurufen, klicken Sie auf Autorisieren, um fortzufahren.
    Klicken Sie, um Cloud Shell zu autorisieren.
  2. Terminalfenster öffnen
    1. Klicke auf das Dreistrich-Menü Dreistrich-Menüsymbol.
    2. Klicken Sie auf Terminal.
    3. Klicken Sie auf Neues Terminal
      Neues Terminal im Cloud Shell-Editor öffnen.
  3. Konfigurieren Sie im Terminal Ihre Projekt-ID:
    gcloud config set project [PROJECT_ID]
    
    Ersetzen Sie [PROJECT_ID] durch die ID Ihres Projekts. Wenn Ihre Projekt-ID beispielsweise lab-example-project lautet, lautet der Befehl:
    gcloud config set project lab-project-id-example
    
    Wenn Sie die folgende Meldung erhalten, dass gcloud Ihre Anmeldedaten für die GCPI API anfordert, klicken Sie auf Autorisieren, um fortzufahren.
    Klicken Sie, um Cloud Shell zu autorisieren.
    Nach erfolgreicher Ausführung sollte die folgende Meldung angezeigt werden:
    Updated property [core/project].
    
    Wenn du die Meldung WARNING und die Frage Do you want to continue (Y/N)? siehst, hast du wahrscheinlich die Projekt-ID falsch eingegeben. Drücken Sie N, Enter und versuchen Sie dann noch einmal, den Befehl gcloud config set project auszuführen, nachdem Sie die richtige Projekt-ID gefunden haben.
  4. Optional: Wenn Sie die Projekt-ID nicht finden können, führen Sie den folgenden Befehl aus, um die Projekt-IDs aller Ihrer Projekte in absteigender Reihenfolge nach Erstellungszeit aufzulisten:
    gcloud projects list \
         --format='value(projectId,createTime)' \
         --sort-by=~createTime
    

5. Google APIs aktivieren

Aktivieren Sie im Terminal die für dieses Lab erforderlichen Google APIs:

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

Die Ausführung dieses Befehls kann einige Zeit dauern. Schließlich wird eine Meldung wie diese angezeigt:

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

Wenn Sie eine Fehlermeldung erhalten, die mit ERROR: (gcloud.services.enable) HttpError accessing beginnt und Fehlerdetails wie unten enthält, wiederholen Sie den Befehl nach einer Verzögerung von 1 bis 2 Minuten.

"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. Generative AI-Anwendung erstellen

In diesem Schritt schreiben Sie den Code für die einfache anfragebasierte Anwendung, die mit dem Gemini-Modell 10 lustige Fakten zu einem Tier Ihrer Wahl anzeigt. So erstellen Sie den Anwendungscode:

  1. Erstellen Sie im Terminal das Verzeichnis codelab-o11y:
    mkdir "${HOME}/codelab-o11y"
    
  2. Ändern Sie das aktuelle Verzeichnis in codelab-o11y:
    cd "${HOME}/codelab-o11y"
    
  3. Laden Sie den Bootstrap-Code der Java-Anwendung mit dem Spring-Framework-Starter herunter:
    curl https://start.spring.io/starter.zip \
        -d dependencies=web \
        -d javaVersion=17 \
        -d type=maven-project \
        -d bootVersion=3.4.1 -o java-starter.zip
    
  4. Entfernen Sie die Archivierung des Bootstrap-Codes aus dem aktuellen Ordner:
    unzip java-starter.zip
    
  5. Entfernen Sie die Archivdatei aus dem Ordner:
    rm java-starter.zip
    
  6. Erstellen Sie eine project.toml-Datei, um die Java-Laufzeitversion zu definieren, die beim Bereitstellen des Codes in Cloud Run verwendet werden soll:
    cat > "${HOME}/codelab-o11y/project.toml" << EOF
    [[build.env]]
        name = "GOOGLE_RUNTIME_VERSION"
        value = "17"
    EOF
    
  7. Fügen Sie der pom.xml-Datei Google Cloud SDK-Abhängigkeiten hinzu:
    1. Fügen Sie das Google Cloud Core-Paket hinzu:
      sed -i 's/<dependencies>/<dependencies>\
      \
              <dependency>\
                  <groupId>com.google.cloud<\/groupId>\
                  <artifactId>google-cloud-core<\/artifactId>\
                  <version>2.49.1<\/version>\
              <\/dependency>\
              /g' "${HOME}/codelab-o11y/pom.xml"
      
    2. Fügen Sie das Google Cloud Vertex AI-Paket hinzu:
      sed -i 's/<dependencies>/<dependencies>\
      \
              <dependency>\
                  <groupId>com.google.cloud<\/groupId>\
                  <artifactId>google-cloud-vertexai<\/artifactId>\
                  <version>1.16.0<\/version>\
              <\/dependency>\
              /g' "${HOME}/codelab-o11y/pom.xml"
      
  8. Öffnen Sie die Datei DemoApplication.java im Cloud Shell-Editor:
    cloudshell edit "${HOME}/codelab-o11y/src/main/java/com/example/demo/DemoApplication.java"
    
    Im Editorfenster über dem Terminal sollte jetzt der gescaffoldte Quellcode der Datei DemoApplication.java angezeigt werden. Der Quellcode der Datei sieht in etwa so aus:
    package com.example.demo;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class DemoApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(DemoApplication.class, args);
        }
    }
    
  9. Ersetzen Sie den Code im Editor durch die unten stehende Version. Wenn Sie den Code ersetzen möchten, löschen Sie den Inhalt der Datei und kopieren Sie den folgenden Code in den Editor:
    package com.example.demo;
    
    import java.io.IOException;
    import java.util.Collections;
    
    import javax.annotation.PostConstruct;
    import javax.annotation.PreDestroy;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    import com.google.cloud.ServiceOptions;
    import com.google.cloud.vertexai.VertexAI;
    import com.google.cloud.vertexai.api.GenerateContentResponse;
    import com.google.cloud.vertexai.generativeai.GenerativeModel;
    import com.google.cloud.vertexai.generativeai.ResponseHandler;
    
    @SpringBootApplication
    public class DemoApplication {
    
        public static void main(String[] args) {
            String port = System.getenv().getOrDefault("PORT", "8080");
            SpringApplication app = new SpringApplication(DemoApplication.class);
            app.setDefaultProperties(Collections.singletonMap("server.port", port));
            app.run(args);
        }
    }
    
    @RestController
    class HelloController {
        private final String projectId = ServiceOptions.getDefaultProjectId();
        private VertexAI vertexAI;
        private GenerativeModel model;
    
        @PostConstruct
        public void init() {
            vertexAI = new VertexAI(projectId, "us-central1");
            model = new GenerativeModel("gemini-1.5-flash", vertexAI);
        }
    
        @PreDestroy
        public void destroy() {
            vertexAI.close();
        }
    
        @GetMapping("/")
        public String getFacts(@RequestParam(defaultValue = "dog") String animal) throws IOException {
            String prompt = "Give me 10 fun facts about " + animal + ". Return this as html without backticks.";
            GenerateContentResponse response = model.generateContent(prompt);
            return ResponseHandler.getText(response);
        }
    }
    
    Nach einigen Sekunden wird der Code im Cloud Shell-Editor automatisch gespeichert.

Code der Gen AI-Anwendung in Cloud Run bereitstellen

  1. Führen Sie im Terminalfenster den Befehl aus, um den Quellcode der Anwendung in Cloud Run bereitzustellen.
    gcloud run deploy codelab-o11y-service \
         --source="${HOME}/codelab-o11y/" \
         --region=us-central1 \
         --allow-unauthenticated
    
    Sie sehen die folgende Aufforderung, dass mit dem Befehl ein neues Repository erstellt wird. Klicken Sie auf 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)?
    
    Die Bereitstellung kann einige Minuten dauern. Nach Abschluss des Bereitstellungsvorgangs wird eine Ausgabe wie die folgende angezeigt:
    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. Kopieren Sie die angezeigte Cloud Run-Dienst-URL in einen separaten Tab oder ein separates Fenster in Ihrem Browser. Alternativ können Sie den folgenden Befehl im Terminal ausführen, um die Dienst-URL zu drucken, und dann bei gedrückter Strg-Taste auf die angezeigte URL klicken, um sie zu öffnen:
    gcloud run services list \
         --format='value(URL)' \
         --filter='SERVICE:"codelab-o11y-service"'
    
    Wenn die URL geöffnet wird, erhalten Sie möglicherweise den Fehler 500 oder die Meldung:
    Sorry, this is just a placeholder...
    
    Die Bereitstellung der Dienste wurde nicht abgeschlossen. Warten Sie einen Moment und aktualisieren Sie die Seite. Am Ende sehen Sie einen Text, der mit Fun Dog Facts beginnt und 10 lustige Fakten über Hunde enthält.

Interagieren Sie mit der App, um interessante Fakten über verschiedene Tiere zu erfahren. Fügen Sie dazu den Parameter animal an die URL an, z. B. ?animal=[ANIMAL], wobei [ANIMAL] ein Tiername ist. Fügen Sie beispielsweise ?animal=cat hinzu, um 10 lustige Fakten über Katzen zu erhalten, oder ?animal=sea turtle, um 10 lustige Fakten über Meeresschildkröten zu erhalten.

7. Vertex API-Aufrufe prüfen

Durch die Analyse von Google API-Aufrufen erhalten Sie Antworten auf Fragen wie „Wer ruft eine bestimmte API wo und wann auf?“. Audits sind wichtig, wenn Sie Probleme mit Ihrer Anwendung beheben, den Ressourcenverbrauch untersuchen oder eine forensische Softwareanalyse durchführen.

Mit Audit-Logs können Sie Administrator- und Systemaktivitäten erfassen sowie Aufrufe von API-Vorgängen vom Typ „Daten lesen“ und „Daten schreiben“ protokollieren. Wenn Sie Vertex AI-Anfragen zum Generieren von Inhalten prüfen möchten, müssen Sie in der Cloud Console Audit-Logs für „Datenlesevorgänge“ aktivieren.

  1. Klicken Sie auf die Schaltfläche unten, um die Seite „Audit-Logs“ in der Cloud Console zu öffnen.

  2. Achten Sie darauf, dass auf der Seite das Projekt ausgewählt ist, das Sie für dieses Lab erstellt haben. Das ausgewählte Projekt wird links oben auf der Seite direkt neben dem Dreistrich-Menü angezeigt:
    Drop-down-Menü „Projekt“ in der Google Cloud Console
    Wählen Sie bei Bedarf das richtige Projekt aus der Drop-down-Liste aus.
  3. Suchen Sie in der Tabelle Konfiguration für Audit-Logs zum Datenzugriff in der Spalte „Dienst“ nach dem Dienst Vertex AI API und aktivieren Sie das Kästchen links neben dem Dienstnamen.
    Vertex AI API auswählen
  4. Wählen Sie im Infofeld rechts den Analysetyp „Datenlesevorgang“ aus.
    Protokolle für Datenlesevorgänge prüfen
  5. Klicken Sie auf Speichern.

Öffnen Sie die Dienst-URL, um Audit-Logs zu generieren. Aktualisieren Sie die Seite und ändern Sie dabei den Wert des Parameters ?animal=, um andere Ergebnisse zu erhalten.

Audit-Logs analysieren

  1. Klicken Sie auf die Schaltfläche unten, um die Seite „Log-Explorer“ in der Cloud Console zu öffnen:

  2. Fügen Sie den folgenden Filter in den Bereich „Abfrage“ ein.
    LOG_ID("cloudaudit.googleapis.com%2Fdata_access") AND
    protoPayload.serviceName="aiplatform.googleapis.com"
    
    Der Bereich „Abfrage“ ist ein Editor, der sich oben auf der Seite „Log-Explorer“ befindet:
    Audit-Logs abfragen
  3. Klicken Sie auf Abfrage ausführen.
  4. Wählen Sie einen der Einträge im Audit-Log aus und maximieren Sie die Felder, um die im Log erfassten Informationen zu prüfen.
    Sie können Details zum Vertex API-Aufruf sehen, einschließlich der verwendeten Methode und des Modells. Außerdem sehen Sie die Identität des Aufrufers und die Berechtigungen, die den Aufruf autorisiert haben.

8. Interaktionen mit generativer KI protokollieren

In Audit-Logs finden Sie keine API-Anfrageparameter oder Antwortdaten. Diese Informationen können jedoch für die Fehlerbehebung bei der Anwendungs- und Workflowanalyse wichtig sein. In diesem Schritt schließen wir diese Lücke, indem wir Anwendungsprotokolle hinzufügen.

Bei der Implementierung wird Logback mit Spring Boot verwendet, um Anwendungsprotokolle in die Standardausgabe zu drucken. Bei dieser Methode werden Informationen, die in die Standardausgabe gedruckt werden, mit Cloud Run erfasst und automatisch in Cloud Logging aufgenommen. Damit Informationen als strukturierte Daten erfasst werden können, sollten die ausgedruckten Protokolle entsprechend formatiert sein. Folgen Sie der Anleitung unten, um der Anwendung Funktionen für strukturiertes Logging hinzuzufügen.

  1. Kehren Sie im Browser zum Cloud Shell-Fenster (oder -Tab) zurück.
  2. Erstellen Sie eine neue Datei LoggingEventGoogleCloudEncoder.java im Cloud Shell-Editor und öffnen Sie sie:
    cloudshell edit "${HOME}/codelab-o11y/src/main/java/com/example/demo/LoggingEventGoogleCloudEncoder.java"
    
  3. Kopieren Sie den folgenden Code und fügen Sie ihn ein, um den Logback-Encoder zu implementieren, der das Protokoll als JSON-String im strukturierten Protokollformat von Google Cloud codiert:
    package com.example.demo;
    
    import static ch.qos.logback.core.CoreConstants.UTF_8_CHARSET;
    
    import java.time.Instant;
    import ch.qos.logback.core.encoder.EncoderBase;
    import ch.qos.logback.classic.Level;
    import ch.qos.logback.classic.spi.ILoggingEvent;
    import java.util.HashMap;
    
    import com.google.gson.Gson;
    
    public class LoggingEventGoogleCloudEncoder extends EncoderBase<ILoggingEvent>  {
        private static final byte[] EMPTY_BYTES = new byte[0];
        private final Gson gson = new Gson();
    
        @Override
        public byte[] headerBytes() {
            return EMPTY_BYTES;
        }
    
        @Override
        public byte[] encode(ILoggingEvent e) {
            var timestamp = Instant.ofEpochMilli(e.getTimeStamp());
            var fields = new HashMap<String, Object>() {
                {
                    put("timestamp", timestamp.toString());
                    put("severity", severityFor(e.getLevel()));
                    put("message", e.getMessage());
                }
            };
            var params = e.getKeyValuePairs();
            if (params != null && params.size() > 0) {
                params.forEach(kv -> fields.putIfAbsent(kv.key, kv.value));
            }
            var data = gson.toJson(fields) + "\n";
            return data.getBytes(UTF_8_CHARSET);
        }
    
        @Override
        public byte[] footerBytes() {
            return EMPTY_BYTES;
        }
    
        private static String severityFor(Level level) {
            switch (level.toInt()) {
                case Level.TRACE_INT:
                return "DEBUG";
                case Level.DEBUG_INT:
                return "DEBUG";
                case Level.INFO_INT:
                return "INFO";
                case Level.WARN_INT:
                return "WARNING";
                case Level.ERROR_INT:
                return "ERROR";
                default:
                return "DEFAULT";
            }
        }
    }
    
  4. Erstellen Sie eine neue Datei logback.xml im Cloud Shell-Editor und öffnen Sie sie:
    cloudshell edit "${HOME}/codelab-o11y/src/main/resources/logback.xml"
    
  5. Kopieren Sie die folgende XML-Datei und fügen Sie sie ein, um Logback so zu konfigurieren, dass der Encoder mit dem Logback-Appender verwendet wird, der Protokolle auf die Standardausgabe druckt:
    <?xml version="1.0" encoding="UTF-8"?>
    <configuration debug="true">
        <appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
            <encoder class="com.example.demo.LoggingEventGoogleCloudEncoder"/>
        </appender>
    
        <root level="info">
            <appender-ref ref="Console" />
        </root>
    </configuration>
    
  6. Öffnen Sie die Datei DemoApplication.java noch einmal im Cloud Shell-Editor:
    cloudshell edit "${HOME}/codelab-o11y/src/main/java/com/example/demo/DemoApplication.java"
    
  7. Ersetzen Sie den Code im Editor durch die unten stehende Version, um Gen AI-Anfrage und ‑Antwort zu protokollieren. Wenn Sie den Code ersetzen möchten, löschen Sie den Inhalt der Datei und kopieren Sie den folgenden Code in den Editor:
    package com.example.demo;
    
    import java.io.IOException;
    import java.util.Collections;
    
    import javax.annotation.PostConstruct;
    import javax.annotation.PreDestroy;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    import com.google.cloud.ServiceOptions;
    import com.google.cloud.vertexai.VertexAI;
    import com.google.cloud.vertexai.api.GenerateContentResponse;
    import com.google.cloud.vertexai.generativeai.GenerativeModel;
    import com.google.cloud.vertexai.generativeai.ResponseHandler;
    
    @SpringBootApplication
    public class DemoApplication {
    
        public static void main(String[] args) {
            String port = System.getenv().getOrDefault("PORT", "8080");
            SpringApplication app = new SpringApplication(DemoApplication.class);
            app.setDefaultProperties(Collections.singletonMap("server.port", port));
            app.run(args);
        }
    }
    
    @RestController
    class HelloController {
        private final String projectId = ServiceOptions.getDefaultProjectId();
        private VertexAI vertexAI;
        private GenerativeModel model;
        private final Logger LOGGER = LoggerFactory.getLogger(HelloController.class);
    
        @PostConstruct
        public void init() {
            vertexAI = new VertexAI(projectId, "us-central1");
            model = new GenerativeModel("gemini-1.5-flash", vertexAI);
        }
    
        @PreDestroy
        public void destroy() {
            vertexAI.close();
        }
    
        @GetMapping("/")
        public String getFacts(@RequestParam(defaultValue = "dog") String animal) throws IOException {
            String prompt = "Give me 10 fun facts about " + animal + ". Return this as html without backticks.";
            GenerateContentResponse response = model.generateContent(prompt);
            LOGGER.atInfo()
                    .addKeyValue("animal", animal)
                    .addKeyValue("prompt", prompt)
                    .addKeyValue("response", response)
                    .log("Content is generated");
            return ResponseHandler.getText(response);
        }
    }
    

Nach einigen Sekunden werden Ihre Änderungen automatisch im Cloud Shell-Editor gespeichert.

Code der Gen AI-Anwendung in Cloud Run bereitstellen

  1. Führen Sie im Terminalfenster den Befehl aus, um den Quellcode der Anwendung in Cloud Run bereitzustellen.
    gcloud run deploy codelab-o11y-service \
         --source="${HOME}/codelab-o11y/" \
         --region=us-central1 \
         --allow-unauthenticated
    
    Sie sehen die folgende Aufforderung, dass mit dem Befehl ein neues Repository erstellt wird. Klicken Sie auf 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)?
    
    Die Bereitstellung kann einige Minuten dauern. Nach Abschluss des Bereitstellungsvorgangs wird eine Ausgabe wie die folgende angezeigt:
    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. Kopieren Sie die angezeigte Cloud Run-Dienst-URL in einen separaten Tab oder ein separates Fenster in Ihrem Browser. Alternativ können Sie den folgenden Befehl im Terminal ausführen, um die Dienst-URL zu drucken, und dann bei gedrückter Strg-Taste auf die angezeigte URL klicken, um sie zu öffnen:
    gcloud run services list \
         --format='value(URL)' \
         --filter='SERVICE:"codelab-o11y-service"'
    
    Wenn die URL geöffnet wird, erhalten Sie möglicherweise den Fehler 500 oder die Meldung:
    Sorry, this is just a placeholder...
    
    Die Bereitstellung der Dienste wurde nicht abgeschlossen. Warten Sie einen Moment und aktualisieren Sie die Seite. Am Ende sehen Sie einen Text, der mit Fun Dog Facts beginnt und 10 lustige Fakten über Hunde enthält.

Öffnen Sie die Dienst-URL, um Anwendungsprotokolle zu generieren. Aktualisieren Sie die Seite und ändern Sie dabei den Wert des Parameters ?animal=, um andere Ergebnisse zu erhalten.
So rufen Sie die Anwendungsprotokolle auf:

  1. Klicken Sie auf die Schaltfläche unten, um die Seite „Log-Explorer“ in der Cloud Console zu öffnen:

  2. Fügen Sie den folgenden Filter in den Abfragebereich 2 in der Log-Explorer-Benutzeroberfläche ein:
    LOG_ID("run.googleapis.com%2Fstdout") AND
    severity=DEBUG
    
  3. Klicken Sie auf Abfrage ausführen.

Das Ergebnis der Abfrage enthält Protokolle mit Prompt und Vertex AI-Antwort, einschließlich Sicherheitsbewertungen.

9. Interaktionen mit generativer KI zählen

Cloud Run schreibt verwaltete Messwerte, mit denen bereitgestellte Dienste überwacht werden können. Von Nutzern verwaltete Monitoring-Messwerte bieten mehr Kontrolle über Daten und Häufigkeit der Aktualisierung der Messwerte. Zur Implementierung eines solchen Messwerts muss Code geschrieben werden, der Daten erfasst und in Cloud Monitoring schreibt. Im nächsten (optionalen) Schritt erfahren Sie, wie Sie dies mit dem OpenTelemetry SDK implementieren.

In diesem Schritt wird eine Alternative zur Implementierung von Nutzermesswerten im Code gezeigt: logbasierte Messwerte. Mit logbasierten Messwerten können Sie Monitoring-Messwerte aus den Logeinträgen generieren, die Ihre Anwendung in Cloud Logging schreibt. Wir verwenden die im vorherigen Schritt implementierten Anwendungsprotokolle, um einen logbasierten Messwert für den Typzähler zu definieren. Der Messwert zählt die Anzahl der erfolgreichen Aufrufe der Vertex API.

  1. Sehen Sie sich das Fenster des Log-Explorers an, den wir im vorherigen Schritt verwendet haben. Klicken Sie im Bereich „Abfrage“ auf das Drop-down-Menü Aktionen, um es zu öffnen. Im Screenshot unten sehen Sie das Menü:
    Symbolleiste für Abfrageergebnisse mit Drop-down-Menü „Aktionen“
  2. Wählen Sie im Menü die Option Messwert erstellen aus, um den Bereich Logbasierten Messwert erstellen zu öffnen.
  3. So konfigurieren Sie einen neuen Zählermesswert im Bereich Logbasierten Messwert erstellen:
    1. Legen Sie den Messwerttyp fest: Wählen Sie Zähler aus.
    2. Legen Sie im Abschnitt Details die folgenden Felder fest:
      • Name des Logmesswerts: Legen Sie als Namen model_interaction_count fest. Für die Benennung gelten einige Einschränkungen. Weitere Informationen dazu finden Sie unter Fehlerbehebung bei Benennungsbeschränkungen.
      • Beschreibung: Geben Sie eine Beschreibung für den Messwert ein. z. B. Number of log entries capturing successful call to model inference..
      • Einheiten: Lassen Sie dieses Feld leer oder geben Sie die Zahl 1 ein.
    3. Lassen Sie die Werte im Abschnitt Filterauswahl unverändert. Beachten Sie, dass im Feld Build-Filter derselbe Filter verwendet wird, mit dem wir die Anwendungsprotokolle aufgerufen haben.
    4. Optional: Fügen Sie ein Label hinzu, mit dem sich die Anzahl der Anrufe für jedes Tier zählen lässt. HINWEIS: Dieses Label kann die Kardinalität des Messwerts erheblich erhöhen und wird für die Produktion nicht empfohlen:
      1. Klicken Sie auf Label hinzufügen.
      2. Legen Sie im Abschnitt Labels die folgenden Felder fest:
        • Labelname: Legen Sie als Namen animal fest.
        • Beschreibung: Geben Sie die Beschreibung des Labels ein. Beispiel: Animal parameter.
        • Labeltyp: Wählen Sie STRING aus.
        • Feldname: Geben Sie jsonPayload.animal ein.
        • Regulärer Ausdruck: Lassen Sie das Feld leer.
      3. Klicken Sie auf Fertig.
    5. Klicken Sie auf Messwert erstellen, um den Messwert zu erstellen.

Sie können einen logbasierten Messwert auch auf der Seite Logbasierte Messwerte, mit dem gcloud logging metrics create-Befehl oder mit der google_logging_metric-Terraform-Ressource erstellen.

Öffnen Sie die Dienst-URL, um Messwertdaten zu generieren. Aktualisieren Sie die geöffnete Seite mehrmals, um das Modell mehrmals aufzurufen. Verwenden Sie wie zuvor verschiedene Tiere im Parameter.

Geben Sie die PromQL-Abfrage ein, um nach den logbasierten Messwertdaten zu suchen. So geben Sie eine PromQL-Abfrage ein:

  1. Klicken Sie auf die Schaltfläche unten, um die Seite „Metrics Explorer“ in der Cloud Console zu öffnen:

  2. Klicken Sie in der Symbolleiste des Bereichs „Query Builder“ auf die Schaltfläche < > MQL oder < > PromQL. Die Schaltfläche befindet sich an der Stelle, die auf der Abbildung unten markiert ist.
    Position der Schaltfläche für MQLs im Metrics Explorer
  3. Prüfen Sie, ob PromQL im Schalter Sprache ausgewählt ist. Die Sprachschaltfläche befindet sich in derselben Symbolleiste, mit der Sie Ihre Abfrage formatieren können.
  4. Geben Sie die Abfrage in den Editor Abfragen ein:
    sum(rate(logging_googleapis_com:user_model_interaction_count{monitored_resource="cloud_run_revision"}[${__interval}]))
    
    Weitere Informationen zur Verwendung von PromQL finden Sie unter PromQL in Cloud Monitoring.
  5. Klicken Sie auf Abfrage ausführen. Sie sehen ein Liniendiagramm, das diesem Screenshot ähnelt:
    Abgefragte Messwerte anzeigen

    Wenn die Ein/Aus-Schaltfläche Automatisch ausführen aktiviert ist, wird die Schaltfläche Abfrage ausführen nicht angezeigt.

10. Optional: OpenTelemetry für Monitoring und Tracing verwenden

Wie im vorherigen Schritt erwähnt, ist es möglich, Messwerte mit dem OpenTelemetry (Otel) SDK zu implementieren. Die Verwendung von OTel in Multi-Service-Architekturen wird empfohlen. In diesem Schritt wird gezeigt, wie einer Spring Boot-Anwendung OTel-Instrumentierung hinzugefügt wird. In diesem Schritt gehen Sie so vor:

  • Spring Boot-Anwendung mit automatischen Tracing-Funktionen instrumentieren
  • Zählermesswert zum Überwachen der Anzahl der erfolgreichen Modellaufrufe implementieren
  • Traces mit Anwendungslogs korrelieren

Für Dienste auf Produktebene wird die Verwendung des OTel-Collectors empfohlen, um alle Observabilitātsdaten aus mehreren Diensten zu erfassen und zu verarbeiten. Der Code in diesem Schritt verwendet aus Gründen der Einfachheit keinen Collector. Stattdessen werden OTel-Exporte verwendet, die Daten direkt in Google Cloud schreiben.

Spring Boot-Anwendung mit OTel-Komponenten und automatischem Tracing einrichten

  1. Kehren Sie im Browser zum Cloud Shell-Fenster (oder -Tab) zurück.
  2. Aktualisieren Sie im Terminal die Datei application.permissions mit zusätzlichen Konfigurationsparametern:
    cat >> "${HOME}/codelab-o11y/src/main/resources/application.properties" << EOF
    otel.logs.exporter=none
    otel.traces.exporter=google_cloud_trace
    otel.metrics.exporter=google_cloud_monitoring
    otel.resource.attributes.service.name=codelab-o11y-service
    otel.traces.sampler=always_on
    EOF
    
    Mit diesen Parametern wird der Export von Observabilitӓtsdaten nach Cloud Trace und Cloud Monitoring definiert und die Stichprobenerhebung für alle Traces erzwungen.
  3. Fügen Sie der Datei pom.xml die erforderlichen OpenTelemetry-Abhängigkeiten hinzu:
    sed -i 's/<dependencies>/<dependencies>\
    \
            <dependency>\
                <groupId>io.opentelemetry.instrumentation<\/groupId>\
                <artifactId>opentelemetry-spring-boot-starter<\/artifactId>\
            <\/dependency>\
            <dependency>\
                <groupId>com.google.cloud.opentelemetry<\/groupId>\
                <artifactId>exporter-auto<\/artifactId>\
                <version>0.33.0-alpha<\/version>\
            <\/dependency>\
            <dependency>\
                <groupId>com.google.cloud.opentelemetry<\/groupId>\
                <artifactId>exporter-trace<\/artifactId>\
                <version>0.33.0<\/version>\
            <\/dependency>\
            <dependency>\
                <groupId>com.google.cloud.opentelemetry<\/groupId>\
                <artifactId>exporter-metrics<\/artifactId>\
                <version>0.33.0<\/version>\
            <\/dependency>\
    /g' "${HOME}/codelab-o11y/pom.xml"
    
  4. Fügen Sie der Datei pom.xml die OpenTelemetry-BOM hinzu:
    sed -i 's/<\/properties>/<\/properties>\
        <dependencyManagement>\
            <dependencies>\
                <dependency>\
                    <groupId>io.opentelemetry.instrumentation<\/groupId>\
                    <artifactId>opentelemetry-instrumentation-bom<\/artifactId>\
                    <version>2.12.0<\/version>\
                    <type>pom<\/type>\
                    <scope>import<\/scope>\
                <\/dependency>\
            <\/dependencies>\
        <\/dependencyManagement>\
    /g' "${HOME}/codelab-o11y/pom.xml"
    
  5. Öffnen Sie die Datei DemoApplication.java noch einmal im Cloud Shell-Editor:
    cloudshell edit "${HOME}/codelab-o11y/src/main/java/com/example/demo/DemoApplication.java"
    
  6. Ersetzen Sie den aktuellen Code durch die Version, die einen Leistungsmesswert inkrementiert. Wenn Sie den Code ersetzen möchten, löschen Sie den Inhalt der Datei und kopieren Sie den folgenden Code in den Editor:
    package com.example.demo;
    
    import io.opentelemetry.api.common.AttributeKey;
    import io.opentelemetry.api.common.Attributes;
    import io.opentelemetry.api.OpenTelemetry;
    import io.opentelemetry.api.metrics.LongCounter;
    
    import java.io.IOException;
    import java.util.Collections;
    
    import javax.annotation.PostConstruct;
    import javax.annotation.PreDestroy;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    import com.google.cloud.ServiceOptions;
    import com.google.cloud.vertexai.VertexAI;
    import com.google.cloud.vertexai.api.GenerateContentResponse;
    import com.google.cloud.vertexai.generativeai.GenerativeModel;
    import com.google.cloud.vertexai.generativeai.ResponseHandler;
    
    
    @SpringBootApplication
    public class DemoApplication {
    
        public static void main(String[] args) {
            String port = System.getenv().getOrDefault("PORT", "8080");
            SpringApplication app = new SpringApplication(DemoApplication.class);
            app.setDefaultProperties(Collections.singletonMap("server.port", port));
            app.run(args);
        }
    }
    
    @RestController
    class HelloController {
        private final String projectId = ServiceOptions.getDefaultProjectId();
        private VertexAI vertexAI;
        private GenerativeModel model;
        private final Logger LOGGER = LoggerFactory.getLogger(HelloController.class);
        private static final String INSTRUMENTATION_NAME = "genai-o11y/java/workshop/example";
        private static final AttributeKey<String> ANIMAL = AttributeKey.stringKey("animal");
        private final LongCounter counter;
    
        public HelloController(OpenTelemetry openTelemetry) {
            this.counter = openTelemetry.getMeter(INSTRUMENTATION_NAME)
                    .counterBuilder("model_call_counter")
                    .setDescription("Number of successful model calls")
                    .build();
        }
    
        @PostConstruct
        public void init() {
            vertexAI = new VertexAI(projectId, "us-central1");
            model = new GenerativeModel("gemini-1.5-flash", vertexAI);
        }
    
        @PreDestroy
        public void destroy() {
            vertexAI.close();
        }
    
        @GetMapping("/")
        public String getFacts(@RequestParam(defaultValue = "dog") String animal) throws IOException {
            String prompt = "Give me 10 fun facts about " + animal + ". Return this as html without backticks.";
            GenerateContentResponse response = model.generateContent(prompt);
            LOGGER.atInfo()
                    .addKeyValue("animal", animal)
                    .addKeyValue("prompt", prompt)
                    .addKeyValue("response", response)
                    .log("Content is generated");
            counter.add(1, Attributes.of(ANIMAL, animal));
            return ResponseHandler.getText(response);
        }
    }
    
  7. Öffnen Sie die Datei LoggingEventGoogleCloudEncoder.java noch einmal im Cloud Shell-Editor:
    cloudshell edit "${HOME}/codelab-o11y/src/main/java/com/example/demo/LoggingEventGoogleCloudEncoder.java"
    
  8. Ersetzen Sie den aktuellen Code durch die Version, die den aufgezeichneten Protokollen Trace-Attribute hinzufügt. Durch das Hinzufügen der Attribute können Protokolle mit den richtigen Trace-Bereichen korreliert werden. Wenn Sie den Code ersetzen möchten, löschen Sie den Inhalt der Datei und kopieren Sie den folgenden Code in den Editor:
    package com.example.demo;
    
    import static ch.qos.logback.core.CoreConstants.UTF_8_CHARSET;
    
    import java.time.Instant;
    import java.util.HashMap;
    
    import ch.qos.logback.core.encoder.EncoderBase;
    import ch.qos.logback.classic.Level;
    import ch.qos.logback.classic.spi.ILoggingEvent;
    import com.google.cloud.ServiceOptions;
    import io.opentelemetry.api.trace.Span;
    import io.opentelemetry.api.trace.SpanContext;
    import io.opentelemetry.context.Context;
    
    import com.google.gson.Gson;
    
    
    public class LoggingEventGoogleCloudEncoder extends EncoderBase<ILoggingEvent>  {
        private static final byte[] EMPTY_BYTES = new byte[0];
        private final Gson gson;
        private final String projectId;
        private final String tracePrefix;
    
    
        public LoggingEventGoogleCloudEncoder() {
            this.gson = new Gson();
            this.projectId = lookUpProjectId();
            this.tracePrefix = "projects/" + (projectId == null ? "" : projectId) + "/traces/";
        }
    
        private static String lookUpProjectId() {
            return ServiceOptions.getDefaultProjectId();
        }
    
        @Override
        public byte[] headerBytes() {
            return EMPTY_BYTES;
        }
    
        @Override
        public byte[] encode(ILoggingEvent e) {
            var timestamp = Instant.ofEpochMilli(e.getTimeStamp());
            var fields = new HashMap<String, Object>() {
                {
                    put("timestamp", timestamp.toString());
                    put("severity", severityFor(e.getLevel()));
                    put("message", e.getMessage());
                    SpanContext context = Span.fromContext(Context.current()).getSpanContext();
                    if (context.isValid()) {
                        put("logging.googleapis.com/trace", tracePrefix + context.getTraceId());
                        put("logging.googleapis.com/spanId", context.getSpanId());
                        put("logging.googleapis.com/trace_sampled", Boolean.toString(context.isSampled()));
                    }
                }
            };
            var params = e.getKeyValuePairs();
            if (params != null && params.size() > 0) {
                params.forEach(kv -> fields.putIfAbsent(kv.key, kv.value));
            }
            var data = gson.toJson(fields) + "\n";
            return data.getBytes(UTF_8_CHARSET);
        }
    
        @Override
        public byte[] footerBytes() {
            return EMPTY_BYTES;
        }
    
        private static String severityFor(Level level) {
            switch (level.toInt()) {
                case Level.TRACE_INT:
                return "DEBUG";
                case Level.DEBUG_INT:
                return "DEBUG";
                case Level.INFO_INT:
                return "INFO";
                case Level.WARN_INT:
                return "WARNING";
                case Level.ERROR_INT:
                return "ERROR";
                default:
                return "DEFAULT";
            }
        }
    }
    

Nach einigen Sekunden werden Ihre Änderungen automatisch im Cloud Shell-Editor gespeichert.

Code der Gen AI-Anwendung in Cloud Run bereitstellen

  1. Führen Sie im Terminalfenster den Befehl aus, um den Quellcode der Anwendung in Cloud Run bereitzustellen.
    gcloud run deploy codelab-o11y-service \
         --source="${HOME}/codelab-o11y/" \
         --region=us-central1 \
         --allow-unauthenticated
    
    Sie sehen die folgende Aufforderung, dass mit dem Befehl ein neues Repository erstellt wird. Klicken Sie auf 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)?
    
    Die Bereitstellung kann einige Minuten dauern. Nach Abschluss des Bereitstellungsvorgangs wird eine Ausgabe wie die folgende angezeigt:
    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. Kopieren Sie die angezeigte Cloud Run-Dienst-URL in einen separaten Tab oder ein separates Fenster in Ihrem Browser. Alternativ können Sie den folgenden Befehl im Terminal ausführen, um die Dienst-URL zu drucken, und dann bei gedrückter Strg-Taste auf die angezeigte URL klicken, um sie zu öffnen:
    gcloud run services list \
         --format='value(URL)' \
         --filter='SERVICE:"codelab-o11y-service"'
    
    Wenn die URL geöffnet wird, erhalten Sie möglicherweise den Fehler 500 oder die Meldung:
    Sorry, this is just a placeholder...
    
    Die Bereitstellung der Dienste wurde nicht abgeschlossen. Warten Sie einen Moment und aktualisieren Sie die Seite. Am Ende sehen Sie einen Text, der mit Fun Dog Facts beginnt und 10 lustige Fakten über Hunde enthält.

Wenn Sie Telemetry-Daten generieren möchten, öffnen Sie die Dienst-URL. Aktualisieren Sie die Seite und ändern Sie dabei den Wert des Parameters ?animal=, um andere Ergebnisse zu erhalten.

Anwendungs-Traces untersuchen

  1. Klicken Sie auf die Schaltfläche unten, um die Seite „Trace Explorer“ in der Cloud Console zu öffnen:

  2. Wählen Sie einen der letzten Protokolle aus. Sie sollten fünf oder sechs Bereiche sehen, die wie im Screenshot unten aussehen.
    App-Span im Trace-Explorer
  3. Suchen Sie den Span, der den Aufruf an den Ereignis-Handler (die Methode fun_facts) nachverfolgt. Es ist die letzte Span-Element mit dem Namen /.
  4. Wählen Sie im Bereich Trace-Details die Option Protokolle und Ereignisse aus. Sie sehen Anwendungslogs, die mit diesem bestimmten Span übereinstimmen. Die Korrelation wird anhand der Trace- und Span-IDs im Trace und im Log erkannt. Sie sollten das Anwendungsprotokoll sehen, in dem der Prompt geschrieben wurde, und die Antwort der Vertex API.

Zählermesswert untersuchen

  1. Klicken Sie auf die Schaltfläche unten, um die Seite „Metrics Explorer“ in der Cloud Console zu öffnen:

  2. Klicken Sie in der Symbolleiste des Bereichs „Query Builder“ auf die Schaltfläche < > MQL oder < > PromQL. Die Schaltfläche befindet sich an der Stelle, die auf der Abbildung unten markiert ist.
    Position der Schaltfläche für MQLs im Metrics Explorer
  3. Prüfen Sie, ob PromQL im Schalter Sprache ausgewählt ist. Die Sprachschaltfläche befindet sich in derselben Symbolleiste, mit der Sie Ihre Abfrage formatieren können.
  4. Geben Sie die Abfrage in den Editor Abfragen ein:
    sum(rate(workload_googleapis_com:model_call_counter{monitored_resource="generic_task"}[${__interval}]))
    
  5. Klicken Sie auf Abfrage ausführen.Wenn die Ein/Aus-Schaltfläche Automatisch ausführen aktiviert ist, wird die Schaltfläche Abfrage ausführen nicht angezeigt.

11. (Optional) Verschleierte vertrauliche Informationen aus Protokollen

In Schritt 10 haben wir Informationen zur Interaktion der Anwendung mit dem Gemini-Modell protokolliert. Dazu gehörten der Name des Tieres, der eigentliche Prompt und die Antwort des Modells. Das Speichern dieser Informationen im Protokoll sollte zwar sicher sein, ist aber für viele andere Szenarien nicht unbedingt erforderlich. Der Prompt kann personenbezogene oder anderweitig vertrauliche Daten enthalten, die ein Nutzer nicht gespeichert haben möchte. Sie können die sensiblen Daten, die in Cloud Logging geschrieben werden, unkenntlich machen. Um Codeänderungen zu minimieren, wird die folgende Lösung empfohlen.

  1. Pub/Sub-Thema zum Speichern eingehender Logeinträge erstellen
  2. Erstellen Sie eine Log-Senke, die aufgenommene Protokolle an das Pub/Sub-Thema weiterleitet.
  3. So erstellen Sie eine Dataflow-Pipeline, die Logs, die an ein Pub/Sub-Thema weitergeleitet werden, ändert:
    1. Logeintrag aus dem Pub/Sub-Thema lesen
    2. Mit der DLP Inspection API die Nutzlast des Eintrags auf vertrauliche Informationen prüfen
    3. Entfernen Sie die sensiblen Daten in der Nutzlast mit einer der DLP-Entfernungsmethoden.
    4. Verschleierten Logeintrag in Cloud Logging schreiben
  4. Stellen Sie die Pipeline bereit.

12. (Optional) Bereinigen

Um zu vermeiden, dass Ihnen Kosten für die im Codelab verwendeten Ressourcen und APIs in Rechnung gestellt werden, sollten Sie nach Abschluss des Labs eine Bereinigung durchführen. Am einfachsten vermeiden Sie weitere Kosten, indem Sie das für das Codelab erstellte Projekt löschen.

  1. Führen Sie zum Löschen des Projekts den Befehl „delete project“ im Terminal aus:
    PROJECT_ID=$(gcloud config get-value project)
    gcloud projects delete ${PROJECT_ID} --quiet
    
    Wenn Sie Ihr Cloud-Projekt löschen, wird die Abrechnung für alle in diesem Projekt verwendeten Ressourcen und APIs beendet. Es sollte folgende Meldung angezeigt werden, wobei PROJECT_ID Ihre Projekt-ID ist:
    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. Optional: Wenn Sie eine Fehlermeldung erhalten, sehen Sie in Schritt 5 nach, wie Sie die Projekt-ID ermitteln, die Sie während des Labs verwendet haben. Ersetzen Sie ihn durch den Befehl in der ersten Anweisung. Wenn Ihre Projekt-ID beispielsweise lab-example-project lautet, lautet der Befehl:
    gcloud projects delete lab-project-id-example --quiet
    

13. Glückwunsch

In diesem Lab haben Sie eine Anwendung für generative KI erstellt, die mit dem Gemini-Modell Vorhersagen trifft. und die Anwendung mit wichtigen Monitoring- und Logging-Funktionen ausgestattet. Sie haben die Anwendung und die Änderungen aus dem Quellcode in Cloud Run bereitgestellt. Anschließend können Sie mit den Google Cloud-Produkten zur Beobachtbarkeit die Leistung der Anwendung im Blick behalten und so für deren Zuverlässigkeit sorgen.

Wenn Sie an einer UX-Forschungsstudie teilnehmen möchten, um die Produkte zu verbessern, mit denen Sie heute gearbeitet haben, registrieren Sie sich hier.

Hier sind einige Möglichkeiten, wie Sie Ihr Wissen vertiefen können: