Java での生成 AI アプリケーションの実用的なオブザーバビリティ手法

1. 概要

生成 AI アプリケーションには、他のアプリケーションと同様にオブザーバビリティが必要です。生成 AI に必要な特別なオブザーバビリティ手法はありますか?

このラボでは、シンプルな生成 AI アプリケーションを作成します。Cloud Run にデプロイします。また、Google Cloud のオブザーバビリティ サービスとプロダクトを使用して、基本的なモニタリング機能とロギング機能を実装します。

学習内容

  • Cloud Shell エディタで Vertex AI を使用するアプリケーションを作成する
  • GitHub にアプリケーション コードを保存する
  • gcloud CLI を使用して、アプリケーションのソースコードを Cloud Run にデプロイする
  • 生成 AI アプリケーションにモニタリング機能とロギング機能を追加する
  • ログベースの指標の使用
  • Open Telemetry SDK を使用してロギングとモニタリングを実装する
  • 責任ある AI のデータ処理に関する分析情報を取得する

2. 前提条件

Google アカウントをまだお持ちでない場合は、新しいアカウントを作成する必要があります。

3. プロジェクトの設定

  1. Google アカウントで Google Cloud コンソールにログインします。
  2. 新しいプロジェクトを作成するか、既存のプロジェクトを再利用します。作成または選択したプロジェクトのプロジェクト ID を書き留めます。
  3. プロジェクトの課金を有効にします
    • このラボを完了すると、請求額は $5 未満になります。
    • このラボの最後の手順に沿ってリソースを削除すると、それ以上の料金は発生しません。
    • 新規ユーザーは、300 米ドル分の無料トライアルをご利用いただけます。
  4. Cloud Billing の [マイ プロジェクト] で課金が有効になっていることを確認します。
    • 新しいプロジェクトの Billing account 列に Billing is disabled と表示されている場合:
      1. Actions 列のその他アイコンをクリックします。
      2. [お支払い情報を変更] をクリックします。
      3. 使用する請求先アカウントを選択します
    • ライブイベントに参加している場合は、アカウントの名前が Google Cloud Platform 無料トライアルの請求先アカウントになっている可能性があります。

4. Cloud Shell エディタを準備する

  1. Cloud Shell エディタに移動します。認証情報を使用して gcloud を呼び出すように Cloud Shell を承認するよう求める次のメッセージが表示されたら、[承認] をクリックして続行します。
    Cloud Shell を承認する
  2. ターミナル ウィンドウを開く
    1. ハンバーガー メニュー ハンバーガー メニュー アイコン をクリックします。
    2. [Terminal] をクリックします。
    3. [New Terminal
      ] をクリックします。Cloud Shell エディタで新しいターミナルを開く
  3. ターミナルで、プロジェクト ID を構成します。
    gcloud config set project [PROJECT_ID]
    
    [PROJECT_ID] は、プロジェクトの ID に置き換えます。たとえば、プロジェクト ID が lab-example-project の場合、コマンドは次のようになります。
    gcloud config set project lab-project-id-example
    
    gcloud が GCPI API の認証情報をリクエストしていることを示す次のメッセージが表示されたら、[Authorize] をクリックして続行します。
    Cloud Shell を承認する
    正常に実行されると、次のメッセージが表示されます。
    Updated property [core/project].
    
    WARNING が表示され、Do you want to continue (Y/N)? と表示された場合は、プロジェクト ID が正しく入力されていない可能性があります。N を押し、Enter を押します。正しいプロジェクト ID が見つかったら、gcloud config set project コマンドをもう一度実行します。
  4. (省略可)プロジェクト ID を見つけることができない場合は、次のコマンドを実行して、すべてのプロジェクトのプロジェクト ID を作成時間で降順に並べ替えて表示します。
    gcloud projects list \
         --format='value(projectId,createTime)' \
         --sort-by=~createTime
    

5. Google API を有効にする

