sviddo commited on
Commit
24101a8
2 Parent(s): 0e9529c 8fd1f61

Merge branch 'vlad' of https://gitlab.com/tangibleai/community/mathtext-fastapi into vlad

Browse files
mathtext_fastapi/conversation_manager.py CHANGED
@@ -10,9 +10,13 @@ import requests
10
  from dotenv import load_dotenv
11
  from mathtext_fastapi.nlu import evaluate_message_with_nlu
12
  from mathtext_fastapi.math_quiz_fsm import MathQuizFSM
 
13
  from supabase import create_client
14
  from transitions import Machine
15
 
 
 
 
16
  load_dotenv()
17
 
18
  SUPA = create_client(
@@ -106,6 +110,90 @@ def pickle_and_encode_state_machine(state_machine):
106
  return dump_encoded
107
 
108
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  def return_next_conversational_state(context_data, user_message, contact_uuid):
110
  """ Evaluates the conversation's current state to determine the next state
111
 
@@ -126,45 +214,36 @@ def return_next_conversational_state(context_data, user_message, contact_uuid):
126
  elif context_data['state'] == 'addition-question-sequence' or \
127
  user_message == 'add':
128
 
129
- fsm_check = SUPA.table('state_machines').select("*").eq(
130
- "contact_uuid",
131
- contact_uuid
132
- ).execute()
133
 
134
- if fsm_check.data == []:
135
- math_quiz_state_machine = MathQuizFSM()
136
- messages = [math_quiz_state_machine.response_text]
137
- dump_encoded = pickle_and_encode_state_machine(math_quiz_state_machine)
138
 
139
- SUPA.table('state_machines').insert({
140
- 'contact_uuid': contact_uuid,
141
- 'addition3': dump_encoded
142
- }).execute()
143
  else:
144
- undump_encoded = base64.b64decode(
145
- fsm_check.data[0]['addition3'].encode('utf-8')
146
- )
147
- math_quiz_state_machine = pickle.loads(undump_encoded)
148
-
149
- print("student answer")
150
- print(math_quiz_state_machine.student_answer)
151
- print("user_message")
152
- print(user_message)
153
-
154
- math_quiz_state_machine.student_answer = user_message
155
- math_quiz_state_machine.correct_answer = str(math_quiz_state_machine.correct_answer)
156
- messages = math_quiz_state_machine.validate_answer()
157
- dump_encoded = pickle_and_encode_state_machine(math_quiz_state_machine)
158
- SUPA.table('state_machines').update({
159
- 'addition3': dump_encoded
160
- }).eq(
161
- "contact_uuid", contact_uuid
162
- ).execute()
163
 
164
  if user_message == 'exit':
165
  state_label = 'exit'
166
  else:
167
- state_label = 'addition-question-sequence'
168
 
169
  input_prompt = messages.pop()
170
 
@@ -173,15 +252,15 @@ def return_next_conversational_state(context_data, user_message, contact_uuid):
173
  'input_prompt': input_prompt,
174
  'state': state_label
175
  }
176
- elif user_message == 'subtract':
177
- message_package = {
178
- 'messages': [
179
- "Time for some subtraction!",
180
- "Type your response as a number. For example, for '1 - 1', you'd write 0."
181
- ],
182
- 'input_prompt': "Here's the first one... What's 3-1?",
183
- 'state': "subtract-question-sequence"
184
- }
185
  elif context_data['state'] == 'exit' or user_message == 'exit':
186
  message_package = {
187
  'messages': [
@@ -198,8 +277,12 @@ def return_next_conversational_state(context_data, user_message, contact_uuid):
198
  'input_prompt': "Please type add or subtract to start a math activity.",
199
  'state': "reprompt-menu-options"
200
  }
 
201
  return message_package
202
 
 
 
 
203
 
204
  def manage_conversation_response(data_json):
205
  """ Calls functions necessary to determine message and context data to send
@@ -227,11 +310,18 @@ def manage_conversation_response(data_json):
227
  # TODO: Need to incorporate nlu_response into wormhole by checking answers against database (spreadsheet?)
228
  nlu_response = evaluate_message_with_nlu(message_data)
229
 
230
- message_package = return_next_conversational_state(
231
- context_data,
232
- user_message,
233
- contact_uuid
234
- )
 
 
 
 
 
 
 
235
 
236
  print("MESSAGE PACKAGE")
237
  print(message_package)
@@ -255,15 +345,32 @@ def manage_conversation_response(data_json):
255
  )
256
 
257
  # Update the context object with the new state of the conversation
