Greg Thompson commited on
Commit
24df821
1 Parent(s): ff85563

Integrate transitions state machine for addition quiz

Browse files
mathtext_fastapi/conversation_manager.py CHANGED
@@ -1,13 +1,21 @@
 
1
  import os
2
  import json
 
3
  import requests
4
 
5
  from dotenv import load_dotenv
6
  from mathtext_fastapi.nlu import evaluate_message_with_nlu
 
 
 
7
 
8
  load_dotenv()
9
 
10
- # os.environ.get('SUPABASE_URL')
 
 
 
11
 
12
 
13
  def create_text_message(message_text, whatsapp_id):
@@ -89,7 +97,7 @@ def create_interactive_message(message_text, button_options, whatsapp_id):
89
  return data
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
@@ -107,14 +115,32 @@ def return_next_conversational_state(context_data, user_message):
107
  'state': "welcome-sequence"
108
  }
109
  elif user_message == 'add':
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  message_package = {
111
- 'messages': [
112
- "Great, let's do some addition",
113
- "First, we'll start with single digits.",
114
- "Type your response as a number. For example, for '1 + 1', you'd write 2."
115
- ],
116
- 'input_prompt': "Here's the first one... What's 2+2?",
117
- 'state': "add-question-sequence"
118
  }
119
  elif user_message == 'subtract':