ターミナルで、このラボに必要な Google API を有効にします。

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

このコマンドが完了するまでに時間がかかります。最終的に、次のような成功メッセージが表示されます。

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

ERROR: (gcloud.services.enable) HttpError accessing で始まり、次のようなエラーの詳細を含むエラー メッセージが表示された場合は、1 ~ 2 分遅延してからコマンドを再試行してください。

"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. 生成 AI アプリケーションを作成する

このステップでは、Gemini モデルを使用して、選択した動物に関する 10 個の面白い事実を表示する単純なリクエスト ベースのアプリケーションのコードを作成します。アプリケーション コードを作成するには、次の操作を行います。

  1. ターミナルで、codelab-o11y ディレクトリを作成します。
    mkdir "${HOME}/codelab-o11y"
    
  2. 現在のディレクトリを codelab-o11y に変更します。
    cd "${HOME}/codelab-o11y"
    
  3. Spring フレームワーク スターターを使用して、Java アプリケーションのブートストラップ コードをダウンロードします。
    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. ブートストラップ コードを現在のフォルダにアーカイブ解除します。
    unzip java-starter.zip
    
  5. フォルダからアーカイブ ファイルを削除します。
    rm java-starter.zip
    
  6. project.toml ファイルを作成して、Cloud Run にコードをデプロイするときに使用する Java ランタイム バージョンを定義します。
    cat > "${HOME}/codelab-o11y/project.toml" << EOF
    [[build.env]]
        name = "GOOGLE_RUNTIME_VERSION"
        value = "17"
    EOF
    
  7. pom.xml ファイルに Google Cloud SDK の依存関係を追加します。
    1. Google Cloud Core パッケージを追加します。
      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. Google Cloud Vertex AI パッケージを追加します。
      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. Cloud Shell エディタで DemoApplication.java ファイルを開きます。
    cloudshell edit "${HOME}/codelab-o11y/src/main/java/com/example/demo/DemoApplication.java"
    
    ファイル DemoApplication.java のスキャフォールディングされたソースコードが、ターミナルの上のエディタ ウィンドウに表示されます。ファイルのソースコードは次のようになります。
    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. エディタのコードを次のバージョンに置き換えます。コードを置き換えるには、ファイルの内容を削除してから、次のコードをエディタにコピーします。
    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);
        }
    }
    
    数秒後に、Cloud Shell エディタがコードを自動的に保存します。

生成 AI アプリケーションのコードを Cloud Run にデプロイする

  1. ターミナル ウィンドウでコマンドを実行して、アプリケーションのソースコードを Cloud Run にデプロイします。
    gcloud run deploy codelab-o11y-service \
         --source="${HOME}/codelab-o11y/" \
         --region=us-central1 \
         --allow-unauthenticated
    
    コマンドで新しいリポジトリが作成されることを知らせるプロンプトが次のように表示されます。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)?
    
    デプロイ プロセスには数分かかることがあります。デプロイ プロセスが完了すると、次のような出力が表示されます。
    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. 表示された Cloud Run サービス URL をコピーして、ブラウザの別のタブまたはウィンドウに貼り付けます。または、ターミナルで次のコマンドを実行してサービス URL を出力し、表示された URL を Ctrl キーを押しながらクリックして URL を開きます。
    gcloud run services list \
         --format='value(URL)' \
         --filter='SERVICE:"codelab-o11y-service"'
    
    URL を開くと、500 エラーが発生するか、次のメッセージが表示されることがあります。
    Sorry, this is just a placeholder...
    
    サービスがデプロイを完了していないことを意味します。しばらく待ってからページを更新します。最後に、犬に関する面白い事実で始まり、犬に関する 10 個の面白い事実を含むテキストが表示されます。

