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 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 への認証情報をリクエストしているというメッセージが表示されたら、[承認] をクリックして続行します。
    クリックして Cloud Shell を承認する
    実行が成功すると、次のようなメッセージが表示されます。
    Updated property [core/project].
    
    WARNING が表示され、Do you want to continue (Y/N)? を求められた場合は、プロジェクト ID が正しく入力されていない可能性があります。正しいプロジェクト ID が見つかったら、N キー、Enter キーを押して、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. Google Cloud SDK の依存関係を pom.xml ファイルに追加します。
    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 パラメータを URL に追加します(?animal=[ANIMAL] など)。ここで、[ANIMAL] は動物の名前です。たとえば、?animal=cat を追加すると猫に関する 10 個の豆知識が、?animal=sea turtle を追加するとウミガメに関する 10 個の豆知識が返されます。

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

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

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

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

  2. このラボ用に作成したプロジェクトがページで選択されていることを確認します。選択したプロジェクトは、ページの左上にあるハンバーガー メニューに表示されます。
    Google Cloud コンソールのプロジェクト プルダウン
    必要に応じて、コンボボックスから正しいプロジェクトを選択します。
  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. Clooud 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. Clooud 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. エディタのコードを以下のバージョンに置き換えて、生成 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. クエリ エディタにクエリを入力します。
    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 Collector を使用して複数のサービスからすべてのオブザーバビリティ データを収集して取り込むことです。このステップのコードでは、わかりやすくするためにコレクタを使用していません。代わりに、データを 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. OpenTelemetry BOM を pom.xml ファイルに追加します。
    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. クエリ エディタにクエリを入力します。
    sum(rate(workload_googleapis_com:model_call_counter{monitored_resource="generic_task"}[${__interval}]))
    
  5. [クエリを実行] をクリックします。[自動実行] の切り替えが有効になっている場合、[クエリを実行] ボタンは表示されません。

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

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

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

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

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

  1. プロジェクトを削除するには、ターミナルで delete project コマンドを実行します。
    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)調査にご協力いただける場合は、こちらからご登録ください。

学習を継続するためのオプションは次のとおりです。