From 792fc638d9b769beccd1e16ce16493455bbe1263 Mon Sep 17 00:00:00 2001 From: OdyAsh Date: Tue, 7 Jan 2025 09:35:07 +0200 Subject: [PATCH] feat: enhance WhatsApp integration with retention settings and debug logging --- sql/00_ansari_db_data_model.png | Bin 0 -> 66070 bytes sql/10_create_whatsapp_tables.sql | 19 ++-- sql/ansari_db_data_model_before_whatsapp.png | Bin 48097 -> 0 bytes src/ansari/agents/ansari.py | 30 ++++-- src/ansari/ansari_db.py | 60 ++++++------ src/ansari/app/main_api.py | 52 ++++++----- src/ansari/app/main_whatsapp.py | 28 +++--- src/ansari/config.py | 2 +- src/ansari/presenters/whatsapp_presenter.py | 93 +++++++++++-------- src/ansari/util/general_helpers.py | 47 +++++++++- 10 files changed, 213 insertions(+), 118 deletions(-) create mode 100644 sql/00_ansari_db_data_model.png delete mode 100644 sql/ansari_db_data_model_before_whatsapp.png diff --git a/sql/00_ansari_db_data_model.png b/sql/00_ansari_db_data_model.png new file mode 100644 index 0000000000000000000000000000000000000000..de4a2fa17fc757e3a308a42c2220470d8d169e3d GIT binary patch literal 66070 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