アプリを操作して、さまざまな動物に関する豆知識を入手してみましょう。これを行うには、?animal=[ANIMAL] のように、animal パラメータを URL に追加します。ここで、[ANIMAL] は動物の名前です。たとえば、?animal=cat を追加すると猫に関する 10 個の豆知識が、?animal=sea turtle を追加するとウミガメに関する 10 個の豆知識が返されます。

7. Vertex API 呼び出しを監査する

Google API 呼び出しを監査すると、「誰がいつどこで特定の API を呼び出したか」などの質問に対する回答が得られます。監査は、アプリケーションのトラブルシューティング、リソース消費の調査、ソフトウェア フォレンジック分析の実行を行う場合に重要です。

監査ログを使用すると、管理アクティビティとシステム アクティビティを追跡し、「データ読み取り」API オペレーションと「データ書き込み」API オペレーションの呼び出しをログに記録できます。コンテンツを生成する Vertex AI リクエストを監査するには、Cloud コンソールで「データ読み取り」監査ログを有効にする必要があります。

  1. 下のボタンをクリックして、Cloud コンソールの [監査ログ] ページを開きます。

  2. このラボ用に作成したプロジェクトがページで選択されていることを確認します。選択したプロジェクトは、ハンバーガー メニューの右側のページ左上に表示されます。
    Google Cloud Console のプロジェクト プルダウン
    必要に応じて、コンボボックスから正しいプロジェクトを選択します。
  3. [データアクセス監査ログの構成] テーブルの [サービス] 列で Vertex AI API サービスを見つけ、サービス名の左側にあるチェックボックスをオンにしてサービスを選択します。
    Vertex AI API を選択する
  4. 右側の情報パネルで、[データ読み取り] 監査タイプを選択します。
    データ読み取りログを確認する
  5. [保存] をクリックします。

監査ログを生成するには、サービス URL を開きます。?animal= パラメータの値を変更しながらページを更新して、異なる結果を取得します。

監査ログの詳細を見る

  1. 下のボタンをクリックして、Cloud コンソールでログ エクスプローラ ページを開きます。

  2. 次のフィルタを [クエリ] ペインに貼り付けます。
    LOG_ID("cloudaudit.googleapis.com%2Fdata_access") AND
    protoPayload.serviceName="aiplatform.googleapis.com"
    
    クエリ ペインは、ログ エクスプローラ ページの上部にあるエディタです。
    監査ログのクエリ
  3. [クエリを実行] をクリックします。
  4. 監査ログエントリのいずれかを選択し、フィールドを展開して、ログにキャプチャされた情報を確認します。
    使用されたメソッドやモデルなど、Vertex API 呼び出しの詳細を確認できます。呼び出し元の ID と、呼び出しを承認した権限も確認できます。

8. 生成 AI を使用してやり取りをログに記録する

監査ログに API リクエスト パラメータやレスポンス データは記録されません。ただし、この情報はアプリケーションとワークフローの分析のトラブルシューティングに役立つことがあります。このステップでは、アプリケーション ロギングを追加してこのギャップを埋めます。

実装では、Spring Boot で Logback を使用して、アプリケーション ログを標準出力に出力します。この方法では、標準出力に出力された情報をキャプチャして Cloud Logging に自動的に取り込む Cloud Run の機能を使用します。情報を構造化データとして取得するには、出力されたログを適切にフォーマットする必要があります。以下の手順に沿って、構造化ロギング機能をアプリケーションに追加します。

  1. ブラウザの [Cloud Shell] ウィンドウ(またはタブ)に戻ります。
  2. Cloud Shell エディタで新しいファイル LoggingEventGoogleCloudEncoder.java を作成して開きます。
    cloudshell edit "${HOME}/codelab-o11y/src/main/java/com/example/demo/LoggingEventGoogleCloudEncoder.java"
    
  3. 次のコードをコピーして貼り付け、Google Cloud 構造化ログ形式に従ってログを文字列化された JSON としてエンコードする Logback エンコーダを実装します。
    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. Cloud Shell エディタで新しいファイル logback.xml を作成して開きます。
    cloudshell edit "${HOME}/codelab-o11y/src/main/resources/logback.xml"
    
  5. 次の XML をコピーして貼り付け、標準出力にログを出力する Logback アペンダーでエンコーダを使用するように Logback を構成します。
    <?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. Cloud Shell エディタで DemoApplication.java ファイルを再度開きます。
    cloudshell edit "${HOME}/codelab-o11y/src/main/java/com/example/demo/DemoApplication.java"
    
  7. エディタのコードを次のバージョンに置き換えて、Gen AI リクエストとレスポンスをログに記録します。コードを置き換えるには、ファイルの内容を削除してから、次のコードをエディタにコピーします。
    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);
        }
    }
    

