聊天機器人應用實務

Line Bot Again

Web Server

Line Bot

網站空間/網址?

開發語言?

架網站

  • 網站開發語言: 以Flask為例
  • 網址取得/轉址: ngrok

Flask+ngrok架設網站運作方式

NGROK服務

網站空間: localhost(本機)

網站開發:

Flask

網址: ngrok提供

'''
將程式碼存為一個 .py 檔案,如app.py並在命令列中執行該檔案以啟動伺服器。例如:
   python app.py
則應該看到"Hello, World!"
'''
# 引入 Flask 模組
from flask import Flask

# 建立 Flask 物件
app = Flask(__name__)

# 設定路由和對應的處理函式
@app.route('/')
def home():
    return 'Hello, World!'

# 啟動 Flask 伺服器
if __name__ == '__main__':
    app.run()
pip install flask

安裝Flask套件

Flask+ngrok架設網站網站

app.py

Flask+ngrok架設網站使用ngrok

下載並安裝 ngrok

Flask+ngrok架設網站使用ngrok

前往 https://ngrok.com/ 註冊免費帳號

登入後即可看到authtoken

Flask+ngrok架設網站使用ngrok

在命令列中,切換到你的 ngrok 安裝目錄

ngrok http 5000

告知 ngrok 監聽本機 5000 埠號(Flask 的預設埠號)

你的公開網址

程式必須維持執行

ngrok config add-authtoken 你的authtoken

設定一次即可

Flask+ngrok架設網站Colab

from google.colab import drive
drive.mount('/content/drive', force_remount=True) # 名稱

!mkdir -p /drive    # 在colab環境建立drive資料夾
#umount /drive
!mount --bind /content/drive/MyDrive/drive # 綁定雲端硬碟
!mkdir -p /drive/ngrok-ssh                 # 建立資料夾
!mkdir -p ~/.ssh                     

1. 新建Colab筆記本,如ngrok_hello,將此筆記本接上雲端硬碟

ngrok_hello.ipynb

!mkdir -p /drive/ngrok-ssh
%cd /drive/ngrok-ssh
!wget https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip -O ngrok-stable-linux-amd64.zip
!unzip -u ngrok-stable-linux-amd64.zip
!cp /drive/ngrok-ssh/ngrok /ngrok
!chmod +x /ngrok

ngrok_hello.ipynb

2.下載ngrok,安裝至雲端硬碟

Flask+ngrok架設網站Colab

!/ngrok authtoken 貼上你的authtoken

3.設定authtoken

ngrok_hello.ipynb

from flask import Flask
from flask_ngrok import run_with_ngrok

app = Flask(__name__)
run_with_ngrok(app)

@app.route("/")
def home():
    return f"<h1>hello World!</h1>"

app.run()

ngrok_hello.ipynb

5. 呼叫run_with_ngrok()執行web app

!pip install flask_ngrok

4.安裝flask_ngrok

架網站web hook

  • Line Messaging API複習

1. 前往LINE Developers Console建立Channel
(使用Messaging API)

 

2. 完成Flask Web程式端的設定

 

3. 從ngrok執行端取得Line所需之 web hook URL

 

4. 完成Line web hook設定(Line 平台端)

 

整合Line平台Messaging API

步驟1: 建立Line Channel使用Line Messaging API

1.  前往LINE Developers Console建立Channel

你的系統(如Dialogflow)

Channel是LINE平台與你的系統之間的「溝通路徑」

(HTTP協定、採用「訂閱」機制)

建立Line Channel使用Line Messaging API

建立Line Channel使用Line Messaging API

1-2.  以開發者身份註冊(僅需設定一次)

輸入姓名、Email

建立Line Channel使用Line Messaging API

1-3.  建立新的Provider

輸入Provider名稱

建立Line Channel使用Line Messaging API

1-4.  建立Messaging API channel  (2/3)

建立Line Channel使用Line Messaging API

1-4.  建立Messaging API channel  (3/3)

建立Line Channel使用Line Messaging API

1-5.  確認Channel已成功建立

步驟2: 設定Channel secret, Channel access token環境變數

建議名稱

用於Flask程式中

從Line Developers Console複製過來

確定後,再開啟CMD

from flask_ngrok import run_with_ngrok
from flask import Flask, request
# 載入 LINE Message API 相關函式庫
from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import MessageEvent, TextMessage, TextSendMessage
# 載入 json 標準函式庫,處理回傳的資料格式
import json
import os

