把盞言歡,款款而談,ChatGPT結合釘釘機器人(outgoing回撥)打造人工智慧群聊/單聊場景,基於Python3.10

2022-12-08 12:00:46

就像黑火藥時代裡突然誕生的核彈一樣,OpenAI的ChatGPT語言模型的橫空出世,是人工智慧技術發展史上的一個重要里程碑。這是一款無與倫比、超凡絕倫的模型,能夠進行自然語言推理和對話,並且具有出色的語言生成能力。

好吧,本篇的開頭其實是由ChatGPT生成的:

沒辦法,面對這個遠超時代的AI產品,我們能說什麼呢?頂禮膜拜?驚為天人?任何言語對於描述ChatGPT來說已經是蒼白無力的,而辭海中的形容詞在面對ChatGPT時也已經鞭長莫及。

一句話:言語不能贊其偉大。

本次我們利用ChatGPT的開放API接入釘釘群聊/單聊機器人,讓釘釘機器人具備進行自然語言推理和對話的能力,所謂化腐朽為神奇,不過如此。

註冊和使用OpenAi的ChatGPT

首先註冊OpenAi平臺:https://beta.openai.com/ ,由於ChatGPT過於火爆,導致很多地區無法正常註冊,這裡推薦使用北美地區的代理IP,與此同時,一定要注意,如果之後希望使用後端的API介面方式呼叫ChatGPT,就不要使用谷歌或者微軟的三方賬號進行登入,否則無法通過郵箱和祕鑰交換OpenAi平臺的access_token,切記。

同時,接受驗證碼手機號也必須是北美地區的手機號,這裡推薦一個北美地區的接碼平臺:https://sms.qisms.com/index 非常好用。

註冊成功之後,這裡推薦github上開源大神rawandahmad698已經封裝好的開源SDK,避免重複造輪子:https://github.com/rawandahmad698/PyChatGPT

安裝SDK:

pip3 install chatgptpy --upgrade

安裝好之後,編寫測試指令碼:

chat = Chat(email="OpenAi郵箱", password="OpenAi密碼",proxies="代理地址")  
  
answer = chat.ask("你好")  
  
print(answer)

注意,執行程式碼之前,一定要使用代理proxies,並且確保是北美地區的IP地址。

程式返回:

