已經學習了一陣子如何執行大語言模型,但一直沒有產生實質的功能。直到最近的一個不算理想的契機,我們家申請了印尼看護來照顧我爸,由於她只會講一點中文,所以只能用 Line 和她溝通比較方便。但要在 Google 翻譯和 Line 之間不斷貼來貼去,也非常麻煩。
此外,Google 翻譯的印尼文轉中文功能很不理想,因此萌生了自己開發一個翻譯機器人的想法。這個過程包含了三個主要步驟。
- 申請 LINE BOT
- 寫 webhook 的程式碼
- 多驗証幾次
本文一開始是參考這篇來完成的,其實我會比較推薦初學者按照他的流程來操作,從申請到運行 Webhook App 都有詳細說明,應該可以一次上手。我的文章則是經過精簡的版本,許多部分都被忽略(例如 GCP 申請、憑證申請),只保留了重要內容作為自己的紀錄,以便下次寫另一個機器人時不會忘記。
申請與設定 LINE Bot
帳號說明
關於 Line Bot 帳號之間的關係,可能需要先做一下說明。
我們需要申請一個開發者帳號和官方帳號。開發者帳號可以由原本的一般帳號轉換而成,而一個開發者帳號下可以創造多個供應商(Provider),每個供應商可以管理多個官方帳號(Official Account)。
因此,在管理頁面中會有兩個主要部分:
申請帳號
先登入開發者帳號管理頁面,可以用本的一般帳戶轉換成開發者帳戶。完成後,應該會看到下列畫面,按下 Create 來申請一個 Provider(供應商),設定供應商名稱,此處假定為 firstaiapp。
申請後,就可以看到帳號已產生,並提示沒有 channel。
往下移動一點,申請 Message API。
這邊會自動導到官方帳號申請頁面,填入資料後進行申請,後續要再進行綁定供應商,申請中可以先不用申請帳號認証。
申請好後,會自己進入官方帳號頁面。進入右方的設定。
接著就會看到如下畫面
接著設定以下項目
- 設定–>帳號設定 –>右側畫面, 勾選「接受邀請加入群組或多人聊天室」,不允許「接收媒體或檔案」
- 設定–>Message API –> 啟用 Message API –> 供應商選 firstaiapp –> 隱私權服務條款先不選 –> webhook 網址假定為 「https://myfirstaiapp.com/bot1/callback」,按下生成「Channel Access Token」
- 設定–>回應設定–>右側畫面, 勾選「聊天」,取消「加入好友的歡迎訊息」,取消「回應時間」,勾選「手動聊天+自動回應訊息」
記下 Message API 下的 Channel Secret 與 Access Token,後面寫回應程式時會用到。回到開發者帳戶後,就可看到供應商裡加入的 Message API 官方帳戶了。
設定 Apache 代理
由於我們希望在一台主機裡,可以服務好幾個官方帳號 webhook,所以在 web server 上需設定代理。編輯 /etc/apache2/sites-enabled/000-default-le-ssl.conf, 在 </VirtualHost> 前加入下列設定。
1 2 3 4 5 |
ProxyPass "/bot1" "http://127.0.0.1:5000/" ProxyPassReverse "/bot1" "http://127.0.0.1:5000/" <Location /bot1> Require all granted </Location> |
然後重啟 apache
1 |
apachectl restart |
撰寫 webhook 程式
當申請完一個官方帳號後,它會自動被加入到開發者帳號的好友名單中,並主動向你發送訊息。在二人聊天室或多人聊天室裡,其他人發出的訊息會傳送到前面提到的 webhook 網址進行處理。因此,只要對此訊息做出適當的回應,就可以展現機器人的功能。
範例程式 – 翻譯機器人
這邊就直接以我在使用的「印尼文」 <—> 「中文」翻譯機器人的程式碼當例子。這是一個 python 程式,安裝的依賴包如下
1 2 3 |
line-bot-sdk flask openai |
程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
from flask import Flask, request, abort from openai import OpenAI from linebot.v3 import ( WebhookHandler ) from linebot.v3.exceptions import ( InvalidSignatureError ) from linebot.v3.messaging import ( Configuration, ApiClient, MessagingApi, ReplyMessageRequest, TextMessage ) from linebot.v3.webhooks import ( MessageEvent, TextMessageContent ) import os from linebot.models import SourceGroup api_key = "OPENAI_KEY" ACCESS_TOKEN='LINE_ACCESS_TOKEN' ACCESS_SECRET='LINE_ACCESS_SECRET' client = OpenAI(api_key=api_key) app = Flask(__name__) configuration = Configuration(access_token=ACCESS_TOKEN) handler = WebhookHandler(ACCESS_SECRET) def gpt4_chat_sync(msg): response = client.chat.completions.create(model="gpt-4o", messages=[ {"role": "system", "content": "你是一個翻譯機器人,當看到印尼文時,將它翻譯成繁體中文。看到中文時,將它翻> 譯成印尼文,看到英文時不翻譯。"}, {"role": "user", "content": msg} ], max_tokens=300, temperature=0, top_p=0) return response.choices[0].message.content def groupAllowed(name): if name.find("KEYWORD_OF_YOUR_GROUPNAME") >= 0: print(f"Group 「{name}」 允許使用服務") return True else: print(f"Group 「{name}」 不允許使用服務") return False def get_chatgrp_name(event, line_bot_api): """ 取得群組名稱,由於使用了 try,若不是群組聊天,會觸發 except 而創回預設錯誤 """ try: group_id = event.source.group_id summary = line_bot_api.get_group_summary(group_id) return summary.group_name except: return "獲取群組名稱失敗" # 監聽所有來自 /callback 的 Post Request @app.route("/callback", methods=['POST']) def callback(): # get X-Line-Signature header value signature = request.headers['X-Line-Signature'] # get request body as text body = request.get_data(as_text=True) app.logger.info("Request body: " + body) # handle webhook body try: handler.handle(body, signature) except InvalidSignatureError: abort(400) return 'OK' @handler.add(MessageEvent, message=TextMessageContent) def handle_message(event): with ApiClient(configuration) as api_client: line_bot_api = MessagingApi(api_client) grpname=get_chatgrp_name(event, line_bot_api) if groupAllowed(grpname) != True: return False line_bot_api.reply_message_with_http_info( ReplyMessageRequest( reply_token=event.reply_token, messages=[TextMessage(text=gpt4_chat_sync(event.message.text))] ) ) if __name__ == "__main__": port = int(os.environ.get('PORT', 5000)) app.run(host='0.0.0.0', port=port) |
這裡做了簡單的用戶管制,以避免其他人濫用 credit。只有在群組名稱中包含特定字串 KEYWORD_OF_YOUR_GROUPNAME
的訊息才會得到回應。請將此處的值修改為你希望使用的關鍵字。
結語
這可以算作是我第一個實用化的機器人 + AI 案例,也是我在 AI 實用化方面邁出的重要一步。