120
  message_package = {
@@ -165,13 +191,15 @@ def manage_conversation_response(data_json):
165
 
166
  whatsapp_id = message_data['author_id']
167
  user_message = message_data['message_body']
 
168
 
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 = {
 
1
+ import dill as pickle
2
  import os
3
  import json
4
+ import random
5
  import requests
6
 
7
  from dotenv import load_dotenv
8
  from mathtext_fastapi.nlu import evaluate_message_with_nlu
9
+ from math_quiz_fsm import MathQuizFSM
10
+
11
+ from transitions import Machine
12
 
13
  load_dotenv()
14
 
15
+ SUPA = create_client(
16
+ os.environ.get('SUPABASE_URL'),
17
+ os.environ.get('SUPABASE_KEY')
18
+ )
19
 
20
 
21
  def create_text_message(message_text, whatsapp_id):
 
97
  return data
98
 
99
 
100
+ def return_next_conversational_state(context_data, user_message, contact_uuid):
101
  """ Evaluates the conversation's current state to determine the next state
102
 
103
  Input
 
115
  'state': "welcome-sequence"
116
  }
117
  elif user_message == 'add':
118
+
119
+ fsm_check = SUPA.table('state_machines').select("*").eq(
120
+ "uuid",
121
+ contact_uuid
122
+ ).execute()
123
+
124
+ if fsm_check.data == []:
125
+ math_quiz_state_machine = MathQuizFSM()
126
+ messages = [math_quiz_state_machine.response_text]
127
+ dump = pickle.dumps(math_quiz_state_machine)
128
+
129
+ # TODO: Check how to save - JSONB?
130
+ SUPA.table('state_machines').insert(dump).execute()
131
+ else:
132
+ math_quiz_state_machine = pickle.loads(fsm_check.data['add'])
133
+ math_quiz_state_machine.student_answer
134
+ messages = math_quiz_state_machine.validate()
135
+ dump = pickle.dumps(math_quiz_state_machine)
136
+ SUPA.table('state_machines').update(dump).eq(
137
+ "contact_uuid", contact_uuid
138
+ ).execute()
139
+
140
  message_package = {
141
+ 'messages': messages,
142
+ 'input_prompt': "temporary value",
143
+ 'state': "addition-question-sequence"
 
 
 
 
144
  }
145
  elif user_message == 'subtract':
146
  message_package = {
 
191
 
192
  whatsapp_id = message_data['author_id']
193
  user_message = message_data['message_body']
194
+ contact_uuid = message_data['contact_uuid']
195
 
196
  # TODO: Need to incorporate nlu_response into wormhole by checking answers against database (spreadsheet?)
197
  nlu_response = evaluate_message_with_nlu(message_data)
198
 
199
  message_package = return_next_conversational_state(
200
  context_data,
201
+ user_message,
202
+ contact_uuid
203
  )
204
 
205
  headers = {
mathtext_fastapi/math_quiz_fsm.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ from transitions import Machine
3
+
4
+
5
+ class MathQuizFSM(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__(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')
37
+ self.machine.on_exit_quiz_question('validate_answer')
38
+
39
+ def validate_answer(self):
40
+ if self.student_answer == 'exit':
41
+ self.machine.set_state('quiz_end')
42
+ return ["Come back any time!"]
43
+ elif self.correct_answer == self.student_answer:
44
+ self.machine.set_state('quiz_question')
45
+ self.generate_math_problem()
46
+ return ['Great job!', self.response_text]
47
+ else:
48
+ return ["That's not quite right. Try again.", self.response_text]
49
+
50
+ def generate_math_problem(self):
51
+ self.question_nums = random.sample(range(1,100),2)
52
+ self.response_text = f"What is {self.question_nums[0]} + {self.question_nums[1]}"
53
+ self.correct_answer = self.question_nums[0] + self.question_nums[1]
requirements.txt CHANGED
@@ -1,7 +1,10 @@
 
1
  mathtext @ git+https://gitlab.com/tangibleai/community/mathtext@main
2
  fastapi==0.74.*
3
  pydantic==1.10.*
4
  requests==2.27.*
5
  sentencepiece==0.1.*
6
  supabase
 
7
  uvicorn==0.17.*
 
 
1
+ dill
2
  mathtext @ git+https://gitlab.com/tangibleai/community/mathtext@main
3
  fastapi==0.74.*
4
  pydantic==1.10.*
5
  requests==2.27.*
6
  sentencepiece==0.1.*
7
  supabase
8
+ transitions
9
  uvicorn==0.17.*
10
+
scripts/make_request.py CHANGED
@@ -13,7 +13,7 @@ def add_message_text_to_sample_object(message_text):
13
  "test message"
14
 
15
  Output
16
- - b_string: json b-string - an object simulating what Turn.io sends to the API endpoint using the message_text
17
 
18
  Example Output
19
  b'{"context": "hi", "message_data": {"author_id": "+57787919091", "author_type": "OWNER", "contact_uuid": "j43hk26-2hjl-43jk-hnk2-k4ljl46j0ds09", "message_body": "test message", "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"}}'
@@ -28,23 +28,24 @@ def add_message_text_to_sample_object(message_text):
28
  return b_string
29
 
30
 
31
- def run_simulated_request(endpoint, sample_answer, context = None):
32
  print(f"Case: {sample_answer}")
33
  b_string = add_message_text_to_sample_object(sample_answer)
34
 
35
  if endpoint == 'sentiment-analysis' or endpoint == 'text2int':
36
- request = requests.post(url=
37
- f'http://localhost:7860/{endpoint}',
38
  json={'content': sample_answer}
39
  ).json()
40
  else:
41
- request = requests.post(url=
42
- f'http://localhost:7860/{endpoint}',
43
  data=b_string
44
  ).json()
45
 
46
  print(request)
47
 
 
48
  run_simulated_request('sentiment-analysis', 'I reject it')
49
  run_simulated_request('text2int', 'seven thousand nine hundred fifty seven')
50
  run_simulated_request('nlu', 'test message')
@@ -66,10 +67,10 @@ run_simulated_request('manager', 'exit')
66
  # NOTE: This is actually a bstring, not a dict
67
  simplified_json = {
68
  "context": {
69
- "user":"+57787919091",
70
- "state":"answer-addition-problem",
71
- "bot_message":"What is 2+2?",
72
- "user_message":"eight",
73
  "type": "ask"
74
  },
75
  "message_data": {
@@ -85,7 +86,6 @@ simplified_json = {
85
  }
86
 
87
 
88
-
89
  # Full example of event data from Turn.io
90
  # simplified_json is built from this in Turn.io
91
  # full_json = {
@@ -144,4 +144,4 @@ simplified_json = {
144
  # 'type': 'text'
145
  # },
146
  # 'type': 'message'
147
- # }
 
13
  "test message"
14
 
15
  Output
16
+ - b_string: json b-string - simulated Turn.io message data
17
 
18
  Example Output
19
  b'{"context": "hi", "message_data": {"author_id": "+57787919091", "author_type": "OWNER", "contact_uuid": "j43hk26-2hjl-43jk-hnk2-k4ljl46j0ds09", "message_body": "test message", "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"}}'
 
28
  return b_string
29
 
30
 
31
+ def run_simulated_request(endpoint, sample_answer, context=None):
32
  print(f"Case: {sample_answer}")
33
  b_string = add_message_text_to_sample_object(sample_answer)
34
 
35
  if endpoint == 'sentiment-analysis' or endpoint == 'text2int':
36
+ request = requests.post(
37
+ url=f'http://localhost:7860/{endpoint}',
38
  json={'content': sample_answer}
39
  ).json()
40
  else:
41
+ request = requests.post(
42
+ url=f'http://localhost:7860/{endpoint}',
43
  data=b_string
44
  ).json()
45
 
46
  print(request)
47
 
48
+
49
  run_simulated_request('sentiment-analysis', 'I reject it')
50
  run_simulated_request('text2int', 'seven thousand nine hundred fifty seven')
51
  run_simulated_request('nlu', 'test message')
 
67
  # NOTE: This is actually a bstring, not a dict
68
  simplified_json = {
69
  "context": {
70
+ "user": "+57787919091",
71
+ "state": "answer-addition-problem",
72
+ "bot_message": "What is 2+2?",
73
+ "user_message": "eight",
74
  "type": "ask"
75
  },
76
  "message_data": {
 
86
  }
87
 
88
 
 
89
  # Full example of event data from Turn.io
90
  # simplified_json is built from this in Turn.io
91
  # full_json = {
 
144
  # 'type': 'text'
145
  # },
146
  # 'type': 'message'
147
+ # }