[OpenAI] Email address: ********  
[OpenAI] Password: *********  
[OpenAI] Using proxy: {'http': 'http://localhost:4780', 'https': 'http://localhost:4780'}  
[OpenAI] Beginning auth process  
[OpenAI][1] Making request to https://chat.openai.com/auth/login  
[OpenAI][1] Request was successful  
[OpenAI][2] Beginning part two  
[OpenAI][2] Grabbing CSRF token from https://chat.openai.com/api/auth/csrf  
[OpenAI][2] Request was successful  
[OpenAI][2] CSRF Token: 1b1357a34e4b0b9a74e999372fe0413ab981c9a72e030a54b3bf172bd6176c5e  
[OpenAI][3] Beginning part three  
[OpenAI][3] Making request to https://chat.openai.com/api/auth/signin/auth0?prompt=login  
[OpenAI][3] Request was successful  
[OpenAI][3] Callback URL: https://auth0.openai.com/authorize?client_id=TdJIcbe16WoTHtN95nyywh5E4yOo6ItG&scope=openid%20email%20profile%20offline_access%20model.request%20model.read%20organization.read&response_type=code&redirect_uri=https%3A%2F%2Fchat.openai.com%2Fapi%2Fauth%2Fcallback%2Fauth0&audience=https%3A%2F%2Fapi.openai.com%2Fv1&prompt=login&state=RJt9U13ATPmlt795xMNohQZcUNOytZNvHoq3JI8HGZ4&code_challenge=Pq97ptna00Ybak2dUmIMhR3eqmXZnZz-Fij7otMMw7U&code_challenge_method=S256  
[OpenAI][4] Making request to https://auth0.openai.com/authorize?client_id=TdJIcbe16WoTHtN95nyywh5E4yOo6ItG&scope=openid%20email%20profile%20offline_access%20model.request%20model.read%20organization.read&response_type=code&redirect_uri=https%3A%2F%2Fchat.openai.com%2Fapi%2Fauth%2Fcallback%2Fauth0&audience=https%3A%2F%2Fapi.openai.com%2Fv1&prompt=login&state=RJt9U13ATPmlt795xMNohQZcUNOytZNvHoq3JI8HGZ4&code_challenge=Pq97ptna00Ybak2dUmIMhR3eqmXZnZz-Fij7otMMw7U&code_challenge_method=S256  
[OpenAI][4] Request was successful  
[OpenAI][4] Current State: hKFo2SA5VzlqUDA0Mkl5TnQtNUpYcGRBU0ZfRkhQVUY1eVpWV6Fur3VuaXZlcnNhbC1sb2dpbqN0aWTZIGMzU0xvbThRUXFxMTczeVg4bF8zRFZnYVNOM2M3Q0RFo2NpZNkgVGRKSWNiZTE2V29USHROOTVueXl3aDVFNHlPbzZJdEc  
[OpenAI][5] Making request to https://auth0.openai.com/u/login/identifier?state=hKFo2SA5VzlqUDA0Mkl5TnQtNUpYcGRBU0ZfRkhQVUY1eVpWV6Fur3VuaXZlcnNhbC1sb2dpbqN0aWTZIGMzU0xvbThRUXFxMTczeVg4bF8zRFZnYVNOM2M3Q0RFo2NpZNkgVGRKSWNiZTE2V29USHROOTVueXl3aDVFNHlPbzZJdEc  
[OpenAI][5] Request was successful  
[OpenAI][5] No captcha detected  
[OpenAI][6] Making request to https://auth0.openai.com/u/login/identifier  
[OpenAI][6] Email found  
[OpenAI][7] Entering password...  
[OpenAI][7] Password was correct  
[OpenAI][7] Old state: hKFo2SA5VzlqUDA0Mkl5TnQtNUpYcGRBU0ZfRkhQVUY1eVpWV6Fur3VuaXZlcnNhbC1sb2dpbqN0aWTZIGMzU0xvbThRUXFxMTczeVg4bF8zRFZnYVNOM2M3Q0RFo2NpZNkgVGRKSWNiZTE2V29USHROOTVueXl3aDVFNHlPbzZJdEc  
[OpenAI][7] New State: c3SLom8QQqq173yX8l_3DVgaSN3c7CDE  
[OpenAI][8] Making request to https://auth0.openai.com/authorize/resume?state=c3SLom8QQqq173yX8l_3DVgaSN3c7CDE  
[OpenAI][8] All good  
[OpenAI][8] Access Token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ik1UaEVOVUpHTkVNMVFURTRNMEZCTWpkQ05UZzVNRFUxUlRVd1FVSkRNRU13UmtGRVFrRXpSZyJ9.eyJodHRwczovL2FwaS5vcGVuYWkuY29tL3Byb2ZpbGUiOnsiZW1haWwiOiJ6Y3hleTI5MTFAb3V0bG9vay5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiZ2VvaXBfY291bnRyeSI6IlVTIn0sImh0dHBzOi8vYXBpLm9wZW5haS5jb20vYXV0aCI6eyJ1c2VyX2lkIjoidXNlci1IcHQ2SXF6R0k0RW43V213dGdzaUVOUjUifSwiaXNzIjoiaHR0cHM6Ly9hdXRoMC5vcGVuYWkuY29tLyIsInN1YiI6ImF1dGgwfDYzOTA3ZWRiMTQzYTFkZjQxMzk5Yzc0YyIsImF1ZCI6WyJodHRwczovL2FwaS5vcGVuYWkuY29tL3YxIiwiaHR0cHM6Ly9vcGVuYWkuYXV0aDAuY29tL3VzZXJpbmZvIl0sImlhdCI6MTY3MDQ1OTkzNywiZXhwIjoxNjcwNTQ2MzM3LCJhenAiOiJUZEpJY2JlMTZXb1RIdE45NW55eXdoNUU0eU9vNkl0RyIsInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUgbW9kZWwucmVhZCBtb2RlbC5yZXF1ZXN0IG9yZ2FuaXphdGlvbi5yZWFkIG9mZmxpbmVfYWNjZXNzIn0.PtXKhJqwudNKLIkNRc5OO6T7Tsl8ydZ8WWnCJ3Ax2c40CQibRTiGLDmfvk2gW5pVIkOpKldWYs6Jrd8UVi0Ih9VMDwS9JL6HpZKsoRaIhy6r6l7AW5vMMQN-l0ntCsgefQeGIrwtCTUsIklN8dyZDkRkympC2AzRkayAcFvFckXTHi_J5Fivr5J7We_OM4cGFJEKTLkaSw6MnYku-uYwAKPVEpFsF7fLnUBRQxn5Zz90FhdeLYEg4IUjPWKPp1iMbp_fa9qhwwtKBwogtrIVzq2t8NdUotoNYgoo2uV2xjQWC2m4V4C_xgkSzLj2TTtRJMOYKGH-lHWs2_yRQF0wOg  
[OpenAI][9] Saving access token...  
[OpenAI][8] Saved access token

