# Linebot 進階 - 小專案課程3 記憶傳承人 ## 建議事項 - 請至少對第一堂社課 + ngrok範例影片有一定印象 - 本地端運行 + ngrok : [Linebot 開發 – 測試環境建立以快速測試](/M__0hsU6SSq0IWTxOOffLg) ## 你可以先開的畫面 - [今天Fork的專案](https://github.com/bsbacon0966/GDG_linebot_W3) - [Firebase](https://firebase.google.com/) ## 今日課程 目標:使用 Linebot 結合 Firebase 使你的機器人具有記憶效果 - 申請Firebase憑證 - 透過呼叫collection->document來進行讀、寫資料 - 一些設計Linebot可以有的小技巧 >[!Warning]**此課程的調性** >Linebot + Firebase,之中的程式呼叫相對複雜很多,很可能一般0基礎開發的學員很難理解,請如果真的不知道該怎麼辦,呼叫助教或是ChatGPT來協助你 --- ## 第0步 : 為何要結合Firebase 如果你有跟上前兩週的課程內容,應該會發現一件事: **LineBot 自身並沒有記憶能力**,而且我們現階段做的作品都是程式判斷要回應甚麼而已,每一次的訊息事件,對 Bot 來說都是「獨立的」。 所以我們可以透過Firebase,來讓我們的Linebot有一個"集中資料庫",儲存使用者輸入的資料。 --- ## 第1步 : 申請Firebase憑證 - 搜尋[Firebase](https://firebase.google.com/),並進到此畫面,點選下方"Get Started in Console" <div style="text-align: center;"> <img src="https://hackmd.io/_uploads/rkb4K5Dyee.png" style="width: 100%; border: 2px solid black; padding: 5px;" /> </div> - 點選"建立Firebase專案" <div style="text-align: center;"> <img src="https://hackmd.io/_uploads/BJeqF5DJll.png" style="width: 75%; border: 2px solid black; padding: 5px;" /> </div> - 輸入專案名稱,取隨意名字都可以,輸入完後點選"繼續" <div style="text-align: center;"> <img src="https://hackmd.io/_uploads/rkGW5qv1ll.png" style="width: 75%; border: 2px solid black; padding: 5px;" /> </div> - 這裡他會要你選擇"Google Analytics"帳號,點選"Default"即可,**只要你沒有在此寫你的信用卡帳戶,使用Firebase都是免費的** <div style="text-align: center;"> <img src="https://hackmd.io/_uploads/BySQq5P1xl.png" style="width: 75%; border: 2px solid black; padding: 5px;" /> </div> - 進入到專案後,點左側的 "建構->Firestore Database" <div style="text-align: center;"> <img src="https://hackmd.io/_uploads/SkUnq9PJxl.png" style="width: 75%; border: 2px solid black; padding: 5px;" /> </div> - 點選"建立資料庫",讓他架設資料庫 <div style="text-align: center;"> <img src="https://hackmd.io/_uploads/B17eo5wJll.png" style="width: 75%; border: 2px solid black; padding: 5px;" /> </div> - 進入後,他會詢問你"安全性規則",先幫我點選"測試版模式"啟動他 <div style="text-align: center;"> <img src="https://hackmd.io/_uploads/ryt7j9wkle.png" style="width: 75%; border: 2px solid black; padding: 5px;" /> </div> - 建立完後,看向左上角的"專案總覽 -> 專案設定" <div style="text-align: center;"> <img src="https://hackmd.io/_uploads/BJi0oqwkel.png" style="width: 75%; border: 2px solid black; padding: 5px;" /> </div> - 看到"專案設定 -> 服務帳戶",往下滑,直到看到"產生新的私密金鑰" <div style="text-align: center;"> <img src="https://hackmd.io/_uploads/r1oZhcwkxe.png" style="width: 75%; border: 2px solid black; padding: 5px;" /> </div> <div style="text-align: center;"> <img src="https://hackmd.io/_uploads/BJUG2qvygl.png" style="width: 75%; border: 2px solid black; padding: 5px;" /> </div> - 這時,他會下載一個.json的檔案,**請將其改名成"firebase_key.json"**,這個就是連線到Firebase的憑證 <div style="text-align: center;"> <img src="https://hackmd.io/_uploads/Hy3_hcD1eg.png" style="width: 60%; border: 2px solid black; padding: 5px;" /> </div> --- ## 第2步 : 與程式專案結合(這裡以Codespace作範例) >[!Important]Codespace可以做的,不代表本地端可以 >研究後確認,如果你在Codespce運行```python app.py``` >他會給你一個選項為"設為公用",這樣他給你的"轉送網址"貼到Linebot的Webhook URL使可以運行的 >如果你使本地端運行程式碼,請回到上方看ngrok的架設 >![image](https://hackmd.io/_uploads/BkdteoP1el.png) ### 2-0 今天的範例Code 請對[此專案](https://github.com/bsbacon0966/GDG_linebot_W3)進行fork,此檔案與第一周的範例程式碼相近。 ### 2-1 基本設置 - 將剛剛下載的json憑證貼上 - 將原有的```.env.example```改名成```.env``` <div style="text-align: center;"> <img src="https://hackmd.io/_uploads/H1dw-jDylg.png" style="width: 60%; border: 2px solid black; padding: 5px;" /> </div> - 將.env檔案中的```LINE_TOKEN```和```LINE_SECRET```貼上你Linebot的Token與Secret資訊 <div style="text-align: center;"> <img src="https://hackmd.io/_uploads/ByvTWjDJxe.png" style="width: 100%; border: 2px solid black; padding: 5px;" /> </div> - 將.gitignore中,新增```firebase_key.json```,這樣等等push回到Github上時,**==你的json憑證不會跟著上傳到公開場所==** <div style="text-align: center;"> <img src="https://hackmd.io/_uploads/r1PJQjP1eg.png" style="width: 80%; border: 2px solid black; padding: 5px;" /> </div> - 在終端機輸入```python app.py```,如果剛剛輸入的.env順利的話,他就會運行 <div style="text-align: center;"> <img src="https://hackmd.io/_uploads/HJdwMjwJlg.png" style="width: 70%; border: 2px solid black; padding: 5px;" /> </div> - 運行時,他會跳通知說"可以設為公開",請點選"公開" <div style="text-align: center;"> <img src="https://hackmd.io/_uploads/BypSQiv1le.png" style="width: 70%; border: 2px solid black; padding: 5px;" /> </div> - 點選後,你會看到"轉送位址",你可以將其複製下來,並貼到Linebot -> Webhook URL,就可以直接測試你新寫好的Linebot程式碼了! <div style="text-align: center;"> <img src="https://hackmd.io/_uploads/B1NKQswklg.png" style="width: 100%; border: 2px solid black; padding: 5px;" /> </div> ## 第3步 : 先使用看看Firebase ```python= # Firebase 初始化 ###################################################### cred = credentials.Certificate("firebase_key.json") # 放你的金鑰路徑 firebase_admin.initialize_app(cred) db = firestore.client() ###################################################### ``` ```python= # 設置一個事件處理器來處理 TextMessage 事件 @handler.add(MessageEvent, message=TextMessage) def handle_message(event: Event): if event.message.type == "text": user_message = event.message.text # 使用者的訊息 app.logger.info(f"收到的訊息: {user_message}") # 存到 Firestore(範例結構: GDG/W3/records/{儲存訊息}) ################################################################### reply_text = "你說了:" + user_message line_bot_api.reply_message(reply_token, TextSendMessage(text=reply_text)) doc_address = db.collection("GDG").document("W3") doc = doc_address.get() if doc.exists: doc_data = doc.to_dict() history = doc_data.get("record", []) else: history = [] print(history) history.append(reply_text) doc_address.set({"record": history}) ################################################################### ``` 將程式碼貼上後,運行```python app.py```,即可開始運行! 回到剛剛創建的Firebase,如果有看到下圖的格式,那恭喜你的linebot有了資料庫。 <div style="text-align: center;"> <img src="https://hackmd.io/_uploads/SymsoFOyeg.png" style="width: 100%; border: 2px solid black; padding: 5px;" /> </div> ## 第4步 : 看懂Firebase的程式碼 ### 4-1 Firebase結構 <div style="text-align: center;"> <img src="https://hackmd.io/_uploads/BJS3hFuJex.png" style="width: 80%; border: 2px solid black; padding: 5px;" /> </div> >[!Note]通俗的記法 > Collection = 抽屜的標籤 > Document = 當前抽屜的文件名稱 > Document的標籤層 = 當前文件中,標註的變數名稱與資料 則我們如果要儲存資料,那我們就要: - 先導引到目標Document - 讀取Document中,目標變數名稱 - 將資料轉成.dict,讓Python得以看懂 ``` python= doc_address = db.collection("GDG").document("W3") #指向目標document地址 doc = doc_address.get() #取出地址中的資料 if doc.exists: doc_data = doc.to_dict() history = doc_data.get("record", []) #取出資料中,指定標籤的資料 else: history = [] print(history) ``` 則剛剛送給Firebase的資料就全在```history```這個變數中 ```python= history.append(reply_text) doc_address.set({"record": history}) ``` 我們可以將rely_text插入到history變數中,並且最後對document地址宣告set,把history的資訊推回到Firebase中。 ### 4-2 有了記憶後,結合Gemini吧! 初始化設定Gemini ```python= # Gemini 初始化 ###################################################### genai.configure(api_key=os.getenv("GEMINI_API_KEY")) model = genai.GenerativeModel("gemini-1.5-pro") ###################################################### ``` 請把當前格子的程式碼改成這樣 ```python= # 存到 Firestore(範例結構: GDG/W3_memory/records/{儲存訊息}) ################################################################### doc_address = db.collection("GDG").document("W3_memory") doc = doc_address.get() if doc.exists: doc_data = doc.to_dict() history = doc_data.get("record", []) else: history = [] print(history) gemini_response = model.generate_content(f"之前談話歷史為{history},請回答{user_message}") gemini_response_text = gemini_response.text.strip() line_bot_api.reply_message(reply_token, TextSendMessage(text=gemini_response_text)) history.append({ "user": user_message, "gemini_reply": gemini_response_text }) doc_address.set({"record": history}) ################################################################### ``` 上述程式碼的目標 : 將Gemini的回應與user的輸入包裝再一起,並且將其傳送到指定地址的document中。 <div style="text-align: center;"> <img src="https://hackmd.io/_uploads/r1-OP9uygx.png" style="width: 50%; border: 2px solid black; padding: 5px;" /> </div> 只要有了資料庫,並且調用資料庫資料給Gemini看,就可以使"每次對答"都能夠記住之前說過甚麼。 ## 第5步 : 當你開發到一定階段後 >[!Warning]接下來的步驟 >因為Render實在有點難理解我在想甚麼,所以如果我們要將作品推到Render上,有一個下下策,需要一定轉換 請將你原先的Firebase初始化改成: ```python= # === 初始化 Firebase === firebase_cred_str = os.getenv("FIREBASE_KEY") #放在遠端得環境變數中 firebase_initialized = False if firebase_cred_str: cred_dict = json.loads(firebase_cred_str) # 將 JSON 字串轉回 dict cred = credentials.Certificate(cred_dict) initialize_app(cred) db = firestore.client() firebase_initialized = True else: raise ValueError("未設定 FIREBASE_CREDENTIAL_JSON") ######################################################### ``` 並且驅動在Github上面的程式碼`change.py`,他會將你的Firebase憑證轉格式,你應該會得到一條結果 <div style="text-align: center;"> <img src="https://hackmd.io/_uploads/SJPrJpegle.png" style="width: 100%; border: 2px solid black; padding: 5px;" /> </div> 請把```FIREBASE_KEY=```後面的所有東西複製下來 <div style="text-align: center;"> <img src="https://hackmd.io/_uploads/r1s5Jpxexe.png" style="width: 100%; border: 2px solid black; padding: 5px;" /> </div> 把剛剛那一串打進```ENVIRONMENT->FIREBASE_KEY```當中,正常來說就可以在雲端上跑了。 ## 第6步 : 一些開發Linebot可以有的小知識 ### 取得使用者 ID(userId)做身份追蹤 ```python= user_id = event.source.user_id ``` 在def handle_message(event),除了可以提取詢問的內容,也可以提取"詢問人的userID",可以運來做身分認證。 ### 取得使用者的基本資料 ```python= profile = line_bot_api.get_profile(user_id) name = profile.display_name ``` 可以利用user_id提取出該使用者的部分資訊,例如姓名。 (---有找到新的資料就會繼續更新---)