Phân tích và gắn nhãn thư trong Gmail bằng Gemini và Vertex AI

Bạn có 5 phút rảnh không?
Hãy giúp chúng tôi cải thiện tài liệu về Google Workspace bằng cách trả lời một bản khảo sát trực tuyến ngắn.

Giải pháp này sử dụng Vertex AI và Gemini để phân tích các thư trong Gmail và gắn nhãn cho các thư đó dựa trên cảm xúc của người dùng.

Trình độ lập trình: Trung cấp
Thời lượng: 30 phút
Loại dự án: Tiện ích bổ sung cho Google Workspace

  • Một tiện ích bổ sung của Google Workspace giúp mở rộng Gmail trong một thanh bên.
    Hình 1: Tiện ích bổ sung Phân tích tình cảm hiển thị một thanh bên trong Gmail, nơi người dùng có thể nhắc Gemini phân tích và áp dụng nhãn cho các thư dựa trên tình cảm.
  • Thư có cảm xúc trung tính trong Gmail.
    Hình 2: Tiện ích bổ sung gắn nhãn GIỌNG ĐIỆU TRUNG LẬP 😐 cho một thư trong Gmail.
  • Một thư Gmail có cảm xúc tích cực.
    Hình 3: Tiện ích bổ sung gắn nhãn HAPPY TONE 😊 cho một thư trong Gmail.
  • Thư Gmail có cảm xúc tiêu cực.
    Hình 4: Tiện ích bổ sung gắn nhãn GIỌNG ĐIỆU BẤT MÃN 😡 cho một thư trong Gmail.

Mục tiêu

  • Tìm hiểu chức năng của giải pháp.
  • Tìm hiểu những việc mà các dịch vụ của Google thực hiện trong giải pháp.
  • Thiết lập môi trường.
  • Thiết lập dự án Google Apps Script.
  • Chạy tập lệnh.

Giới thiệu về giải pháp này

Ảnh chụp màn hình tiện ích bổ sung Phân tích tình cảm của Google Workspace

Đây là một tiện ích bổ sung của Google Workspace, có chức năng áp dụng nhãn dựa trên cảm xúc của các thư trong Gmail. Để phân tích nội dung tin nhắn, tiện ích bổ sung này sử dụng Vertex AI để nhắc mô hình Gemini 2.5 Flash và trả về một trong các cảm xúc sau:

  • Tích cực
  • Tiêu cực
  • Bình thường

Khi nhận được câu trả lời từ Gemini, tiện ích bổ sung sẽ áp dụng nhãn Gmail tương ứng cho thư.

Để giới hạn yêu cầu đối với Vertex AI API, tiện ích bổ sung này chỉ phân tích và áp dụng nhãn cho 10 thư gần đây nhất trong hộp thư đến của người dùng Gmail. Để tìm hiểu thêm về hạn mức và giới hạn, hãy truy cập vào tài liệu về Vertex AI.

Cách hoạt động

Giải pháp này được xây dựng trong Google Apps Script và sử dụng các dịch vụ cũng như sản phẩm sau đây của Google:

  • Vertex AI API – Nhắc mô hình Gemini 2.5 Flash phân tích nội dung của thư trong Gmail và xác định cảm xúc.
  • Các dịch vụ của Apps Script:

    • Dịch vụ Gmail – Truy xuất và áp dụng nhãn cho thư trong Gmail. Không bắt buộc, tạo thư mẫu để kiểm thử tiện ích bổ sung.
    • Dịch vụ thẻ – Tạo giao diện người dùng của tiện ích bổ sung xuất hiện dưới dạng một thanh bên trong Gmail.
    • Dịch vụ tìm nạp URL – Kết nối với Vertex AI API để phân tích quan điểm.
    • Dịch vụ tập lệnh – Để gọi Vertex AI API, hãy lấy mã truy cập OAuth 2.0 cho tiện ích bổ sung bằng phương thức getOAuthToken.

Điều kiện tiên quyết

Thiết lập môi trường

Phần này giải thích cách định cấu hình và thiết lập môi trường của bạn trong Google Cloud Console và Apps Script.