首次執行程式會通過代理自動登入OpenAi平臺,並且換取token,最後將token儲存在本地。

隨後返回ChatGPT的資訊:

➜  mydemo git:(master) ✗ /opt/homebrew/bin/python3.10 "/Users/liuyue/wodfan/work/mydemo/test_chatgpt.py"  
Using proxies: http://localhost:4780  
你好,很高興為你提供幫助。有什麼需要我幫忙的嗎?

至此,ChatGPT介面就偵錯好了。

設定釘釘Dingding機器人

隨後,我們來設定C端的機器人,注意這裡一定要使用支援outgoing回撥的企業機器人,而不是普通的機器人,參考檔案:https://open.dingtalk.com/document/group/enterprise-created-chatbot

建立好企業機器人之後,獲取機器人應用的Key和祕鑰,同時設定好出口IP和介面地址:

所謂出口IP即呼叫釘釘服務合法的ip,訊息接受地址是接受C端資訊的地址,這裡我們使用非同步非阻塞的Tornado框架來構建接受資訊服務:

import hmac  
import hashlib  
import base64  
import json  
import tornado  
  
from tornado.options import define, options  
define('port', default=8000, help='default port',type=int)  
  
class Robot(tornado.web.RequestHandler):  
  
    async def post(self):  
  
  
        timestamp = self.request.headers.get('timestamp', None)  
  
        sign = self.request.headers.get('sign', None)  
        app_secret = '釘釘機器人祕鑰'  
        app_secret_enc = app_secret.encode('utf-8')  
        string_to_sign = '{}\n{}'.format(timestamp, app_secret)  
        string_to_sign_enc = string_to_sign.encode('utf-8')  
        hmac_code = hmac.new(app_secret_enc, string_to_sign_enc, digestmod=hashlib.sha256).digest()  
        my_sign = base64.b64encode(hmac_code).decode('utf-8')  
        if sign != my_sign:  
            return self.finish({"errcode":1,"msg":"簽名有誤"})  
        data = json.loads(self.request.body)  
        text = data['text']["content"]  
        atUsers = data.get("atUsers",None)  
        uid = data.get("senderStaffId",None)  
        return self.finish({"errcode":0,"msg":text})  
  
urlpatterns = [  
    (r"/robot_chat/",Robot),  
]  
  
  
# 建立Tornado範例  
application = tornado.web.Application(urlpatterns,debug=True)  
  
  
if __name__ == "__main__":  
    tornado.options.parse_command_line()  
    application.listen(options.port)  
    tornado.ioloop.IOLoop.instance().start()

這裡我們通過Robot非同步控制器來接受所有來自釘釘使用者端的資訊,即人類對機器人說的話,需要注意的是,後端服務需要對請求頭中的timestamp和sign進行驗證,以判斷是否是來自釘釘的合法請求,避免其他仿冒釘釘呼叫開發者的HTTPS服務傳送資料。

所以這裡一旦簽名有問題,就結束邏輯:



timestamp = self.request.headers.get('timestamp', None)  
  
sign = self.request.headers.get('sign', None)  
app_secret = '釘釘機器人祕鑰'  
app_secret_enc = app_secret.encode('utf-8')  
string_to_sign = '{}\n{}'.format(timestamp, app_secret)  
string_to_sign_enc = string_to_sign.encode('utf-8')  
hmac_code = hmac.new(app_secret_enc, string_to_sign_enc, digestmod=hashlib.sha256).digest()  
my_sign = base64.b64encode(hmac_code).decode('utf-8')  
if sign != my_sign:  
    return self.finish({"errcode":1,"msg":"簽名有誤"})


最後該介面會返回發信人id(uid)以及具體資訊內容(text)。

至此,後端接受服務就設定好了。

