שיטות מעשיות למעקב אחרי אפליקציות של AI גנרטיבי ב-Java

1. סקירה כללית

אפליקציות של בינה מלאכותית גנרטיבית דורשות יכולת תצפית כמו כל אפליקציה אחרת. האם יש שיטות מיוחדות של יכולת תצפית שנדרשות לAI גנרטיבי?

בשיעור ה-Lab הזה תלמדו ליצור אפליקציה פשוטה של בינה מלאכותית גנרטיבית. פורסים אותו ב-Cloud Run. וגם להוסיף לו יכולות חיוניות של מעקב ורישום ביומן באמצעות שירותים ומוצרים של ניראות (observability) ב-Google Cloud.

מה תלמדו

  • כתיבת אפליקציה שמשתמשת ב-Vertex AI באמצעות Cloud Shell Editor
  • אחסון קוד האפליקציה ב-GitHub
  • פריסה של קוד המקור של האפליקציה ב-Cloud Run באמצעות CLI של gcloud
  • הוספת יכולות מעקב ורישום ביומן לאפליקציה של AI גנרטיבי
  • שימוש במדדים מבוססי-יומנים
  • הטמעת רישום ביומן ומעקב באמצעות Open Telemetry SDK
  • תובנות לגבי טיפול אחראי בנתונים של AI

2. דרישות מוקדמות

אם אין לכם חשבון Google, תצטרכו ליצור חשבון חדש.

3. הגדרת הפרויקט

  1. נכנסים למסוף Google Cloud באמצעות חשבון Google.
  2. יוצרים פרויקט חדש או בוחרים לעשות שימוש חוזר בפרויקט קיים. כותבים את מזהה הפרויקט שיצרתם או שבחרתם.
  3. מפעילים את החיוב בפרויקט.
    • עלות השלמת שיעור ה-Lab הזה אמורה להיות פחות מ-5$.
    • כדי למנוע חיובים נוספים, תוכלו לפעול לפי השלבים שמפורטים בסוף שיעור ה-Lab הזה כדי למחוק את המשאבים.
    • משתמשים חדשים זכאים לתקופת ניסיון בחינם בשווי 300$‎.
  4. מוודאים שהחיוב מופעל בקטע My projects (הפרויקטים שלי) בחיוב ב-Cloud
    • אם בעמודה Billing account מופיע הערך Billing is disabled בפרויקט החדש:
      1. לוחצים על שלוש הנקודות בעמודה Actions.
      2. לוחצים על שינוי פרטי החיוב.
      3. בוחרים את החשבון לחיוב שבו רוצים להשתמש.
    • אם אתם משתתפים באירוע בשידור חי, סביר להניח שהשם של החשבון יהיה Google Cloud Platform Trial Billing Account.

4. הכנת Cloud Shell Editor

  1. עוברים אל Cloud Shell Editor. אם תוצג ההודעה הבאה עם בקשה לאשר ל-Cloud Shell לבצע קריאה ל-gcloud באמצעות פרטי הכניסה שלכם, לוחצים על Authorize (אישור) כדי להמשיך.
    לוחצים כדי לאשר את Cloud Shell
  2. פותחים את חלון הטרמינל
    1. לוחצים על תפריט שלושת הקווים סמל התפריט של שלושת הקווים.
    2. לוחצים על Terminal (מסוף).
    3. לוחצים על מסוף חדש
      פתיחת טרמינל חדש ב-Cloud Shell Editor.
  3. בתחנה, מגדירים את מזהה הפרויקט:
    gcloud config set project [PROJECT_ID]
    
    מחליפים את [PROJECT_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)?, סביר להניח שהזנתם את מזהה הפרויקט בצורה שגויה. אחרי שמוצאים את מזהה הפרויקט הנכון, לוחצים על N, על Enter ומנסים להריץ שוב את הפקודה gcloud config set project.
  4. (אופציונלי) אם אתם לא מצליחים למצוא את מזהה הפרויקט, תוכלו להריץ את הפקודה הבאה כדי לראות את מזהי הפרויקטים שלכם, ממוינים לפי זמן היצירה בסדר יורד:
    gcloud projects list \
         --format='value(projectId,createTime)' \
         --sort-by=~createTime
    