258
- context = {
259
- "context":{
260
- "user": whatsapp_id,
261
- "state": message_package['state'],
262
- "bot_message": message_package['input_prompt'],
263
- "user_message": user_message,
264
- "type": 'ask'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
265
  }
266
- }
267
 
268
  return context
269
 
 
10
  from dotenv import load_dotenv
11
  from mathtext_fastapi.nlu import evaluate_message_with_nlu
12
  from mathtext_fastapi.math_quiz_fsm import MathQuizFSM
13
+ from mathtext_fastapi.math_subtraction_fsm import MathSubtractionFSM
14
  from supabase import create_client
15
  from transitions import Machine
16
 
17
+ from scripts.quiz.generators import start_interactive_math
18
+ from scripts.quiz.hints import generate_hint
19
+
20
  load_dotenv()
21
 
22
  SUPA = create_client(
 
110
  return dump_encoded
111
 
112
 
113
+ def manage_math_quiz_fsm(user_message, contact_uuid, type):
114
+ fsm_check = SUPA.table('state_machines').select("*").eq(
115
+ "contact_uuid",
116
+ contact_uuid
117
+ ).execute()
118
+
119
+ if fsm_check.data == []:
120
+ if type == 'addition':
121
+ math_quiz_state_machine = MathQuizFSM()
122
+ else:
123
+ math_quiz_state_machine = MathSubtractionFSM()
124
+ messages = [math_quiz_state_machine.response_text]
125
+ dump_encoded = pickle_and_encode_state_machine(math_quiz_state_machine)
126
+
127
+ SUPA.table('state_machines').insert({
128
+ 'contact_uuid': contact_uuid,
129
+ f'{type}': dump_encoded
130
+ }).execute()
131
+ else:
132
+ undump_encoded = base64.b64decode(
133
+ fsm_check.data[0][type].encode('utf-8')
134
+ )
135
+ math_quiz_state_machine = pickle.loads(undump_encoded)
136
+
137
+ math_quiz_state_machine.student_answer = user_message
138
+ math_quiz_state_machine.correct_answer = str(math_quiz_state_machine.correct_answer)
139
+ messages = math_quiz_state_machine.validate_answer()
140
+ dump_encoded = pickle_and_encode_state_machine(math_quiz_state_machine)
141
+ SUPA.table('state_machines').update({
142
+ f'{type}': dump_encoded
143
+ }).eq(
144
+ "contact_uuid", contact_uuid
145
+ ).execute()
146
+ return messages
147
+
148
+
149
+ def use_quiz_module_approach(user_message, context_data):
150
+ print("USER MESSAGE")
151
+ print(user_message)
152
+ print("=======================")
153
+ if user_message == 'add':
154
+ context_result = start_interactive_math()
155
+ message_package = {
156
+ 'messages': [
157
+ "Great, let's do some addition",
158
+ "First, we'll start with single digits.",
159
+ "Type your response as a number. For example, for '1 + 1', you'd write 2."
160
+ ],
161
+ 'input_prompt': context_result['text'],
162
+ 'state': "addition-question-sequence"
163
+ }
164
+
165
+ elif user_message == context_data.get('right_answer'):
166
+ context_result = start_interactive_math(
167
+ context_data['number_correct'],
168
+ context_data['number_incorrect'],
169
+ context_data['level']
170
+ )
171
+ message_package = {
172
+ 'messages': [
173
+ "That's right, great!",
174
+ ],
175
+ 'input_prompt': context_result['text'],
176
+ 'state': "addition-question-sequence"
177
+ }
178
+ else:
179
+ context_result = generate_hint(
180
+ context_data['question_numbers'],
181
+ context_data['right_answer'],
182
+ context_data['number_correct'],
183
+ context_data['number_incorrect'],
184
+ context_data['level'],
185
+ context_data['hints_used']
186
+ )
187
+ message_package = {
188
+ 'messages': [
189
+ context_result['text'],
190
+ ],
191
+ 'input_prompt': context_data['text'],
192
+ 'state': "addition-question-sequence"
193
+ }
194
+ return message_package, context_result
195
+
196
+
197
  def return_next_conversational_state(context_data, user_message, contact_uuid):
198
  """ Evaluates the conversation's current state to determine the next state
199
 
 
214
  elif context_data['state'] == 'addition-question-sequence' or \
215
  user_message == 'add':
216
 
217
+ # Used in FSM
218
+ # messages = manage_math_quiz_fsm(user_message, contact_uuid)
 
 
219
 
220
+ message_package, context_result = use_quiz_module_approach(user_message, context_data)
 
 
 
221
 
222
+ if user_message == 'exit':
223
+ state_label = 'exit'
 
 
224
  else:
225
+ state_label = 'addition-question-sequence'
226
+ # Used in FSM
227
+ # input_prompt = messages.pop()
228
+ # message_package = {
229
+ # 'messages': messages,
230
+ # 'input_prompt': input_prompt,
231
+ # 'state': state_label
232
+ # }
233
+
234
+ print("MESSAGE PACKAGE")
235
+ print(message_package)
236
+ context_data = context_result
237
+ message_package['state'] = state_label
238
+
239
+ elif context_data['state'] == 'subtraction-question-sequence' or \
240
+ user_message == 'subtract':
241
+ messages = manage_math_quiz_fsm(user_message, contact_uuid, 'subtraction')
 
 
242
 
243
  if user_message == 'exit':
244
  state_label = 'exit'
245
  else:
246
+ state_label = 'subtraction-question-sequence'
247
 
248
  input_prompt = messages.pop()
249
 
 
252
  'input_prompt': input_prompt,
253
  'state': state_label
254
  }
255
+
256
+ # message_package = {
257
+ # 'messages': [
258
+ # "Time for some subtraction!",
259
+ # "Type your response as a number. For example, for '1 - 1', you'd write 0."
260
+ # ],
261
+ # 'input_prompt': "Here's the first one... What's 3-1?",
262
+ # 'state': "subtract-question-sequence"
263
+ # }
264
  elif context_data['state'] == 'exit' or user_message == 'exit':
265
  message_package = {
266
  'messages': [
 
277
  'input_prompt': "Please type add or subtract to start a math activity.",
278
  'state': "reprompt-menu-options"
279
  }
280
+ # Used in FSM
281
  return message_package
282
 
283
+ # Used in quiz folder approach
284
+ # return context_result, message_package
285
+
286
 
287
  def manage_conversation_response(data_json):
288
  """ Calls functions necessary to determine message and context data to send
 
310
  # TODO: Need to incorporate nlu_response into wormhole by checking answers against database (spreadsheet?)
311
  nlu_response = evaluate_message_with_nlu(message_data)
312
 
313
+ if context_data['state'] == 'addition':
314
+ context_result, message_package = return_next_conversational_state(
315
+ context_data,
316
+ user_message,
317
+ contact_uuid
318
+ )
319
+ else:
320
+ message_package = return_next_conversational_state(
321
+ context_data,
322
+ user_message,
323
+ contact_uuid
324
+ )
325
 
326
  print("MESSAGE PACKAGE")
327
  print(message_package)
 
345
  )
