# GAS : Google App Script與生程式AI工作 基礎篇
>[!Note]基本目標 : 整理投稿者履歷並生成面試問答題目
>許多面試者根據Google表單回答了基本問題,而這些資料放置在google sheet當中
>而我們現在並沒有這麼多時間安排每位面試者的面試題目,所以我們決定透過Gemini來輔助,自動化整理履歷資料與生成個人化的面試題目
## 完整程式碼
[連線到Gemini](https://github.com/bsbacon0966/Google-App-Script-30-days-example/blob/main/day15/Initial_Gemini.js)
[Gemini x Google試算表 基礎程式碼](https://github.com/bsbacon0966/Google-App-Script-30-days-example/blob/main/day15/basic.js)
[Gemini x Google試算表 進階程式碼](https://github.com/bsbacon0966/Google-App-Script-30-days-example/blob/main/day15/advance.js)
## Gemini 是什麼
Gemini是Google推出的多模態人工智慧模型,具備強大的能力,能同時識別文本、圖像、音頻、影片和程式碼等五種不同類型的資訊,這使得它在處理複雜任務時非常靈活,不僅能理解和生成高品質的文字內容,還能生成多種主流編程語言(如Python、Java、C++)的代碼。
## Gemini API Key 取得與使用
### 第一步 : 到[Google AI Studio](https://aistudio.google.com/)取得API Key
<div style="text-align: center;">
<img src="https://hackmd.io/_uploads/Bk9daIx5ke.png" style="width: 70%; border: 2px solid black; padding: 5px;" />
</div>
點擊Sign in Google AI Studio,登入自己的Google帳號
<div style="text-align: center;">
<img src="https://hackmd.io/_uploads/B1IC6Lg5Je.png" style="width: 75%; border: 2px solid black; padding: 5px;" />
</div>
進入到Studio中,點擊`API Keys -> Create API Key`,如果你有出現`Search Google Cloud project`此欄位的話,請選擇**Generative Language Client**或**Gemini AI**帳號,並且Create API
<div style="text-align: center;">
<img src="https://hackmd.io/_uploads/BknGkwg5Jg.png" style="width: 70%; border: 2px solid black; padding: 5px;" />
</div>
而你將會得到一串API Key,請複製後好好保存,並且不要洩漏出去,以免出現安全疑慮
>[!Important] Generative Language Client帳號 或 Gemini AI帳號
> 這個帳號主要是一個專門建立API Key的一個GCP帳號,此帳號有免費與付費專案(預設為免費,不用填寫信用卡資訊)。
> <div style="text-align: center;">
<img src="https://hackmd.io/_uploads/HyDAkDg5kg.png" style="width: 60%; border: 2px solid black; padding: 5px;" />
</div>
### 第二步: 到新建的Google App Script檔案中,切到左側`目錄->專案設定`
<div style="text-align: center;">
<img src="https://hackmd.io/_uploads/HyW2QPrcye.png" style="width: 60%; border: 2px solid black; padding: 5px;" />
</div>
我們將在這裡將剛剛格到的API Key存放到Properties Service
<div style="text-align: center;">
<img src="https://hackmd.io/_uploads/BJ4VVPrq1g.png" style="width: 70%; border: 2px solid black; padding: 5px;" />
</div>
到`指令碼屬性->屬性與值`填寫,屬性名稱命名為你喜歡的名字(範例設定為`GEMINI_API_KEY`),並且將API Key填到`值`的欄位當中
<div style="text-align: center;">
<img src="https://hackmd.io/_uploads/Sk1trwHqJl.png" style="width: 100%; border: 2px solid black; padding: 5px;" />
</div>
回到編譯器中,設定變數GEMINI_API為"儲存在Properties Service中,Property屬性名稱為`GEMINI_API_KEY`的值"
並且,我們可以寫if判斷是否抓取成功,如果沒有跳出錯誤訊息,代表到現在你的設定都是順利的
>[!Important] 為何要這樣設定,儲存到Property後要又要用getProperty()去撈
>如果直接貼上API Key代碼,就像把家裡的鑰匙放在保險箱裡,直接寫在程式碼中就像把鑰匙貼在門上,誰都能輕易取得。
>Property 就像保險箱,安全地儲存敏感資訊(API 金鑰),只有授權的程式才能存取。
### 第三步: 構建 API 端點與配置請求參數
<div style="text-align: center;">
<img src="https://hackmd.io/_uploads/rkZHVKr9yl.png" style="width: 100%; border: 2px solid black; padding: 5px;" />
</div>
接下來,是一連串url(API端點)、payload(請求體)、option(配置請求)建立:
#### 1. 建立URL作為API端點
```json
var url = https://generativelanguage.googleapis.com/v1/models/gemini-pro:generateContent?key=GEMINI_API_KEY
```
此統一資源定位符(URL)作為 Gemini Pro 模型之應用程式介面(API)端點,其構成明確指示:
- 調用模型為「gemini-pro」。
- 執行操作為「generateContent」,即請求模型生成文本內容。
- key=GEMINI_API_KEY,即代表我們的"身份驗證金鑰"也需一併傳上
#### 2. payload設定
```json
var payload = {
"contents": [ // contents 陣列,包含一個或多個內容物件
{
"parts": [
{
"text": "Explain how AI works" // 要發送給 AI 模型的文字提示
}
]
}
]
};
```
將所有要上傳的資訊包裝到`contents 內容物件`中:
contents 陣列包含一個或多個對話內容,每個內容物件代表一輪詢問。在每個內容物件 (contents 內的元素) 中,parts 陣列存放該輪對話的具體內容
- "parts" : 包含一個部分物件,可以想像成一輪詢問下我們要提出的問題
- 在parts中 : "text"用於指定該部分物件的內容為文字形式的提示或回應。
```json
{
"contents": [
{
"parts": [
{
"text": "這是什麼圖片?請描述它。"
},
{
"inlineData": {
"mimeType": "image/jpeg",
"data": "BASE64_ENCODED_IMAGE_DATA"
}
}
]
},
{
"parts": [
{
"text": "根據剛才的描述,這張圖片的應用場景有哪些?"
}
]
}
]
}
```
以上述結構而言,
- 第一輪對話
- 提問:「這是什麼圖片?請描述它。」
- 附加一張 image/jpeg 格式的圖片(Base64 編碼)。
- 第二輪對話
- 提問:「根據你對這張圖片的描述,它的應用場景有哪些?」(此問題依賴第一輪對話的輸出,即對圖片的描述)。
#### 3. option請求參數配置
```json
var options = {
'method': 'post', // 使用 POST 方法發送請求
'contentType': 'application/json', // 設定內容類型為 JSON
'payload': JSON.stringify(payload) // 將 payload 物件轉換為 JSON 字串
};
```
我們要配置請求參數,以明確指示對指定 URL 所執行的操作
- 'method': 'post' : 說明要提交請求
- 'contentType': 'application/json' : 設定請求體的內容類型為 application/json,表明數據格式為 JSON
- 'payload': JSON.stringify(payload) : 將 payload 物件序列化為 JSON 字串,以符合 Gemini API 對請求體數據格式的要求
#### 4. 發送請求並處理回應
```json
var response = UrlFetchApp.fetch(url, options); // 使用 UrlFetchApp.fetch() 方法發送 HTTP 請求,並將回應存儲在 response 變數中
var json = JSON.parse(response.getContentText()); // 將 API 回應的 JSON 字串解析為 JavaScript 物件
var text = json.candidates[0].content.parts[0].text; // 從 JSON 物件中提取 AI 模型生成的回應文本
Logger.log(text); // 將回應文本記錄到 Google Apps Script 的日誌中
```
使用 UrlFetchApp.fetch() 方法向指定的 URL(API 端點)發送 HTTP 請求,其中包含剛剛的請求參數一併上傳,並利用response接住所回傳的資訊,並將其物件中提取回應體的內容,提取回應結構中text文字的部分。
最終利用`Logger.log(text)`查看結果。
<div style="text-align: center;">
<img src="https://hackmd.io/_uploads/BkOuNYSqke.png" style="width: 100%; border: 2px solid black; padding: 5px;" />
</div>
如果你看到他所介紹AI工作的方法,那恭喜你正式體驗過與Gemini互動!
### 第四步: 讓Gemini與Google sheet互動
<div style="text-align: center;">
<img src="https://hackmd.io/_uploads/Hyny_Yr5ye.png" style="width: 100%; border: 2px solid black; padding: 5px;" />
</div>
現在我們想要完成今天的基礎目標,那我們就需要連線到Google試算表,抓取相關資訊後並將其包裝到payload(請求體)上。
現在這張試算表,sheet1中有"姓名"、"聯絡信箱"、"投稿參與組別"、"對於科技知識與工具,你有什麼專長、學習歷程或應用經歷?",那我們需要抓取最後的面試者敘述來做面試題目的生成。
<div style="text-align: center;">
<img src="https://hackmd.io/_uploads/H1go2trckg.png" style="width: 100%; border: 2px solid black; padding: 5px;" />
</div>
跟Day3一樣,先將指定Google 試算表ID填入,讓Google app script找到,並且對欄位index做宣告,以得到更好的閱讀體驗。
<div style="text-align: center;">
<img src="https://hackmd.io/_uploads/r1-3jYBcyg.png" style="width: 100%; border: 2px solid black; padding: 5px;" />
</div>
上述的步驟都跟day3的範例很像,重點是**如果Gemini要看到面試者的資訊,那就需要把資訊放在options中的payload**
我們設定變數Prompt,將面試者"對於科技知識與工具,你有什麼專長、學習歷程或應用經歷?"文字內容儲存到變數Prompt中,並且將Prompt變數包裝到payload。
<div style="text-align: center;">
<img src="https://hackmd.io/_uploads/ryHlrgn5Jx.png" style="width: 100%; border: 2px solid black; padding: 5px;" />
</div>
如果我們要發送請求,那一樣需要把url與option準備好,將剛剛的Prompt轉成JSON格式後,將其包裝到options當中,並利用`UrlFetchApp.fetch(url,options)`發送。
<div style="text-align: center;">
<img src="https://hackmd.io/_uploads/SkVX2FB5kg.png" style="width: 100%; border: 2px solid black; padding: 5px;" />
</div>
最終詢問的結果拆包後查看text的部分
<div style="text-align: center;">
<img src="https://hackmd.io/_uploads/rJuMAKB91g.png" style="width: 100%; border: 2px solid black; padding: 5px;" />
</div>
Gemini就可以讀取到在Google 試算表上,面試者的資訊並且完成我們所想要的目標了!
<div style="text-align: center;">
<img src="https://hackmd.io/_uploads/rk3-y5rqkg.png" style="width: 100%; border: 2px solid black; padding: 5px;" />
</div>
當然我們可以將Gemini回應存放到Sheet中。
這樣我們就有AI的輔助下,能夠做到快速生成需要面試面試者的個人化題目了!
Day 15收工!
## 進階挑戰
>[!Warning] 進階目標 : 根據投稿組別,生成更準確的面試題目
>現在我們的公司有許多職位,每個職位所需負責的職責與人員需求皆有一定區別
>那我們如何讓Gemini,理解各組組別的差異,並且依照各組職責生成更好的面試題目?
><div style="text-align: center;">
<img src="https://hackmd.io/_uploads/BkZakqHqkx.png" style="width: 20%; border: 2px solid black; padding: 5px;" />
</div>
>> 組別職責說明:
>> 活動組:需要負責統籌規劃與科技或社團招生的活動,需要一定活動規劃能力
>> 技術組:需要能夠教學與編寫與現今相關的科技工具教材,需要一定教學經歷
>> 行銷組:需要負責社團社群經營,並且與校外尋求合作機會
>> 美宣組:需要輔佐社群經營,設計出符合當年主視覺設計圖
>> 財政組:需要能夠管理財務分配與審查金流與預算規劃
在使用生成式AI時,有一個觀念挺重要,那就是Prompt Engineering
>[!Important]什麼是Prompt Engineering?其實你在點咖啡的時候就體驗過Prompt Engineering了!
> 想像一下,你走進咖啡店,**對店員說:「我要咖啡」**
> 你可能會得到任何一種咖啡如美式、摩卡,甚至甜度、冷熱、杯數都完全取決於店員的猜測,結果往往與你的預期不符,導致需要花費大量時間重新敘述要求。
>
> 但如果你清楚地說: **「您好,我要點兩杯咖啡。一杯大杯冰美式,加一包糖,希望使用深度烘焙的咖啡豆;一杯熱拿鐵,改用燕麥奶,不加糖,拉花圖案希望是貓咪」**
>這樣的指令更具體,店員能夠準確地提供你想要的兩杯咖啡。
>
> 這就像在使用 AI 時的 Prompt Engineering——你的Prompt越清晰、細緻,AI 生成的回應就越符合你的期待。
為了使Gemini能夠理解面試者投稿組別間的職責差異,我們需透過Prompt工程,將各組的職責內容明確地提供給Gemini。
<div style="text-align: center;">
<img src="https://hackmd.io/_uploads/rJftjcH5kx.png" style="width: 100%; border: 2px solid black; padding: 5px;" />
</div>
首先,我們可以建立一份「組別職責對照表」,詳細列出每個組別的職責說明,作為 Gemini 理解各組差異的依據。
<div style="text-align: center;">
<img src="https://hackmd.io/_uploads/ryE3j5Bq1l.png" style="width: 100%; border: 2px solid black; padding: 5px;" />
</div>
在構建 Prompt 時,我們將依據當前面試者所投稿的組別,從「組別職責對照表」中擷取對應的職責說明,並將其納入 Prompt 中
如此一來,Gemini 就能夠根據面試者所屬組別的職責,生成更具針對性的面試題目
這樣進階任務就完成了!