app = Flask(__name__)

@app.route("/", methods=['POST'])
def linebot():
    body = request.get_data(as_text=True)               # 取得收到的訊息內容
    try:
        json_data = json.loads(body)                # json 格式化訊息內容
        access_token = os.environ.get('CHANNEL_ACCESS_TOKEN')
        secret = os.environ.get('CHANNEL_SECRET')
        line_bot_api = LineBotApi(access_token)         # 確認 token 是否正確
        handler = WebhookHandler(secret)            # 確認 secret 是否正確
        signature = request.headers['X-Line-Signature'] # 加入回傳的 headers
        handler.handle(body, signature)             # 綁定訊息回傳的相關資訊
        msg = json_data['events'][0]['message']['text'] # 取得 LINE 收到的文字訊息
        tk = json_data['events'][0]['replyToken']       # 取得回傳訊息的 Token
        line_bot_api.reply_message(tk,TextSendMessage(msg))  # 回傳訊息
        print(msg, tk)                    # 印出內容
    except:
        print(body)                  # 如果發生錯誤,印出收到的內容
    return 'OK'                 # 驗證 Webhook 使用,不能省略
if __name__ == "__main__":
  run_with_ngrok(app)           # 串連 ngrok 服務
  app.run()

app.py

步驟3: 執行Flask App, 啟動ngrok服務

若出現[WinError 10061] 無法連線,因為目標電腦拒絕連線

代表先前Flask App未關閉,必須手動關閉服務

1.查詢所有執行中程序(process), 依字串':5000'過濾

netstat -ano | findstr :5000

2. 終止程序,最右邊數字為process id

taskkill /PID 最右邊數字 /F

步驟3: 執行Flask App, 啟動ngrok服務

ngrok http 5000
ngrok config add-authtoken 你的authtoken

找到你的ngrok程式所在資料夾,設定authtoken(只須做一次)

Flask App正常執行後,綁定ngrok服務

複製ngrok提供的網址

步驟4: Line後台設定web hook網址

# OpenAI 函式庫
from openai import OpenAI  

# Flask函式庫
from flask import Flask, request

# 載入 LINE Message API 相關函式庫
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
#... web hook 程式

app.py (part 1: importing)

Webhook程式 import

# ...import 略(見上頁)...


app = Flask(__name__)

# 建立OpenAI Client, 需有環境變數OPENAI_API_KEY
client = OpenAI()  

# 從Line Developers Console取得Line Messaging API Keys
# 一樣設定為環境變數
access_token = os.environ.get('CHANNEL_ACCESS_TOKEN')
secret = os.environ.get('CHANNEL_SECRET')

# 設定呼叫Line ApiClient所需環境: client server溝通必須有合法的access_token
configuration = Configuration(access_token=access_token)
# 建立Web hook handler,必須有合法的channel secret
handler = WebhookHandler(secret)

# ...web hook程式 略...
# ...
# ...

if __name__ == "__main__":
    app.run()

Webhook程式 OpenAI, Line Web hook準備工作

app.py (part 2: 準備工作)

# ...import 略(見上頁)...

# ...準備工作openai client, line messaging api參數等 略...

@app.route("/callback", methods=['POST'])
def callback():
    # 取得 X-Line-Signature header數值
    signature = request.headers['X-Line-Signature']
    
    # 取得 request body 的文字內容
    body = request.get_data(as_text=True)
    app.logger.info("Request body: " + body)
   
    try:           
        # 處理 webhook body內容,交由@handler.add, handler.default處理
        handler.handle(body, signature)
    except InvalidSignatureError:
        app.logger.info("Invalid signature. 檢查Channel access token與channel secret設定")
        abort(400)
    return 'OK'
# ...web hook handler 略
# ....

Webhook程式 回呼函式-主程式

app.py (part 3: callback)