数秒後、Cloud Shell エディタは変更を自動的に保存します。

生成 AI アプリケーションのコードを Cloud Run にデプロイする

  1. ターミナル ウィンドウでコマンドを実行して、アプリケーションのソースコードを Cloud Run にデプロイします。
    gcloud run deploy codelab-o11y-service \
         --source="${HOME}/codelab-o11y/" \
         --region=us-central1 \
         --allow-unauthenticated
    
    コマンドで新しいリポジトリが作成されることを知らせるプロンプトが次のように表示されます。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)?
    
    デプロイ プロセスには数分かかることがあります。デプロイ プロセスが完了すると、次のような出力が表示されます。
    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. 表示された Cloud Run サービス URL をコピーして、ブラウザの別のタブまたはウィンドウに貼り付けます。または、ターミナルで次のコマンドを実行してサービス URL を出力し、表示された URL を Ctrl キーを押しながらクリックして URL を開きます。
    gcloud run services list \
         --format='value(URL)' \
         --filter='SERVICE:"codelab-o11y-service"'
    
    URL を開くと、500 エラーが発生するか、次のメッセージが表示されることがあります。
    Sorry, this is just a placeholder...
    
    サービスがデプロイを完了していないことを意味します。しばらく待ってからページを更新します。最後に、犬に関する面白い事実で始まり、犬に関する 10 個の面白い事実を含むテキストが表示されます。