4#dN2yEKd^Q#MEVg6xf#b>`QMtR=svqMIbpBO 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 FzX6+Drf2{F literal 0 HcmV?d00001 diff --git a/sql/10_create_whatsapp_tables.sql b/sql/10_create_whatsapp_tables.sql index e410e64..1ad8617 100644 --- a/sql/10_create_whatsapp_tables.sql +++ b/sql/10_create_whatsapp_tables.sql @@ -47,11 +47,14 @@ CREATE TABLE messages_whatsapp ( -- Debugging notes: run below commands to untie dependencies and drop tables -- (if you're still prototyping with the tables' final schema) --- -- Drop foreign key constraints --- ALTER TABLE users_whatsapp DROP CONSTRAINT users_whatsapp_user_id_fkey; --- ALTER TABLE messages_whatsapp DROP CONSTRAINT messages_whatsapp_user_id_whatsapp_fkey; - --- -- Drop the tables --- DROP TABLE IF EXISTS users_whatsapp; --- DROP TABLE IF EXISTS messages_whatsapp; --- DROP TABLE IF EXISTS threads_whatsapp; \ No newline at end of file +-- -- Drop foreign key constraints +--ALTER TABLE users_whatsapp DROP CONSTRAINT users_whatsapp_user_id_fkey; +--ALTER TABLE messages_whatsapp +-- DROP CONSTRAINT messages_whatsapp_user_id_whatsapp_fkey, +-- DROP CONSTRAINT messages_whatsapp_thread_id_fkey; +--ALTER TABLE threads_whatsapp DROP CONSTRAINT threads_whatsapp_user_id_whatsapp_fkey; + +-- -- Drop the tables +--DROP TABLE IF EXISTS users_whatsapp; +--DROP TABLE IF EXISTS messages_whatsapp; +--DROP TABLE IF EXISTS threads_whatsapp; diff --git a/sql/ansari_db_data_model_before_whatsapp.png b/sql/ansari_db_data_model_before_whatsapp.png deleted file mode 100644 index 367499af0b6327ebcd5d97fc27024c2ff7eacae9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48097 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 NrU34u%#(iL@gG52aQOfL diff --git a/src/ansari/agents/ansari.py b/src/ansari/agents/ansari.py index a5aa5ec..395bcb0 100644 --- a/src/ansari/agents/ansari.py +++ b/src/ansari/agents/ansari.py @@ -1,3 +1,4 @@ +import copy import hashlib import json import os @@ -92,6 +93,28 @@ def replace_message_history(self, message_history, use_tool=True, stream=True): if m: yield m + def _debug_log_truncated_message_history(self, message_history, count: int, failures: int): + """ + Logs a truncated version of the message history for debugging purposes. + + Args: + message_history (list): The message history to be truncated and logged. + """ + trunc_msg_hist = copy.deepcopy(message_history) + if ( + len(trunc_msg_hist) > 1 + and isinstance(trunc_msg_hist[0], dict) + and "role" in trunc_msg_hist[0] + and trunc_msg_hist[0]["role"] == "system" + and "content" in trunc_msg_hist[0] + ): + sys_p = trunc_msg_hist[0]["content"] + trunc_msg_hist[0]["content"] = sys_p[:15] + "..." + + logger.info( + f"Process attempt #{count+failures+1} of this message history:\n" + "-" * 60 + f"\n{trunc_msg_hist}\n" + "-" * 60, + ) + @observe(capture_input=False, capture_output=False) def process_message_history(self, use_tool=True, stream=True): """ @@ -110,12 +133,7 @@ def process_message_history(self, use_tool=True, stream=True): failures = 0 while self.message_history[-1]["role"] != "assistant" or "tool_call_id" in self.message_history[-1]: try: - logger.info( - f"Process attempt #{count+failures+1} of this message history:\n" - + "-" * 60 - + f"\n{self.message_history}\n" - + "-" * 60, - ) + self._debug_log_truncated_message_history(self.message_history, count, failures) # This is pretty complicated so leaving a comment. # We want to yield from so that we can send the sequence through the input # Also use tools only if we haven't tried too many times (failure) diff --git a/src/ansari/ansari_db.py b/src/ansari/ansari_db.py index ab778a9..506fe12 100644 --- a/src/ansari/ansari_db.py +++ b/src/ansari/ansari_db.py @@ -24,7 +24,9 @@ class MessageLogger: without having to share details about the user_id and the thread_id """ - def __init__(self, db: "AnsariDB", user_id: int, thread_id: int, trace_id: int, to_whatsapp: bool = False) -> None: + def __init__(self, db: "AnsariDB", user_id: int, thread_id: int, trace_id: int = None, to_whatsapp: bool = False) -> None: + if not to_whatsapp and trace_id is None: + raise ValueError("trace_id must be provided when not logging to WhatsApp") self.user_id = user_id self.thread_id = thread_id self.trace_id = trace_id @@ -167,7 +169,7 @@ def _execute_query( which_fetch = [which_fetch] * len(query) caller_function_name = inspect.stack()[1].function - logger.debug(f"Function {caller_function_name}() \nis running queries: \n{query} \nwith params: \n{params}") + logger.debug(f"Running DB function: {caller_function_name}()") results = [] with self.get_connection() as conn: @@ -249,7 +251,7 @@ def register(self, email, first_name, last_name, password_hash): self._execute_query(insert_cmd, (email, password_hash, first_name, last_name)) return {"status": "success"} except Exception as e: - logger.warning(f"Error is {e}") + logger.warning(f"Warning (possbile error): {e}") return {"status": "failure", "error": str(e)} def register_whatsapp(self, phone_num: str, db_cols_to_vals: dict) -> dict: @@ -281,7 +283,7 @@ def register_whatsapp(self, phone_num: str, db_cols_to_vals: dict) -> dict: return {"status": "success"} except Exception as e: - logger.warning(f"Error is {e}") + logger.warning(f"Warning (possbile error): {e}") return {"status": "failure", "error": str(e)} def account_exists(self, email): @@ -290,7 +292,7 @@ def account_exists(self, email): result = self._execute_query(select_cmd, (email,), "one")[0] return result is not None except Exception as e: - logger.warning(f"Error is {e}") + logger.warning(f"Warning (possbile error): {e}") return False def account_exists_whatsapp(self, phone_num): @@ -299,7 +301,7 @@ def account_exists_whatsapp(self, phone_num): result = self._execute_query(select_cmd, (phone_num,), "one")[0] return result is not None except Exception as e: - logger.warning(f"Error is {e}") + logger.warning(f"Warning (possbile error): {e}") return False def save_access_token(self, user_id, token): @@ -313,7 +315,7 @@ def save_access_token(self, user_id, token): "token_db_id": inserted_id, } except Exception as e: - logger.warning(f"Error is {e}") + logger.warning(f"Warning (possbile error): {e}") return {"status": "failure", "error": str(e)} def save_refresh_token(self, user_id, token, access_token_id): @@ -322,7 +324,7 @@ def save_refresh_token(self, user_id, token, access_token_id): self._execute_query(insert_cmd, (user_id, token, access_token_id)) return {"status": "success", "token": token} except Exception as e: - logger.warning(f"Error is {e}") + logger.warning(f"Warning (possbile error): {e}") return {"status": "failure", "error": str(e)} def save_reset_token(self, user_id, token): @@ -334,7 +336,7 @@ def save_reset_token(self, user_id, token): self._execute_query(insert_cmd, (user_id, token, token)) return {"status": "success", "token": token} except Exception as e: - logger.warning(f"Error is {e}") + logger.warning(f"Warning (possbile error): {e}") return {"status": "failure", "error": str(e)} def retrieve_user_info(self, email): @@ -346,7 +348,7 @@ def retrieve_user_info(self, email): return user_id, existing_hash, first_name, last_name return None, None, None, None except Exception as e: - logger.warning(f"Error is {e}") + logger.warning(f"Warning (possbile error): {e}") return None, None, None, None def retrieve_user_info_whatsapp(self, phone_num: str, db_cols: Union[list, str]) -> Optional[Tuple]: @@ -382,7 +384,7 @@ def retrieve_user_info_whatsapp(self, phone_num: str, db_cols: Union[list, str]) return result return None except Exception as e: - logger.warning(f"Error is {e}") + logger.warning(f"Warning (possbile error): {e}") return None def add_feedback(self, user_id, thread_id, message_id, feedback_class, comment): @@ -393,7 +395,7 @@ def add_feedback(self, user_id, thread_id, message_id, feedback_class, comment): self._execute_query(insert_cmd, (user_id, thread_id, message_id, feedback_class, comment)) return {"status": "success"} except Exception as e: - logger.warning(f"Error is {e}") + logger.warning(f"Warning (possbile error): {e}") return {"status": "failure", "error": str(e)} def create_thread(self, user_id): @@ -403,7 +405,7 @@ def create_thread(self, user_id): inserted_id = result[0] if result else None return {"status": "success", "thread_id": inserted_id} except Exception as e: - logger.warning(f"Error is {e}") + logger.warning(f"Warning (possbile error): {e}") return {"status": "failure", "error": str(e)} def create_thread_whatsapp(self, user_id_whatsapp: int, thread_name: str) -> str: @@ -426,7 +428,7 @@ def create_thread_whatsapp(self, user_id_whatsapp: int, thread_name: str) -> str result = self._execute_query(insert_cmd, (user_id_whatsapp, thread_name), "one")[0] return result[0] if result else None except Exception as e: - logger.warning(f"Error is {e}") + logger.warning(f"Warning (possbile error): {e}") return None def get_all_threads(self, user_id): @@ -435,7 +437,7 @@ def get_all_threads(self, user_id): result = self._execute_query(select_cmd, (user_id,), "all")[0] return [{"thread_id": x[0], "thread_name": x[1], "updated_at": x[2]} for x in result] if result else [] except Exception as e: - logger.warning(f"Error is {e}") + logger.warning(f"Warning (possbile error): {e}") return [] def set_thread_name(self, thread_id, user_id, thread_name): @@ -454,7 +456,7 @@ def set_thread_name(self, thread_id, user_id, thread_name): ) return {"status": "success"} except Exception as e: - logger.warning(f"Error is {e}") + logger.warning(f"Warning (possbile error): {e}") return {"status": "failure", "error": str(e)} def append_message(self, user_id, thread_id, role, content, tool_name=None): @@ -474,7 +476,7 @@ def append_message(self, user_id, thread_id, role, content, tool_name=None): return {"status": "success"} except Exception as e: - logger.warning(f"Error is {e}") + logger.warning(f"Warning (possbile error): {e}") return {"status": "failure", "error": str(e)} def append_message_whatsapp(self, user_id_whatsapp: int, thread_id: int, db_cols_to_vals: dict) -> dict: @@ -511,7 +513,7 @@ def append_message_whatsapp(self, user_id_whatsapp: int, thread_id: int, db_cols return {"status": "success"} except Exception as e: - logger.warning(f"Error is {e}") + logger.warning(f"Warning (possbile error): {e}") return {"status": "failure", "error": str(e)} def get_thread(self, thread_id, user_id): @@ -542,7 +544,7 @@ def get_thread(self, thread_id, user_id): } return retval except Exception as e: - logger.warning(f"Error is {e}") + logger.warning(f"Warning (possbile error): {e}") return {} def get_thread_llm(self, thread_id, user_id): @@ -574,7 +576,7 @@ def get_thread_llm(self, thread_id, user_id): } return retval except Exception as e: - logger.warning(f"Error is {e}") + logger.warning(f"Warning (possbile error): {e}") return {} def get_thread_llm_whatsapp(self, thread_id: str, user_id_whatsapp: int) -> list[dict]: @@ -603,7 +605,7 @@ def get_thread_llm_whatsapp(self, thread_id: str, user_id_whatsapp: int) -> list else [] ) except Exception as e: - logger.warning(f"Error is {e}") + logger.warning(f"Warning (possbile error): {e}") return [] def get_last_message_time_whatsapp(self, user_id_whatsapp: int) -> tuple[Optional[str], Optional[datetime]]: @@ -630,7 +632,7 @@ def get_last_message_time_whatsapp(self, user_id_whatsapp: int) -> tuple[Optiona return result[0], result[1] return None, None except Exception as e: - logger.warning(f"Error is {e}") + logger.warning(f"Warning (possbile error): {e}") return None, None def snapshot_thread(self, thread_id, user_id): @@ -649,7 +651,7 @@ def snapshot_thread(self, thread_id, user_id): logger.info(f"Result is {result}") return result[0] if result else None except Exception as e: - logger.warning(f"Error is {e}") + logger.warning(f"Warning (possbile error): {e}") return {"status": "failure", "error": str(e)} def get_snapshot(self, share_uuid): @@ -662,7 +664,7 @@ def get_snapshot(self, share_uuid): return json.loads(result[0]) return {} except Exception as e: - logger.warning(f"Error is {e}") + logger.warning(f"Warning (possbile error): {e}") return {} def delete_thread(self, thread_id, user_id): @@ -675,7 +677,7 @@ def delete_thread(self, thread_id, user_id): self._execute_query([delete_cmd_1, delete_cmd_2], [params, params]) return {"status": "success"} except Exception as e: - logger.warning(f"Error is {e}") + logger.warning(f"Warning (possbile error): {e}") return {"status": "failure", "error": str(e)} def delete_access_refresh_tokens_pair(self, refresh_token): @@ -715,7 +717,7 @@ def delete_access_token(self, user_id, token): self._execute_query(delete_cmd, (user_id, token)) return {"status": "success"} except Exception as e: - logger.warning(f"Error is {e}") + logger.warning(f"Warning (possbile error): {e}") return {"status": "failure", "error": str(e)} def logout(self, user_id, token): @@ -725,7 +727,7 @@ def logout(self, user_id, token): self._execute_query(delete_cmd, (user_id, token)) return {"status": "success"} except Exception as e: - logger.warning(f"Error is {e}") + logger.warning(f"Warning (possbile error): {e}") return {"status": "failure", "error": str(e)} def set_pref(self, user_id, key, value): @@ -750,7 +752,7 @@ def update_password(self, user_id, new_password_hash): self._execute_query(update_cmd, (new_password_hash, user_id)) return {"status": "success"} except Exception as e: - logger.warning(f"Error is {e}") + logger.warning(f"Warning (possbile error): {e}") return {"status": "failure", "error": str(e)} def update_user_whatsapp(self, phone_num: str, db_cols_to_vals: dict) -> dict: @@ -782,7 +784,7 @@ def update_user_whatsapp(self, phone_num: str, db_cols_to_vals: dict) -> dict: return {"status": "success"} except Exception as e: - logger.warning(f"Error is {e}") + logger.warning(f"Warning (possbile error): {e}") return {"status": "failure", "error": str(e)} def convert_message(self, msg): diff --git a/src/ansari/app/main_api.py b/src/ansari/app/main_api.py index d6c2a5f..7ebb85a 100644 --- a/src/ansari/app/main_api.py +++ b/src/ansari/app/main_api.py @@ -7,12 +7,13 @@ from diskcache import FanoutCache, Lock from fastapi import Depends, FastAPI, HTTPException, Request from fastapi.middleware.cors import CORSMiddleware -from fastapi.responses import StreamingResponse +from fastapi.responses import JSONResponse, StreamingResponse from jinja2 import Environment, FileSystemLoader from langfuse.decorators import langfuse_context, observe from pydantic import BaseModel from sendgrid import SendGridAPIClient from sendgrid.helpers.mail import Mail +from starlette.exceptions import HTTPException as StarletteHTTPException from zxcvbn import zxcvbn from ansari.agents import Ansari, AnsariWorkflow @@ -25,13 +26,25 @@ logger = get_logger() - # Register the UUID type globally psycopg2.extras.register_uuid() app = FastAPI() +# Custom exception handler, which aims to log FastAPI-related exceptions before raising them +# Details: https://fastapi.tiangolo.com/tutorial/handling-errors/#override-request-validation-exceptions +# Side note: apparently, there's no need to write another `RequestValidationError`-related function, +# contrary to what's mentioned in the above URL. +@app.exception_handler(StarletteHTTPException) +async def http_exception_handler(request, exc: HTTPException): + logger.error(f"{exc}") + return JSONResponse( + status_code=exc.status_code, + content={"detail": exc.detail}, + ) + + def add_app_middleware(): origins = get_settings().ORIGINS @@ -39,14 +52,11 @@ def add_app_middleware(): if get_settings().DEBUG_MODE: # Change "3000" to the port of your frontend server (3000 is the default there) local_origin = "http://localhost:3000" + zrok_origin = get_settings().ZROK_SHARE_TOKEN.get_secret_value() + ".share.zrok.io" + # If we don't execute the code below, we'll get a "400 Bad Request" error when + # trying to access the API from the local frontend origins.append(local_origin) - zrok_origin = "https://" + get_settings().ZROK_SHARE_TOKEN.get_secret_value() + ".share.zrok.io" origins.append(zrok_origin) - # If we don't do this, we'll get a "400 Bad Request" error when trying to access the API from the local frontend - logger.debug( - f"Added {local_origin} and zrok's origin to the list of allowed origins for debugging purposes " - + "(assuming local frontend's port is 3000)..." - ) app.add_middleware( CORSMiddleware, @@ -57,11 +67,7 @@ def add_app_middleware(): ) -def main(): - add_app_middleware() - - -main() +add_app_middleware() db = AnsariDB(get_settings()) ansari = Ansari(get_settings()) @@ -76,16 +82,20 @@ def main(): if __name__ == "__main__" and get_settings().DEBUG_MODE: # Programatically start a Uvicorn server while debugging (development) for easier control/accessibility + # I.e., just run: + # `python src/ansari/app/main_api.py` # Note 1: if you instead run - # uvicorn main_api:app --host YOUR_HOST --port YOUR_PORT - # in the terminal, then this block will be ignored + # `uvicorn main_api:app --host YOUR_HOST --port YOUR_PORT` + # in the terminal, then this `if __name__ ...` block will be ignored + # Note 2: you have to use zrok to test whatsapp's webhook locally, - # Check the resources at `.env.example` file for more details - # Run commands: - # `zrok enable SECRET_TOKEN_GENERATED_BY_ZROK_FOR_YOUR_DEVICE` (should be run only once) - # `zrok reserve public localhost:8000 -n ZROK_SHARE_TOKEN` (should be run only once) - # (if error occurs, contact odyash on GitHub) - # `zrok share reserved ZROK_SHARE_TOKEN` + # Check the resources at `.env.example` file for more details, but TL;DR: + # Run 3 commands below: + # Only run on initial setup (if error occurs, contact odyash on GitHub): + # `zrok enable SECRET_TOKEN_GENERATED_BY_ZROK_FOR_YOUR_DEVICE` + # `zrok reserve public localhost:8000 -n ZROK_SHARE_TOKEN` + # Run on initial setup and upon starting a new terminal session: + # `zrok share reserved ZROK_SHARE_TOKEN` import uvicorn filename_without_extension = os.path.splitext(os.path.basename(__file__))[0] diff --git a/src/ansari/app/main_whatsapp.py b/src/ansari/app/main_whatsapp.py index 8fd966e..46420ea 100644 --- a/src/ansari/app/main_whatsapp.py +++ b/src/ansari/app/main_whatsapp.py @@ -91,18 +91,24 @@ async def main_webhook(request: Request, cors_ok: bool = Depends(validate_cors)) # Get relevant info from Meta's API ( - business_phone_number_id, from_whatsapp_number, incoming_msg_type, incoming_msg_body, ) = result - # Check if the user's phone number is stored in users_whatsapp table - await presenter.check_and_register_user( + # Check if the user's phone number is stored in users_whatsapp table and register if not + # Returns false if user's not found and thier registration fails + user_found: bool = await presenter.check_and_register_user( from_whatsapp_number, incoming_msg_type, incoming_msg_body, ) + if not user_found: + await presenter.send_whatsapp_message( + from_whatsapp_number, + "Sorry, we couldn't register you to our Database. Please try again later.", + ) + return # Check if the incoming message is a location if incoming_msg_type == "location": @@ -123,17 +129,17 @@ async def main_webhook(request: Request, cors_ok: bool = Depends(validate_cors)) # Rest of the code below is for processing text messages sent by the whatsapp user incoming_msg_text = incoming_msg_body["body"] - # Send acknowledgment message (only in DEBUG_MODE) - if get_settings().DEBUG_MODE: - await presenter.send_whatsapp_message( - from_whatsapp_number, - f"Ack: {incoming_msg_text}", - ) + # # Send acknowledgment message (only in DEBUG_MODE) + # # and if dev. doesn't need it, comment it out :] + # if get_settings().DEBUG_MODE: + # await presenter.send_whatsapp_message( + # from_whatsapp_number, + # f"Ack: {incoming_msg_text}", + # ) # Send a typing indicator to the sender # Side note: As of 2024-12-21, Meta's WhatsApp API does not support typing indicators - # Source URL results from this query: - # https://www.google.com/search?q=typing+indicator+whatsapp+api&sca_esv=25db03e20fe2a1e6&source=lnt&tbs=qdr:y + # Source: Search "typing indicator whatsapp api" on Google await presenter.send_whatsapp_message(from_whatsapp_number, "...") # Actual code to process the incoming message using Ansari agent then reply to the sender diff --git a/src/ansari/config.py b/src/ansari/config.py index cb10732..5e5cccb 100644 --- a/src/ansari/config.py +++ b/src/ansari/config.py @@ -126,7 +126,7 @@ def get_resource_path(filename): WHATSAPP_TEST_BUSINESS_PHONE_NUMBER_ID: SecretStr | None = Field(default=None) WHATSAPP_ACCESS_TOKEN_FROM_SYS_USER: SecretStr | None = Field(default=None) WHATSAPP_VERIFY_TOKEN_FOR_WEBHOOK: SecretStr | None = Field(default=None) - WHATSAPP_CHAT_RETENTION_HOURS: int = Field(default=3) + WHATSAPP_CHAT_RETENTION_HOURS: int = Field(default=3) # This is hardcoded to 0.05 when DEBUG_MODE is True ZROK_SHARE_TOKEN: SecretStr = Field(default="") template_dir: DirectoryPath = Field(default=get_resource_path("templates")) diskcache_dir: str = Field(default="diskcache_dir") diff --git a/src/ansari/presenters/whatsapp_presenter.py b/src/ansari/presenters/whatsapp_presenter.py index c5ceb45..3cadf11 100644 --- a/src/ansari/presenters/whatsapp_presenter.py +++ b/src/ansari/presenters/whatsapp_presenter.py @@ -1,6 +1,6 @@ import copy from datetime import datetime -from typing import Any +from typing import Any, Literal import httpx @@ -17,9 +17,6 @@ # instead of duplicating `db` instances? Will this cost more resources? db = AnsariDB(get_settings()) -# TODO(odyash) now: test the application on 0.05, so that 0.05 * 60 * 60 = 3 minutes :] -# Tip: check the `logger.debug`s below to help you out while testing - class WhatsAppPresenter: def __init__( @@ -48,6 +45,8 @@ async def extract_relevant_whatsapp_message_details( Returns None if the extraction fails. """ + # logger.debug(f"Received payload from WhatsApp user:\n{body}") + if not ( body.get("object") and (entry := body.get("entry", [])) @@ -61,13 +60,13 @@ async def extract_relevant_whatsapp_message_details( raise Exception(error_msg) if "statuses" in value: - status = value["statuses"]["status"] - timestamp = value["statuses"]["timestamp"] - # This log isn't important if we don't want to track when an Ansari's replied message is - # delivered to or read by the recipient - logger.debug( - f"WhatsApp status update received:\n({status} at {timestamp}.)", - ) + # status = value["statuses"]["status"] + # timestamp = value["statuses"]["timestamp"] + # # This log isn't important if we don't want to track when an Ansari's replied message is + # # delivered to or read by the recipient + # logger.debug( + # f"WhatsApp status update received:\n({status} at {timestamp}.)", + # ) return "status update" if "messages" not in value: @@ -77,11 +76,8 @@ async def extract_relevant_whatsapp_message_details( ) raise Exception(error_msg) - logger.info(f"Received payload from WhatsApp user:\n{body}") incoming_msg = value["messages"][0] - # Extract the business phone number ID from the webhook payload - business_phone_number_id = value["metadata"]["phone_number_id"] # Extract the phone number of the WhatsApp sender from_whatsapp_number = incoming_msg["from"] # Meta API note: Meta sends "errors" key when receiving unsupported message types @@ -90,8 +86,9 @@ async def extract_relevant_whatsapp_message_details( # Extract the message of the WhatsApp sender (could be text, image, etc.) incoming_msg_body = incoming_msg[incoming_msg_type] + logger.info(f"Received a supported whatsapp message from {from_whatsapp_number}: {incoming_msg_body}") + return ( - business_phone_number_id, from_whatsapp_number, incoming_msg_type, incoming_msg_body, @@ -116,15 +113,26 @@ async def check_and_register_user( None """ # Check if the user's phone number is stored in users_whatsapp table - if not db.account_exists_whatsapp(phone_num=from_whatsapp_number): - if incoming_msg_type == "text": - incoming_msg_text = incoming_msg_body["body"] - user_lang = get_language_from_text(incoming_msg_text) - else: - # TODO(odyash, good_first_issue): use lightweight library/solution that gives us language from country code - # instead of hardcoding "en" in below code - user_lang = "en" - db.register_whatsapp(from_whatsapp_number, {"preferred_language": user_lang}) + if db.account_exists_whatsapp(phone_num=from_whatsapp_number): + return True + + # Else, register the user with the detected language + if incoming_msg_type == "text": + incoming_msg_text = incoming_msg_body["body"] + user_lang = get_language_from_text(incoming_msg_text) + else: + # TODO(odyash, good_first_issue): use lightweight library/solution that gives us language from country code + # instead of hardcoding "en" in below code + user_lang = "en" + status: Literal["success", "failure"] = db.register_whatsapp(from_whatsapp_number, {"preferred_language": user_lang})[ + "status" + ] + if status == "success": + logger.info(f"Registered new whatsapp user (lang: {user_lang})!: {from_whatsapp_number}") + return True + else: + logger.error(f"Failed to register new whatsapp user: {from_whatsapp_number}") + return False async def send_whatsapp_message( self, @@ -152,12 +160,10 @@ async def send_whatsapp_message( async with httpx.AsyncClient() as client: response = await client.post(url, headers=headers, json=json_data) response.raise_for_status() # Raise an exception for HTTP errors - logger.info( - f"Ansari responsded to WhatsApp user: {from_whatsapp_number} with:\n{msg_body}", - ) - logger.debug( - f"So, status code and text of that WhatsApp response:\n{response.status_code}\n{response.text}", - ) + if msg_body != "...": + logger.info( + f"Ansari responsded to WhatsApp user: {from_whatsapp_number} with:\n{msg_body}", + ) async def handle_text_message( self, @@ -175,18 +181,28 @@ async def handle_text_message( logger.info(f"Whatsapp user said: {incoming_txt_msg}") # Get user's ID from users_whatsapp table + # Note we're not checking for user's existence here, as we've already done that in `main_webhook()` user_id_whatsapp = db.retrieve_user_info_whatsapp(from_whatsapp_number, "id")[0] # Get details of thread with latest updated_at column thread_id, last_message_time = db.get_last_message_time_whatsapp(user_id_whatsapp) - # Create a new thread if X hours have passed since last message - passed_time = (datetime.now() - last_message_time).total_seconds() - logger.debug(f"Time passed since user ({user_id_whatsapp})'s last whatsapp message: {passed_time}") + # Calculate the time passed since the last message + if last_message_time is None: + passed_time = float("inf") + else: + passed_time = (datetime.now() - last_message_time).total_seconds() + + logger.debug(f"Time passed since user ({user_id_whatsapp})'s last whatsapp message: {passed_time / 60:.1f}mins") + + # Determine the allowed retention time if get_settings().DEBUG_MODE: - allowed_time = 0.05 * 60 * 60 # 3 minutes + reten_hours = 0.05 # so allowed_time == 3 minutes else: - allowed_time = get_settings().WHATSAPP_CHAT_RETENTION_HOURS * 60 * 60 + reten_hours = get_settings().WHATSAPP_CHAT_RETENTION_HOURS + allowed_time = reten_hours * 60 * 60 + + # Create a new thread if X hours have passed since last message if thread_id is None or passed_time > allowed_time: first_few_words = " ".join(incoming_txt_msg.split()[:6]) thread_id = db.create_thread_whatsapp(user_id_whatsapp, first_few_words) @@ -203,8 +219,8 @@ async def handle_text_message( message_history_for_debugging = [msg for msg in message_history if msg["role"] in {"user", "assistant"}] # Note: obviously, this log output won't consider Ansari's response, as it still happens later in the code below logger.debug( - f"#msgs (user/asisstant only) retrieved for user ({user_id_whatsapp})'s current whatsapp thread: " - + len(message_history_for_debugging) + f"#msgs (user/assistant only) retrieved for user ({user_id_whatsapp})'s current whatsapp thread: " + + str(len(message_history_for_debugging)) ) # Setting up `MessageLogger` for Ansari, so it can log (i.e., store) its response to the DB @@ -226,7 +242,8 @@ async def handle_text_message( "Ansari returned an empty response. Please rephrase your question, then try again.", ) except Exception as e: - logger.error(f"Error processing message: {e}") + logger.error(f"Error processing message: {e}. Details are in next log.") + logger.exception(e) await self.send_whatsapp_message( from_whatsapp_number, "An unexpected error occurred while processing your message. Please try again later.", diff --git a/src/ansari/util/general_helpers.py b/src/ansari/util/general_helpers.py index b37e639..66b817a 100644 --- a/src/ansari/util/general_helpers.py +++ b/src/ansari/util/general_helpers.py @@ -11,7 +11,7 @@ # Defined in a separate file to avoid circular imports between main_*.py files def validate_cors(request: Request, settings: Settings = Depends(get_settings)) -> bool: try: - logger.debug(f"Headers of raw request are: {request.headers}") + # logger.debug(f"Headers of raw request are: {request.headers}") origins = get_settings().ORIGINS incoming_origin = [ request.headers.get("origin", ""), # If coming from ansari's frontend website @@ -22,11 +22,39 @@ def validate_cors(request: Request, settings: Settings = Depends(get_settings)) if any(i_o in origins for i_o in incoming_origin) or mobile == "ANSARI": logger.debug("CORS OK") return True - raise HTTPException(status_code=502, detail="Not Allowed Origin") + raise HTTPException(status_code=502, detail=f"Incoming origin/host: {incoming_origin} is not in origin list") except PyJWTError: raise HTTPException(status_code=403, detail="Could not validate credentials") +def _check_if_mostly_english(text: str, threshold: float = 0.8): + """ + Check if the majority of characters in the input string lie within the ASCII range 65 to 122. + + Parameters: + - text (str): The string to check. + - threshold (float): The threshold percentage (e.g., 0.8 for 80%). + + Returns: + - bool: True if the percentage of characters in the range is above the threshold, False otherwise. + """ + + # Count total characters in the input string + total_chars = len(text) + + if total_chars == 0: + return False # If the string is empty, return False + + # Count characters within the ASCII range 65 to 122 + count_in_range = sum(1 for char in text if 65 <= ord(char) <= 122) + + # Calculate the percentage of characters in range + percentage_in_range = count_in_range / total_chars + + # Check if this percentage meets or exceeds the threshold + return percentage_in_range >= threshold + + def get_language_from_text(text: str) -> str: """Extracts the language from the given text. @@ -37,7 +65,18 @@ def get_language_from_text(text: str) -> str: str: The language extracted from the given text in ISO 639-1 format ("en", "ar", etc.). """ + + if len(text) < 45 and _check_if_mostly_english(text): + # If user starts with small phrases like "Al salamu Alyykom", + # they get translated to "tl/id/etc." for some reason, + # so default to "en" in this case + logger.debug("Defaulting to English due to short English text") + return "en" + try: - return detect(text) - except Exception: + detected_lang = detect(text) + except Exception as e: + logger.error(f'Error detecting language (so will return "en" instead): {e}') return "en" + + return detected_lang