# ...import 略(見上頁)...
# ...準備工作openai client, line messaging api參數等 略...
# ...web hook handler主程式 略
@handler.add(MessageEvent, message=TextMessageContent)
def handle_message(event):
    with ApiClient(configuration) as api_client:
        msg = event.message.text
        ai_msg = msg[:6].lower()   # 取出文字的前五個字元,轉換成小寫
        reply_msg = ''        
        if ai_msg == 'hi ai:':     # 取出文字的前五個字元是 hi ai:
            # 將第六個字元之後的訊息發送給 OpenAI
            response = client.chat.completions.create(
                model='gpt-3.5-turbo',
                messages=[ {'role': 'user', 'content': msg[6:]}],
                    max_tokens=256,
                    temperature=0.5,
                )
            # 接收到回覆訊息後,移除換行符號
            reply_msg = response.choices[0].message.content.replace('\n','')
        else:
            reply_msg = msg
        text_message = TextMessage(text=reply_msg)        
        line_bot_api = MessagingApi(api_client)
        line_bot_api.reply_message_with_http_info(
          ReplyMessageRequest(reply_token=event.reply_token,messages=[text_message])
        )
# ....

Webhook程式 回呼函式-handler

app.py (part 4: handler)

from openai import OpenAI
import os

from flask import Flask, request

# 載入 LINE Message API 相關函式庫
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 json

app = Flask(__name__)
client = OpenAI()  # for OpenAI API calls
access_token = os.environ.get('CHANNEL_ACCESS_TOKEN')
secret = os.environ.get('CHANNEL_SECRET')

configuration = Configuration(access_token=access_token)
handler = WebhookHandler(secret)
        
@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)
    #json_data = json.loads(body)
    #print(json_data)
    try:           
        # handle webhook body
        handler.handle(body, signature)
    except InvalidSignatureError:
        app.logger.info("Invalid signature. 檢查Channel access token與channel secret設定")
        abort(400)
    return 'OK'

@handler.add(MessageEvent, message=TextMessageContent)
def handle_message(event):
    with ApiClient(configuration) as api_client:
        msg = event.message.text
        # 取出文字的前五個字元,轉換成小寫
        ai_msg = msg[:6].lower()
        reply_msg = ''
        # 取出文字的前五個字元是 hi ai:
        if ai_msg == 'hi ai:':            
            # 將第六個字元之後的訊息發送給 OpenAI
            response = client.chat.completions.create(
                model='gpt-3.5-turbo',
                messages=[
                    {'role': 'user', 'content': msg[6:]}],
                max_tokens=256,
                temperature=0.5,
                )
            # 接收到回覆訊息後,移除換行符號
            reply_msg = response.choices[0].message.content.replace('\n','')
        else:
            reply_msg = msg
        text_message = TextMessage(text=reply_msg)
        
        line_bot_api = MessagingApi(api_client)
        line_bot_api.reply_message_with_http_info(
            ReplyMessageRequest(
                reply_token=event.reply_token,
                messages=[text_message]
            )
        )

if __name__ == "__main__":
    app.run()

app.py (完整)

其他範例

@handler.add(MessageEvent, message=TextMessageContent)
def handle_message(event):
    with ApiClient(configuration) as api_client:
        msg = event.message.text
        if not str(msg).startswith('/'):  # 如果開頭不是/線字元,代表非指令
            reply_msg = msg    # 非指令直接回覆
        else:
            tokens = str(msg).split() # 空白字元分割
            command = tokens[0]
            reply_msg = ''        
            if command == '/說笑話':            
                response = client.chat.completions.create(
                    model='gpt-3.5-turbo',
                    messages=[
                        {'role': 'system', 'content': '你是說笑話專家'}, # 角色
                        {'role': 'user', 'content': '說一則笑話'}],     # 指令
                    max_tokens=256,
                    temperature=0.5,
                )
                # 接收到回覆訊息後,移除換行符號
                reply_msg = response.choices[0].message.content.replace('\n','')
            else:
                reply_msg = msg
        text_message = TextMessage(text=reply_msg)        
        line_bot_api = MessagingApi(api_client)
        line_bot_api.reply_message_with_http_info(
            ReplyMessageRequest(
                reply_token=event.reply_token,messages=[text_message]
            )
        )
# ....

 說笑話 (部份)

from openai import OpenAI

client = OpenAI(
    api_key= "你的API_KEY"
)

response = client.chat.completions.create(
    model = "gpt-3.5-turbo",
    messages=[
        {
            "role": "user",
            "content": "建立一個包含 8 個問題的清單,用於訪問一位科幻作家。"
        }
    ],
    temperature=0.5,  # 回應變化的多樣性,0:變化最少, 1:變化最多
    max_tokens=1024  # 最多1024個token
)

print(response.choices[0].message.content)  # 訊息

範例:面試提問generation生成

聊天機器人應用實務

By Leuo-Hong Wang

聊天機器人應用實務

  • 279