アプリケーション ログを生成するには、サービス URL を開きます。?animal= パラメータの値を変更しながらページを更新して、異なる結果を取得します。
アプリケーション ログを表示するには、次の操作を行います。

  1. 下のボタンをクリックして、Cloud コンソールでログ エクスプローラ ページを開きます。

  2. 次のフィルタをクエリペイン(ログ エクスプローラのインターフェースの #2)に貼り付けます。
    LOG_ID("run.googleapis.com%2Fstdout") AND
    severity=DEBUG
    
  3. [クエリを実行] をクリックします。

クエリの結果には、プロンプトと Vertex AI レスポンスを含むログが表示され、安全性評価が含まれます。

9. 生成 AI とのインタラクション数をカウントする

Cloud Run は、デプロイされたサービスのモニタリングに使用できるマネージド指標を書き込みます。ユーザー管理のモニタリング指標を使用すると、指標のデータと更新頻度をより細かく制御できます。このような指標を実装するには、データを収集して Cloud Monitoring に書き込むコードを作成する必要があります。OpenTelemetry SDK を使用して実装する方法については、次の(省略可)の手順をご覧ください。

このステップでは、コードでユーザー指標を実装する代替手段として、ログベースの指標を紹介します。ログベースの指標を使用すると、アプリケーションが Cloud Logging に書き込むログエントリからモニタリング指標を生成できます。前の手順で実装したアプリケーション ログを使用して、タイプ カウンタのログベースの指標を定義します。この指標は、Vertex API への成功した呼び出しの数をカウントします。

  1. 前の手順で使用したログ エクスプローラのウィンドウを確認します。[クエリ] ペインで、[アクション] プルダウン メニューを見つけてクリックし、開きます。メニューについては、下のスクリーンショットをご覧ください。
    [操作] プルダウン メニューが表示された [クエリ結果] ツールバー
  2. 開いたメニューで [指標を作成] を選択して、[ログベースの指標を作成] パネルを開きます。
  3. ログベースの指標の作成パネルで新しいカウンタ指標を構成する手順は次のとおりです。
    1. [指標タイプ] を [カウンタ] に設定します。
    2. [詳細] セクションで次のフィールドを設定します。
      • ログ指標の名前: 名前を model_interaction_count に設定します。命名に関する制限事項が適用されます。詳細については、命名に関する制限事項のトラブルシューティングをご覧ください。
      • 説明: 指標の説明を入力します。例: Number of log entries capturing successful call to model inference.
      • 単位: 空白のままにするか、数字「1」を挿入します。
    3. [フィルタの選択] セクションの値はそのままにします。[ビルドフィルタ] フィールドには、アプリケーション ログの表示に使用したフィルタと同じフィルタが設定されています。
    4. (省略可)各動物の通話数をカウントするのに役立つラベルを追加します。注: このラベルは指標のカーディナリティを大幅に増加させる可能性があるため、本番環境での使用は推奨されません。
      1. [ラベルを追加] をクリックします。
      2. [ラベル] セクションで次のフィールドを設定します。
        • ラベル名: 名前を animal に設定します。
        • 説明: ラベルの説明を入力します。例: Animal parameter
        • ラベルタイプ: STRING を選択します。
        • フィールド名: jsonPayload.animal と入力します。
        • 正規表現: 空白のままにします。
      3. [完了] をクリックします
    5. [指標を作成] をクリックして、指標を作成します。

ログベースの指標ページから、gcloud logging metrics create CLI コマンドまたは google_logging_metric Terraform リソースを使用して、ログベースの指標を作成することもできます。

指標データを生成するには、サービス URL を開きます。開いたページを数回更新して、モデルを複数回呼び出します。前と同様に、パラメータに別の動物を使用してみてください。

ログベースの指標データを検索する PromQL クエリを入力します。PromQL クエリを入力するには、次の手順を実行します。

  1. 次のボタンをクリックして、Cloud コンソールの [Metrics Explorer] ページを開きます。

  2. クエリビルダー ペインのツールバーで、[< > MQL] または [< > PromQL] という名前のボタンを選択します。ボタンの位置については、下の画像をご覧ください。
    Metrics Explorer の MQL ボタンの位置
  3. [言語] 切り替えで [PromQL] が選択されていることを確認します。言語切り替えボタンは、クエリの書式設定を行うのと同じツールバーにあります。
  4. [Queries] エディタにクエリを入力します。
    sum(rate(logging_googleapis_com:user_model_interaction_count{monitored_resource="cloud_run_revision"}[${__interval}]))
    
    PromQL の使用の詳細については、Cloud Monitoring の PromQL をご覧ください。
  5. [RUN QUERY] をクリックします。次のスクリーンショットのような折れ線グラフが表示されます。
    クエリされた指標を表示する

    [自動実行] の切り替えが有効になっている場合、[クエリを実行] ボタンは表示されません。

10. (省略可)モニタリングとトレースに Open Telemetry を使用する

前のステップで説明したように、OpenTelemetry(Otel)SDK を使用して指標を実装できます。マルチサービス アーキテクチャで OTel を使用することをおすすめします。このステップでは、Spring Boot アプリケーションに OTel 計測を追加する方法を説明します。このステップでは、次の操作を行います。

  • 自動トレース機能を使用して Spring Boot アプリケーションを計測する
  • モデル呼び出しの成功回数をモニタリングするカウンタ指標を実装する
  • トレースとアプリケーション ログを関連付ける

プロダクト レベルのサービスに推奨されるアーキテクチャは、OTel コレクタを使用して、複数のサービスからすべてのオブザーバビリティ データを収集して取り込むことです。このステップのコードでは、わかりやすくするためにコレクタを使用していません。代わりに、データを Google Cloud に直接書き込む OTel エクスポートを使用します。

OTel コンポーネントと自動トレースを使用して Spring Boot アプリケーションを設定する

  1. ブラウザの [Cloud Shell] ウィンドウ(またはタブ)に戻ります。
  2. ターミナルで、追加の構成パラメータを使用して application.permissions ファイルを更新します。
    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
    
    これらのパラメータは、オブザーバビリティ データを Cloud Trace と Cloud Monitoring にエクスポートし、すべてのトレースのサンプリングを適用します。
  3. 必要な OpenTelemetry 依存関係を pom.xml ファイルに追加します。
    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. pom.xml ファイルに OpenTelemetry BOM を追加します。
    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. Cloud Shell エディタで DemoApplication.java ファイルを再度開きます。
    cloudshell edit "${HOME}/codelab-o11y/src/main/java/com/example/demo/DemoApplication.java"
    
  6. 現在のコードを、パフォーマンス指標を増分するバージョンに置き換えます。コードを置き換えるには、ファイルの内容を削除してから、次のコードをエディタにコピーします。
    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. Cloud Shell エディタで LoggingEventGoogleCloudEncoder.java ファイルを再度開きます。
    cloudshell edit "${HOME}/codelab-o11y/src/main/java/com/example/demo/LoggingEventGoogleCloudEncoder.java"
    
  8. 現在のコードを、書き込まれたログにトレース属性を追加するバージョンに置き換えます。属性を追加すると、ログを正しいトレーススパンに関連付けることができます。コードを置き換えるには、ファイルの内容を削除してから、次のコードをエディタにコピーします。
    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";
            }
        }
    }
    

