Spaces:
Runtime error
Runtime error
Greg Thompson
commited on
Commit
•
12058fc
1
Parent(s):
185d33a
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
|