Định cấu hình dự án trên Cloud trong Google Cloud Console

Phần này hướng dẫn bạn cách bật Vertex AI API và định cấu hình màn hình đồng ý OAuth trong dự án Cloud.

Bật Vertex AI API

  1. Trong Google Cloud Console, hãy mở dự án Google Cloud của bạn rồi bật Vertex AI API:

    Bật API

  2. Xác nhận rằng bạn đang bật API trong dự án Cloud chính xác, sau đó nhấp vào Tiếp theo.

  3. Xác nhận rằng bạn đang bật đúng API, sau đó nhấp vào Bật.

Định cấu hình màn hình xin phép bằng OAuth

Tiện ích bổ sung của Google Workspace yêu cầu phải có cấu hình màn hình đồng ý. Việc định cấu hình màn hình đồng ý OAuth của tiện ích bổ sung sẽ xác định nội dung mà Google hiển thị cho người dùng.

  1. Trong bảng điều khiển Google Cloud, hãy chuyển đến phần Trình đơn > > Thương hiệu.

    Chuyển đến phần Thương hiệu

  2. Nếu đã định cấu hình , bạn có thể định cấu hình các chế độ cài đặt sau đây cho Màn hình đồng ý OAuth trong phần Thương hiệu, Đối tượngQuyền truy cập vào dữ liệu. Nếu bạn thấy thông báo chưa được định cấu hình, hãy nhấp vào Bắt đầu:
    1. Trong phần Thông tin về ứng dụng, hãy nhập tên cho ứng dụng trong mục Tên ứng dụng.
    2. Trong phần Email hỗ trợ người dùng, hãy chọn một địa chỉ email hỗ trợ mà người dùng có thể liên hệ với bạn nếu họ có thắc mắc về sự đồng ý của mình.
    3. Nhấp vào Tiếp theo.
    4. Trong phần Đối tượng người xem, hãy chọn Nội bộ.
    5. Nhấp vào Tiếp theo.
    6. Trong phần Thông tin liên hệ, hãy nhập Địa chỉ email để bạn có thể nhận thông báo về mọi thay đổi đối với dự án của mình.
    7. Nhấp vào Tiếp theo.
    8. Trong phần Hoàn tất, hãy xem kỹ Chính sách dữ liệu người dùng của dịch vụ API của Google. Nếu bạn đồng ý, hãy chọn Tôi đồng ý với Chính sách dữ liệu người dùng của dịch vụ API của Google.
    9. Nhấp vào Tiếp tục.
    10. Nhấp vào Tạo.
  3. Hiện tại, bạn có thể bỏ qua bước thêm phạm vi. Trong tương lai, khi tạo một ứng dụng để sử dụng bên ngoài tổ chức Google Workspace, bạn phải thay đổi Loại người dùng thành Bên ngoài. Sau đó, hãy thêm các phạm vi uỷ quyền mà ứng dụng của bạn yêu cầu. Để tìm hiểu thêm, hãy xem hướng dẫn đầy đủ về cách Định cấu hình sự đồng ý của OAuth.

Tạo và thiết lập dự án Apps Script

Để tạo và thiết lập dự án Apps Script cho tiện ích bổ sung, hãy hoàn tất các bước sau:

  1. Nhấp vào nút sau để mở dự án Apps Script Phân tích cảm xúc trong Gmail bằng Gemini và Vertex AI.
    Mở dự án Apps Script

  2. Nhấp vào Tổng quan .

  3. Trên trang tổng quan, hãy nhấp vào biểu tượng Tạo bản sao Biểu tượng để tạo bản sao.

  4. Lấy số dự án trên Cloud:

    1. Trong Bảng điều khiển Google Cloud, hãy chuyển đến phần Trình đơn > IAM và Quản trị > Cài đặt.

      Chuyển đến phần IAM và Cài đặt quản trị

    2. Sao chép giá trị trong trường Số dự án.
  5. Kết nối dự án trên Cloud với dự án Apps Script:

    1. Trong dự án Apps Script mà bạn đã sao chép, hãy nhấp vào Cài đặt dự án Biểu tượng cho chế độ cài đặt dự án.
    2. Trong mục Dự án trên Google Cloud Platform (GCP), hãy nhấp vào Thay đổi dự án.
    3. Trong mục Số dự án trên GCP, hãy dán số dự án trên Cloud.
    4. Nhấp vào Đặt dự án.

