Spaces:
Runtime error
Runtime error
Greg Thompson
commited on
Commit
·
8acf519
1
Parent(s):
48c823d
Update code for PEP8 formatting
Browse files- app.py +9 -9
- mathtext_fastapi/conversation_manager.py +26 -18
- mathtext_fastapi/logging.py +31 -11
- mathtext_fastapi/nlu.py +24 -8
app.py
CHANGED
|
@@ -12,7 +12,7 @@ from mathtext.text2int import text2int
|
|
| 12 |
from pydantic import BaseModel
|
| 13 |
|
| 14 |
from mathtext_fastapi.logging import prepare_message_data_for_logging
|
| 15 |
-
from mathtext_fastapi.conversation_manager import
|
| 16 |
from mathtext_fastapi.nlu import evaluate_message_with_nlu
|
| 17 |
|
| 18 |
app = FastAPI()
|
|
@@ -54,10 +54,10 @@ def text2int_ep(content: Text = None):
|
|
| 54 |
@app.post("/manager")
|
| 55 |
async def programmatic_message_manager(request: Request):
|
| 56 |
"""
|
| 57 |
-
Calls
|
| 58 |
|
| 59 |
Input
|
| 60 |
-
request.body: dict -
|
| 61 |
{
|
| 62 |
"author_id": "+47897891",
|
| 63 |
"contact_uuid": "j43hk26-2hjl-43jk-hnk2-k4ljl46j0ds09",
|
|
@@ -70,7 +70,7 @@ async def programmatic_message_manager(request: Request):
|
|
| 70 |
}
|
| 71 |
|
| 72 |
Output
|
| 73 |
-
context: dict -
|
| 74 |
{
|
| 75 |
"user": "47897891",
|
| 76 |
"state": "welcome-message-state",
|
|
@@ -86,13 +86,13 @@ async def programmatic_message_manager(request: Request):
|
|
| 86 |
|
| 87 |
@app.post("/nlu")
|
| 88 |
async def evaluate_user_message_with_nlu_api(request: Request):
|
| 89 |
-
""" Calls
|
| 90 |
-
|
| 91 |
Input
|
| 92 |
-
- request.body:
|
| 93 |
-
|
| 94 |
Output
|
| 95 |
-
- int_data_dict or sent_data_dict:
|
| 96 |
{'type':'integer', 'data': '8'}
|
| 97 |
{'type':'sentiment', 'data': 'negative'}
|
| 98 |
"""
|
|
|
|
| 12 |
from pydantic import BaseModel
|
| 13 |
|
| 14 |
from mathtext_fastapi.logging import prepare_message_data_for_logging
|
| 15 |
+
from mathtext_fastapi.conversation_manager import manage_conversation_response
|
| 16 |
from mathtext_fastapi.nlu import evaluate_message_with_nlu
|
| 17 |
|
| 18 |
app = FastAPI()
|
|
|
|
| 54 |
@app.post("/manager")
|
| 55 |
async def programmatic_message_manager(request: Request):
|
| 56 |
"""
|
| 57 |
+
Calls conversation management function to determine the next state
|
| 58 |
|
| 59 |
Input
|
| 60 |
+
request.body: dict - message data for the most recent user response
|
| 61 |
{
|
| 62 |
"author_id": "+47897891",
|
| 63 |
"contact_uuid": "j43hk26-2hjl-43jk-hnk2-k4ljl46j0ds09",
|
|
|
|
| 70 |
}
|
| 71 |
|
| 72 |
Output
|
| 73 |
+
context: dict - the information for the current state
|
| 74 |
{
|
| 75 |
"user": "47897891",
|
| 76 |
"state": "welcome-message-state",
|
|
|
|
| 86 |
|
| 87 |
@app.post("/nlu")
|
| 88 |
async def evaluate_user_message_with_nlu_api(request: Request):
|
| 89 |
+
""" Calls nlu evaluation and returns the nlu_response
|
| 90 |
+
|
| 91 |
Input
|
| 92 |
+
- request.body: json - message data for the most recent user response
|
| 93 |
+
|
| 94 |
Output
|
| 95 |
+
- int_data_dict or sent_data_dict: dict - the type of NLU run and result
|
| 96 |
{'type':'integer', 'data': '8'}
|
| 97 |
{'type':'sentiment', 'data': 'negative'}
|
| 98 |
"""
|
mathtext_fastapi/conversation_manager.py
CHANGED
|
@@ -11,14 +11,14 @@ load_dotenv()
|
|
| 11 |
|
| 12 |
|
| 13 |
def create_text_message(message_text, whatsapp_id):
|
| 14 |
-
""" Fills a template with
|
| 15 |
|
| 16 |
Inputs
|
| 17 |
- message_text: str - the content that the message should display
|
| 18 |
- whatsapp_id: str - the message recipient's phone number
|
| 19 |
|
| 20 |
Outputs
|
| 21 |
-
- message_data: dict - a preformatted template with
|
| 22 |
"""
|
| 23 |
message_data = {
|
| 24 |
"preview_url": False,
|
|
@@ -38,7 +38,7 @@ def create_button_objects(button_options):
|
|
| 38 |
- button_options: list - a list of text to be displayed in buttons
|
| 39 |
|
| 40 |
Output
|
| 41 |
-
- button_arr: list -
|
| 42 |
|
| 43 |
NOTE: Not fully implemented and tested
|
| 44 |
"""
|
|
@@ -56,11 +56,13 @@ def create_button_objects(button_options):
|
|
| 56 |
|
| 57 |
|
| 58 |
def create_interactive_message(message_text, button_options, whatsapp_id):
|
| 59 |
-
""" Fills a template
|
| 60 |
|
| 61 |
* NOTE: Not fully implemented and tested
|
| 62 |
-
* NOTE/TODO: It is possible to create other kinds of messages
|
| 63 |
-
|
|
|
|
|
|
|
| 64 |
|
| 65 |
Inputs
|
| 66 |
- message_text: str - the content that the message should display
|
|
@@ -88,17 +90,18 @@ def create_interactive_message(message_text, button_options, whatsapp_id):
|
|
| 88 |
|
| 89 |
|
| 90 |
def return_next_conversational_state(context_data, user_message):
|
| 91 |
-
""" Evaluates the current state
|
| 92 |
|
| 93 |
Input
|
| 94 |
-
- context_data: dict - data about the conversation's current state
|
| 95 |
- user_message: str - the message the user sent in response to the state
|
| 96 |
|
| 97 |
Output
|
| 98 |
-
- message_package: dict - a series of messages and
|
| 99 |
"""
|
| 100 |
-
if context_data['user_message'] == '' and
|
| 101 |
-
|
|
|
|
| 102 |
'messages': [],
|
| 103 |
'input_prompt': "Welcome to our math practice. What would you like to try? Type add or subtract.",
|
| 104 |
'state': "welcome-sequence"
|
|
@@ -141,11 +144,11 @@ def return_next_conversational_state(context_data, user_message):
|
|
| 141 |
return message_package
|
| 142 |
|
| 143 |
|
| 144 |
-
def
|
| 145 |
-
"""
|
| 146 |
|
| 147 |
Input
|
| 148 |
-
- data_json: dict -
|
| 149 |
|
| 150 |
Output
|
| 151 |
- context: dict - a record of the state at a given point a conversation
|
|
@@ -166,7 +169,10 @@ def manage_conversational_response(data_json):
|
|
| 166 |
# TODO: Need to incorporate nlu_response into wormhole by checking answers against database (spreadsheet?)
|
| 167 |
nlu_response = evaluate_message_with_nlu(message_data)
|
| 168 |
|
| 169 |
-
message_package = return_next_conversational_state(
|
|
|
|
|
|
|
|
|
|
| 170 |
|
| 171 |
headers = {
|
| 172 |
'Authorization': f"Bearer {os.environ.get('TURN_AUTHENTICATION_TOKEN')}",
|
|
@@ -176,7 +182,11 @@ def manage_conversational_response(data_json):
|
|
| 176 |
# Send all messages for the current state before a user input prompt (text/button input request)
|
| 177 |
for message in message_package['messages']:
|
| 178 |
data = create_text_message(message, whatsapp_id)
|
| 179 |
-
r = requests.post(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
|
| 181 |
# Update the context object with the new state of the conversation
|
| 182 |
context = {
|
|
@@ -191,8 +201,6 @@ def manage_conversational_response(data_json):
|
|
| 191 |
|
| 192 |
return context
|
| 193 |
|
| 194 |
-
|
| 195 |
-
|
| 196 |
# data = {
|
| 197 |
# "to": whatsapp_id,
|
| 198 |
# "type": "interactive",
|
|
|
|
| 11 |
|
| 12 |
|
| 13 |
def create_text_message(message_text, whatsapp_id):
|
| 14 |
+
""" Fills a template with input values to send a text message to Whatsapp
|
| 15 |
|
| 16 |
Inputs
|
| 17 |
- message_text: str - the content that the message should display
|
| 18 |
- whatsapp_id: str - the message recipient's phone number
|
| 19 |
|
| 20 |
Outputs
|
| 21 |
+
- message_data: dict - a preformatted template filled with inputs
|
| 22 |
"""
|
| 23 |
message_data = {
|
| 24 |
"preview_url": False,
|
|
|
|
| 38 |
- button_options: list - a list of text to be displayed in buttons
|
| 39 |
|
| 40 |
Output
|
| 41 |
+
- button_arr: list - preformatted button objects filled with the inputs
|
| 42 |
|
| 43 |
NOTE: Not fully implemented and tested
|
| 44 |
"""
|
|
|
|
| 56 |
|
| 57 |
|
| 58 |
def create_interactive_message(message_text, button_options, whatsapp_id):
|
| 59 |
+
""" Fills a template to create a button message for Whatsapp
|
| 60 |
|
| 61 |
* NOTE: Not fully implemented and tested
|
| 62 |
+
* NOTE/TODO: It is possible to create other kinds of messages
|
| 63 |
+
with the 'interactive message' template
|
| 64 |
+
* Documentation:
|
| 65 |
+
https://whatsapp.turn.io/docs/api/messages#interactive-messages
|
| 66 |
|
| 67 |
Inputs
|
| 68 |
- message_text: str - the content that the message should display
|
|
|
|
| 90 |
|
| 91 |
|
| 92 |
def return_next_conversational_state(context_data, user_message):
|
| 93 |
+
""" Evaluates the conversation's current state to determine the next state
|
| 94 |
|
| 95 |
Input
|
| 96 |
+
- context_data: dict - data about the conversation's current state
|
| 97 |
- user_message: str - the message the user sent in response to the state
|
| 98 |
|
| 99 |
Output
|
| 100 |
+
- message_package: dict - a series of messages and prompt to send
|
| 101 |
"""
|
| 102 |
+
if context_data['user_message'] == '' and \
|
| 103 |
+
context_data['state'] == 'start-conversation':
|
| 104 |
+
message_package = {
|
| 105 |
'messages': [],
|
| 106 |
'input_prompt': "Welcome to our math practice. What would you like to try? Type add or subtract.",
|
| 107 |
'state': "welcome-sequence"
|
|
|
|
| 144 |
return message_package
|
| 145 |
|
| 146 |
|
| 147 |
+
def manage_conversation_response(data_json):
|
| 148 |
+
""" Calls functions necessary to determine message and context data to send
|
| 149 |
|
| 150 |
Input
|
| 151 |
+
- data_json: dict - message data from Turn.io/Whatsapp
|
| 152 |
|
| 153 |
Output
|
| 154 |
- context: dict - a record of the state at a given point a conversation
|
|
|
|
| 169 |
# TODO: Need to incorporate nlu_response into wormhole by checking answers against database (spreadsheet?)
|
| 170 |
nlu_response = evaluate_message_with_nlu(message_data)
|
| 171 |
|
| 172 |
+
message_package = return_next_conversational_state(
|
| 173 |
+
context_data,
|
| 174 |
+
user_message
|
| 175 |
+
)
|
| 176 |
|
| 177 |
headers = {
|
| 178 |
'Authorization': f"Bearer {os.environ.get('TURN_AUTHENTICATION_TOKEN')}",
|
|
|
|
| 182 |
# Send all messages for the current state before a user input prompt (text/button input request)
|
| 183 |
for message in message_package['messages']:
|
| 184 |
data = create_text_message(message, whatsapp_id)
|
| 185 |
+
r = requests.post(
|
| 186 |
+
f'https://whatsapp.turn.io/v1/messages',
|
| 187 |
+
data=json.dumps(data),
|
| 188 |
+
headers=headers
|
| 189 |
+
)
|
| 190 |
|
| 191 |
# Update the context object with the new state of the conversation
|
| 192 |
context = {
|
|
|
|
| 201 |
|
| 202 |
return context
|
| 203 |
|
|
|
|
|
|
|
| 204 |
# data = {
|
| 205 |
# "to": whatsapp_id,
|
| 206 |
# "type": "interactive",
|
mathtext_fastapi/logging.py
CHANGED
|
@@ -6,7 +6,10 @@ from supabase import create_client
|
|
| 6 |
|
| 7 |
load_dotenv()
|
| 8 |
|
| 9 |
-
SUPA = create_client(
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
|
| 12 |
def log_message_data_through_supabase_api(table_name, log_data):
|
|
@@ -18,32 +21,43 @@ def format_datetime_in_isoformat(dt):
|
|
| 18 |
|
| 19 |
|
| 20 |
def get_or_create_supabase_entry(table_name, insert_data, check_variable=None):
|
| 21 |
-
""" Checks
|
| 22 |
|
| 23 |
Input:
|
| 24 |
- table_name: str- the name of the table in Supabase that is being examined
|
| 25 |
- insert_data: json - the data to insert
|
| 26 |
-
- check_variable: str/None - the specific field to
|
| 27 |
|
| 28 |
Result
|
| 29 |
- logged_data - an object with the Supabase data
|
| 30 |
|
| 31 |
"""
|
| 32 |
if table_name == 'contact':
|
| 33 |
-
resp = SUPA.table('contact').select("*").eq(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
else:
|
| 35 |
-
resp = SUPA.table(table_name).select("*").eq(
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
if len(resp.data) == 0:
|
| 38 |
-
logged_data = log_message_data_through_supabase_api(
|
|
|
|
|
|
|
|
|
|
| 39 |
else:
|
| 40 |
logged_data = resp
|
| 41 |
return logged_data
|
| 42 |
|
| 43 |
|
| 44 |
-
|
| 45 |
def prepare_message_data_for_logging(message_data, nlu_response):
|
| 46 |
-
""" Builds
|
| 47 |
|
| 48 |
Input:
|
| 49 |
- message_data: an object with the full message data from Turn.io/Whatsapp
|
|
@@ -52,7 +66,11 @@ def prepare_message_data_for_logging(message_data, nlu_response):
|
|
| 52 |
'name': "Rori",
|
| 53 |
# Autogenerated fields: id, created_at, modified_at
|
| 54 |
}
|
| 55 |
-
project_data_log = get_or_create_supabase_entry(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
contact_data = {
|
| 58 |
'project': project_data_log.data[0]['id'], # FK
|
|
@@ -64,7 +82,6 @@ def prepare_message_data_for_logging(message_data, nlu_response):
|
|
| 64 |
}
|
| 65 |
contact_data_log = get_or_create_supabase_entry('contact', contact_data)
|
| 66 |
|
| 67 |
-
|
| 68 |
del message_data['author_id']
|
| 69 |
|
| 70 |
message_data = {
|
|
@@ -81,4 +98,7 @@ def prepare_message_data_for_logging(message_data, nlu_response):
|
|
| 81 |
'request_object': message_data
|
| 82 |
# Autogenerated fields: created_at, modified_at
|
| 83 |
}
|
| 84 |
-
message_data_log = log_message_data_through_supabase_api(
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
load_dotenv()
|
| 8 |
|
| 9 |
+
SUPA = create_client(
|
| 10 |
+
os.environ.get('SUPABASE_URL'),
|
| 11 |
+
os.environ.get('SUPABASE_KEY')
|
| 12 |
+
)
|
| 13 |
|
| 14 |
|
| 15 |
def log_message_data_through_supabase_api(table_name, log_data):
|
|
|
|
| 21 |
|
| 22 |
|
| 23 |
def get_or_create_supabase_entry(table_name, insert_data, check_variable=None):
|
| 24 |
+
""" Checks if project or contact exists and adds entry if not found
|
| 25 |
|
| 26 |
Input:
|
| 27 |
- table_name: str- the name of the table in Supabase that is being examined
|
| 28 |
- insert_data: json - the data to insert
|
| 29 |
+
- check_variable: str/None - the specific field to check for existing match
|
| 30 |
|
| 31 |
Result
|
| 32 |
- logged_data - an object with the Supabase data
|
| 33 |
|
| 34 |
"""
|
| 35 |
if table_name == 'contact':
|
| 36 |
+
resp = SUPA.table('contact').select("*").eq(
|
| 37 |
+
"original_contact_id",
|
| 38 |
+
insert_data['original_contact_id']
|
| 39 |
+
).eq(
|
| 40 |
+
"project",
|
| 41 |
+
insert_data['project']
|
| 42 |
+
).execute()
|
| 43 |
else:
|
| 44 |
+
resp = SUPA.table(table_name).select("*").eq(
|
| 45 |
+
check_variable,
|
| 46 |
+
insert_data[check_variable]
|
| 47 |
+
).execute()
|
| 48 |
|
| 49 |
if len(resp.data) == 0:
|
| 50 |
+
logged_data = log_message_data_through_supabase_api(
|
| 51 |
+
table_name,
|
| 52 |
+
insert_data
|
| 53 |
+
)
|
| 54 |
else:
|
| 55 |
logged_data = resp
|
| 56 |
return logged_data
|
| 57 |
|
| 58 |
|
|
|
|
| 59 |
def prepare_message_data_for_logging(message_data, nlu_response):
|
| 60 |
+
""" Builds objects for each table and logs them to the database
|
| 61 |
|
| 62 |
Input:
|
| 63 |
- message_data: an object with the full message data from Turn.io/Whatsapp
|
|
|
|
| 66 |
'name': "Rori",
|
| 67 |
# Autogenerated fields: id, created_at, modified_at
|
| 68 |
}
|
| 69 |
+
project_data_log = get_or_create_supabase_entry(
|
| 70 |
+
'project',
|
| 71 |
+
project_data,
|
| 72 |
+
'name'
|
| 73 |
+
)
|
| 74 |
|
| 75 |
contact_data = {
|
| 76 |
'project': project_data_log.data[0]['id'], # FK
|
|
|
|
| 82 |
}
|
| 83 |
contact_data_log = get_or_create_supabase_entry('contact', contact_data)
|
| 84 |
|
|
|
|
| 85 |
del message_data['author_id']
|
| 86 |
|
| 87 |
message_data = {
|
|
|
|
| 98 |
'request_object': message_data
|
| 99 |
# Autogenerated fields: created_at, modified_at
|
| 100 |
}
|
| 101 |
+
message_data_log = log_message_data_through_supabase_api(
|
| 102 |
+
'message',
|
| 103 |
+
message_data
|
| 104 |
+
)
|
mathtext_fastapi/nlu.py
CHANGED
|
@@ -5,11 +5,11 @@ import re
|
|
| 5 |
|
| 6 |
|
| 7 |
def build_nlu_response_object(type, data, confidence):
|
| 8 |
-
"""
|
| 9 |
Inputs
|
| 10 |
- type: str - the type of nlu run (integer or sentiment-analysis)
|
| 11 |
- data: str - the student message
|
| 12 |
-
- confidence: - the nlu confidence score
|
| 13 |
"""
|
| 14 |
return {'type': type, 'data': data, 'confidence': confidence}
|
| 15 |
|
|
@@ -25,19 +25,23 @@ def test_for_float_or_int(message_data, message_text):
|
|
| 25 |
def test_for_number_sequence(message_text_arr, message_data, message_text):
|
| 26 |
nlu_response = {}
|
| 27 |
if all(ele.isdigit() for ele in message_text_arr):
|
| 28 |
-
nlu_response = build_nlu_response_object(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
prepare_message_data_for_logging(message_data, nlu_response)
|
| 30 |
return nlu_response
|
| 31 |
|
| 32 |
|
| 33 |
def run_text2int_on_each_list_item(message_text_arr):
|
| 34 |
-
"""
|
| 35 |
|
| 36 |
Input
|
| 37 |
- message_text_arr: list - a set of text extracted from the student message
|
| 38 |
|
| 39 |
Output
|
| 40 |
-
- student_response_arr: list - a set of integers
|
| 41 |
"""
|
| 42 |
student_response_arr = []
|
| 43 |
for student_response in message_text_arr:
|
|
@@ -74,12 +78,24 @@ def evaluate_message_with_nlu(message_data):
|
|
| 74 |
student_response_arr = run_text2int_on_each_list_item(message_text_arr)
|
| 75 |
if 32202 in student_response_arr:
|
| 76 |
sentiment_api_resp = sentiment(message_text)
|
| 77 |
-
nlu_response = build_nlu_response_object(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
else:
|
| 79 |
if len(student_response_arr) > 1:
|
| 80 |
-
nlu_response = build_nlu_response_object(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
else:
|
| 82 |
-
nlu_response = build_nlu_response_object(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
|
| 84 |
prepare_message_data_for_logging(message_data, nlu_response)
|
| 85 |
return nlu_response
|
|
|
|
| 5 |
|
| 6 |
|
| 7 |
def build_nlu_response_object(type, data, confidence):
|
| 8 |
+
""" Turns nlu results into an object to send back to Turn.io
|
| 9 |
Inputs
|
| 10 |
- type: str - the type of nlu run (integer or sentiment-analysis)
|
| 11 |
- data: str - the student message
|
| 12 |
+
- confidence: - the nlu confidence score (sentiment) or '' (integer)
|
| 13 |
"""
|
| 14 |
return {'type': type, 'data': data, 'confidence': confidence}
|
| 15 |
|
|
|
|
| 25 |
def test_for_number_sequence(message_text_arr, message_data, message_text):
|
| 26 |
nlu_response = {}
|
| 27 |
if all(ele.isdigit() for ele in message_text_arr):
|
| 28 |
+
nlu_response = build_nlu_response_object(
|
| 29 |
+
'integer',
|
| 30 |
+
','.join(message_text_arr),
|
| 31 |
+
''
|
| 32 |
+
)
|
| 33 |
prepare_message_data_for_logging(message_data, nlu_response)
|
| 34 |
return nlu_response
|
| 35 |
|
| 36 |
|
| 37 |
def run_text2int_on_each_list_item(message_text_arr):
|
| 38 |
+
""" Attempts to convert each list item to an integer
|
| 39 |
|
| 40 |
Input
|
| 41 |
- message_text_arr: list - a set of text extracted from the student message
|
| 42 |
|
| 43 |
Output
|
| 44 |
+
- student_response_arr: list - a set of integers (32202 for error code)
|
| 45 |
"""
|
| 46 |
student_response_arr = []
|
| 47 |
for student_response in message_text_arr:
|
|
|
|
| 78 |
student_response_arr = run_text2int_on_each_list_item(message_text_arr)
|
| 79 |
if 32202 in student_response_arr:
|
| 80 |
sentiment_api_resp = sentiment(message_text)
|
| 81 |
+
nlu_response = build_nlu_response_object(
|
| 82 |
+
'sentiment',
|
| 83 |
+
sentiment_api_resp[0]['label'],
|
| 84 |
+
sentiment_api_resp[0]['score']
|
| 85 |
+
)
|
| 86 |
else:
|
| 87 |
if len(student_response_arr) > 1:
|
| 88 |
+
nlu_response = build_nlu_response_object(
|
| 89 |
+
'integer',
|
| 90 |
+
','.join(str(num) for num in student_response_arr),
|
| 91 |
+
''
|
| 92 |
+
)
|
| 93 |
else:
|
| 94 |
+
nlu_response = build_nlu_response_object(
|
| 95 |
+
'integer',
|
| 96 |
+
student_response_arr[0],
|
| 97 |
+
''
|
| 98 |
+
)
|
| 99 |
|
| 100 |
prepare_message_data_for_logging(message_data, nlu_response)
|
| 101 |
return nlu_response
|