この Codelab について
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. プロジェクトの設定
- Google アカウントを使用して Google Cloud コンソールにログインします。
- 新しいプロジェクトを作成するか、既存のプロジェクトを再利用します。作成または選択したプロジェクトのプロジェクト ID をメモします。
- プロジェクトの課金を有効にします。
- このラボの完了にかかる料金は $5 未満です。
- このラボの最後にある手順に沿ってリソースを削除すると、それ以上の請求が発生しなくなります。
- 新規ユーザーは、300 米ドル分の無料トライアルをご利用いただけます。
- Cloud 課金システムの [マイ プロジェクト] で課金が有効になっていることを確認します。
- 新しいプロジェクトの
Billing account
列にBilling is disabled
と表示されている場合:- [
Actions
] 列のその他アイコンをクリックします。 - [お支払い情報を変更] をクリックします。
- 使用する請求先アカウントを選択します。
- [
- ライブ イベントに参加している場合、アカウントの名前は Google Cloud Platform 無料トライアルの請求先アカウント になる可能性があります。
- 新しいプロジェクトの
4. Cloud Shell エディタを準備する
- Cloud Shell エディタに移動します。認証情報を使用して gcloud を呼び出すことを Cloud Shell に承認するよう求めるメッセージが表示されたら、[承認] をクリックして続行します。
- ターミナル ウィンドウを開く
- ハンバーガー メニュー
をクリックします。
- [Terminal] をクリックします。
- [New Terminal
] をクリックします。
- ハンバーガー メニュー
- ターミナルで、プロジェクト ID を構成します。
gcloud config set project [PROJECT_ID]
[PROJECT_ID]
は、プロジェクトの ID に置き換えます。たとえば、プロジェクト ID がlab-example-project
の場合、コマンドは次のようになります。 gcloud が GCPI API への認証情報をリクエストしているというメッセージが表示されたら、[承認] をクリックして続行します。gcloud config set project lab-project-id-example
実行が成功すると、次のようなメッセージが表示されます。Updated property [core/project].
WARNING
が表示され、Do you want to continue (Y/N)?
を求められた場合は、プロジェクト ID が正しく入力されていない可能性があります。正しいプロジェクト ID が見つかったら、N
キー、Enter
キーを押して、gcloud config set project
コマンドをもう一度実行してみてください。 - (省略可)プロジェクト 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 Go アプリケーションを作成する
このステップでは、Gemini モデルを使用して、選択した動物に関する 10 個の興味深い情報を表示する、シンプルなリクエストベースのアプリケーションのコードを記述します。次の手順でアプリケーション コードを作成します。
- ターミナルで
codelab-o11y
ディレクトリを作成します。mkdir ~/codelab-o11y
- 現在のディレクトリを
codelab-o11y
に変更します。cd ~/codelab-o11y
- Go モジュールを初期化します。
go mod init codelab
- Vertex AI SDK for Go をインストールします。
go get cloud.google.com/go/vertexai/genai
- Go 用のメタデータ ライブラリをインストールして、現在のプロジェクト ID を取得します。
go get cloud.google.com/go/compute/metadata
setup.go
ファイルを作成し、Cloud Shell エディタで開きます。 初期化コードをホストするために使用されます。cloudshell edit setup.go
setup.go
という名前の新しい空のファイルがエディタ ウィンドウに表示されます。- 次のコードをコピーして、開いた
setup.go
ファイルに貼り付けます。package main
import (
"context"
"os"
"cloud.google.com/go/compute/metadata"
)
func projectID(ctx context.Context) (string, error) {
var projectID = os.Getenv("GOOGLE_CLOUD_PROJECT")
if projectID == "" {
return metadata.ProjectIDWithContext(ctx)
}
return projectID, nil
} - ターミナル ウィンドウに戻り、次のコマンドを実行して Cloud Shell エディタで
main.go
ファイルを作成して開きます。 ターミナルの上にあるエディタ ウィンドウに空のファイルが表示されます。画面は次のようになります。cloudshell edit main.go
- 次のコードをコピーして、開いた
main.go
ファイルに貼り付けます。 数秒後、Cloud Shell エディタによってコードが自動的に保存されます。package main
import (
"context"
"fmt"
"net/http"
"os"
"cloud.google.com/go/vertexai/genai"
)
var model *genai.GenerativeModel
func main() {
ctx := context.Background()
projectID, err := projectID(ctx)
if err != nil {
return
}
var client *genai.Client
client, err = genai.NewClient(ctx, projectID, "us-central1")
if err != nil {
return
}
defer client.Close()
model = client.GenerativeModel("gemini-1.5-flash-001")
http.HandleFunc("/", Handler)
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
if err := http.ListenAndServe(":"+port, nil); err != nil {
return
}
}
func Handler(w http.ResponseWriter, r *http.Request) {
animal := r.URL.Query().Get("animal")
if animal == "" {
animal = "dog"
}
prompt := fmt.Sprintf("Give me 10 fun facts about %s. Return the results as HTML without markdown backticks.", animal)
resp, err := model.GenerateContent(r.Context(), genai.Text(prompt))
if err != nil {
w.WriteHeader(http.StatusTooManyRequests)
return
}
if len(resp.Candidates) > 0 && len(resp.Candidates[0].Content.Parts) > 0 {
htmlContent := resp.Candidates[0].Content.Parts[0]
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprint(w, htmlContent)
}
}
生成 AI アプリケーションのコードを Cloud Run にデプロイする
- ターミナル ウィンドウでコマンドを実行して、アプリケーションのソースコードを Cloud Run にデプロイします。
コマンドで新しいリポジトリが作成されることを知らせるプロンプトが表示されます。[gcloud run deploy codelab-o11y-service \
--source="${HOME}/codelab-o11y/" \
--region=us-central1 \
--allow-unauthenticatedEnter
] をクリックします。 デプロイ プロセスには数分かかることがあります。デプロイ プロセスが完了すると、次のような出力が表示されます。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
- 表示された Cloud Run サービスの URL を、ブラウザの別のタブまたはウィンドウにコピーします。または、ターミナルで次のコマンドを実行してサービス URL を出力し、表示された URL をクリックして Ctrl キーを押しながら URL を開きます。
URL を開くと、500 エラーが発生したり、次のようなメッセージが表示されたりすることがあります。gcloud run services list \
--format='value(URL)' \
--filter='SERVICE:"codelab-o11y-service"' サービスがデプロイを完了していないことを意味します。しばらく待ってからページを更新します。最後に、犬に関する面白い情報で始まるテキストが表示され、犬に関する 10 の面白い情報が記載されています。Sorry, this is just a placeholder...
アプリを操作して、さまざまな動物に関する豆知識を調べてみましょう。そのためには、animal
パラメータを URL に追加します(?animal=[ANIMAL]
のようにします。ここで、[ANIMAL]
は動物の名前です)。たとえば、?animal=cat
を追加すると猫に関する 10 個の豆知識が、?animal=sea turtle
を追加するとウミガメに関する 10 個の豆知識が返されます。
7. Vertex API 呼び出しを監査する
Google API 呼び出しを監査すると、「特定の API を呼び出したのは誰か、いつ、どこで呼び出したか」などの質問に答えることができます。監査は、アプリケーションのトラブルシューティング、リソース使用量の調査、ソフトウェアのフォレンジック分析を行う際に重要です。
監査ログを使用すると、管理アクティビティとシステム アクティビティを追跡できるほか、「データ読み取り」API オペレーションと「データ書き込み」API オペレーションの呼び出しをログに記録できます。コンテンツを生成するための Vertex AI リクエストを監査するには、Cloud コンソールで「データ読み取り」監査ログを有効にする必要があります。
- 下のボタンをクリックして、Cloud コンソールの [監査ログ] ページを開きます。
- このラボ用に作成したプロジェクトがページで選択されていることを確認します。選択したプロジェクトは、ページの左上にあるハンバーガー メニューに表示されます。
必要に応じて、コンボボックスから正しいプロジェクトを選択します。 - [データアクセス監査ログの構成] テーブルの [サービス] 列で、
Vertex AI API
サービスを見つけて、サービス名の左側にあるチェックボックスをオンにして、サービスを選択します。 - 右側の情報パネルで、監査タイプとして [データ読み取り] を選択します。
- [保存] をクリックします。
監査ログを生成するには、サービスの URL を開きます。?animal=
パラメータの値を変更しながらページを更新して、異なる結果を取得します。
監査ログを確認する
- 次のボタンをクリックして、Cloud コンソールでログ エクスプローラ ページを開きます。
- 次のフィルタを [クエリ] ペインに貼り付けます。
クエリペインは、ログ エクスプローラ ページの上部にあるエディタです。LOG_ID("cloudaudit.googleapis.com%2Fdata_access") AND
protoPayload.serviceName="aiplatform.googleapis.com" - [クエリを実行] をクリックします。
- 監査ログエントリのいずれかを選択し、フィールドを開いてログにキャプチャされた情報を調べます。
Vertex API 呼び出しの詳細(使用されたメソッドやモデルなど)を確認できます。また、呼び出し元の ID と、呼び出しを承認した権限も確認できます。
8. 生成 AI とのインタラクションをロギングする
監査ログに API リクエスト パラメータやレスポンス データが記録されていません。ただし、この情報は、アプリケーションとワークフローの分析のトラブルシューティングに役立ちます。このステップでは、アプリケーション ロギングを追加してこのギャップを埋めます。ロギングでは、標準の Go log/slog
パッケージを使用して構造化ログを書き込みます。log/slog
パッケージは、Google Cloud にログを書き込む方法を認識していません。標準出力への書き込みをサポートしています。ただし、Cloud Run には、標準出力に出力された情報をキャプチャして Cloud Logging に自動的に取り込む機能があります。構造化ログを正しく取得するには、出力されるログを適切にフォーマットする必要があります。以下の手順に沿って、Go アプリケーションに構造化ロギング機能を追加します。
- ブラウザの [Cloud Shell] ウィンドウ(またはタブ)に戻ります。
- ターミナルで
setup.go
を再度開きます。cloudshell edit ~/codelab-o11y/setup.go
- コードを、ロギングを設定するバージョンに置き換えます。コードを置き換えるには、ファイルの内容を削除し、次のコードをコピーしてエディタに貼り付けます。
package main
import (
"context"
"os"
"log/slog"
"cloud.google.com/go/compute/metadata"
)
func projectID(ctx context.Context) (string, error) {
var projectID = os.Getenv("GOOGLE_CLOUD_PROJECT")
if projectID == "" {
return metadata.ProjectIDWithContext(ctx)
}
return projectID, nil
}
func setupLogging() {
opts := &slog.HandlerOptions{
Level: slog.LevelDebug,
ReplaceAttr: func(group []string, a slog.Attr) slog.Attr {
switch a.Key {
case slog.LevelKey:
a.Key = "severity"
if level := a.Value.Any().(slog.Level); level == slog.LevelWarn {
a.Value = slog.StringValue("WARNING")
}
case slog.MessageKey:
a.Key = "message"
case slog.TimeKey:
a.Key = "timestamp"
}
return a
},
}
jsonHandler := slog.NewJSONHandler(os.Stdout, opts)
slog.SetDefault(slog.New(jsonHandler))
} - ターミナルに戻り、
main.go
を再度開きます。cloudshell edit ~/codelab-o11y/main.go
- アプリケーション コードを、モデルとのインタラクションをログに記録するバージョンに置き換えます。コードを置き換えるには、ファイルの内容を削除し、次のコードをコピーしてエディタに貼り付けます。
package main
import (
"context"
"fmt"
"net/http"
"os"
"encoding/json"
"log/slog"
"cloud.google.com/go/vertexai/genai"
)
var model *genai.GenerativeModel
func main() {
ctx := context.Background()
projectID, err := projectID(ctx)
if err != nil {
return
}
setupLogging()
var client *genai.Client
client, err = genai.NewClient(ctx, projectID, "us-central1")
if err != nil {
slog.ErrorContext(ctx, "Failed to marshal response to JSON", slog.Any("error", err))
os.Exit(1)
}
defer client.Close()
model = client.GenerativeModel("gemini-1.5-flash-001")
http.HandleFunc("/", Handler)
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
if err := http.ListenAndServe(":"+port, nil); err != nil {
slog.ErrorContext(ctx, "Failed to start the server", slog.Any("error", err))
os.Exit(1)
}
}
func Handler(w http.ResponseWriter, r *http.Request) {
animal := r.URL.Query().Get("animal")
if animal == "" {
animal = "dog"
}
prompt := fmt.Sprintf("Give me 10 fun facts about %s. Return the results as HTML without markdown backticks.", animal)
resp, err := model.GenerateContent(r.Context(), genai.Text(prompt))
if err != nil {
w.WriteHeader(http.StatusTooManyRequests)
return
}
jsonBytes, err := json.Marshal(resp)
if err != nil {
slog.Error("Failed to marshal response to JSON", slog.Any("error", err))
} else {
slog.DebugContext(r.Context(), "content is generated", slog.String("animal", animal),
slog.String("prompt", prompt), slog.String("response", string(jsonBytes)))
}
if len(resp.Candidates) > 0 && len(resp.Candidates[0].Content.Parts) > 0 {
htmlContent := resp.Candidates[0].Content.Parts[0]
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprint(w, htmlContent)
}
}
ロギングは、ログを stdout
に出力するように構成されています。ここで、ログは Cloud Run Logging エージェントによって収集され、非同期で Cloud Logging に取り込まれます。main()
関数を変更して、構造化されたフォーマット ガイドラインに準拠した JSON スキーマを使用するように Go 標準の構造化ログを設定します。すべての return
ステートメントは、終了前にエラーログを書き込むコードに置き換えられます。Handler()
関数は、Vertex AI API 呼び出しからレスポンスを受信したときに構造化ログを書き込むように計測されています。ログには、リクエストの動物パラメータ、モデルのプロンプトとレスポンスがキャプチャされます。
数秒後、Cloud Shell エディタによって変更が自動的に保存されます。
生成 AI アプリケーションのコードを Cloud Run にデプロイする
- ターミナル ウィンドウでコマンドを実行して、アプリケーションのソースコードを Cloud Run にデプロイします。
コマンドで新しいリポジトリが作成されることを知らせるプロンプトが表示されます。[gcloud run deploy codelab-o11y-service \
--source="${HOME}/codelab-o11y/" \
--region=us-central1 \
--allow-unauthenticatedEnter
] をクリックします。 デプロイ プロセスには数分かかることがあります。デプロイ プロセスが完了すると、次のような出力が表示されます。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
- 表示された Cloud Run サービスの URL を、ブラウザの別のタブまたはウィンドウにコピーします。または、ターミナルで次のコマンドを実行してサービス URL を出力し、表示された URL をクリックして Ctrl キーを押しながら URL を開きます。
URL を開くと、500 エラーが発生するか、次のようなメッセージが表示されることがあります。gcloud run services list \
--format='value(URL)' \
--filter='SERVICE:"codelab-o11y-service"' サービスがデプロイを完了していないことを意味します。しばらく待ってからページを更新します。最後に、犬に関する面白い情報で始まるテキストが表示され、犬に関する 10 の面白い情報が記載されています。Sorry, this is just a placeholder...
アプリケーション ログを生成するには、サービス URL を開きます。?animal=
パラメータの値を変更しながらページを更新して、異なる結果を取得します。
アプリケーション ログを表示する手順は次のとおりです。
- 次のボタンをクリックして、Cloud コンソールでログ エクスプローラ ページを開きます。
- 次のフィルタを [クエリ] ペイン(ログ エクスプローラ インターフェースの 2)に貼り付けます。
LOG_ID("run.googleapis.com%2Fstdout") AND
severity=DEBUG - [クエリを実行] をクリックします。
クエリの結果には、プロンプトと Vertex AI レスポンス(安全性評価を含む)を含むログが表示されます。
9. 生成 AI でインタラクションをカウントする
Cloud Run は、デプロイされたサービスをモニタリングするために使用できるマネージド指標を書き込みます。ユーザー管理のモニタリング指標を使用すると、データと指標の更新頻度をより細かく制御できます。このような指標を実装するには、データを収集して Cloud Monitoring に書き込むコードを記述する必要があります。OpenTelemetry SDK を使用して実装する方法については、次の(省略可)手順をご覧ください。
このステップでは、コードにユーザー指標を実装する代替方法であるログベースの指標について説明します。ログベースの指標を使用すると、アプリケーションが Cloud Logging に書き込むログエントリからモニタリング指標を生成できます。前の手順で実装したアプリケーション ログを使用して、タイプ カウンタのログベースの指標を定義します。この指標は、Vertex API への成功した呼び出しの数をカウントします。
- 前の手順で使用したログ エクスプローラのウィンドウを確認します。[クエリ] ペインで [アクション] プルダウン メニューを見つけてクリックし、開きます。メニューは以下のスクリーンショットをご覧ください。
- 開いたメニューで [指標を作成] を選択し、[ログベースの指標を作成] パネルを開きます。
- 次の手順に沿って、[ログベースの指標を作成] パネルで新しいカウンタ指標を構成します。
- [指標タイプ] を [カウンタ] に設定します。
- [詳細] セクションで次のフィールドを設定します。
- ログ指標の名前: 名前を
model_interaction_count
に設定します。命名に関する制限事項が適用されます。詳細については、命名制限のトラブルシューティングをご覧ください。 - 説明: 指標の説明を入力します。例:
Number of log entries capturing successful call to model inference.
- 単位: 空白のままにするか、数字「
1
」を挿入します。
- ログ指標の名前: 名前を
- [フィルタの選択] セクションの値はそのままにします。[フィルタを作成] フィールドには、アプリケーション ログの表示に使用したフィルタと同じフィルタが設定されています。
- (省略可)動物ごとの呼び出し回数をカウントするラベルを追加します。注: このラベルを使用すると、指標のカーディナリティが大幅に増加する可能性があるため、本番環境での使用はおすすめしません。
- [ラベルを追加] をクリックします。
- [ラベル] セクションで次のフィールドを設定します。
- ラベル名: 名前を
animal
に設定します。 - 説明: ラベルの説明を入力します。例:
Animal parameter
- ラベルタイプ:
STRING
を選択します。 - フィールド名:
jsonPayload.animal
と入力します。 - 正規表現: 空白のままにします。
- ラベル名: 名前を
- [完了] をクリックします
- [指標を作成] をクリックして、メトリックを作成します。
[ログベースの指標] ページから、gcloud logging metrics create
CLI コマンドまたは google_logging_metric
Terraform リソースを使用してログベースの指標を作成することもできます。
指標データを生成するには、サービスの URL を開きます。開いたページを数回更新して、モデルを複数回呼び出します。前回と同様に、パラメータで別の動物を使用してみます。
PromQL クエリを入力して、ログベースの指標データを検索します。PromQL クエリを入力する手順は次のとおりです。
- 次のボタンをクリックして、Cloud コンソールで Metrics Explorer ページを開きます。
- クエリビルダー ペインのツールバーで、[< > MQL] または [< > PromQL] という名前のボタンを選択します。ボタンの場所については、下の画像をご覧ください。
- [言語] 切り替えで [PromQL] が選択されていることを確認します。言語切り替えボタンは、クエリの書式設定を行うのと同じツールバーにあります。
- クエリ エディタにクエリを入力します。
PromQL の使用の詳細については、Cloud Monitoring の PromQL をご覧ください。sum(rate(logging_googleapis_com:user_model_interaction_count{monitored_resource="cloud_run_revision"}[${__interval}]))
- [RUN QUERY] をクリックします。次のスクリーンショットに示すような折れ線グラフが表示されます。
[自動実行] の切り替えが有効になっている場合、[クエリを実行] ボタンは表示されません。
10. (省略可)モニタリングとトレースに Open Telemetry を使用する
前の手順で説明したように、OpenTelemetry(Otel)SDK を使用して指標を実装できます。マイクロサービス アーキテクチャで OTel を使用することをおすすめします。このステップでは、次のことを説明します。
- アプリケーションのトレースとモニタリングをサポートするように OTel コンポーネントを初期化する
- Cloud Run 環境のリソース メタデータで OTel 構成にデータを入力する
- 自動トレース機能を使用して Flask アプリケーションを計測する
- 成功したモデル呼び出しの数をモニタリングするカウンタ指標を実装する
- トレースとアプリケーション ログを関連付ける
プロダクト レベルのサービスに推奨されるアーキテクチャは、OTel コレクタを使用して、1 つ以上のサービスのすべてのオブザーバビリティ データを収集して取り込むことです。このステップのコードでは、わかりやすくするためにコレクタを使用していません。代わりに、データを Google Cloud に直接書き込む OTel エクスポートを使用します。
トレースおよび指標モニタリング用の OTel コンポーネントを設定する
- ブラウザの [Cloud Shell] ウィンドウ(またはタブ)に戻ります。
- ターミナルで
setup.go
を再度開きます。cloudshell edit ~/codelab-o11y/setup.go
- コードを、OpenTelemetry のトレースと指標の収集を初期化するバージョンに置き換えます。コードを置き換えるには、ファイルの内容を削除し、次のコードをコピーしてエディタに貼り付けます。
package main
import (
"context"
"errors"
"fmt"
"net/http"
"os"
"log/slog"
"go.opentelemetry.io/contrib/detectors/gcp"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/contrib/propagators/autoprop"
"go.opentelemetry.io/otel"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.27.0"
"go.opentelemetry.io/otel/trace"
cloudmetric "github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric"
cloudtrace "github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace"
"cloud.google.com/go/compute/metadata"
)
var (
projID string
)
func projectID(ctx context.Context) (string, error) {
var projectID = os.Getenv("GOOGLE_CLOUD_PROJECT")
if projectID == "" {
return metadata.ProjectIDWithContext(ctx)
}
return projectID, nil
}
func setupLogging() {
opts := &slog.HandlerOptions{
Level: slog.LevelDebug,
ReplaceAttr: func(group []string, a slog.Attr) slog.Attr {
switch a.Key {
case slog.LevelKey:
a.Key = "severity"
if level := a.Value.Any().(slog.Level); level == slog.LevelWarn {
a.Value = slog.StringValue("WARNING")
}
case slog.MessageKey:
a.Key = "message"
case slog.TimeKey:
a.Key = "timestamp"
}
return a
},
}
jsonHandler := slog.NewJSONHandler(os.Stdout, opts)
instrumentedHandler := handlerWithSpanContext(jsonHandler)
slog.SetDefault(slog.New(instrumentedHandler))
}
type spanContextLogHandler struct {
slog.Handler
}
func handlerWithSpanContext(handler slog.Handler) *spanContextLogHandler {
return &spanContextLogHandler{Handler: handler}
}
func (t *spanContextLogHandler) Handle(ctx context.Context, record slog.Record) error {
if s := trace.SpanContextFromContext(ctx); s.IsValid() {
trace := fmt.Sprintf("projects/%s/traces/%s", projID, s.TraceID())
record.AddAttrs(
slog.Any("logging.googleapis.com/trace", trace),
)
record.AddAttrs(
slog.Any("logging.googleapis.com/spanId", s.SpanID()),
)
record.AddAttrs(
slog.Bool("logging.googleapis.com/trace_sampled", s.TraceFlags().IsSampled()),
)
}
return t.Handler.Handle(ctx, record)
}
func setupTelemetry(ctx context.Context) (shutdown func(context.Context) error, err error) {
var shutdownFuncs []func(context.Context) error
shutdown = func(ctx context.Context) error {
var err error
for _, fn := range shutdownFuncs {
err = errors.Join(err, fn(ctx))
}
shutdownFuncs = nil
return err
}
projID, err = projectID(ctx)
if err != nil {
err = errors.Join(err, shutdown(ctx))
return
}
res, err2 := resource.New(
ctx,
resource.WithDetectors(gcp.NewDetector()),
resource.WithTelemetrySDK(),
resource.WithAttributes(semconv.ServiceNameKey.String(os.Getenv("K_SERVICE"))),
)
if err2 != nil {
err = errors.Join(err2, shutdown(ctx))
return
}
otel.SetTextMapPropagator(autoprop.NewTextMapPropagator())
texporter, err2 := cloudtrace.New(cloudtrace.WithProjectID(projID))
if err2 != nil {
err = errors.Join(err2, shutdown(ctx))
return
}
tp := sdktrace.NewTracerProvider(
sdktrace.WithSampler(sdktrace.AlwaysSample()),
sdktrace.WithResource(res),
sdktrace.WithBatcher(texporter))
shutdownFuncs = append(shutdownFuncs, tp.Shutdown)
otel.SetTracerProvider(tp)
mexporter, err2 := cloudmetric.New(cloudmetric.WithProjectID(projID))
if err2 != nil {
err = errors.Join(err2, shutdown(ctx))
return
}
mp := sdkmetric.NewMeterProvider(
sdkmetric.WithReader(sdkmetric.NewPeriodicReader(mexporter)),
sdkmetric.WithResource(res),
)
shutdownFuncs = append(shutdownFuncs, mp.Shutdown)
otel.SetMeterProvider(mp)
return shutdown, nil
}
func registerHttpHandler(route string, handleFn http.HandlerFunc) {
instrumentedHandler := otelhttp.NewHandler(otelhttp.WithRouteTag(route, handleFn), route)
http.Handle(route, instrumentedHandler)
} - ターミナルに戻り、次のコマンドを実行して
go.mod
ファイル内の Go モジュール定義を更新します。go mod tidy
- ターミナルに戻り、
main.go
を再度開きます。cloudshell edit ~/codelab-o11y/main.go
- 現在のコードを、HTTP トレース計測を行い、パフォーマンス指標を書き込むバージョンに置き換えます。コードを置き換えるには、ファイルの内容を削除し、次のコードをコピーしてエディタに貼り付けます。
package main
import (
"context"
"errors"
"fmt"
"net/http"
"os"
"encoding/json"
"log/slog"
"cloud.google.com/go/vertexai/genai"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
)
var model *genai.GenerativeModel
var counter metric.Int64Counter
const scopeName = "genai-o11y/go/workshop/example"
func main() {
ctx := context.Background()
projectID, err := projectID(ctx)
if err != nil {
return
}
setupLogging()
shutdown, err := setupTelemetry(ctx)
if err != nil {
slog.ErrorContext(ctx, "error setting up OpenTelemetry", slog.Any("error", err))
os.Exit(1)
}
meter := otel.Meter(scopeName)
counter, err = meter.Int64Counter("model_call_counter")
if err != nil {
slog.ErrorContext(ctx, "error setting up OpenTelemetry", slog.Any("error", err))
os.Exit(1)
}
var client *genai.Client
client, err = genai.NewClient(ctx, projectID, "us-central1")
if err != nil {
slog.ErrorContext(ctx, "Failed to marshal response to JSON", slog.Any("error", err))
os.Exit(1)
}
defer client.Close()
model = client.GenerativeModel("gemini-1.5-flash-001")
registerHttpHandler("/", Handler)
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
if err = errors.Join(http.ListenAndServe(":"+port, nil), shutdown(ctx)); err != nil {
slog.ErrorContext(ctx, "Failed to start the server", slog.Any("error", err))
os.Exit(1)
}
}
func Handler(w http.ResponseWriter, r *http.Request) {
animal := r.URL.Query().Get("animal")
if animal == "" {
animal = "dog"
}
prompt := fmt.Sprintf("Give me 10 fun facts about %s. Return the results as HTML without markdown backticks.", animal)
resp, err := model.GenerateContent(r.Context(), genai.Text(prompt))
if err != nil {
w.WriteHeader(http.StatusTooManyRequests)
return
}
jsonBytes, err := json.Marshal(resp)
if err != nil {
slog.ErrorContext(r.Context(), "Failed to marshal response to JSON", slog.Any("error", err))
} else {
slog.DebugContext(r.Context(), "content is generated", slog.String("animal", animal),
slog.String("prompt", prompt), slog.String("response", string(jsonBytes)))
}
if len(resp.Candidates) > 0 && len(resp.Candidates[0].Content.Parts) > 0 {
clabels := []attribute.KeyValue{attribute.Key("animal").String(animal)}
counter.Add(r.Context(), 1, metric.WithAttributes(clabels...))
htmlContent := resp.Candidates[0].Content.Parts[0]
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprint(w, htmlContent)
}
}
これで、アプリケーションは OpenTelemetry SDK を使用して、トレースでコード実行を計測し、成功した実行回数を指標としてカウントする実装を行うようになりました。main()
メソッドが変更され、Google Cloud Tracing と Monitoring に直接書き込むトレースと指標の OpenTelemetry エクスポータが設定されるようになりました。また、追加の構成を実行して、収集されたトレースおよび指標に Cloud Run 環境に関連するメタデータを入力します。Handler()
関数は、Vertex AI API 呼び出しが有効な結果を返すたびに指標カウンタをインクリメントするように更新されます。
数秒後、Cloud Shell エディタによって変更が自動的に保存されます。
生成 AI アプリケーションのコードを Cloud Run にデプロイする
- ターミナル ウィンドウでコマンドを実行して、アプリケーションのソースコードを Cloud Run にデプロイします。
コマンドで新しいリポジトリが作成されることを知らせるプロンプトが表示されます。[gcloud run deploy codelab-o11y-service \
--source="${HOME}/codelab-o11y/" \
--region=us-central1 \
--allow-unauthenticatedEnter
] をクリックします。 デプロイ プロセスには数分かかることがあります。デプロイ プロセスが完了すると、次のような出力が表示されます。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
- 表示された Cloud Run サービスの URL を、ブラウザの別のタブまたはウィンドウにコピーします。または、ターミナルで次のコマンドを実行してサービス URL を印刷し、表示された URL をクリックして Ctrl キーを押しながら URL を開きます。
URL を開くと、500 エラーが発生するか、次のようなメッセージが表示されることがあります。gcloud run services list \
--format='value(URL)' \
--filter='SERVICE:"codelab-o11y-service"' サービスがデプロイを完了していないことを意味します。しばらく待ってからページを更新します。最後に、犬に関する面白い情報で始まるテキストが表示され、犬に関する 10 の面白い情報が記載されています。Sorry, this is just a placeholder...
テレメトリー データを生成するには、サービスの URL を開きます。?animal=
パラメータの値を変更しながらページを更新して、異なる結果を取得します。
アプリケーション トレースを調べる
- 次のボタンをクリックして、Cloud コンソールで Trace エクスプローラ ページを開きます。
- 最新のトレースを選択します。次のスクリーンショットのような 5 ~ 6 つのスパンが表示されます。
- イベント ハンドラ(
fun_facts
メソッド)への呼び出しをトレースするスパンを探します。/
という名前の最後のスパンが対象です。 - [トレースの詳細] ペインで、[ログとイベント] を選択します。この特定のスパンに関連するアプリケーション ログが表示されます。相関は、トレース内とログ内のトレース ID とスパン ID を使用して検出されます。プロンプトと Vertex API のレスポンスを書き込んだアプリケーション ログが表示されます。
カウンタ指標を確認する
- 次のボタンをクリックして、Cloud コンソールで Metrics Explorer ページを開きます。
- クエリビルダー ペインのツールバーで、[< > MQL] または [< > PromQL] という名前のボタンを選択します。ボタンの場所については、下の画像をご覧ください。
- [言語] 切り替えで [PromQL] が選択されていることを確認します。言語切り替えボタンは、クエリの書式設定を行うのと同じツールバーにあります。
- クエリ エディタにクエリを入力します。
sum(rate(workload_googleapis_com:model_call_counter{monitored_resource="generic_task"}[${__interval}]))
- [クエリを実行] をクリックします。[自動実行] の切り替えが有効になっている場合、[クエリを実行] ボタンは表示されません。
11. (省略可)ログから難読化された機密情報
ステップ 10 では、アプリケーションが Gemini モデルとやり取りした際の情報がログに記録されました。この情報には、動物の名前、実際のプロンプト、モデルのレスポンスが含まれていました。この情報をログに保存することは安全ですが、他の多くのシナリオでは必要ありません。プロンプトには、ユーザーが保存を希望しない個人情報や機密情報が含まれている場合があります。この問題に対処するには、Cloud Logging に書き込まれる機密データを難読化します。コードの変更を最小限に抑えるには、次のソリューションをおすすめします。
- 受信したログエントリを保存する Pub/Sub トピックを作成する
- 取り込まれたログを PubSub トピックにリダイレクトするログシンクを作成します。
- 次の手順に沿って、Pub/Sub トピックにリダイレクトされたログを変更する Dataflow パイプラインを作成します。
- Pub/Sub トピックからログエントリを読み取る
- DLP 検査 API を使用して、エントリのペイロードに機密情報がないか検査する
- DLP の秘匿化方法のいずれかを使用して、ペイロード内の機密情報を秘匿化する
- 難読化されたログエントリを Cloud Logging に書き込む
- パイプラインをデプロイする
12. (省略可)クリーンアップ
Codelab で使用したリソースと API に対して課金されるリスクを回避するため、Lab の終了後にクリーンアップすることをおすすめします。課金を停止する最も簡単な方法は、Codelab 用に作成したプロジェクトを削除することです。
- プロジェクトを削除するには、ターミナルで delete project コマンドを実行します。
Cloud プロジェクトを削除すると、そのプロジェクト内で使用されているすべてのリソースと API に対する課金が停止します。次のようなメッセージが表示されます。ここでPROJECT_ID=$(gcloud config get-value project)
gcloud projects delete ${PROJECT_ID} --quietPROJECT_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.
- (省略可)エラーが発生した場合は、手順 5 でラボで使用したプロジェクト ID を確認します。それを最初の手順のコマンドに置き換えます。たとえば、プロジェクト ID が
lab-example-project
の場合、コマンドは次のようになります。gcloud projects delete lab-project-id-example --quiet
13. 完了
このラボでは、Gemini モデルを使用して予測を行う生成 AI アプリケーションを作成しました。必要なモニタリング機能とロギング機能をアプリケーションに実装しました。アプリケーションと変更をソースコードから Cloud Run にデプロイしました。次に、Google Cloud Observability プロダクトを使用してアプリケーションのパフォーマンスを追跡し、アプリケーションの信頼性を確保します。
本日ご利用いただいたプロダクトの改善につながるユーザー エクスペリエンス(UX)調査にご協力いただける場合は、こちらからご登録ください。
学習を継続するためのオプションは次のとおりです。
- Codelab Gemini を搭載したチャットアプリを Cloud Run にデプロイする方法
- Codelab Cloud Run で Gemini 関数呼び出しを使用する方法
- Cloud Run Jobs Video Intelligence API を使用して動画をシーンごとに処理する方法
- オンデマンド ワークショップ Google Kubernetes Engine オンボーディング
- アプリケーション ログを使用してカウンタ指標と分布指標を構成する方法の詳細
- OpenTelemetry サイドカーを使用して OTLP 指標を書き込む
- Google Cloud での Open Telemetry の使用に関するリファレンス