346
 
347
  # Update the context object with the new state of the conversation
348
+ if context_data['state'] == 'addition':
349
+ context = {
350
+ "context": {
351
+ "user": whatsapp_id,
352
+ "state": message_package['state'],
353
+ "bot_message": message_package['input_prompt'],
354
+ "user_message": user_message,
355
+ "type": 'ask',
356
+ # Necessary for quiz folder approach
357
+ "text": context_result.get('text'),
358
+ "question_numbers": context_result.get('question_numbers'),
359
+ "right_answer": context_result.get('right_answer'),
360
+ "number_correct": context_result.get('number_correct'),
361
+ "hints_used": context_result.get('hints_used'),
362
+ }
363
+ }
364
+ else:
365
+ context = {
366
+ "context": {
367
+ "user": whatsapp_id,
368
+ "state": message_package['state'],
369
+ "bot_message": message_package['input_prompt'],
370
+ "user_message": user_message,
371
+ "type": 'ask',
372
+ }
373
  }
 
374
 
375
  return context
376
 
mathtext_fastapi/math_quiz_fsm.py CHANGED
@@ -16,21 +16,26 @@ class MathQuizFSM(object):
16
  ['exit', 'quiz_question', 'quiz_end'],
17
  ]
18
 
19
- def __init__(self):
 
 
 
 
 
20
  # Instantiate the FSM
21
  self.machine = Machine(
22
- model=self,
23
- states=MathQuizFSM.states,
24
  transitions=MathQuizFSM.transitions,
25
- initial='quiz_start'
26
  )
27
 
28
  # Instantiate variables necessary for tracking activity
29
- self.question_nums = [2, 3]
30
- self.correct_answer = 5
31
- self.student_answer = 0
32
  self.is_correct_answer = False
33
- self.response_text = "What is 2 + 3?"
34
 
35
  # Define functions to run on transitions
36
  self.machine.on_enter_quiz_question('generate_math_problem')
 
16
  ['exit', 'quiz_question', 'quiz_end'],