5. הפעלת Google APIs

במסוף, מפעילים את ממשקי 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 ומכילה פרטי שגיאה כמו בהמשך, צריך לנסות שוב את הפקודה לאחר עיכוב של דקה או שתיים.

"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. יצירת אפליקציה של בינה מלאכותית גנרטיבית

בשלב הזה תכתבו קוד של אפליקציה פשוטה מבוססת-בקשה שמשתמשת במודל Gemini כדי להציג 10 עובדות מעניינות על חיה לבחירתכם. כדי ליצור את קוד האפליקציה, מבצעים את הפעולות הבאות:

  1. בטרמינל, יוצרים את הספרייה codelab-o11y:
    mkdir "${HOME}/codelab-o11y"
    
  2. משנים את הספרייה הנוכחית ל-codelab-o11y:
    cd "${HOME}/codelab-o11y"
    
  3. מורידים את קוד ה-bootstrap של אפליקציית Java באמצעות Spring framework starter:
    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. פותחים את קובץ הארכיון של קוד ה-bootstrap בתיקייה הנוכחית:
    unzip java-starter.zip
    
  5. ומסירים את קובץ הארכיון מהתיקייה:
    rm java-starter.zip
    
  6. יוצרים קובץ project.toml כדי להגדיר את גרסת סביבת זמן הריצה של Java שישמשת לפריסה של הקוד ב-Cloud Run:
    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:
      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. פותחים את הקובץ DemoApplication.java ב-Cloud Shell Editor:
    cloudshell edit "${HOME}/codelab-o11y/src/main/java/com/example/demo/DemoApplication.java"
    
    קוד המקור של הקובץ DemoApplication.java עם שלד אמור להופיע עכשיו בחלון העריכה מעל מסוף ה-CLI. קוד המקור של הקובץ יהיה דומה לזה:
    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 Editor.

פריסת הקוד של אפליקציית ה-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. מעתיקים את כתובת ה-URL של שירות Cloud Run שמוצגת לכרטיסייה או לחלון נפרד בדפדפן. לחלופין, אפשר להריץ את הפקודה הבאה בטרמינל כדי להדפיס את כתובת ה-URL של השירות, וללחוץ על כתובת ה-URL שמוצגת תוך כדי לחיצה על המקש Ctrl כדי לפתוח אותה:
    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 מסוג 'קריאת נתונים' ו'כתיבת נתונים'. כדי לבדוק בקשות של Vertex AI ליצירת תוכן, צריך להפעיל את יומני הביקורת Data Read במסוף Cloud.

  1. לוחצים על הלחצן הבא כדי לפתוח את הדף Audit Logs במסוף Cloud

  2. מוודאים שבפרויקט שנוצר בשיעור ה-Lab הזה נבחר. הפרויקט שנבחר מוצג בפינה הימנית העליונה של הדף, ממש מתחת לתפריט ההמבורגר:
    התפריט הנפתח של פרויקטים במסוף Google Cloud
    אם צריך, בוחרים את הפרויקט הנכון מתיבת הסימון המשולבת.
  3. בטבלה Data Access audit logs configuration, בעמודה Service, מחפשים את השירות Vertex AI API ובוחרים אותו על ידי סימון התיבה שמשמאל לשם השירות.
    בוחרים ב-Vertex AI API.
  4. בחלונית המידע שמשמאל, בוחרים בסוג הביקורת 'קריאת נתונים'.
    בדיקת יומני קריאת הנתונים
  5. לוחצים על שמירה.

כדי ליצור יומני ביקורת, פותחים את כתובת ה-URL של השירות. כדי לקבל תוצאות שונות, אפשר לרענן את הדף תוך שינוי הערך של הפרמטר ?animal=.

