-
Notifications
You must be signed in to change notification settings - Fork 14
/
app.py
235 lines (193 loc) · 7.96 KB
/
app.py
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
import io
import os
import openai
import pydub
import requests
import soundfile as sf
import speech_recognition as sr
from flask import Flask, jsonify, request
app = Flask(__name__)
# OpenAi API key
openai.api_key = os.getenv("OPENAI_API_KEY")
# Access token for your WhatsApp business account app
whatsapp_token = os.environ.get("WHATSAPP_TOKEN")
# Verify Token defined when configuring the webhook
verify_token = os.environ.get("VERIFY_TOKEN")
# Message log dictionary to enable conversation over multiple messages
message_log_dict = {}
# language for speech to text recoginition
# TODO: detect this automatically based on the user's language
LANGUGAGE = "en-US"
# get the media url from the media id
def get_media_url(media_id):
headers = {
"Authorization": f"Bearer {whatsapp_token}",
}
url = f"https://graph.facebook.com/v16.0/{media_id}/"
response = requests.get(url, headers=headers)
print(f"media id response: {response.json()}")
return response.json()["url"]
# download the media file from the media url
def download_media_file(media_url):
headers = {
"Authorization": f"Bearer {whatsapp_token}",
}
response = requests.get(media_url, headers=headers)
print(f"first 10 digits of the media file: {response.content[:10]}")
return response.content
# convert ogg audio bytes to audio data which speechrecognition library can process
def convert_audio_bytes(audio_bytes):
ogg_audio = pydub.AudioSegment.from_ogg(io.BytesIO(audio_bytes))
ogg_audio = ogg_audio.set_sample_width(4)
wav_bytes = ogg_audio.export(format="wav").read()
audio_data, sample_rate = sf.read(io.BytesIO(wav_bytes), dtype="int32")
sample_width = audio_data.dtype.itemsize
print(f"audio sample_rate:{sample_rate}, sample_width:{sample_width}")
audio = sr.AudioData(audio_data, sample_rate, sample_width)
return audio
# run speech recognition on the audio data
def recognize_audio(audio_bytes):
recognizer = sr.Recognizer()
audio_text = recognizer.recognize_google(audio_bytes, language=LANGUGAGE)
return audio_text
# handle audio messages
def handle_audio_message(audio_id):
audio_url = get_media_url(audio_id)
audio_bytes = download_media_file(audio_url)
audio_data = convert_audio_bytes(audio_bytes)
audio_text = recognize_audio(audio_data)
message = (
"Please summarize the following message in its original language "
f"as a list of bullet-points: {audio_text}"
)
return message
# send the response as a WhatsApp message back to the user
def send_whatsapp_message(body, message):
value = body["entry"][0]["changes"][0]["value"]
phone_number_id = value["metadata"]["phone_number_id"]
from_number = value["messages"][0]["from"]
headers = {
"Authorization": f"Bearer {whatsapp_token}",
"Content-Type": "application/json",
}
url = "https://graph.facebook.com/v15.0/" + phone_number_id + "/messages"
data = {
"messaging_product": "whatsapp",
"to": from_number,
"type": "text",
"text": {"body": message},
}
response = requests.post(url, json=data, headers=headers)
print(f"whatsapp message response: {response.json()}")
response.raise_for_status()
# create a message log for each phone number and return the current message log
def update_message_log(message, phone_number, role):
initial_log = {
"role": "system",
"content": "You are a helpful assistant named WhatsBot.",
}
if phone_number not in message_log_dict:
message_log_dict[phone_number] = [initial_log]
message_log = {"role": role, "content": message}
message_log_dict[phone_number].append(message_log)
return message_log_dict[phone_number]
# remove last message from log if OpenAI request fails
def remove_last_message_from_log(phone_number):
message_log_dict[phone_number].pop()
# make request to OpenAI
def make_openai_request(message, from_number):
try:
message_log = update_message_log(message, from_number, "user")
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=message_log,
temperature=0.7,
)
response_message = response.choices[0].message.content
print(f"openai response: {response_message}")
update_message_log(response_message, from_number, "assistant")
except Exception as e:
print(f"openai error: {e}")
response_message = "Sorry, the OpenAI API is currently overloaded or offline. Please try again later."
remove_last_message_from_log(from_number)
return response_message
# handle WhatsApp messages of different type
def handle_whatsapp_message(body):
message = body["entry"][0]["changes"][0]["value"]["messages"][0]
if message["type"] == "text":
message_body = message["text"]["body"]
elif message["type"] == "audio":
audio_id = message["audio"]["id"]
message_body = handle_audio_message(audio_id)
response = make_openai_request(message_body, message["from"])
send_whatsapp_message(body, response)
# handle incoming webhook messages
def handle_message(request):
# Parse Request body in json format
body = request.get_json()
print(f"request body: {body}")
try:
# info on WhatsApp text message payload:
# https://developers.facebook.com/docs/whatsapp/cloud-api/webhooks/payload-examples#text-messages
if body.get("object"):
if (
body.get("entry")
and body["entry"][0].get("changes")
and body["entry"][0]["changes"][0].get("value")
and body["entry"][0]["changes"][0]["value"].get("messages")
and body["entry"][0]["changes"][0]["value"]["messages"][0]
):
handle_whatsapp_message(body)
return jsonify({"status": "ok"}), 200
else:
# if the request is not a WhatsApp API event, return an error
return (
jsonify({"status": "error", "message": "Not a WhatsApp API event"}),
404,
)
# catch all other errors and return an internal server error
except Exception as e:
print(f"unknown error: {e}")
return jsonify({"status": "error", "message": str(e)}), 500
# Required webhook verifictaion for WhatsApp
# info on verification request payload:
# https://developers.facebook.com/docs/graph-api/webhooks/getting-started#verification-requests
def verify(request):
# Parse params from the webhook verification request
mode = request.args.get("hub.mode")
token = request.args.get("hub.verify_token")
challenge = request.args.get("hub.challenge")
# Check if a token and mode were sent
if mode and token:
# Check the mode and token sent are correct
if mode == "subscribe" and token == verify_token:
# Respond with 200 OK and challenge token from the request
print("WEBHOOK_VERIFIED")
return challenge, 200
else:
# Responds with '403 Forbidden' if verify tokens do not match
print("VERIFICATION_FAILED")
return jsonify({"status": "error", "message": "Verification failed"}), 403
else:
# Responds with '400 Bad Request' if verify tokens do not match
print("MISSING_PARAMETER")
return jsonify({"status": "error", "message": "Missing parameters"}), 400
# Sets homepage endpoint and welcome message
@app.route("/", methods=["GET"])
def home():
return "WhatsApp OpenAI Webhook is listening!"
# Accepts POST and GET requests at /webhook endpoint
@app.route("/webhook", methods=["POST", "GET"])
def webhook():
if request.method == "GET":
return verify(request)
elif request.method == "POST":
return handle_message(request)
# Route to reset message log
@app.route("/reset", methods=["GET"])
def reset():
global message_log_dict
message_log_dict = {}
return "Message log resetted!"
if __name__ == "__main__":
app.run(debug=True, use_reloader=True)