Kiểm thử tiện ích bổ sung

Để dùng thử tiện ích bổ sung, hãy cài đặt một bản triển khai thử nghiệm rồi mở tiện ích bổ sung đó trong Gmail:

  1. Tạo và cài đặt một bản triển khai kiểm thử Apps Script:
    1. Trong dự án Apps Script mà bạn đã sao chép, hãy nhấp vào Trình chỉnh sửa .
    2. Mở tệp Code.gs rồi nhấp vào Chạy. Khi được nhắc, hãy cho phép tập lệnh chạy.
    3. Nhấp vào Triển khai > Kiểm thử việc triển khai.
    4. Nhấp vào Cài đặt > Xong.
  2. Mở Gmail.

    Truy cập vào Gmail

  3. Trên thanh bên phải, hãy mở tiện ích bổ sung Phân tích tình cảm.

  4. Nếu được nhắc, hãy cho phép tiện ích bổ sung.

  5. Không bắt buộc: Để tạo thông báo kiểm thử bằng tiện ích bổ sung, hãy nhấp vào Tạo email mẫu. Ba thư xuất hiện trong hộp thư đến của bạn. Nếu bạn không thấy các mục này, hãy làm mới trang.

  6. Để thêm nhãn, hãy nhấp vào Phân tích email.

Tiện ích bổ sung này xem xét 10 thư gần đây nhất trong hộp thư đến của bạn, sau đó áp dụng một trong các nhãn sau dựa trên nội dung thư:

  • HAPPY TONE 😊
  • GIỌNG ĐIỆU TRUNG LẬP 😐
  • GIỌNG ĐIỆU BẤT MÃN 😡

Xem lại mã

Xem lại mã Apps Script cho giải pháp này:

Xem mã nguồn

Code.gs

gmail-sentiment-analysis/Code.gs
/*
Copyright 2024 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/**
 * Triggered when the add-on is opened from the Gmail homepage.
 *
 * @param {Object} e - The event object.
 * @returns {Card} - The homepage card.
 */
function onHomepageTrigger(e) {
  return buildHomepageCard();
}

Cards.gs

gmail-sentiment-analysis/Cards.gs
/*
Copyright 2024-2025 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/**
 * Builds the main card displayed on the Gmail homepage.
 *
 * @returns {Card} - The homepage card.
 */
function buildHomepageCard() {
  // Create a new card builder
  const cardBuilder = CardService.newCardBuilder();

  // Create a card header
  const cardHeader = CardService.newCardHeader();
  cardHeader.setImageUrl('https://fonts.gstatic.com/s/i/googlematerialicons/mail/v6/black-24dp/1x/gm_mail_black_24dp.png');
  cardHeader.setImageStyle(CardService.ImageStyle.CIRCLE);
  cardHeader.setTitle("Analyze your Gmail");

  // Add the header to the card
  cardBuilder.setHeader(cardHeader);

  // Create a card section
  const cardSection = CardService.newCardSection();

  // Create buttons for generating sample emails and analyzing sentiment
  const buttonSet = CardService.newButtonSet();

  // Create "Generate sample emails" button
  const generateButton = createFilledButton('Generate sample emails', 'generateSampleEmails', '#34A853');
  buttonSet.addButton(generateButton);

  // Create "Analyze emails" button
  const analyzeButton = createFilledButton('Analyze emails', 'analyzeSentiment', '#FF0000');
  buttonSet.addButton(analyzeButton);

  // Add the button set to the section
  cardSection.addWidget(buttonSet);

  // Add the section to the card
  cardBuilder.addSection(cardSection);

  // Build and return the card
  return cardBuilder.build();
}

