在 Java 中實作生成式 AI 應用程式的實際可觀察性技術

1. 總覽

生成式 AI 應用程式與其他應用程式一樣,都需要可觀測性。生成式 AI 是否需要特殊的觀測技術?

在本實驗室中,您將建立簡單的生成式 AI 應用程式。將其部署至 Cloud Run。並使用 Google Cloud Observability 服務和產品,為其導入必要的監控和記錄功能。

學習目標

  • 使用 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 編輯器。如果系統顯示以下訊息,要求您授權 Cloud Shell 使用憑證呼叫 gcloud,請按一下「Authorize」(授權)繼續操作。
    按一下即可授權 Cloud Shell
  2. 開啟終端機視窗
    1. 按一下漢堡選單 漢堡選單圖示
    2. 按一下「終端機」
    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 有誤。按下 NEnter,找到正確的專案 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 Framework Starter 下載 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
    
  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 服務網址複製到瀏覽器的另一個分頁或視窗。或者,您也可以在終端機中執行下列指令,列印服務網址,然後按住 Ctrl 鍵並點選顯示的網址,開啟該網址:
    gcloud run services list \
         --format='value(URL)' \
         --filter='SERVICE:"codelab-o11y-service"'
    
    開啟網址時,您可能會收到 500 錯誤訊息,或看到以下訊息:
    Sorry, this is just a placeholder...
    
    這表示服務未完成部署作業。請稍候片刻,然後重新整理頁面。最後你會看到以「Fun Dog Facts」(狗狗趣味小知識) 開頭的文字,其中包含 10 個狗狗趣味小知識。

嘗試與應用程式互動,瞭解各種動物的有趣知識。如要這麼做,請將 animal 參數附加至網址,例如 ?animal=[ANIMAL],其中 [ANIMAL] 是動物名稱。舉例來說,附加 ?animal=cat 即可取得 10 個關於貓咪的趣味知識,附加 ?animal=sea turtle 則可取得 10 個關於海龜的趣味知識。

7. 稽核 Vertex API 呼叫

稽核 Google API 呼叫可回答「誰在何時何地呼叫特定 API?」等問題。在排解應用程式問題、調查資源耗用情形或執行軟體鑑識分析時,稽核作業非常重要。

稽核記錄可讓您追蹤管理員和系統活動,以及記錄對「資料讀取」和「資料寫入」API 作業的呼叫。如要稽核生成內容的 Vertex AI 要求,您必須在 Cloud 控制台中啟用「資料讀取」稽核記錄

  1. 按一下下方按鈕,在 Cloud 控制台中開啟「稽核記錄」頁面

  2. 確認頁面已選取您為這個實驗室建立的專案。所選專案會顯示在頁面左上角,漢堡選單的右側:
    Google Cloud 控制台專案下拉式選單
    如有需要,請從下拉式方塊選取正確的專案。
  3. 在「資料存取稽核記錄設定」表格的「服務」欄中,找出 Vertex AI API 服務,然後選取服務名稱左側的核取方塊,選取該服務。
    選取 Vertex AI API
  4. 在右側的資訊面板中,選取「資料讀取」稽核類型。
    檢查資料讀取記錄
  5. 按一下 [儲存]

如要產生稽核記錄,請開啟服務網址。重新整理頁面,同時變更 ?animal= 參數的值,即可取得不同的結果。

瞭解稽核記錄

  1. 按一下下方按鈕,在 Cloud 控制台中開啟「記錄檔探索工具」頁面:

  2. 將下列篩選器貼到「查詢」窗格。
    LOG_ID("cloudaudit.googleapis.com%2Fdata_access") AND
    protoPayload.serviceName="aiplatform.googleapis.com"
    
    「查詢」窗格是位於「Logs Explorer」頁面頂端的編輯器:
    查詢稽核記錄
  3. 點選「執行查詢」
  4. 選取其中一個稽核記錄項目,然後展開欄位,檢查記錄中擷取的資訊。
    您可以查看 Vertex API 呼叫的詳細資料,包括使用的方法和模型。您也可以查看呼叫者的身分,以及授權呼叫的權限。

8. 記錄與生成式 AI 的互動

您不會在稽核記錄中找到 API 要求參數或回應資料。不過,這項資訊對於排解應用程式和工作流程分析問題可能非常重要。在本步驟中,我們將新增應用程式記錄,填補這項缺口。

實作作業會使用 Logback 和 Spring Boot,將應用程式記錄列印至標準輸出。這個方法會運用 Cloud Run 功能,擷取列印至標準輸出的資訊,並自動擷取至 Cloud Logging。如要以結構化資料的形式擷取資訊,請務必按照相應格式列印記錄。請按照下列操作說明,在應用程式中新增結構化記錄功能。

  1. 返回瀏覽器中的「Cloud Shell」視窗 (或分頁)。
  2. 在 Cloud Shell 編輯器中建立並開啟新檔案 LoggingEventGoogleCloudEncoder.java
    cloudshell edit "${HOME}/codelab-o11y/src/main/java/com/example/demo/LoggingEventGoogleCloudEncoder.java"
    
  3. 複製並貼上下列程式碼,實作 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";
            }
        }
    }
    
  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. 將編輯器中的程式碼換成下方的版本,記錄生成式 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 服務網址複製到瀏覽器的另一個分頁或視窗。或者,您也可以在終端機中執行下列指令,列印服務網址,然後按住 Ctrl 鍵並點選顯示的網址,開啟該網址:
    gcloud run services list \
         --format='value(URL)' \
         --filter='SERVICE:"codelab-o11y-service"'
    
    開啟網址時,您可能會收到 500 錯誤訊息,或看到以下訊息:
    Sorry, this is just a placeholder...
    
    這表示服務未完成部署作業。請稍候片刻,然後重新整理頁面。最後你會看到以「Fun Dog Facts」(狗狗趣味小知識) 開頭的文字,其中包含 10 個狗狗趣味小知識。