下面就是後端推播服務,首先,根據官方檔案:https://open.dingtalk.com/document/orgapp-server/obtain-the-access_token-of-an-internal-app?spm=ding_open_doc.document.0.0.5f255239xgW3zE

需要獲取釘釘介面的token:

def get_token(self):  
  
        res = requests.post("https://api.dingtalk.com/v1.0/oauth2/accessToken",data=json.dumps({"appKey":self._appKey,"appSecret":self._appSecret}),headers={"Content-Type":"application/json"})  
  
        token = res.json()["accessToken"]  
  
        return token

隨後,根據檔案:https://open.dingtalk.com/document/group/chatbots-send-one-on-one-chat-messages-in-batches?spm=ding_open_doc.document.0.0.22e749acXECz5m

我們來設定單聊推播:

# 單聊  
    def send_message(self,uid,message):  
  
        res = requests.post("https://api.dingtalk.com/v1.0/robot/oToMessages/batchSend",data=json.dumps({"robotCode":self._appKey,"userIds":[uid],"msgKey":"sampleText","msgParam":'{"content":"'+message+'"}'}),headers={"Content-Type":"application/json","x-acs-dingtalk-access-token":self._token})  
  
        print(res.text)

具體效果:

接著,繼續根據官方檔案:https://open.dingtalk.com/document/robots/guide-to-user-access-for-intra-enterprise-robot-group-chat

設定群聊推播方法:

# 群聊  
    def send_user(self,uid,message):  
  
        data = {  
        "at": {  
            "atUserIds": [  
                uid  
            ]  
        },  
        "text": {  
            "content": message  
        },  
        "msgtype": "text"  
        }  
  
        res = requests.post(self._webhook,data=json.dumps(data),headers={"Content-Type":"application/json"})  
  
        print(res.text)

群聊效果:

這裡需要注意的是,單聊是通過介面的方式進行推播,而群內聊天是通過webhook方式進行推播,關於webhook,請移玉步至:使用python3.7設定開發釘釘群自定義機器人(2020年新版攻略)

完整程式碼:

import requests  
import json  
  
from pychatgpt import Chat  
  
class DingDing:  
  
  
    def __init__(self,appKey=None,appSecret=None) -> None:  
  
        self._appKey = appKey  
  
        self._appSecret = appSecret  
          
        self._token = self.get_token()  
  
        # 機器人webhook地址  
        self._webhook = ""  
  
          
  
  
    def get_token(self):  
  
        res = requests.post("https://api.dingtalk.com/v1.0/oauth2/accessToken",data=json.dumps({"appKey":self._appKey,"appSecret":self._appSecret}),headers={"Content-Type":"application/json"})  
  
        token = res.json()["accessToken"]  
  
        return token  
  
    # 單聊  
    def send_message(self,uid,message):  
  
        res = requests.post("https://api.dingtalk.com/v1.0/robot/oToMessages/batchSend",data=json.dumps({"robotCode":self._appKey,"userIds":[uid],"msgKey":"sampleText","msgParam":'{"content":"'+message+'"}'}),headers={"Content-Type":"application/json","x-acs-dingtalk-access-token":self._token})  
  
        print(res.text)  
  
    # 群聊  
    def send_user(self,uid,message):  
  
        data = {  
        "at": {  
            "atUserIds": [  
                uid  
            ]  
        },  
        "text": {  
            "content": message  
        },  
        "msgtype": "text"  
        }  
  
        res = requests.post(self._webhook,data=json.dumps(data),headers={"Content-Type":"application/json"})  
  
        print(res.text)  
  
  
  
  
if __name__ == '__main__':  
  
    dingding = DingDing("appkey","appSecret")  
  
    #chat = Chat(email="OpenAi郵箱", password="OpenAi密碼",proxies="代理地址")  
  
    #answer = chat.ask("你好")  
  
    # 單聊  
    #dingding.send_message('uid',answer)  
  
    # 群聊  
    #dingding.send_user('uid',answer)  
  
    #print(answer)

至此,後端推播服務就設定好了。

結語

最後,奉上Github專案地址,與眾親同饗:https://github.com/zcxey2911/Python_ChatGPT_ForDingding_OpenAi ,毫無疑問,ChatGPT是NLP領域歷史上最偉大的專案,沒有之一,偉大,就是技術層面的極致,你同意嗎?