/**
 * Creates a filled text button with the specified text, function, and color.
 *
 * @param {string} text - The text to display on the button.
 * @param {string} functionName - The name of the function to call when the button is clicked.
 * @param {string} color - The background color of the button.
 * @returns {TextButton} - The created text button.
 */
function createFilledButton(text, functionName, color) {
  // Create a new text button
  const textButton = CardService.newTextButton();

  // Set the button text
  textButton.setText(text);

  // Set the action to perform when the button is clicked
  const action = CardService.newAction();
  action.setFunctionName(functionName);
  textButton.setOnClickAction(action);

  // Set the button style to filled
  textButton.setTextButtonStyle(CardService.TextButtonStyle.FILLED);

  // Set the background color
  textButton.setBackgroundColor(color);

  return textButton;
}

/**
 * Creates a notification response with the specified text.
 *
 * @param {string} notificationText - The text to display in the notification.
 * @returns {ActionResponse} - The created action response.
 */
function buildNotificationResponse(notificationText) {
  // Create a new notification
  const notification = CardService.newNotification();
  notification.setText(notificationText);

  // Create a new action response builder
  const actionResponseBuilder = CardService.newActionResponseBuilder();

  // Set the notification for the action response
  actionResponseBuilder.setNotification(notification);

  // Build and return the action response
  return actionResponseBuilder.build();
}

Gmail.gs

gmail-sentiment-analysis/Gmail.gs
/*
Copyright 2024-2025 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/**
 * Analyzes the sentiment of the first 10 threads in the inbox
 * and labels them accordingly.
 *
 * @returns {ActionResponse} - A notification confirming completion.
 */
function analyzeSentiment() {
  // Analyze and label emails
  analyzeAndLabelEmailSentiment();

  // Return a notification
  return buildNotificationResponse("Successfully completed sentiment analysis");
}

/**
 * Analyzes the sentiment of emails and applies appropriate labels.
 */
function analyzeAndLabelEmailSentiment() {
  // Define label names
  const labelNames = ["HAPPY TONE 😊", "NEUTRAL TONE 😐", "UPSET TONE 😡"];

  // Get or create labels for each sentiment
  const positiveLabel = GmailApp.getUserLabelByName(labelNames[0]) || GmailApp.createLabel(labelNames[0]);
  const neutralLabel = GmailApp.getUserLabelByName(labelNames[1]) || GmailApp.createLabel(labelNames[1]);
  const negativeLabel = GmailApp.getUserLabelByName(labelNames[2]) || GmailApp.createLabel(labelNames[2]);

  // Get the first 10 threads in the inbox
  const threads = GmailApp.getInboxThreads(0, 10);

  // Iterate through each thread
  for (const thread of threads) {
    // Iterate through each message in the thread
    const messages = thread.getMessages();
    for (const message of messages) {
      // Get the plain text body of the message
      const emailBody = message.getPlainBody();

      // Analyze the sentiment of the email body
      const sentiment = processSentiment(emailBody);

      // Apply the appropriate label based on the sentiment
      if (sentiment === 'positive') {
        thread.addLabel(positiveLabel);
      } else if (sentiment === 'neutral') {
        thread.addLabel(neutralLabel);
      } else if (sentiment === 'negative') {
        thread.addLabel(negativeLabel);
      }
    }
  }
}

/**
 * Generates sample emails for testing the sentiment analysis.
 *
 * @returns {ActionResponse} - A notification confirming email generation.
 */
function generateSampleEmails() {
  // Get the current user's email address
  const userEmail = Session.getActiveUser().getEmail();

  // Define sample emails
  const sampleEmails = [
    {
      subject: 'Thank you for amazing service!',
      body: 'Hi, I really enjoyed working with you. Thank you again!',
      name: 'Customer A'
    },
    {
      subject: 'Request for information',
      body: 'Hello, I need more information on your recent product launch. Thank you.',
      name: 'Customer B'
    },
    {
      subject: 'Complaint!',
      body: '',
      htmlBody: `<p>Hello, You are late in delivery, again.</p>
<p>Please contact me ASAP before I cancel our subscription.</p>`,
      name: 'Customer C'
    }
  ];

  // Send each sample email
  for (const email of sampleEmails) {
    GmailApp.sendEmail(userEmail, email.subject, email.body, {
      name: email.name,
      htmlBody: email.htmlBody
    });
  }

  // Return a notification
  return buildNotificationResponse("Successfully generated sample emails");
}