如要產生應用程式記錄,請開啟服務網址。重新整理頁面,同時變更 ?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. 保留「篩選器選取」部分的值。請注意,「Build filter」(建立篩選器) 欄位與我們用來查看應用程式記錄的篩選器相同。
    4. (選用) 新增標籤,方便計算每種動物的叫聲次數。注意:這個標籤可能會大幅增加指標的基數,因此不建議用於正式環境:
      1. 按一下 [Add label] (新增標籤)
      2. 在「標籤」部分中設定下列欄位:
        • 標籤名稱:將名稱設為 animal
        • 說明:輸入標籤說明。例如:Animal parameter
        • 標籤類型:選取 STRING
        • 欄位名稱:輸入 jsonPayload.animal
        • 規則運算式:留空。
      3. 然後按一下 [完成]
    5. 按一下「建立指標」即可建立指標。

您也可以使用 gcloud logging metrics create CLI 指令google_logging_metric Terraform 資源,從「記錄指標」頁面建立記錄指標。

如要產生指標資料,請開啟服務網址。多次重新整理開啟的頁面,對模型發出多個呼叫。和先前一樣,請嘗試在參數中使用不同動物。

輸入 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。這個步驟會示範如何將 OTel 檢測點新增至 Spring Boot 應用程式。在這個步驟中,您將執行下列操作:

  • 使用自動追蹤功能檢測 Spring Boot 應用程式
  • 導入計數器指標,監控模型呼叫成功次數
  • 將追蹤記錄與應用程式記錄檔建立關聯

建議您使用 OTel 收集器,從多項服務收集及擷取所有可觀測性資料,做為產品層級服務的架構。為簡化程序,這個步驟中的程式碼不會使用收集器。而是使用 OTel 匯出功能,將資料直接寫入 Google Cloud。

使用 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. pom.xml 檔案中新增必要的 OpenTelemetry 依附元件:
    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 服務網址複製到瀏覽器的另一個分頁或視窗。或者,您也可以在終端機中執行下列指令,列印服務網址,然後按住 Ctrl 鍵並點選顯示的網址,開啟該網址:
    gcloud run services list \
         --format='value(URL)' \
         --filter='SERVICE:"codelab-o11y-service"'
    
    開啟網址時,您可能會收到 500 錯誤訊息,或看到以下訊息:
    Sorry, this is just a placeholder...
    
    這表示服務未完成部署作業。請稍候片刻,然後重新整理頁面。最後你會看到以「Fun Dog Facts」(狗狗趣味小知識) 開頭的文字,其中包含 10 個狗狗趣味小知識。

如要產生遙測資料,請開啟服務網址。重新整理頁面,同時變更 ?animal= 參數的值,即可取得不同的結果。

探索應用程式追蹤記錄

  1. 按一下下方按鈕,在 Cloud 控制台中開啟 Trace 探索工具頁面:

  2. 選取其中一個最近的追蹤記錄。您應該會看到 5 或 6 個類似下方螢幕截圖的範圍。
    在 Trace 探索器中查看應用程式時距
  3. 找出追蹤事件處理常式 (fun_facts 方法) 呼叫的範圍。這是最後一個名為「/」的範圍。
  4. 在「追蹤詳細資料」窗格中,選取「記錄和事件」。您會看到與這個特定時距相關聯的應用程式記錄檔。系統會使用追蹤記錄和記錄檔中的追蹤記錄和時距 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. 建立 PubSub 主題,用來儲存傳入的記錄項目
  2. 建立記錄接收器,將擷取的記錄重新導向至 Pub/Sub 主題。
  3. 按照下列步驟建立 Dataflow 管道,修改重新導向至 PubSub 主題的記錄:
    1. 從 Pub/Sub 主題讀取記錄檔項目
    2. 使用 DLP 檢查 API 檢查項目酬載是否含有機密資訊
    3. 使用其中一種 DLP 遮蓋方法,遮蓋酬載中的機密資訊
    4. 將經過模糊處理的記錄項目寫入 Cloud Logging
  4. 部署管道

12. (選用) 清除

為避免因使用程式碼研究室的資源和 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. 恭喜

在本實驗室中,您已建立生成式 AI 應用程式,並使用 Gemini 模型進行預測。並透過基礎的監控和記錄功能檢測應用程式。您已將應用程式和原始碼的變更內容部署至 Cloud Run。接著,您可以使用 Google Cloud Observability 產品追蹤應用程式效能,確保應用程式的可靠性。

如要參與使用者體驗研究,協助我們改善您今天使用的產品,請按這裡註冊

以下提供幾種繼續學習的方式: