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 的「我的專案」
-
中已啟用結帳功能。
- 如果新專案的
Billing account
欄顯示Billing is disabled
,請按照下列步驟操作:- 按一下
Actions
欄中的三點圖示 - 按一下「變更帳單」
- 選取要使用的帳單帳戶
- 按一下
- 如果您參加的是實體活動,帳戶名稱可能會是「Google Cloud Platform 試用帳單帳戶」
- 如果新專案的
4. 準備 Cloud Shell 編輯器
- 前往 Cloud Shell 編輯器。如果系統顯示以下訊息,要求您授權 Cloud Shell 使用您的憑證呼叫 gcloud,請按一下「授權」繼續操作。
- 開啟終端機視窗
- 按一下漢堡選單
- 按一下「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 應用程式
在這個步驟中,您將編寫簡單的以要求為基礎的應用程式程式碼,該程式碼會使用 Gemini 模型,顯示您選擇的動物的 10 個有趣事實。請按照以下步驟建立應用程式程式碼。
- 在終端機中建立
codelab-o11y
目錄:mkdir "${HOME}/codelab-o11y"
- 將目前的目錄變更為
codelab-o11y
:cd "${HOME}/codelab-o11y"
- 使用 Spring 架構啟動工具下載 Java 應用程式的 Bootstrap 程式碼:
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
- 將引導程式碼解壓縮至目前的資料夾:
unzip java-starter.zip
- 並從資料夾中移除封存檔案:
rm java-starter.zip
- 建立
project.toml
檔案,定義將程式碼部署至 Cloud Run 時要使用的 Java 執行階段版本:cat > "${HOME}/codelab-o11y/project.toml" << EOF [[build.env]] name = "GOOGLE_RUNTIME_VERSION" value = "17" EOF
- 將 Google Cloud SDK 依附元件新增至
pom.xml
檔案:- 新增 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"
- 新增 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"
- 新增 Google Cloud Core 套件:
- 在 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); } }
- 將編輯器中的程式碼替換為下方顯示的版本。如要取代程式碼,請刪除檔案內容,然後將下列程式碼複製到編輯器中:
幾秒後,Cloud Shell 編輯器會自動儲存程式碼。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); } }
將 Gen AI 應用程式的程式碼部署至 Cloud Run
- 在終端機視窗中執行指令,將應用程式的原始碼部署至 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
- 將顯示的 Cloud Run 服務網址複製到瀏覽器的其他分頁或視窗中。或者,您也可以在終端機中執行下列指令,列印服務網址,然後按住 Ctrl 鍵點選顯示的網址,開啟網址:
開啟網址時,您可能會收到 500 錯誤或看到以下訊息:gcloud run services list \ --format='value(URL)' \ --filter='SERVICE:"codelab-o11y-service"'
表示服務未完成部署。請稍候片刻,然後重新整理頁面。最後,您會看到開頭為「有趣的狗狗小知識」的文字,其中包含 10 個關於狗狗的小知識。Sorry, this is just a placeholder...
請嘗試與應用程式互動,瞭解不同動物的趣味知識。如要執行這項操作,請將 animal
參數附加到網址,例如 ?animal=[ANIMAL]
,其中 [ANIMAL]
是動物名稱。舉例來說,附加 ?animal=cat
可取得 10 個關於貓咪的趣味小知識,附加 ?animal=sea turtle
則可取得 10 個關於海龜的趣味小知識。
7. 稽核 Vertex API 呼叫
稽核 Google API 呼叫可回答「誰在何時何地呼叫特定 API?」等問題。在排解應用程式問題、調查資源耗用情形或執行軟體鑑識分析時,稽核作業十分重要。
稽核記錄可讓您追蹤管理和系統活動,並記錄「資料讀取」和「資料寫入」API 作業的呼叫。如要稽核 Vertex AI 產生內容的要求,您必須在 Cloud 控制台中啟用「資料讀取」稽核記錄。
- 按一下下方按鈕,在 Cloud 控制台中開啟「稽核記錄」頁面
- 請確認頁面已選取您為本實驗室建立的專案。選取的專案會顯示在頁面左上角的漢堡選單中:
如有需要,請從組合框選取正確的專案。 - 在「資料存取稽核記錄設定」表格中,在「Service」(服務) 欄中找到
Vertex AI API
服務,然後選取服務名稱左側的核取方塊,即可選取該服務。 - 在右側的資訊面板中,選取「資料讀取」稽核類型。
- 按一下 [儲存]。
如要產生稽核記錄,請開啟服務網址。變更 ?animal=
參數的值,然後重新整理頁面,即可取得不同的結果。
探索稽核記錄
- 按一下下方按鈕,在 Cloud 控制台中開啟「Logs Explorer」(記錄檔探索工具) 頁面:
- 將下列篩選器貼到「查詢」窗格中。
「查詢」窗格是位於「Logs Explorer」頁面頂端附近的編輯器:LOG_ID("cloudaudit.googleapis.com%2Fdata_access") AND protoPayload.serviceName="aiplatform.googleapis.com"
- 點選「執行查詢」
- 選取其中一個稽核記錄項目,然後展開欄位,查看記錄中擷取的資訊。
您可以查看 Vertex API 呼叫的詳細資料,包括所用的方法和模型。您也可以查看叫用者的身分,以及呼叫的授權權限。
8. 記錄與生成式 AI 的互動情形
稽核記錄中沒有 API 要求參數或回應資料。不過,這項資訊對於應用程式和工作流程分析的疑難排解作業可能相當重要。在這個步驟中,我們會新增應用程式記錄,以填補這個空白。
實作會使用 Logback 搭配 Spring Boot,將應用程式記錄列印至標準輸出。這個方法採用 Cloud Run 的功能,可擷取輸出至標準輸出的資訊,並自動將資訊匯入 Cloud Logging。為了以結構化資料擷取資訊,請務必按照適當格式輸出記錄。請按照下列操作說明,為應用程式新增結構化記錄功能。
- 返回瀏覽器中的「Cloud Shell」視窗 (或分頁)。
- 在 Cloud Shell 編輯器中建立並開啟新檔案
LoggingEventGoogleCloudEncoder.java
:cloudshell edit "${HOME}/codelab-o11y/src/main/java/com/example/demo/LoggingEventGoogleCloudEncoder.java"
- 複製並貼上下列程式碼,實作 Logback 編碼器,將記錄編碼為字串化 JSON,並遵循 Google Cloud 結構化記錄格式:
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"; } } }
- 在 Cloud Shell 編輯器中建立並開啟新檔案
logback.xml
:cloudshell edit "${HOME}/codelab-o11y/src/main/resources/logback.xml"
- 複製並貼上下列 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>
- 在 Cloud Shell 編輯器中重新開啟
DemoApplication.java
檔案:cloudshell edit "${HOME}/codelab-o11y/src/main/java/com/example/demo/DemoApplication.java"
- 將編輯器中的程式碼替換為下方顯示的版本,以記錄 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 編輯器會自動儲存變更。
將 Gen AI 應用程式的程式碼部署至 Cloud Run
- 在終端機視窗中執行指令,將應用程式的原始碼部署至 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
- 將顯示的 Cloud Run 服務網址複製到瀏覽器的其他分頁或視窗中。或者,您也可以在終端機中執行下列指令,列印服務網址,然後按住 Ctrl 鍵點選顯示的網址,開啟網址:
開啟網址時,您可能會收到 500 錯誤或看到以下訊息:gcloud run services list \ --format='value(URL)' \ --filter='SERVICE:"codelab-o11y-service"'
表示服務未完成部署。請稍候片刻,然後重新整理頁面。最後,您會看到開頭為「有趣的狗狗小知識」的文字,其中包含 10 個關於狗狗的小知識。Sorry, this is just a placeholder...
如要產生應用程式記錄,請開啟服務網址。變更 ?animal=
參數的值,然後重新整理頁面,即可取得不同的結果。
如要查看應用程式記錄,請按照下列步驟操作:
- 按一下下方按鈕,在 Cloud 控制台中開啟「Logs Explorer」(記錄檔探索工具) 頁面:
- 將下列篩選器貼到「查詢」窗格 (記錄檔探索工具介面中的 #2):
LOG_ID("run.googleapis.com%2Fstdout") AND severity=DEBUG
- 點選「執行查詢」
查詢結果會顯示含有提示和 Vertex AI 回應的記錄,包括安全評分。
9. 計算與生成式 AI 的互動次數
Cloud Run 會寫入受管理指標,可用於監控已部署的服務。使用者管理的監控指標可讓您進一步控管資料和指標更新頻率。如要實作這類指標,您必須編寫程式碼來收集資料,並將資料寫入 Cloud Monitoring。請參閱下一個 (選用) 步驟,瞭解如何使用 OpenTelemetry SDK 導入這項功能。
這個步驟會說明在程式碼中導入使用者指標的替代做法,也就是以記錄為基礎的指標。您可以使用記錄指標,根據應用程式寫入 Cloud Logging 的記錄項目產生監控指標。我們會使用先前步驟中實作的應用程式記錄,定義 type counter 的記錄式指標。指標會計算成功呼叫 Vertex API 的次數。
- 請查看我們在上一個步驟中使用的「Logs Explorer」視窗。在「查詢」窗格下方找到「動作」下拉式選單,然後點選該選單來開啟。請參考下方螢幕截圖,找出選單:
- 在開啟的選單中選取「Create metric」,即可開啟「Create log-based metric」面板。
- 請按照下列步驟,在「Create log-based metric」面板中設定新的計數器指標:
- 設定「指標類型」:選取「計數器」。
- 在「詳細資料」部分設定下列欄位:
- 記錄指標名稱:將名稱設為
model_interaction_count
。存在一些命名限制;詳情請參閱疑難排解一文。 - 說明:輸入指標說明。例如:
Number of log entries capturing successful call to model inference.
- 單位:請將這個欄位留空或輸入數字
1
。
- 記錄指標名稱:將名稱設為
- 保留「Filter selection」部分的值。請注意,「Build filter」欄位包含用來查看應用程式記錄的相同篩選器。
- (選用) 新增標籤,以便計算每種動物的呼叫次數。注意:這個標籤可能會大幅增加指標的基數,因此不建議在實際工作環境中使用:
- 按一下 [Add label] (新增標籤)。
- 在「標籤」部分設定下列欄位:
- 標籤名稱:將名稱設為
animal
。 - 說明:輸入標籤的說明。例如
Animal parameter
。 - 標籤類型:選取
STRING
。 - 欄位名稱:輸入
jsonPayload.animal
。 - 規則運算式:留空。
- 標籤名稱:將名稱設為
- 然後按一下 [完成]。
- 點選「建立指標」建立指標。
您也可以使用 gcloud logging metrics create
CLI 指令或 google_logging_metric
Terraform 資源,在「以記錄為依據的指標」頁面建立以記錄為依據的指標。
如要產生指標資料,請開啟服務網址。重新整理已開啟的頁面多次,以便多次呼叫模型。如同先前所述,請嘗試在參數中使用其他動物。
輸入 PromQL 查詢,以便搜尋記錄指標資料。如要輸入 PromQL 查詢,請按照下列步驟操作:
- 按一下下方按鈕,在 Cloud 控制台中開啟「Metrics Explorer」頁面:
- 在查詢建構工具窗格中的工具列中,選取名稱為 < > MQL 或 < > PromQL 的按鈕。請參閱下圖,瞭解按鈕的位置。
- 確認「Language」切換按鈕中已選取「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。這個步驟會示範如何在 Spring Boot 應用程式中加入 OTel 檢測功能。在這個步驟中,您將執行下列操作:
- 使用自動追蹤功能為 Spring Boot 應用程式檢測
- 實作計數器指標,監控成功的模型呼叫次數
- 將追蹤記錄與應用程式記錄相關聯
產品層級服務的建議架構是使用 OTel 收集器,收集及攝入多項服務的所有可觀察性資料。為簡化程序,本步驟中的程式碼不會使用收集器。而是使用 OTel 匯出功能,直接將資料寫入 Google Cloud。
設定含有 OTel 元件和自動追蹤功能的 Spring Boot 應用程式
- 返回瀏覽器中的「Cloud Shell」視窗 (或分頁)。
- 在終端機中,使用其他設定參數更新
application.permissions
檔案: 這些參數可定義將可觀察性資料匯出至 Cloud Trace 和 Cloud Monitoring,並強制取樣所有追蹤記錄。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
- 將必要的 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"
- 將 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"
- 在 Cloud Shell 編輯器中重新開啟
DemoApplication.java
檔案:cloudshell edit "${HOME}/codelab-o11y/src/main/java/com/example/demo/DemoApplication.java"
- 將目前的程式碼替換成可增加成效指標的版本。如要取代程式碼,請刪除檔案內容,然後將下列程式碼複製到編輯器中:
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); } }
- 在 Cloud Shell 編輯器中重新開啟
LoggingEventGoogleCloudEncoder.java
檔案:cloudshell edit "${HOME}/codelab-o11y/src/main/java/com/example/demo/LoggingEventGoogleCloudEncoder.java"
- 將目前的程式碼替換為可在寫入記錄中新增追蹤屬性的版本。新增這些屬性後,記錄檔就能與正確的追蹤區間建立關聯。如要取代程式碼,請刪除檔案內容,然後將下列程式碼複製到編輯器中:
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 編輯器會自動儲存變更。
將 Gen AI 應用程式的程式碼部署至 Cloud Run
- 在終端機視窗中執行指令,將應用程式的原始碼部署至 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
- 將顯示的 Cloud Run 服務網址複製到瀏覽器的其他分頁或視窗中。或者,您也可以在終端機中執行下列指令,列印服務網址,然後按住 Ctrl 鍵點選顯示的網址,開啟網址:
開啟網址時,您可能會收到 500 錯誤或看到以下訊息:gcloud run services list \ --format='value(URL)' \ --filter='SERVICE:"codelab-o11y-service"'
表示服務未完成部署。請稍候片刻,然後重新整理頁面。最後,您會看到開頭為「有趣的狗狗小知識」的文字,其中包含 10 個關於狗狗的小知識。Sorry, this is just a placeholder...
如要產生遙測資料,請開啟服務網址。變更 ?animal=
參數的值,然後重新整理頁面,即可取得不同的結果。
探索應用程式追蹤記錄
- 按一下下方按鈕,在 Cloud 控制台中開啟「Trace explorer」(追蹤記錄探索工具) 頁面:
- 選取最近的追蹤記錄。您應該會看到 5 或 6 個 span,如下方螢幕截圖所示。
- 找出追蹤事件處理常式 (
fun_facts
方法) 呼叫的跨度。這個 span 是最後一個名稱為/
的 span。 - 在「Trace details」窗格中,選取「Logs & events」。您會看到與這個特定時距相關聯的應用程式記錄檔。系統會使用追蹤記錄和記錄中的追蹤記錄和時距 ID 偵測關聯性。您應該會看到寫入提示和 Vertex API 回應的應用程式記錄。
探索計數器指標
- 按一下下方按鈕,在 Cloud 控制台中開啟「Metrics Explorer」頁面:
- 在查詢建構工具窗格中的工具列中,選取名稱為 < > MQL 或 < > PromQL 的按鈕。請參閱下圖,瞭解按鈕的位置。
- 確認「Language」切換按鈕中已選取「PromQL」。語言切換鈕位於可用於設定查詢格式的工具列中。
- 在「查詢」編輯器中輸入查詢:
sum(rate(workload_googleapis_com:model_call_counter{monitored_resource="generic_task"}[${__interval}]))
- 按一下「執行查詢」。如果已啟用「自動執行」切換鈕,系統就不會顯示「執行查詢」按鈕。
11. (選用) 從記錄中模糊處理機密資訊
在步驟 10 中,我們記錄了應用程式與 Gemini 模型互動的相關資訊。這項資訊包括動物的名稱、實際提示,以及模型的回應。雖然將這類資訊儲存在記錄中應該是安全的,但在許多其他情況下,這並非必要。提示訊息可能包含使用者不想儲存的個人或其他機密資訊。為解決這個問題,您可以模糊處理寫入 Cloud Logging 的機密資料。為盡量減少程式碼修改作業,建議您採用下列解決方案。
- 建立 Pub/Sub 主題,用於儲存傳入的記錄項目
- 建立記錄接收器,將擷取的記錄重新導向至 Pub/Sub 主題。
- 建立 Dataflow 管道,按照下列步驟修改重新導向至 Pub/Sub 主題的記錄:
- 讀取 Pub/Sub 主題中的記錄項目
- 使用 DLP 檢查 API 檢查項目的酬載內容是否含有機密資訊
- 使用其中一種 DLP 遮蓋方法遮蓋酬載中的機密資訊
- 將經過模糊處理的記錄項目寫入 Cloud Logging
- 部署管道
12. (選用) 清除
為避免產生費用,建議您在完成實驗室後清理實驗室中使用的資源和 API。如要避免付費,最簡單的方法就是刪除您為了程式碼研究室建立的專案。
- 如要刪除專案,請在終端機中執行刪除專案指令:
刪除 Cloud 專案後,系統就會停止對專案使用的所有資源和 API 收取費用。您應該會看到以下訊息,其中PROJECT_ID=$(gcloud config get-value project) gcloud projects delete ${PROJECT_ID} --quiet
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.
- (選用) 如果您收到錯誤訊息,請參閱步驟 5 找出您在實驗室中使用的專案 ID。將其替換為第一個指令中的指令。舉例來說,如果您的專案 ID 是
lab-example-project
,指令會是:gcloud projects delete lab-project-id-example --quiet
13. 恭喜
在本實驗室中,您建立了使用 Gemini 模型進行預測的 Gen AI 應用程式。並為應用程式提供必要的監控和記錄功能。您已將應用程式和原始碼變更部署至 Cloud Run。接著,您可以使用 Google Cloud Observability 產品追蹤應用程式的效能,確保應用程式可靠。
如想參與使用者體驗 (UX) 研究,協助改善您目前使用的產品,請按這裡註冊。
以下提供一些繼續學習的選項:
- 程式碼研究室:如何在 Cloud Run 上部署 Gemini 支援的即時通訊應用程式
- 程式碼研究室:如何搭配使用 Gemini 函式呼叫與 Cloud Run
- 如何使用 Cloud Run 工作單元 Video Intelligence API 逐場處理影片
- 隨選工作坊:Google Kubernetes Engine 新手上路
- 進一步瞭解如何使用應用程式記錄設定計數器和分布指標
- 使用 OpenTelemetry 副車寫入 OTLP 指標
- 參考資料:在 Google Cloud 中使用 Open Telemetry