数秒後、Cloud Shell エディタは変更を自動的に保存します。

生成 AI アプリケーションのコードを Cloud Run にデプロイする

  1. ターミナル ウィンドウでコマンドを実行して、アプリケーションのソースコードを Cloud Run にデプロイします。
    gcloud run deploy codelab-o11y-service \
         --source="${HOME}/codelab-o11y/" \
         --region=us-central1 \
         --allow-unauthenticated
    
    コマンドで新しいリポジトリが作成されることを知らせるプロンプトが次のように表示されます。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)?
    
    デプロイ プロセスには数分かかることがあります。デプロイ プロセスが完了すると、次のような出力が表示されます。
    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. 表示された Cloud Run サービス URL をコピーして、ブラウザの別のタブまたはウィンドウに貼り付けます。または、ターミナルで次のコマンドを実行してサービス URL を出力し、表示された URL を Ctrl キーを押しながらクリックして URL を開きます。
    gcloud run services list \
         --format='value(URL)' \
         --filter='SERVICE:"codelab-o11y-service"'
    
    URL を開くと、500 エラーが発生するか、次のメッセージが表示されることがあります。
    Sorry, this is just a placeholder...
    
    サービスがデプロイを完了していないことを意味します。しばらく待ってからページを更新します。最後に、犬に関する面白い事実で始まり、犬に関する 10 個の面白い事実を含むテキストが表示されます。

テレメトリー データを生成するには、サービス URL を開きます。?animal= パラメータの値を変更しながらページを更新して、異なる結果を取得します。

アプリケーション トレースを確認する

  1. 下のボタンをクリックして、Cloud コンソールで Trace エクスプローラ ページを開きます。

  2. 最新のトレースのいずれかを選択します。次のスクリーンショットのように、5 つまたは 6 つのスパンが表示されます。
    Trace エクスプローラでのアプリ スパンの表示
  3. イベント ハンドラ(fun_facts メソッド)の呼び出しをトレースするスパンを見つけます。これは、名前が / の最後のスパンになります。
  4. [トレースの詳細] ペインで、[ログとイベント] を選択します。この特定のスパンに関連付けられているアプリケーション ログが表示されます。相関関係は、トレースとログのトレース ID とスパン ID を使用して検出されます。プロンプトと Vertex API のレスポンスを書き込んだアプリケーション ログが表示されます。

