diff --git a/.gitignore b/.gitignore index 14a3dc29..9dbfefc0 100644 --- a/.gitignore +++ b/.gitignore @@ -14,9 +14,11 @@ venv/ .mypy_cache/ .vscode/ .venv +.DS_Store # docs documents.txt +credentials.json # virtualenv .venv diff --git a/backend/src/api/main.py b/backend/src/api/main.py index 976660ae..8fedc3c6 100644 --- a/backend/src/api/main.py +++ b/backend/src/api/main.py @@ -1,5 +1,4 @@ from fastapi import FastAPI - from .routers import graphs, healthcheck app = FastAPI() diff --git a/common/.env.example b/common/.env.example new file mode 100644 index 00000000..84c52bf4 --- /dev/null +++ b/common/.env.example @@ -0,0 +1 @@ +MONGO_DB_URI = YOUR_MONGO_DB_URI \ No newline at end of file diff --git a/common/mongoClient.py b/common/mongoClient.py new file mode 100644 index 00000000..d009b68b --- /dev/null +++ b/common/mongoClient.py @@ -0,0 +1,151 @@ +from pymongo import MongoClient +from pymongo.database import Database +from dotenv import load_dotenv +import os + +load_dotenv() + +# HARDCODED collection names +feedback_collection_name = "feedback" +context_collection_name = "context" + +def get_mongo_db_client() -> Database: + """ + Get the MongoDB client. + + Returns: + - DatabaseClient: The Database client. + + Note: + MongoDB doesn't create a collection or a database until it gets content, so no need to check if the data already exists or not. + """ + + uri = os.getenv("MONGO_DB_URI") + client = MongoClient(uri) + # this is the database that is returned by the client + return client["feedback_db"] + +def submit_feedback(): + """ + Submit feedback Record to the MongoDB database. + + Args: + - question (str): The question for which feedback is being submitted. + - answer (str): The generated answer to the question. + - sources (list[str]): Source data used for the answer. + - context (list[str]): Additional context from the RAG. + - issue (str): Details about the issue. + - version (str): Version information. + + Returns: + - None + """ + + feedback_db_client = get_mongo_client() + + try: + if not check_collection_exists(feedback_collection_name,feedback_db_client,): + create_collection(feedback_collection_name,feedback_db_client, validator={ + '$jsonSchema': { + 'bsonType': 'object', + 'required': ['question', 'answer', 'sources', 'context_ids', 'issue', 'version', 'timestamp'], + 'properties': { + 'question': { + 'bsonType': 'string', + 'description': 'must be a string and is required' + }, + 'answer': { + 'bsonType': 'string', + 'description': 'must be a string and is required' + }, + 'sources': { + 'bsonType': 'array', + 'items': { + 'bsonType': 'objectId' + }, + 'description': 'must be an array of ObjectIds referencing the sources and is required' + }, + 'context': { + 'bsonType': 'array', + 'items': { + 'bsonType': 'string' + }, + 'description': 'must be an array of strings and is required' + }, + 'issue': { + 'bsonType': 'string', + 'description': 'must be a string and is required' + }, + 'version': { + 'bsonType': 'string', + 'description': 'must be a string and is required' + }, + 'timestamp': { + 'bsonType': 'date', + 'description': 'must be a date and is required' + }, + 'status': { + 'enum': ['new', 'processing', 'resolved'], + 'description': 'can only be one of the enum values' + } + } + } + }) + if not check_collection_exists(context_collection_name,feedback_db_client): + create_collection(context_collection_name,feedback_db_client,{ + 'bsonType': 'object', + 'required': ['source', 'timestamp'], + 'properties': { + 'source': { + 'bsonType': 'string', + 'description': 'must be a string and is required' + }, + 'metadata': { + 'bsonType': 'object', + 'description': 'additional metadata for the context' + }, + 'timestamp': { + 'bsonType': 'date', + 'description': 'must be a date and is required' + } + } + } + ) + + except Exception as e: + print(f"Failed to submit feedback: {e}") + return None + +def check_collection_exists(collection_name:str,client_database:Database)->bool: + """ + Check if the collection exists in the database. + + Args: + - collection_name (str): The name of the collection to check. + - client_database (Database): The database to check. + + Returns: + - None + """ + return collection_name in client_database.list_collection_names() + +def create_collection(collection_name:str,client_database:Database,validator:object)->None: + """ + Create a collection in the database. + + Args: + - collection_name (str): The name of the collection to create. + - client_database (Database): The database to create the collection in. + + Returns: + - None + """ + try: + client_database.create_collection(collection_name,validator=validator) + print("Collection created successfully") + except Exception as e: + print(f"Failed to create collection: {e}") + return None + +if __name__ == "__main__": + submit_feedback() diff --git a/common/requirements.txt b/common/requirements.txt new file mode 100644 index 00000000..6bebc3e1 --- /dev/null +++ b/common/requirements.txt @@ -0,0 +1,2 @@ +pymongo==4.6.2 +python-dotenv==1.0.1 diff --git a/frontend/streamlit_app.py b/frontend/streamlit_app.py index df9911a8..d5995188 100644 --- a/frontend/streamlit_app.py +++ b/frontend/streamlit_app.py @@ -221,7 +221,7 @@ def update_state() -> None: question_dict, st.session_state.metadata, st.session_state.chat_history, - ) + ) except Exception as e: st.error(f"Failed to load feedback form: {e}") diff --git a/frontend/utils/feedback.py b/frontend/utils/feedback.py index 56e786e6..6296adb3 100644 --- a/frontend/utils/feedback.py +++ b/frontend/utils/feedback.py @@ -96,6 +96,7 @@ def submit_feedback_to_google_sheet( ] creds = Credentials.from_service_account_file(service_account_file, scopes=scope) + # this is the place where the client is coming from, need to change this for mongoDB client client = gspread.authorize(creds) sheet_id = os.getenv("FEEDBACK_SHEET_ID", "") @@ -170,8 +171,15 @@ def show_feedback_form( feedback = st.sidebar.text_area("Please provide your feedback or report an issue:") if selected_question: - sources = [metadata[selected_question].get("sources", "N/A")] - context = [metadata[selected_question].get("context", "N/A")] + sources = metadata[selected_question].get("sources", ["N/A"]) + if isinstance(sources, str): + + sources = [sources] + + context = metadata[selected_question].get("context", ["N/A"]) + if isinstance(context, str): + + context = [context] if st.sidebar.button("Submit"): selected_index = questions[selected_question] @@ -180,8 +188,8 @@ def show_feedback_form( submit_feedback_to_google_sheet( question=selected_question, answer=gen_ans, - sources=sources, - context=context, + sources=sources, # Now passing as list + context=context, # Now passing as list issue=feedback, version=os.getenv("RAG_VERSION", get_git_commit_hash()), )