17
  ]
18
 
19
+ def __init__(
20
+ self,
21
+ initial_state='quiz_start',
22
+ question_nums=[2, 3],
23
+ initial_student_answer=0,
24
+ ):
25
  # Instantiate the FSM
26
  self.machine = Machine(
27
+ model=self,
28
+ states=MathQuizFSM.states,
29
  transitions=MathQuizFSM.transitions,
30
+ initial=initial_state
31
  )
32
 
33
  # Instantiate variables necessary for tracking activity
34
+ self.question_nums = question_nums
35
+ self.correct_answer = self.question_nums[0] + self.question_nums[1]
36
+ self.student_answer = initial_student_answer
37
  self.is_correct_answer = False
38
+ self.response_text = f"What is {self.question_nums[0]} + {self.question_nums[1]}?"
39
 
40
  # Define functions to run on transitions
41
  self.machine.on_enter_quiz_question('generate_math_problem')
mathtext_fastapi/math_subtraction_fsm.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ from transitions import Machine
3
+
4
+
5
+ class MathSubtractionFSM(object):
6
+ states = [
7
+ 'quiz_start',
8
+ 'quiz_question',
9
+ 'quiz_end'
10
+ ]
11
+
12
+ transitions = [
13
+ ['ask_second_question', 'quiz_start', 'quiz_question'],
14
+ ['ask_next_question', 'quiz_question', 'quiz_question'],
15
+ ['exit', 'quiz_start', 'quiz_end'],
16
+ ['exit', 'quiz_question', 'quiz_end'],
17
+ ]
18
+
19
+ def __init__(
20
+ self,
21
+ initial_state='quiz_start',
22
+ question_nums=[4, 3],
23
+ initial_student_answer=0,
24
+ ):
25
+ # Instantiate the FSM
26
+ self.machine = Machine(
27
+ model=self,
28
+ states=MathSubtractionFSM.states,
29
+ transitions=MathSubtractionFSM.transitions,
30
+ initial=initial_state
31
+ )
32
+
33
+ # Instantiate variables necessary for tracking activity
34
+ self.question_nums = question_nums
35
+ self.correct_answer = self.question_nums[0] - self.question_nums[1]
36
+ self.student_answer = initial_student_answer
37
+ self.is_correct_answer = False
38
+ self.response_text = f"What is {self.question_nums[0]} - {self.question_nums[1]}?"
39
+
40
+ # Define functions to run on transitions
41
+ self.machine.on_enter_quiz_question('generate_math_problem')
42
+ self.machine.on_exit_quiz_question('validate_answer')
43
+
44
+ def validate_answer(self):
45
+ if self.student_answer == 'exit':
46
+ self.machine.set_state('quiz_end')
47
+ return ["Come back any time!"]
48
+ elif self.correct_answer == self.student_answer:
49
+ self.machine.set_state('quiz_question')
50
+ self.generate_math_problem()
51
+ return ['Great job!', self.response_text]
52
+ else:
53
+ return ["That's not quite right. Try again.", self.response_text]
54
+
55
+ def generate_math_problem(self):
56
+ self.question_nums = random.sample(range(1, 100), 2)
57
+ self.response_text = f"What is {self.question_nums[0]} - {self.question_nums[1]}"
58
+ self.correct_answer = self.question_nums[0] - self.question_nums[1]
scripts/make_request.py CHANGED
@@ -20,13 +20,25 @@ def add_message_text_to_sample_object(message_text):
20
 
