TangibleAI/mathtext-fastapi
1
1"""FastAPI endpoint2To run locally use 'uvicorn app:app --host localhost --port 7860'3or4`python -m uvicorn app:app --reload --host localhost --port 7860`5"""6import ast7import json8from json import JSONDecodeError9from logging import getLogger10import mathactive.microlessons.num_one as num_one_quiz11import os12import sentry_sdk13 14from fastapi import FastAPI, Request15from fastapi.responses import JSONResponse16from fastapi.staticfiles import StaticFiles17from fastapi.templating import Jinja2Templates18# from mathtext.sentiment import sentiment19from mathtext.text2int import text2int20from mathtext_fastapi.logging import prepare_message_data_for_logging21from mathtext_fastapi.conversation_manager import manage_conversation_response22from mathtext_fastapi.v2_conversation_manager import manage_conversation_response23from mathtext_fastapi.nlu import evaluate_message_with_nlu24from mathtext_fastapi.nlu import run_intent_classification25from pydantic import BaseModel26 27 28from dotenv import load_dotenv29load_dotenv()30 31log = getLogger(__name__)32 33sentry_sdk.init(34 dsn=os.environ.get('SENTRY_DSN'),35 36 # Set traces_sample_rate to 1.0 to capture 100%37 # of transactions for performance monitoring.38 # We recommend adjusting this value in production,39 traces_sample_rate=1.0,40)41 42app = FastAPI()43 44app.mount("/static", StaticFiles(directory="static"), name="static")45 46templates = Jinja2Templates(directory="templates")47 48 49class Text(BaseModel):50 content: str = ""51 52 53@app.get("/")54def home(request: Request):55 return templates.TemplateResponse("home.html", {"request": request})56 57 58@app.get("/sentry-debug")59async def trigger_error():60 division_by_zero = 1 / 061 62 63@app.post("/hello")64def hello(content: Text = None):65 content = {"message": f"Hello {content.content}!"}66 return JSONResponse(content=content)67 68 69# @app.post("/sentiment-analysis")70# def sentiment_analysis_ep(content: Text = None):71# ml_response = sentiment(content.content)72# content = {"message": ml_response}73# return JSONResponse(content=content)74 75 76@app.post("/text2int")77def text2int_ep(content: Text = None):78 ml_response = text2int(content.content)79 content = {"message": ml_response}80 return JSONResponse(content=content)81 82 83@app.post("/v1/manager")84async def programmatic_message_manager(request: Request):85 """86 Calls conversation management function to determine the next state87 88 Input89 request.body: dict - message data for the most recent user response90 {91 "author_id": "+47897891",92 "contact_uuid": "j43hk26-2hjl-43jk-hnk2-k4ljl46j0ds09",93 "author_type": "OWNER",94 "message_body": "a test message",95 "message_direction": "inbound",96 "message_id": "ABJAK64jlk3-agjkl2QHFAFH",97 "message_inserted_at": "2022-07-05T04:00:34.03352Z",98 "message_updated_at": "2023-02-14T03:54:19.342950Z",99 }100 101 Output102 context: dict - the information for the current state103 {104 "user": "47897891",105 "state": "welcome-message-state",106 "bot_message": "Welcome to Rori!",107 "user_message": "",108 "type": "ask"109 }110 """111 data_dict = await request.json()112 context = manage_conversation_response(data_dict)113 return JSONResponse(context)114 115 116@app.post("/v2/manager")117async def programmatic_message_manager(request: Request):118 """119 Calls conversation management function to determine the next state120 121 Input122 request.body: dict - message data for the most recent user response123 {124 "author_id": "+47897891",125 "contact_uuid": "j43hk26-2hjl-43jk-hnk2-k4ljl46j0ds09",126 "author_type": "OWNER",127 "message_body": "a test message",128 "message_direction": "inbound",129 "message_id": "ABJAK64jlk3-agjkl2QHFAFH",130 "message_inserted_at": "2022-07-05T04:00:34.03352Z",131 "message_updated_at": "2023-02-14T03:54:19.342950Z",132 }133 134 Output135 context: dict - the information for the current state136 {137 "user": "47897891",138 "state": "welcome-message-state",139 "bot_message": "Welcome to Rori!",140 "user_message": "",141 "type": "ask"142 }143 """144 data_dict = await request.json()145 context = manage_conversation_response(data_dict)146 return JSONResponse(context)147 148 149@app.post("/intent-classification")150def intent_classification_ep(content: Text = None):151 ml_response = run_intent_classification(content.content)152 content = {"message": ml_response}153 return JSONResponse(content=content)154 155 156@app.post("/nlu")157async def evaluate_user_message_with_nlu_api(request: Request):158 """ Calls nlu evaluation and returns the nlu_response159 160 Input161 - request.body: json - message data for the most recent user response162 163 Output164 - int_data_dict or sent_data_dict: dict - the type of NLU run and result165 {'type':'integer', 'data': '8', 'confidence': 0}166 {'type':'sentiment', 'data': 'negative', 'confidence': 0.99}167 """168 log.info(f'Received request: {request}')169 log.info(f'Request header: {request.headers}')170 request_body = await request.body()171 log.info(f'Request body: {request_body}')172 request_body_str = request_body.decode()173 log.info(f'Request_body_str: {request_body_str}')174 175 try:176 data_dict = await request.json()177 except JSONDecodeError:178 log.error(f'Request.json failed: {dir(request)}')179 data_dict = {}180 message_data = data_dict.get('message_data')181 182 if not message_data:183 log.error(f'Data_dict: {data_dict}')184 message_data = data_dict.get('message', {})185 nlu_response = evaluate_message_with_nlu(message_data)186 return JSONResponse(content=nlu_response)187 188 189@app.post("/num_one")190async def num_one(request: Request):191 """192 Input: 193 {194 "user_id": 1,195 "message_text": 5,196 }197 Output:198 {199 'messages': 200 ["Let's", 'practice', 'counting', '', '', '46...', '47...', '48...', '49', '', '', 'After', '49,', 'what', 'is', 'the', 'next', 'number', 'you', 'will', 'count?\n46,', '47,', '48,', '49'], 201 'input_prompt': '50', 202 'state': 'question'203 }204 """205 data_dict = await request.json()206 message_data = ast.literal_eval(data_dict.get('message_data', '').get('message_body', ''))207 user_id = message_data['user_id']208 message_text = message_data['message_text']209 return num_one_quiz.process_user_message(user_id, message_text)210 211 212@app.post("/start")213async def ask_math_question(request: Request):214 """Generate a question data215 216 Input217 {218 'difficulty': 0.1,219 'do_increase': True | False220 }221 222 Output223 {224 'text': 'What is 1+2?',225 'difficulty': 0.2,226 'question_numbers': [3, 1, 4]227 }228 """229 data_dict = await request.json()230 message_data = ast.literal_eval(data_dict.get('message_data', '').get('message_body', ''))231 difficulty = message_data['difficulty']232 do_increase = message_data['do_increase']233 234 return JSONResponse(generators.start_interactive_math(difficulty, do_increase))235 236 237@app.post("/hint")238async def get_hint(request: Request):239 """Generate a hint data240 241 Input242 {243 'start': 5,244 'step': 1,245 'difficulty': 0.1246 }247 248 Output249 {250 'text': 'What number is greater than 4 and less than 6?',251 'difficulty': 0.1,252 'question_numbers': [5, 1, 6]253 }254 """255 data_dict = await request.json()256 message_data = ast.literal_eval(data_dict.get('message_data', '').get('message_body', ''))257 start = message_data['start']258 step = message_data['step']259 difficulty = message_data['difficulty']260 261 return JSONResponse(hints.generate_hint(start, step, difficulty))262 263 264@app.post("/question")265async def ask_math_question(request: Request):266 """Generate a question data267 268 Input269 {270 'start': 5,271 'step': 1,272 'question_num': 1 # optional273 }274 275 Output276 {277 'question': 'What is 1+2?',278 'start': 5,279 'step': 1,280 'answer': 6281 }282 """283 data_dict = await request.json()284 message_data = ast.literal_eval(data_dict.get('message_data', '').get('message_body', ''))285 start = message_data['start']286 step = message_data['step']287 arg_tuple = (start, step)288 try:289 question_num = message_data['question_num']290 arg_tuple += (question_num,)291 except KeyError:292 pass293 294 return JSONResponse(questions.generate_question_data(*arg_tuple))295 296 297@app.post("/difficulty")298async def get_hint(request: Request):299 """Generate a number matching difficulty300 301 Input302 {303 'difficulty': 0.01,304 'do_increase': True305 }306 307 Output - value from 0.01 to 0.99 inclusively:308 0.09309 """310 data_dict = await request.json()311 message_data = ast.literal_eval(data_dict.get('message_data', '').get('message_body', ''))312 difficulty = message_data['difficulty']313 do_increase = message_data['do_increase']314 315 return JSONResponse(utils.get_next_difficulty(difficulty, do_increase))316 317 318@app.post("/start_step")319async def get_hint(request: Request):320 """Generate a start and step values321 322 Input323 {324 'difficulty': 0.01,325 'path_to_csv_file': 'scripts/quiz/data.csv' # optional326 }327 328 Output - tuple (start, step):329 (5, 1)330 """331 data_dict = await request.json()332 message_data = ast.literal_eval(data_dict.get('message_data', '').get('message_body', ''))333 difficulty = message_data['difficulty']334 arg_tuple = (difficulty,)335 try:336 path_to_csv_file = message_data['path_to_csv_file']337 arg_tuple += (path_to_csv_file,)338 except KeyError:339 pass340 341 return JSONResponse(utils.get_next_difficulty(*arg_tuple))342 343 344@app.post("/sequence")345async def generate_question(request: Request):346 """Generate a sequence from start, step and optional separator parameter347 348 Input349 {350 'start': 5,351 'step': 1,352 'sep': ', ' # optional353 }354 355 Output356 5, 6, 7357 """358 data_dict = await request.json()359 message_data = ast.literal_eval(data_dict.get('message_data', '').get('message_body', ''))360 start = message_data['start']361 step = message_data['step']362 arg_tuple = (start, step)363 try:364 sep = message_data['sep']365 arg_tuple += (sep,)366 except KeyError:367 pass368 369 return JSONResponse(utils.convert_sequence_to_string(*arg_tuple))370 