עיון ביומני ביקורת

  1. לוחצים על הלחצן הבא כדי לפתוח את הדף Logs Explorer במסוף Cloud:

  2. מדביקים את המסנן הבא בחלונית Query.
    LOG_ID("cloudaudit.googleapis.com%2Fdata_access") AND
    protoPayload.serviceName="aiplatform.googleapis.com"
    
    חלונית השאילתה היא עורך שנמצא בחלק העליון של הדף Logs Explorer:
    שליחת שאילתות ליומני ביקורת
  3. לוחצים על Run query.
  4. בוחרים אחת מהרשומות ביומן הביקורת ומרחיבים את השדות כדי לבדוק את המידע שנשמר ביומן.
    אפשר לראות פרטים על קריאה ל-Vertex API, כולל השיטה והמודל שבהם נעשה שימוש. אפשר גם לראות את הזהות של מבצע הקריאה ואת ההרשאות שהעניקו הרשאה לקריאה.

8. רישום אינטראקציות עם AI גנרטיבי ביומן

פרמטרים של בקשות API או נתוני תגובה לא מופיעים ביומני הביקורת. עם זאת, המידע הזה יכול להיות חשוב לפתרון בעיות באפליקציה ולניתוח תהליכי העבודה. בשלב הזה אנחנו משלימים את הפער הזה על ידי הוספת רישום ביומן של האפליקציה.

ההטמעה משתמשת ב-Logback עם Spring Boot כדי להדפיס יומני אפליקציות לפלט הרגיל. בשיטה הזו נעשה שימוש ביכולת של Cloud Run לתעד מידע שמודפס לפלט הסטנדרטי ולהטמיע אותו ב-Cloud Logging באופן אוטומטי. כדי לתעד מידע כנתונים מובְנים, צריך לעצב את היומנים המודפסים בהתאם. פועלים לפי ההוראות שבהמשך כדי להוסיף לאפליקציה יכולות של רישום ביומן מובנה.

  1. חוזרים לחלון (או לכרטיסייה) של Cloud Shell בדפדפן.
  2. יוצרים קובץ חדש בשם LoggingEventGoogleCloudEncoder.java ופותחים אותו ב-Cloud Shell Editor:
    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. יוצרים קובץ חדש בשם logback.xml ופותחים אותו ב-Cloud Shell Editor:
    cloudshell edit "${HOME}/codelab-o11y/src/main/resources/logback.xml"
    
  5. מעתיקים את ה-XML הבא ומדביקים אותו כדי להגדיר ל-Logback להשתמש במקודד עם ה-appender של 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. פותחים מחדש את הקובץ DemoApplication.java ב-Cloud Shell Editor:
    cloudshell edit "${HOME}/codelab-o11y/src/main/java/com/example/demo/DemoApplication.java"
    
  7. כדי לתעד את הבקשה והתגובה של Gen AI, מחליפים את הקוד בעורך בגרסה שמוצגת בהמשך. כדי להחליף את הקוד, מוחקים את תוכן הקובץ ומעתיקים את הקוד שבהמשך לתוך העורך:
    package com.example.demo;
    
    import java.io.IOException;
    import java.util.Collections;
    
    import javax.annotation.PostConstruct;
    import javax.annotation.PreDestroy;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    import com.google.cloud.ServiceOptions;
    import com.google.cloud.vertexai.VertexAI;
    import com.google.cloud.vertexai.api.GenerateContentResponse;
    import com.google.cloud.vertexai.generativeai.GenerativeModel;
    import com.google.cloud.vertexai.generativeai.ResponseHandler;
    
    @SpringBootApplication
    public class DemoApplication {
    
        public static void main(String[] args) {
            String port = System.getenv().getOrDefault("PORT", "8080");
            SpringApplication app = new SpringApplication(DemoApplication.class);
            app.setDefaultProperties(Collections.singletonMap("server.port", port));
            app.run(args);
        }
    }
    
    @RestController
    class HelloController {
        private final String projectId = ServiceOptions.getDefaultProjectId();
        private VertexAI vertexAI;
        private GenerativeModel model;
        private final Logger LOGGER = LoggerFactory.getLogger(HelloController.class);
    
        @PostConstruct
        public void init() {
            vertexAI = new VertexAI(projectId, "us-central1");
            model = new GenerativeModel("gemini-1.5-flash", vertexAI);
        }
    
        @PreDestroy
        public void destroy() {
            vertexAI.close();
        }
    
        @GetMapping("/")
        public String getFacts(@RequestParam(defaultValue = "dog") String animal) throws IOException {
            String prompt = "Give me 10 fun facts about " + animal + ". Return this as html without backticks.";
            GenerateContentResponse response = model.generateContent(prompt);
            LOGGER.atInfo()
                    .addKeyValue("animal", animal)
                    .addKeyValue("prompt", prompt)
                    .addKeyValue("response", response)
                    .log("Content is generated");
            return ResponseHandler.getText(response);
        }
    }
    