21
  """
22
  message_data = '{' + f'"author_id": "+57787919091", "author_type": "OWNER", "contact_uuid": "j43hk26-2hjl-43jk-hnk2-k4ljl46j0ds09", "message_body": "{message_text}", "message_direction": "inbound", "message_id": "4kl209sd0-a7b8-2hj3-8563-3hu4a89b32", "message_inserted_at": "2023-01-10T02:37:28.477940Z", "message_updated_at": "2023-01-10T02:37:28.487319Z"' + '}'
 
 
23
  context_data = '{' + '"user":"", "state":"start-conversation", "bot_message":"", "user_message":"{message_text}"' + '}'
24
 
 
 
25
  json_string = '{' + f'"context_data": {context_data}, "message_data": {message_data}' + '}'
26
  b_string = json_string.encode("utf-8")
27
 
28
  return b_string
29
 
 
 
 
 
 
 
 
 
30
 
31
  def run_simulated_request(endpoint, sample_answer, context=None):
32
  print(f"Case: {sample_answer}")
@@ -57,42 +69,36 @@ def run_simulated_request(endpoint, sample_answer, context=None):
57
  # run_simulated_request('nlu', 'Today is a wonderful day')
58
  # run_simulated_request('nlu', 'IDK 5?')
59
  # run_simulated_request('manager', '')
60
- # run_simulated_request('manager', 'add')
61
- # run_simulated_request("question", {
62
- # 'number_correct': 0,
63
- # 'number_incorrect': 0,
64
- # 'level': 'easy'
65
- # })
66
- # run_simulated_request("hint", {
67
- # 'question_numbers': [1,2,3],
68
- # 'right_answer': 3,
69
- # 'number_correct': 0,
70
- # 'number_incorrect': 0,
71
- # 'level': 'easy',
72
- # 'hints_used': 0
73
- # })
74
- # run_simulated_request("generate_question", {
75
- # 'level': 'medium'
76
- # })
77
- # run_simulated_request("numbers_by_level", {
78
- # 'level': 'medium'
79
- # })
80
- # run_simulated_request("number_sequence", {
81
- # "current_number": 10,
82
- # "ordinal_number": 2,
83
- # "times": 1
84
- # })
85
- # run_simulated_request("level", {
86
- # "current_level": "hard",
87
- # "level_up": False
88
- # })
89
- run_simulated_request("question_new", {
90
- "start": 10,
91
- "step": 1,
92
- "sequence": "8... 9... 10...",
93
- "question_num": 1
94
  })
95
- # run_simulated_request('manager', 'subtract')
96
  # run_simulated_request('manager', 'exit')
97
 
98
 
 
20
 
21
  """
22
  message_data = '{' + f'"author_id": "+57787919091", "author_type": "OWNER", "contact_uuid": "j43hk26-2hjl-43jk-hnk2-k4ljl46j0ds09", "message_body": "{message_text}", "message_direction": "inbound", "message_id": "4kl209sd0-a7b8-2hj3-8563-3hu4a89b32", "message_inserted_at": "2023-01-10T02:37:28.477940Z", "message_updated_at": "2023-01-10T02:37:28.487319Z"' + '}'
23
+ # context_data = '{' + '"user":"", "state":"addition-question-sequence", "bot_message":"", "user_message":"{message_text}"' + '}'
24
+
25
  context_data = '{' + '"user":"", "state":"start-conversation", "bot_message":"", "user_message":"{message_text}"' + '}'
26
 
27
+ # context_data = '{' + '"user":"", "state":"addition-question-sequence", "bot_message":"", "user_message":"{message_text}","text": "What is 2+3?","question_numbers": [4,3],"right_answer": 7,"number_correct": 2, "number_incorrect": 0, "hints_used": 0, "level": "easy"' + '}'
28
+
29
  json_string = '{' + f'"context_data": {context_data}, "message_data": {message_data}' + '}'
30
  b_string = json_string.encode("utf-8")
31
 
32
  return b_string
33
 
34
+ # """
35
+ # "text": "What is 2+3?",
36
+ # "question_numbers": [2,3],
37
+ # "right_answer": 5,
38
+ # "number_correct": 2,
39
+ # "hints_used": 0,
40
+ # """
41
+
42
 
43
  def run_simulated_request(endpoint, sample_answer, context=None):
44
  print(f"Case: {sample_answer}")
 
69
  # run_simulated_request('nlu', 'Today is a wonderful day')
70
  # run_simulated_request('nlu', 'IDK 5?')
71
  # run_simulated_request('manager', '')
72
+ run_simulated_request('manager', 'add')
73
+ run_simulated_request('manager', 'subtract')
74
+ run_simulated_request("question", {
75
+ 'number_correct': 0,
76
+ 'number_incorrect': 0,
77
+ 'level': 'easy'
78
+ })
79
+ run_simulated_request("hint", {
80
+ 'question_numbers': [1, 2, 3],
81
+ 'right_answer': 3,
82
+ 'number_correct': 0,
83
+ 'number_incorrect': 0,
84
+ 'level': 'easy',
85
+ 'hints_used': 0
86
+ })
87
+ run_simulated_request("generate_question", {
88
+ 'level': 'medium'
89
+ })
90
+ run_simulated_request("numbers_by_level", {
91
+ 'level': 'medium'
92
+ })
93
+ run_simulated_request("number_sequence", {
94
+ "current_number": 10,
95
+ "ordinal_number": 2,
96
+ "times": 1
97
+ })
98
+ run_simulated_request("level", {
99
+ "current_level": "hard",
100
+ "level_up": False
 
 
 
 
 
101
  })
 
102
  # run_simulated_request('manager', 'exit')
103
 
104