カウンタ指標を調べる

  1. 次のボタンをクリックして、Cloud コンソールの [Metrics Explorer] ページを開きます。

  2. クエリビルダー ペインのツールバーで、[< > MQL] または [< > PromQL] という名前のボタンを選択します。ボタンの位置については、下の画像をご覧ください。
    Metrics Explorer の MQL ボタンの位置
  3. [言語] 切り替えで [PromQL] が選択されていることを確認します。言語切り替えボタンは、クエリの書式設定を行うのと同じツールバーにあります。
  4. [Queries] エディタにクエリを入力します。
    sum(rate(workload_googleapis_com:model_call_counter{monitored_resource="generic_task"}[${__interval}]))
    
  5. [クエリを実行] をクリックします。[自動実行] の切り替えが有効になっている場合、[クエリを実行] ボタンは表示されません。

11. (省略可)ログから難読化された機密情報

ステップ 10 では、アプリケーションと Gemini モデルのやり取りに関する情報をロギングしました。この情報には、動物の名前、実際のプロンプト、モデルのレスポンスが含まれていました。この情報をログに保存することは安全ですが、他の多くのシナリオでは必ずしもそうではありません。プロンプトには、ユーザーが保存を望まない個人情報や機密情報が含まれる場合があります。この問題を解決するには、Cloud Logging に書き込まれる機密データを難読化します。コードの変更を最小限に抑えるには、次の解決策をおすすめします。

  1. 受信ログエントリを保存する Pub/Sub トピックを作成する
  2. 取り込まれたログを Pub/Sub トピックにリダイレクトするログシンクを作成します。
  3. 次の手順に沿って、Pub/Sub トピックにリダイレクトされたログを変更する Dataflow パイプラインを作成します。
    1. Pub/Sub トピックからログエントリを読み取る
    2. DLP 検査 API を使用して、エントリのペイロードで機密情報を検査する
    3. DLP 秘匿化メソッドのいずれかを使用して、ペイロード内の機密情報を秘匿化する
    4. 難読化されたログエントリを Cloud Logging に書き込む
  4. パイプラインをデプロイする

12. (省略可)クリーンアップ

この Codelab で使用したリソースと API に対して課金されるリスクを回避するため、ラボを終了したらクリーンアップすることをおすすめします。課金をなくす最も簡単な方法は、コードラボ用に作成したプロジェクトを削除することです。

  1. プロジェクトを削除するには、ターミナルでプロジェクト削除コマンドを実行します。
    PROJECT_ID=$(gcloud config get-value project)
    gcloud projects delete ${PROJECT_ID} --quiet
    
    Cloud プロジェクトを削除すると、そのプロジェクト内で使用されているすべてのリソースと API に対する課金が停止します。次のメッセージが表示されます。ここで、PROJECT_ID はプロジェクト ID です。
    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. (省略可)エラーが発生した場合は、手順 5 を参照して、ラボで使用したプロジェクト ID を確認します。最初の指示のコマンドに置き換えます。たとえば、プロジェクト ID が lab-example-project の場合、コマンドは次のようになります。
    gcloud projects delete lab-project-id-example --quiet
    

13. 完了

このラボでは、Gemini モデルを使用して予測を行う生成 AI アプリケーションを作成しました。また、アプリケーションに不可欠なモニタリングとロギングの機能を実装しました。ソースコードから Cloud Run にアプリケーションと変更をデプロイしました。次に、Google Cloud Observability プロダクトを使用してアプリケーションのパフォーマンスを追跡し、アプリケーションの信頼性を確保します。

本日ご利用いただいたサービスを改善するためのユーザー エクスペリエンス(UX)調査にご協力いただける場合は、こちらからご登録ください

学習を継続するためのオプションをいくつかご紹介します。