אחרי כמה שניות, השינויים נשמרים באופן אוטומטי ב-Cloud Shell Editor.

פריסת הקוד של אפליקציית ה-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. מעתיקים את כתובת ה-URL של שירות Cloud Run שמוצגת לכרטיסייה או לחלון נפרד בדפדפן. לחלופין, אפשר להריץ את הפקודה הבאה בטרמינל כדי להדפיס את כתובת ה-URL של השירות, וללחוץ על כתובת ה-URL שמוצגת תוך כדי לחיצה על המקש Ctrl כדי לפתוח אותה:
    gcloud run services list \
         --format='value(URL)' \
         --filter='SERVICE:"codelab-o11y-service"'
    
    כשפותחים את כתובת ה-URL, יכול להיות שתופיע הודעת השגיאה 500 או ההודעה הבאה:
    Sorry, this is just a placeholder...
    
    המשמעות היא שהשירותים לא סיימו את הפריסה. ממתינים כמה דקות ומרעננים את הדף. בסוף יופיע טקסט שמתחיל בעובדות משעשעות על כלבים ומכיל 10 עובדות משעשעות על כלבים.

כדי ליצור יומני אפליקציות, פותחים את כתובת ה-URL של השירות. כדי לקבל תוצאות שונות, אפשר לרענן את הדף תוך שינוי הערך של הפרמטר ?animal=.
כדי לראות את יומני האפליקציה:

  1. לוחצים על הלחצן הבא כדי לפתוח את הדף Logs Explorer במסוף Cloud:

  2. מדביקים את המסנן הבא בחלונית Query‏ (#2 בממשק של Logs Explorer):
    LOG_ID("run.googleapis.com%2Fstdout") AND
    severity=DEBUG
    
  3. לוחצים על Run query.

בתוצאת השאילתה מוצגים יומנים עם ההנחיה והתגובה של Vertex AI, כולל דירוגים של בטיחות.

9. ספירת אינטראקציות עם AI גנרטיבי

Cloud Run כותב מדדים מנוהלים שאפשר להשתמש בהם כדי לעקוב אחרי שירותים שנפרסו. מדדי מעקב בניהול המשתמשים מספקים יותר שליטה בנתונים ובתדירות של עדכון המדדים. כדי להטמיע מדד כזה, צריך לכתוב קוד שאוסף נתונים וכותב אותם ב-Cloud Monitoring. בשלב הבא (אופציונלי) מוסבר איך מטמיעים את הפתרון באמצעות OpenTelemetry SDK.

בשלב הזה מוצגת אלטרנטיבה להטמעת מדד משתמשים בקוד – מדדים שמבוססים על יומנים. מדדים שמבוססים על יומנים מאפשרים ליצור מדדי מעקב מהרשומות ביומן שהאפליקציה כותבת ב-Cloud Logging. נשתמש ביומני האפליקציה שהטמענו בשלב הקודם כדי להגדיר מדד שמבוסס על יומנים של מספר הסוגים. המדד יספר את מספר הקריאות המוצלחות ל-Vertex API.

  1. מעיינים בחלון של Logs Explorer שבו השתמשנו בשלב הקודם. מתחת לחלונית השאילתה, מאתרים את התפריט הנפתח Actions ולוחצים עליו כדי לפתוח אותו. התפריט מופיע בצילום המסך שלמטה:
    סרגל הכלים של תוצאות השאילתה עם התפריט הנפתח &#39;פעולות&#39;
  2. בתפריט שנפתח, בוחרים באפשרות Create metric כדי לפתוח את החלונית Create log-based metric.
  3. כדי להגדיר מדד חדש של מונה בחלונית Create log-based metric (יצירת מדד שמבוסס על יומן):
    1. מגדירים את סוג המדד: בוחרים באפשרות מספר.
    2. מגדירים את השדות הבאים בקטע פרטים:
      • Log metric name: מגדירים את השם כ-model_interaction_count. חלות הגבלות מסוימות על שמות. פרטים נוספים זמינים במאמר פתרון בעיות בנושא הגבלות על שמות.
      • תיאור: מזינים תיאור למדד. לדוגמה, Number of log entries capturing successful call to model inference..
      • Units: משאירים את השדה הזה ריק או מזינים את הספרה 1.
    3. משאירים את הערכים בקטע בחירת מסנן. שימו לב שבשדה Build filter מופיע אותו מסנן ששימש אותנו כדי לראות את יומני האפליקציות.
    4. (אופציונלי) מוסיפים תווית שתעזור לספור את מספר השיחות לכל חיה. הערה: התווית הזו עלולה להגדיל באופן משמעותי את עוצמת הקבוצה (cardinality) של המדד, ולא מומלץ להשתמש בה בסביבת הייצור:
      1. לוחצים על הוספת תווית.
      2. מגדירים את השדות הבאים בקטע Labels:
        • Label name: מגדירים את השם כ-animal.
        • תיאור: מזינים את התיאור של התווית. לדוגמה, Animal parameter.
        • Label type (סוג התווית): בוחרים באפשרות STRING.
        • שם השדה: מקלידים jsonPayload.animal.
        • ביטוי רגולרי: משאירים את השדה ריק.
      3. לוחצים על סיום
    5. לוחצים על Create metric (יצירת מדד) כדי ליצור את המדד.

אפשר גם ליצור מדד שמבוסס על יומנים מהדף מדדים שמבוססים על יומנים, באמצעות הפקודה gcloud logging metrics create ב-CLI או באמצעות משאב Terraform של google_logging_metric.

כדי ליצור נתוני מדדים, פותחים את כתובת ה-URL של השירות. מרעננים את הדף הפתוח כמה פעמים כדי לבצע כמה קריאות למודל. כמו קודם, נסו להשתמש בבעלי חיים שונים בפרמטר.

מזינים את שאילתה PromQL כדי לחפש את נתוני המדדים שמבוססים על יומנים. כדי להזין שאילתת PromQL:

  1. לוחצים על הלחצן שלמטה כדי לפתוח את הדף Metrics Explorer במסוף Cloud:

  2. בסרגל הכלים של חלונית הכלי ליצירת שאילתות, בוחרים בלחצן < > MQL או בלחצן < > PromQL. המיקום של הלחצן מוצג בתמונה שבהמשך.
    המיקום של הלחצן &#39;לוח מודעות ליצירת לידים&#39; בכלי הניתוחים של מדדים
  3. מוודאים שהאפשרות PromQL מסומנת במתג Language. המתג לשינוי השפה נמצא באותה סרגל הכלים שמאפשר לעצב את השאילתה.
  4. מזינים את השאילתה בעורך Queries:
    sum(rate(logging_googleapis_com:user_model_interaction_count{monitored_resource="cloud_run_revision"}[${__interval}]))
    
    מידע נוסף על השימוש ב-PromQL זמין במאמר PromQL ב-Cloud Monitoring.
  5. לוחצים על Run Query. יוצג תרשים קו שדומה לצילום המסך הזה:
    הצגת המדדים שהוגשה לגביהם שאילתה

    הערה: כשהלחצן הפעלה אוטומטית מופעל, הכפתור הרצת השאילתה לא מוצג.

10. (אופציונלי) שימוש ב-Open Telemetry למעקב ולניתוח נתונים

כפי שצוין בשלב הקודם, אפשר להטמיע מדדים באמצעות OpenTelemetry (Otel) SDK. מומלץ להשתמש ב-Otel בארכיטקטורות של שירותים מרובים. בשלב הזה נסביר איך מוסיפים מכשירי OTel לאפליקציית Spring Boot. בשלב הזה עליכם לבצע את הפעולות הבאות:

  • הוספת כלים לאפליקציית Spring Boot עם יכולות מעקב אוטומטיות
  • הטמעת מדד מונה כדי לעקוב אחרי מספר הקריאות המוצלחות למודלים
  • קורלציה בין המעקב ליומני האפליקציה

הארכיטקטורה המומלצת לשירותים ברמת המוצר היא להשתמש ב-OTel collector כדי לאסוף ולעבד את כל נתוני הניתנות למדידה מכמה שירותים. הקוד בשלב הזה לא משתמש באוסף, מטעמי פשטות. במקום זאת, הוא משתמש בייצוא של OTel שכותב נתונים ישירות ל-Google Cloud.

הגדרת אפליקציית Spring Boot עם רכיבי OTel ומעקב אוטומטי

  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. מוסיפים את ה-BOM של OpenTelemetry לקובץ 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. פותחים מחדש את הקובץ DemoApplication.java ב-Cloud Shell Editor:
    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. פותחים מחדש את הקובץ LoggingEventGoogleCloudEncoder.java ב-Cloud Shell Editor:
    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 Editor.

פריסת הקוד של אפליקציית ה-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. מעתיקים את כתובת ה-URL של שירות Cloud Run שמוצגת לכרטיסייה או לחלון נפרד בדפדפן. לחלופין, אפשר להריץ את הפקודה הבאה בטרמינל כדי להדפיס את כתובת ה-URL של השירות, וללחוץ על כתובת ה-URL שמוצגת תוך כדי לחיצה על המקש Ctrl כדי לפתוח אותה:
    gcloud run services list \
         --format='value(URL)' \
         --filter='SERVICE:"codelab-o11y-service"'
    
    כשפותחים את כתובת ה-URL, יכול להיות שתופיע הודעת השגיאה 500 או ההודעה הבאה:
    Sorry, this is just a placeholder...
    
    המשמעות היא שהשירותים לא סיימו את הפריסה. ממתינים כמה דקות ומרעננים את הדף. בסוף יופיע טקסט שמתחיל בעובדות משעשעות על כלבים ומכיל 10 עובדות משעשעות על כלבים.

כדי ליצור נתוני טלמטריה, פותחים את כתובת ה-URL של השירות. כדי לקבל תוצאות שונות, אפשר לרענן את הדף תוך שינוי הערך של הפרמטר ?animal=.

ניתוח נתוני מעקב של אפליקציות

  1. לוחצים על הלחצן הבא כדי לפתוח את הדף Trace Explorer במסוף Cloud:

  2. בוחרים אחת מהטראקים האחרונים. אמורים להופיע 5 או 6 span שדומים לצילום המסך שבהמשך.
    תצוגה של טווח האפליקציה ב-Trace Explorer
  3. מאתרים את ה-span שמתעד את הקריאה למטפל באירוע (השיטה fun_facts). זה יהיה ה-span האחרון עם השם /.
  4. בחלונית Trace details בוחרים באפשרות Logs & events. יוצגו יומני אפליקציות שתואמים ל-span הספציפי הזה. הזיהוי של הקורלציה מתבצע באמצעות מזהי המעקב והקטע בנתוני המעקב וביומן. אמורה להופיע יומן האפליקציה שבו נכתבה ההנחיה והתגובה של Vertex API.

הסבר על מדד המונה

  1. לוחצים על הלחצן שלמטה כדי לפתוח את הדף Metrics Explorer במסוף Cloud:

  2. בסרגל הכלים של חלונית הכלי ליצירת שאילתות, בוחרים בלחצן < > MQL או בלחצן < > PromQL. המיקום של הלחצן מוצג בתמונה שבהמשך.
    המיקום של הלחצן &#39;לוח מודעות ליצירת לידים&#39; בכלי הניתוחים של מדדים
  3. מוודאים שהאפשרות PromQL מסומנת במתג Language. המתג לשינוי השפה נמצא באותה סרגל הכלים שמאפשר לעצב את השאילתה.
  4. מזינים את השאילתה בעורך Queries:
    sum(rate(workload_googleapis_com:model_call_counter{monitored_resource="generic_task"}[${__interval}]))
    
  5. לוחצים על הרצת השאילתה.כשהמתג הרצה אוטומטית מופעל, הלחצן הרצת השאילתה לא מוצג.

11. (אופציונלי) ערפול מידע רגיש ביומני

בשלב 10 רשמנו ביומן מידע על האינטראקציה של האפליקציה עם מודל Gemini. המידע הזה כלל את שם החיה, את ההנחיה בפועל ואת התשובה של המודל. אמנם אחסון המידע הזה ביומן אמור להיות בטוח, אבל זה לא בהכרח נכון לגבי תרחישים רבים אחרים. ההנחיה עשויה לכלול מידע אישי או מידע רגיש אחר שהמשתמש לא רוצה שיאוחסן. כדי לטפל בבעיה הזו, אפשר להסתיר את המידע הרגיש שנכתב ב-Cloud Logging. כדי לצמצם את השינויים בקוד, מומלץ להשתמש בפתרון הבא.

  1. יצירת נושא PubSub לאחסון רשומות יומן נכנסות
  2. יוצרים בור נתוני יומנים שמנתב מחדש יומנים שסוננו לנושא PubSub.
  3. יוצרים צינור עיבוד נתונים של Dataflow שמשנה יומנים שמנותבים לנושא PubSub לפי השלבים הבאים:
    1. קריאת רשומה ביומן מהנושא ב-PubSub
    2. בדיקת המטען הייעודי (payload) של הרשומה כדי לאתר מידע אישי רגיש באמצעות DLP inspection API
    3. צנזור המידע הרגיש בתוכן המשא באמצעות אחת משיטות הצנזור של DLP
    4. כתיבת רשומת היומן המעורפלת ב-Cloud Logging
  4. פריסת צינור עיבוד הנתונים

12. (אופציונלי) הסרת המשאבים

כדי להימנע מצבירת חיובים על משאבים וממשקי API שבהם השתמשתם ב-codelab, מומלץ לנקות אחרי סיום הקודלאב. הדרך הקלה ביותר לבטל את החיוב היא למחוק את הפרויקט שיצרתם במסגרת הקודלהב.

  1. כדי למחוק את הפרויקט, מריצים את הפקודה delete project בטרמינל:
    PROJECT_ID=$(gcloud config get-value project)
    gcloud projects delete ${PROJECT_ID} --quiet
    
    מחיקת הפרויקט ב-Cloud תפסיק את החיוב על כל המשאבים ו-APIs שבהם נעשה שימוש באותו פרויקט. אמורה להופיע ההודעה הבאה, שבה PROJECT_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 כדי למצוא את מזהה הפרויקט שבו השתמשתם במהלך הסדנה. מחליפים אותו בפקודה שמופיעה בהוראה הראשונה. לדוגמה, אם מזהה הפרויקט הוא lab-example-project, הפקודה תהיה:
    gcloud projects delete lab-project-id-example --quiet
    

13. מזל טוב

בשיעור ה-Lab הזה יצרתם אפליקציית בינה מלאכותית גנרטיבית שמשתמשת במודל Gemini כדי ליצור תחזיות. וגם הוסיפו לאפליקציה יכולות חיוניות של מעקב ורישום ביומן. פרסתם את האפליקציה ואת השינויים מקוד המקור ב-Cloud Run. לאחר מכן, תוכלו להשתמש במוצרי Google Cloud Observability כדי לעקוב אחרי ביצועי האפליקציה, וכך להבטיח את המהימנות שלה.

אם אתם רוצים להשתתף במחקר בנושא חוויית משתמש (UX) כדי לשפר את המוצרים שבהם עבדתם היום, אפשר להירשם כאן.

ריכזנו כאן כמה אפשרויות להמשך הלמידה: