|

使用 Groq AI、GAS 與 LINE Bot 自動管理行程:Google 行事曆整合全攻略

前言

在工作的時候常常會有各種的會議或行程,如果沒有馬上booking很容易就會忘記,因此我想藉由大語言模型製作一個行程紀錄小幫手,能夠萃取文字內容自動記錄行程,以下就用幾個步驟一步一步完成吧

步驟一.註冊groq

Groq有提供開源大語言模型(例如:llama3.3)的免費api可以使用,透過groq所提供的免費api就可以做為解析文字並輸出特定格式的大腦。

選擇free api key

進入groq選擇free api key

我是直接用google帳號連動

註冊groq

建立API Key

建立api key

也可以像chatgpt一樣可以直接在groq上面做提問

像chatgpt一樣可以直接在groq上面做提問

步驟二.取得LineToken

登入line 開發者

登入line 開發者

建立line bot 帳號

建立line bot 帳號

選擇messageAPI

選擇messageAPI

建立完line bot 帳號後選擇messaging api即可以取得剛剛所建立的帳號token,可用於程式取得訊息與推送訊息,

取得帳號token

步驟三.取得google行事曆ID

因為要用程式自動加入行程,因此需要取得行事曆的id,讓程式可以知道要加入行程的是哪一個行事曆,以下為取得行事曆id的步驟

到google 行事曆中的設定

google 行事曆中的設定取得google行事曆ID

新增一個日曆後(也可以使用既有的日曆),即可找到行事曆的id

可找到行事曆的id

步驟四.在google apps scripts 建立服務

從google 雲端硬碟中開啟google apps scripts,如下圖所示

從google 雲端硬碟中開啟google apps scripts

將以下程式貼到google apps scripts並建立服務

程式如下

const LINE_ACCESS_TOKEN = "你的 LINE Channel Access Token";
const CALENDAR_ID = "你的 Google 行事曆 ID";
const GROQ_API_KEY = "你的 GROQ API Key";
const GROQ_MODEL = "llama-3.3-70b-versatile";//"mixtral-8x7b-32768";  // 可以選擇不同的模型

const MAX_RETRIES = 5;  // 最多重試 5 次

// 解析 LINE Bot 傳入的訊息,並嘗試新增行程
function doPost(e) {
  const event = JSON.parse(e.postData.contents);
  event.events.forEach(e => {
    if (e.type === "message" && e.message.type === "text") {
      const userMessage = e.message.text;
      const replyToken = e.replyToken;
      const now_date = new Date().toISOString().split("T")[0]; // 取得 YYYY-MM-DD

      let eventDetails = null;
      let attempt = 0;
      let llmOutput = ""; // 存放 LLM 嘗試的結果

      while (attempt < MAX_RETRIES) {
        let response = parseEventWithGROQ(userMessage, now_date);
        
        if (response.raw) {
          llmOutput = response.raw; // 儲存 LLM 嘗試解析的內容
        }
        
        if (response.parsed && isValidEvent(response.parsed)) {
          eventDetails = response.parsed;
          break;  // 成功解析,跳出迴圈
        }

        attempt++;
      }

      // 如果 5 次都解析失敗,回覆錯誤訊息並提供 LLM 嘗試的結果
      if (!eventDetails || !isValidEvent(eventDetails)) {
        replyToUser(replyToken, `⚠️ 無法解析行程,請用以下格式輸入:
        
『會議 2025-02-10 14:00』  
或  
『2/15 10:00 和小明吃飯』  

🛠 LLM 嘗試解析的結果:\\n\\n${llmOutput || "⚠️ LLM 無法提供有效結果"}`
        );
        return;
      }

      addEventToCalendar(eventDetails);
      replyToUser(replyToken, `✅ 行程已新增:${eventDetails.title}\\n🕒 時間:${eventDetails.startTime}\\n🔔 已設置 30 分鐘前提醒`);
    }
  });
  return ContentService.createTextOutput("OK");
}

// 透過 GROQ 大語言模型解析文字
function parseEventWithGROQ(text, now_date) {
  const prompt = `
  你是一個 AI 助理,專門解析行程資訊。請從以下文字提取行程資訊,請將民國轉為西元(例如:114年轉為2025年),
  你需要先判斷現在日期,行程之時間必定大於現在日期,因此如果缺少年份資訊請補充
  (例如:現在日期2025-03-01,使用者輸入03/05應為2025-03-05)
  格式如下:
  {
    "title": "活動名稱",
    "startTime": "YYYY-MM-DDTHH:MM:SS"
  }
  如果無法解析,請返回 null。
  現在日期:${now_date}
  使用者輸入:${text}
  輸出(僅需輸出格式,其他無關之文字一定不能輸出):
  `;

  const url = "<https://api.groq.com/openai/v1/chat/completions>";
  const payload = {
    model: GROQ_MODEL,
    messages: [{ role: "user", content: prompt }],
    max_tokens: 100
  };

  const options = {
    method: "post",
    headers: {
      "Content-Type": "application/json",
      "Authorization": "Bearer " + GROQ_API_KEY
    },
    payload: JSON.stringify(payload)
  };

  try {
    const response = UrlFetchApp.fetch(url, options);
    const json = JSON.parse(response.getContentText());
    const extractedText = json.choices[0].message.content;

    let parsedJSON = null;
    try {
      parsedJSON = JSON.parse(extractedText);  // 安全解析 JSON
    } catch (error) {
      Logger.log("JSON 解析錯誤: " + error);
    }

    return {
      raw: extractedText,  // LLM 回傳的原始內容
      parsed: parsedJSON   // 嘗試解析成 JSON(如果成功則使用)
    };
  } catch (error) {
    Logger.log("GROQ API Error: " + error);
    return { raw: "解析失敗", parsed: null };
  }
}

// 檢查解析出的行程是否符合格式
function isValidEvent(eventDetails) {
  if (!eventDetails || !eventDetails.title || !eventDetails.startTime) {
    return false;
  }
  const datePattern = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$/;
  return datePattern.test(eventDetails.startTime);
}

// 將行程新增到 Google 行事曆,並加入提醒
function addEventToCalendar(eventDetails) {
  const calendar = CalendarApp.getCalendarById(CALENDAR_ID);

  const startTime = new Date(eventDetails.startTime);
  const endTime = new Date(startTime.getTime() + 60 * 60 * 1000); // 預設 1 小時後結束

  // 創建行程並添加提醒
  const event = calendar.createEvent(eventDetails.title, startTime, endTime);

  // 設定提醒:
  event.addPopupReminder(30);  // 在行程開始前 30 分鐘彈出提醒
  // event.addEmailReminder(60);  // 在行程開始前 60 分鐘發送 Email 提醒
}

// 回覆使用者訊息
function replyToUser(replyToken, message) {
  const url = "<https://api.line.me/v2/bot/message/reply>";
  const payload = {
    replyToken: replyToken,
    messages: [{ type: "text", text: message }]
  };

  const options = {
    method: "post",
    headers: {
      "Content-Type": "application/json",
      "Authorization": "Bearer " + LINE_ACCESS_TOKEN
    },
    payload: JSON.stringify(payload)
  };

  UrlFetchApp.fetch(url, options);
}

會在上述程式中加入while (attempt < MAX_RETRIES)迴圈,而不是直接執行parseEventWithGROQ函式,是因為透過groq上所建立的llama3.3:70b模型,解析文字並輸出成特定格式的過程可能會輸出不合格式的情形,為了確保程式的穩定性因此做5次的遞迴,確保輸出格式正確,能夠加入google 行事曆。

將上述程式貼入google apps script (如下圖)。

將上述程式貼入google apps script

點選新增部屬,部屬成網頁應用程式

點選新增部屬,部屬成網頁應用程式

部屬完成可以得到一串網址。

部屬完成可以得到一串網址

步驟五.部屬到line bot

將得到之網頁應用程式連結貼到line 中的webhook

將得到之網頁應用程式連結貼到line 中的webhook

完成可以開始使用

完成可以開始使用

Similar Posts

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *