├── .gitignore ├── README.md ├── api ├── chatgpt.py ├── index.py └── prompt.py ├── requirements.txt └── vercel.json /.gitignore: -------------------------------------------------------------------------------- 1 | .vercel 2 | *.log 3 | *.pyc 4 | __pycache__ 5 | 6 | # Environments 7 | .env 8 | .venv 9 | env/ 10 | venv/ 11 | ENV/ 12 | env.bak/ 13 | venv.bak/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ChatGPT-Linebot using python flask on vercel 2 | 3 | > 作者註:本專案因為作者我的免費 API 額度已經用完了,然後我現在使用的是 `ChatGPT Plus`, 4 | > 5 | > 如果之後要我本人更新可能要等我哪天改用 ChatGPT API 了... 6 | > 7 | > 現在最新的版本感謝網友 @willismax 提供的 PR:https://github.com/howarder3/GPT-Linebot-python-flask-on-vercel/pull/17 8 | > 9 | > 但同樣的因為我沒有 ChatGPT API 的額度了... 所以也沒辦法測就直接 merge 了, 10 | > 11 | > 如果不能動再請大家幫忙發個 Issues/PR,感謝大大們🙏 12 | 13 | * last updated: 2023/10/9 14 | - 更新說明 by @willismax,與修正的 [repo](https://github.com/willismax/GPT-Linebot-python-flask-on-vercel) (merged) 15 | - vercel.json修正,改為第2版 16 | - 修正單純安裝Flask==2.2.2 會與 Werkzeug 衝突的問題,在 requirements.txt 添加 Werkzeug==2.3.7 ([參考stack overflow](https://stackoverflow.com/questions/77213053/importerror-cannot-import-name-url-quote-from-werkzeug-urls)) 17 | 18 | > `本篇教學無經驗的新手也可學習,無須寫任何程式。` 19 | > 20 | > 無經驗預計 15 ~ 20 分鐘都可以完成。老手最快可能 5 分鐘就搞定 21 | 22 | * 這是使用 python flask 套件撰寫的 ChatGPT-Linebot 23 | * `不需寫 code`,只需去網頁設定一些內容,新手 15 分鐘內也能建立自己的 ChatGPT-Linebot 24 | * Why flask? 簡單好用,且支援 vercel 25 | * Why vercel? `免費`!!!免費額度就很夠一般使用,是 heroku 不再免費後的好選擇 26 | 27 | > ~~註:ChatGPT 與 gpt 是同樣任務的模型,而目前透過 API 只能使用到 GPT-3 (本程式使用的方法)~~ 28 | > 29 | > ~~而非 ChatGPT 使用的 GPT-3.5~~ 30 | > 31 | > 已經改為 GPT-4,與最新的 ChatGPT 相同,只是改為使用 OpenAI API 的方式實現 32 | 33 | # 安裝步驟 34 | 35 | 主要會有四個地方要去:(`這部份不看也沒關係,以下照著做就可以了!`) 36 | 37 | * 我的 github repo:透過 python 串接 openai 的 API,並透過 linebot sdk 提供簡單的訊息回復 38 | * openai:申請 OpenAI 的 API KEY 39 | * line developer:創建機器人 40 | * vercel:提供訊息回復,雖然是 serverless 但已經很符合我們的需求 41 | 42 | 43 | 44 | ## step 1. 至 github fork 專案 45 | 46 | 去我這個專案的 [github repo](https://github.com/howarder3/GPT-Linebot-python-flask-on-vercel "github repo") 47 | 48 | 49 | 50 | 按下面的按鈕, 51 | fork 一份檔案到自己的帳號底下,等等我們會使用到 52 | 53 | > `可以的話,旁邊的 Star 也幫我按一下,是對創作者最大的鼓勵!` 54 | 55 | [![](https://www.wongwonggoods.com/wp-content/uploads/2022/12/截圖-2022-12-13-下午7.27.12.png)](https://www.wongwonggoods.com/portfolio/personal_project/gpt-linebot-python-flask-for-vercel/attachment/%e6%88%aa%e5%9c%96-2022-12-13-%e4%b8%8b%e5%8d%887-27-12/) 56 | 57 | ## step 2. 申請 OpenAI 的 API KEY 58 | 59 | 可以直接去[這裡]( https://beta.openai.com/docs/quickstart/build-your-application "這裡"),一直往下拉,找到這個按鈕,並生成一個 API KEY 60 | 61 | 62 | 63 | [![](https://www.wongwonggoods.com/wp-content/uploads/2022/12/截圖-2022-12-13-下午6.11.22.png)](https://www.wongwonggoods.com/?attachment_id=8016) 64 | 65 | 66 | > 請務必複製下來,這個 KEY 我們取名為 `OPENAI_API_KEY ` 67 | 68 | ## step 3. 去 line developer 建立一個新的機器人 69 | 70 | > 這邊熟悉的人動作應該超快,可以略過, 71 | > 72 | > 以下教學是針對完全沒經驗的新手 73 | 74 | 我們先到[ line developer 的首頁](https://developers.line.biz/zh-hant/ " line developer 的首頁")註冊一下, 75 | 註冊完後,點選 Messaging API。 76 | 77 | [![](https://www.wongwonggoods.com/wp-content/uploads/2022/12/截圖-2022-12-13-下午6.32.33.png)](https://www.wongwonggoods.com/portfolio/personal_project/gpt-linebot-python-flask-for-vercel/attachment/%e6%88%aa%e5%9c%96-2022-12-13-%e4%b8%8b%e5%8d%886-32-33/) 78 | 79 | ### step 3-1. 創建新的 channel 80 | 81 | 第一次使用要創建一個新的 provider 與 channel, 82 | 一個 provider 可以有很多 channel, 83 | 「`而一個 channel 對應的就是一個 chatbot`」, 84 | 這邊以下都照自己想要的名字跟事實填就好。 85 | 86 | 87 | [![](https://www.wongwonggoods.com/wp-content/uploads/2022/12/截圖-2022-12-13-下午6.36.49.png)](https://www.wongwonggoods.com/?attachment_id=8019) 88 | 89 | ### step 3-2. 在 Basic Settings 的分頁,取得 LINE_CHANNEL_SECRET 90 | 91 | 92 | 在 Basic Settings 的分頁,往下找到 channel secret 93 | 94 | 95 | [![](https://www.wongwonggoods.com/wp-content/uploads/2022/12/截圖-2022-12-13-下午6.40.36.png)](https://www.wongwonggoods.com/?attachment_id=8021) 96 | 97 | 98 | 99 | > 請務必複製下來,這個 KEY 我們取名為 `LINE_CHANNEL_SECRET ` 100 | 101 | 102 | ### step 3-3. 在 Messaging API 的分頁,進行一些機器人初始設定 (並加機器人好友) 103 | 104 | 再來我們去上方,選擇 Messaging API 的分頁, 105 | 我們先關閉一些可能會吵的東西 (預設的自動回復之類的), 106 | 107 | 108 | > `這邊可以順便掃一下 QR code 或透過 line ID ,加機器人的 line 好友! ` 109 | 110 | 111 | 我自己是設定如下: 112 | 113 | 114 | [![](https://www.wongwonggoods.com/wp-content/uploads/2022/12/截圖-2022-12-13-下午6.41.06.png)](https://www.wongwonggoods.com/?attachment_id=8022) 115 | 116 | 117 | * 允許加入群組要注意使用 openai 額度可能會超快 118 | * 「`自動回復訊息必關!!!`」,那是 line 的自動回復,不是我們要的 119 | * 歡迎訊息也可以關,這邊我是開著 120 | 121 | ### step 3-4. 在 Messaging API 的分頁,取得 LINE_CHANNEL_ACCESS_TOKEN 122 | 123 | 最後,在 Messaging API 的分頁的最下面, 124 | 找到 channel access token,點選右邊發行,並把他記下來。 125 | 126 | 127 | 128 | [![](https://www.wongwonggoods.com/wp-content/uploads/2022/12/截圖-2022-12-13-下午6.41.14.png)](https://www.wongwonggoods.com/?attachment_id=8020) 129 | 130 | 131 | 132 | > 請務必複製下來,這個 KEY 我們取名為 `LINE_CHANNEL_ACCESS_TOKEN ` 133 | > 134 | > 請不要把這金鑰分享給別人,別人可能會拿去作壞事!!! 135 | 136 | 137 | `然後這邊網頁先不要關,等等還會用到!!!!` 138 | 139 | 140 | ## step 4. 去 vercel 設定相關的環境變數,完成啟動機器人! 141 | 142 | 143 | ### step 4-1. 新增 project 144 | 145 | 去 [vercel 首頁](https://vercel.com/ "vercel 首頁"),add new project 146 | 147 | 148 | 149 | Import Git Repository,選擇你剛剛 fork 的專案 import 150 | 151 | [![](https://www.wongwonggoods.com/wp-content/uploads/2022/12/截圖-2022-12-13-下午7.39.19.png)](https://www.wongwonggoods.com/portfolio/personal_project/gpt-linebot-python-flask-for-vercel/attachment/%e6%88%aa%e5%9c%96-2022-12-13-%e4%b8%8b%e5%8d%887-39-19/) 152 | 153 | ### step 4-2. 設定環境變數 154 | 155 | 選擇「Environment Variables」,把我們剛剛紀錄的 OPENAI_API_KEY、LINE_CHANNEL_SECRET、LINE_CHANNEL_ACCESS_TOKEN 都設定至環境變數, 156 | 按下 Deploy 等待一下就完成囉! 157 | 158 | 159 | 160 | 161 | [![](https://www.wongwonggoods.com/wp-content/uploads/2022/12/截圖-2022-12-13-下午7.46.13.png)](https://www.wongwonggoods.com/portfolio/personal_project/gpt-linebot-python-flask-for-vercel/attachment/%e6%88%aa%e5%9c%96-2022-12-13-%e4%b8%8b%e5%8d%887-46-13/) 162 | 163 | 164 | * 完成後我們可以看到會有以下的三個環境變數 165 | 166 | 167 | [![](https://www.wongwonggoods.com/wp-content/uploads/2022/12/截圖-2022-12-13-下午7.47.23.png)](https://www.wongwonggoods.com/portfolio/personal_project/gpt-linebot-python-flask-for-vercel/attachment/%e6%88%aa%e5%9c%96-2022-12-13-%e4%b8%8b%e5%8d%887-47-23/) 168 | 169 | ### step 4-3. deploy 完成後,可以簡單確認是否有成功 170 | 171 | 去部屬完成的 vercel 頁面,紅框處可以拿到我們要用的網址, 172 | 173 | `把這個網址複製下來,等等要用` 174 | 175 | 這個網址我們也可以點開,應該會出現我們在程式預先寫好的 Hello, World! (應該是一個只有 Hello, World! 的網頁) 176 | 我們可以藉此確定程式有正常的被 Deploy 177 | 178 | 179 | [![](https://www.wongwonggoods.com/wp-content/uploads/2022/12/截圖-2022-12-14-上午1.25.48.png)](https://www.wongwonggoods.com/portfolio/personal_project/gpt-linebot-python-flask-for-vercel/attachment/%e6%88%aa%e5%9c%96-2022-12-14-%e4%b8%8a%e5%8d%881-25-48/) 180 | 181 | 182 | ## step 5. 設定 webhook 183 | 184 | 185 | 回到 line developer 的 Messaging API 分頁, 186 | 將剛剛 step 4-3. 的網址填入,並在後面加上 「/webhook」,例如下圖 187 | 可以用 Verify 看看有沒有問題,通常應該會是寫「Success」 188 | 189 | * 「`記得開啟下面的使用 Use webhook `」 190 | 191 | 192 | 193 | [![](https://www.wongwonggoods.com/wp-content/uploads/2022/12/截圖-2022-12-13-下午9.29.23.png)](https://www.wongwonggoods.com/portfolio/personal_project/gpt-linebot-python-flask-for-vercel/attachment/%e6%88%aa%e5%9c%96-2022-12-13-%e4%b8%8b%e5%8d%889-29-23/) 194 | 195 | 196 | 197 | # 完成圖範例 198 | 199 | 200 | [![](https://www.wongwonggoods.com/wp-content/uploads/2022/12/截圖-2022-12-12-下午11.24.29.png)](https://www.wongwonggoods.com/?attachment_id=8017) 201 | 202 | 203 | [![](https://www.wongwonggoods.com/wp-content/uploads/2022/12/截圖-2022-12-12-下午11.21.45.png)](https://www.wongwonggoods.com/?attachment_id=8018) 204 | 205 | 206 | 207 | 208 | # 此 linebot 的其他一些內建功能 209 | 210 | ## 機器人「說話開關」 211 | 212 | 這個本來是我除錯用的,因為有時候回復一些怪東西會很吵, 213 | 意外得到好評,所以這個功能就被保留下來 214 | 215 | * 輸入「說話」:機器人開啟說話模式,預設是開啟的 216 | * 輸入「閉嘴」:機器人暫停說話模式 (`但一段時間會自動再起動`),閉嘴後將不會對任何對話有反應。輸入「說話」可再次開啟對話。 217 | 218 | ## 其他環境參數功能 219 | 220 | 參考自 [memochou1993/gpt-ai-assistant](https://github.com/memochou1993/gpt-ai-assistant?fbclid=IwAR25uqLdKoDKEQd591fSjyM2sDJJR3Xb-VgcXDIFV_7i3RMWWv2oiyG26RQ) 的作法,下列參數也可藉由設定 vercel 的環境變數來作調控。 221 | 222 | |環境變數名稱 |預設值 |說明 | 223 | |------------------------|--------------- |-----| 224 | | OPENAI_MODEL | text-davinci-003| 請參考 OpenAI 對 [model](https://beta.openai.com/docs/api-reference/completions/create#completions/create-model) 的敘述| 225 | | OPENAI_TEMPERATURE | 0 | 請參考 OpenAI 對 [temperature](https://beta.openai.com/docs/api-reference/completions/create#completions/create-temperature) 的敘述| 226 | | OPENAI_FREQUENCY_PENALTY| 0 | 請參考 OpenAI 對 [frequency_penalty](https://beta.openai.com/docs/api-reference/completions/create#completions/create-frequency_penalty) 的敘述| 227 | | OPENAI_PRESENCE_PENALTY | 0.6 | 請參考 OpenAI 對 [presence_penalty](https://beta.openai.com/docs/api-reference/completions/create#completions/create-presence_penalty) 的敘述| 228 | | OPENAI_MAX_TOKENS | 240 | 請參考 OpenAI 對 [max_tokens](https://beta.openai.com/docs/api-reference/completions/create#completions/create-max_tokens) 的敘述| 229 | | MSG_LIST_LIMIT | 20 | prompt 參數往回參照的句數| 230 | | INIT_LANGUAGE | zh | 決定初始語言,可設置為 "zh" 或 "en"| 231 | 232 | 233 | 234 | # TODO List & Future Work 235 | 236 | 237 | > 目前基本功能都已經有了,然後我比較忙可能沒空一直更新QQ 238 | > 239 | > `還有很多可以優化的地方,歡迎提供 PR!` 240 | 241 | - [x] (已調整完成) 回復文字感覺不是很順 (可能需要研究一下 API 使用方法) 242 | - [x] (已調整完成) 記憶功能 243 | ... 244 | 245 | > 目前算法是「`紀錄使用者與 AI 的前20句對話`」,嘗試推論出下一句話應該要說什麼。以達成延續話題的效果。 246 | 247 | 248 | # 靈感來源 249 | * 本文同步更新至我的個人網站:[【Side Project】(全圖文教學) 用 Python flask 實作類似 ChatGPT 的 Linebot,並部屬至 vercel 上](https://www.wongwonggoods.com/portfolio/personal_project/gpt-linebot-python-flask-for-vercel/) 250 | * 感謝 [memochou1993/gpt-ai-assistant](https://github.com/memochou1993/gpt-ai-assistant?fbclid=IwAR25uqLdKoDKEQd591fSjyM2sDJJR3Xb-VgcXDIFV_7i3RMWWv2oiyG26RQ) 提供的 node.js 版本串接 vercel 示範,讓我有了想把 python linebot 也串進 vercel 的靈感,(目前感覺下來,免費又好用(?)) 251 | * 感謝 [Lanznx/HealthLineBot](https://github.com/Lanznx/HealthLineBot) 給了一個很好的 python Django 範例,然而我不會 Django XD,vercel 官方文件好像也沒有提到這部份,總之後來就改成了 flask 版本,也符合 linebot 推薦的範例。 252 | 253 | 254 | 255 | # 參考資料 256 | 257 | * Line 官方提供的 python flask 製作 linebot 的 sample code [line/line-bot-sdk-python](https://github.com/line/line-bot-sdk-python) 258 | * Vercel 官方提供的 python runtime Flask 範例 [Deploy an example with Flask](https://vercel.com/docs/concepts/functions/serverless-functions/runtimes/python#python-version) 259 | 260 | 261 | -------------------------------------------------------------------------------- /api/chatgpt.py: -------------------------------------------------------------------------------- 1 | from api.prompt import Prompt 2 | import os 3 | from openai import OpenAI 4 | client = OpenAI() 5 | 6 | client.api_key = os.getenv("OPENAI_API_KEY") 7 | 8 | 9 | class ChatGPT: 10 | def __init__(self): 11 | self.prompt = Prompt() 12 | self.model = os.getenv("OPENAI_MODEL", default = "gpt-4-1106-preview") 13 | self.temperature = float(os.getenv("OPENAI_TEMPERATURE", default = 0)) 14 | self.max_tokens = int(os.getenv("OPENAI_MAX_TOKENS", default = 500)) 15 | 16 | def get_response(self): 17 | response = client.chat.completions.create( 18 | model=self.model, 19 | messages=self.prompt.generate_prompt(), 20 | ) 21 | return response.choices[0].message.content 22 | 23 | def add_msg(self, text): 24 | self.prompt.add_msg(text) 25 | -------------------------------------------------------------------------------- /api/index.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request, abort 2 | from linebot import LineBotApi, WebhookHandler 3 | from linebot.exceptions import InvalidSignatureError 4 | from linebot.models import MessageEvent, TextMessage, TextSendMessage 5 | from api.chatgpt import ChatGPT 6 | 7 | import os 8 | 9 | line_bot_api = LineBotApi(os.getenv("LINE_CHANNEL_ACCESS_TOKEN")) 10 | line_handler = WebhookHandler(os.getenv("LINE_CHANNEL_SECRET")) 11 | working_status = os.getenv("DEFALUT_TALKING", default = "true").lower() == "true" 12 | 13 | app = Flask(__name__) 14 | chatgpt = ChatGPT() 15 | 16 | # domain root 17 | @app.route('/') 18 | def home(): 19 | return 'Hello, World!' 20 | 21 | @app.route("/webhook", methods=['POST']) 22 | def callback(): 23 | # get X-Line-Signature header value 24 | signature = request.headers['X-Line-Signature'] 25 | # get request body as text 26 | body = request.get_data(as_text=True) 27 | app.logger.info("Request body: " + body) 28 | # handle webhook body 29 | try: 30 | line_handler.handle(body, signature) 31 | except InvalidSignatureError: 32 | abort(400) 33 | return 'OK' 34 | 35 | 36 | @line_handler.add(MessageEvent, message=TextMessage) 37 | def handle_message(event): 38 | global working_status 39 | if event.message.type != "text": 40 | return 41 | 42 | if event.message.text == "說話": 43 | working_status = True 44 | line_bot_api.reply_message( 45 | event.reply_token, 46 | TextSendMessage(text="我可以說話囉,歡迎來跟我互動 ^_^ ")) 47 | return 48 | 49 | if event.message.text == "閉嘴": 50 | working_status = False 51 | line_bot_api.reply_message( 52 | event.reply_token, 53 | TextSendMessage(text="好的,我乖乖閉嘴 > <,如果想要我繼續說話,請跟我說 「說話」 > <")) 54 | return 55 | 56 | if working_status: 57 | chatgpt.add_msg(f"HUMAN:{event.message.text}?\n") 58 | reply_msg = chatgpt.get_response().replace("AI:", "", 1) 59 | chatgpt.add_msg(f"AI:{reply_msg}\n") 60 | line_bot_api.reply_message( 61 | event.reply_token, 62 | TextSendMessage(text=reply_msg)) 63 | 64 | 65 | if __name__ == "__main__": 66 | app.run() 67 | -------------------------------------------------------------------------------- /api/prompt.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | chat_language = os.getenv("INIT_LANGUAGE", default = "zh") 4 | 5 | MSG_LIST_LIMIT = int(os.getenv("MSG_LIST_LIMIT", default = 7)) 6 | LANGUAGE_TABLE = { 7 | "zh": "哈囉!", 8 | "en": "Hello!" 9 | } 10 | 11 | AI_GUIDELINES = '你是一個AI助教,會用蘇格拉底教學法代替老師初步回應,如果有需要會提醒學生跟老師確認' 12 | 13 | class Prompt: 14 | def __init__(self): 15 | self.msg_list = [] 16 | self.msg_list.append( 17 | { 18 | "role": "system", 19 | "content": f"{LANGUAGE_TABLE[chat_language]}, {AI_GUIDELINES})" 20 | }) 21 | def add_msg(self, new_msg): 22 | if len(self.msg_list) >= MSG_LIST_LIMIT: 23 | self.msg_list.pop(0) 24 | self.msg_list.append({"role": "user", "content": new_msg}) 25 | 26 | def generate_prompt(self): 27 | return self.msg_list -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | flask==2.2.2 2 | Werkzeug==2.3.7 3 | line-bot-sdk 4 | openai -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "builds": [ 4 | { 5 | "src": "api/index.py", 6 | "use": "@vercel/python" 7 | } 8 | ], 9 | "routes": [ 10 | { 11 | "src": "/(.*)", 12 | "dest": "api/index.py" 13 | } 14 | ] 15 | } --------------------------------------------------------------------------------