Vertex.gs

gmail-sentiment-analysis/Vertex.gs
/*
Copyright 2024-2025 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Replace with your project ID
const PROJECT_ID = '[ADD YOUR GCP PROJECT ID HERE]';

// Location for your Vertex AI model
const VERTEX_AI_LOCATION = 'us-central1';

// Model ID to use for sentiment analysis
const MODEL_ID = 'gemini-2.5-flash';

/**
 * Sends the email text to Vertex AI for sentiment analysis.
 *
 * @param {string} emailText - The text of the email to analyze.
 * @returns {string} - The sentiment of the email ('positive', 'negative', or 'neutral').
 */
function processSentiment(emailText) {
  // Construct the API endpoint URL
  const apiUrl = `https://${VERTEX_AI_LOCATION}-aiplatform.googleapis.com/v1/projects/${PROJECT_ID}/locations/${VERTEX_AI_LOCATION}/publishers/google/models/${MODEL_ID}:generateContent`;

  // Prepare the request payload
  const payload = {
    contents: [
      {
        role: "user",
        parts: [
          {
            text: `Analyze the sentiment of the following message: ${emailText}`
          }
        ]
      }
    ],
    generationConfig: {
      temperature: 0.9,
      maxOutputTokens: 1024,
      responseMimeType: "application/json",
      // Expected response format for simpler parsing.
      responseSchema: {
        type: "object",
        properties: {
          response: {
            type: "string",
            enum: ["positive", "negative", "neutral"]
          }
        }
      }
    }
  };

  // Prepare the request options
  const options = {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${ScriptApp.getOAuthToken()}`
    },
    contentType: 'application/json',
    muteHttpExceptions: true, // Set to true to inspect the error response
    payload: JSON.stringify(payload)
  };

  // Make the API request
  const response = UrlFetchApp.fetch(apiUrl, options);

  // Parse the response. There are two levels of JSON responses to parse.
  const parsedResponse = JSON.parse(response.getContentText());
  const sentimentResponse = JSON.parse(parsedResponse.candidates[0].content.parts[0].text).response;

  // Return the sentiment
  return sentimentResponse;
}

appsscript.json

gmail-sentiment-analysis/appsscript.json
{
  "timeZone": "America/Toronto",
  "oauthScopes": [
    "https://www.googleapis.com/auth/cloud-platform",
    "https://www.googleapis.com/auth/gmail.addons.execute",
    "https://www.googleapis.com/auth/gmail.labels",
    "https://www.googleapis.com/auth/gmail.modify",
    "https://www.googleapis.com/auth/script.external_request",
    "https://www.googleapis.com/auth/userinfo.email"
  ],
  "addOns": {
    "common": {
      "name": "Sentiment Analysis",
      "logoUrl": "https://fonts.gstatic.com/s/i/googlematerialicons/sentiment_extremely_dissatisfied/v6/black-24dp/1x/gm_sentiment_extremely_dissatisfied_black_24dp.png"
    },
    "gmail": {
      "homepageTrigger": {
        "runFunction": "onHomepageTrigger",
        "enabled": true
      }
    }
  },
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8"
}

Dọn dẹp

Để tránh phát sinh phí cho tài khoản Google Cloud của bạn đối với các tài nguyên được dùng trong hướng dẫn này, bạn nên xoá dự án trên Cloud.

  1. Trong Google Cloud Console, hãy chuyển đến trang Quản lý tài nguyên. Nhấp vào Trình đơn > IAM và Quản trị > Quản lý tài nguyên.

    Chuyển đến Resource Manager

  2. Trong danh sách dự án, hãy chọn dự án bạn muốn xoá rồi nhấp vào biểu tượng Xoá .
  3. Trong hộp thoại, hãy nhập mã dự án rồi nhấp vào Tắt để xoá dự án.

Các bước tiếp theo