diff --git a/MinuteMate/README.md b/MinuteMate/README.md index 7f0ec008..2b8228bf 100644 --- a/MinuteMate/README.md +++ b/MinuteMate/README.md @@ -17,6 +17,6 @@ ##### Setup - Docker -TODO + diff --git a/MinuteMate/back/Dockerfile b/MinuteMate/back/Dockerfile index ecc035d7..6ab98f60 100644 --- a/MinuteMate/back/Dockerfile +++ b/MinuteMate/back/Dockerfile @@ -1,6 +1,7 @@ -FROM python:3.11-slim -WORKDIR /App -COPY . /App -RUN python -m pip install -r requirements.txt +FROM python:3.11 +WORKDIR /app +COPY . /app +RUN pip install --upgrade pip +RUN pip install -r requirements.txt EXPOSE 8000 -CMD [" ", "start","--port","8002","--host","0.0.0.0"] +CMD ["uvicorn", "main:app","--port","8000","--host","localhost"] diff --git a/MinuteMate/back/docker-compose.yml b/MinuteMate/back/docker-compose.yml new file mode 100644 index 00000000..66606986 --- /dev/null +++ b/MinuteMate/back/docker-compose.yml @@ -0,0 +1,12 @@ +services: + chat_backend: + build: + context: ./ + dockerfile: Dockerfile + ports: + - 8000:8000 + environment: + - WEAVIATE_ENDPOINT_URL=$WEAVIATE_ENDPOINT_URL + - WEAVIATE_API_KEY=$WEAVIATE_API_KEY + - OPENAI_API_KEY=$OPENAI_API_KEY + diff --git a/MinuteMate/back/main.py b/MinuteMate/back/main.py index 43bb144a..a1d5adee 100644 --- a/MinuteMate/back/main.py +++ b/MinuteMate/back/main.py @@ -1,199 +1,277 @@ -from fastapi import FastAPI, HTTPException -from pydantic import BaseModel - +### IMPORTS ### import os - -from rake_nltk import Rake - +from dotenv import load_dotenv +from typing import Optional, List +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from pydantic import BaseModel, Field +# import ssl +import logging +logging.basicConfig(level=logging.INFO) import weaviate from weaviate.classes.init import Auth -from weaviate.classes.init import AdditionalConfig -from weaviate.classes.init import Timeout -from weaviate.classes.query import Rerank -from weaviate.classes.query import MetadataQuery - +# from weaviate.classes.query import Rerank, MetadataQuery import openai from openai import OpenAI - - -# Initialize the FastAPI app +from rake_nltk import Rake +import nltk # TODO are we actually using NLTK? +try: + nltk.download('punkt') + nltk.download('punkt_tab') + nltk.download('stopwords') +except Exception as e: + print(f"Error downloading NLTK resources: {e}") + +### CONSTANTS ### +SEARCH_TYPES = { + 'keyword': 'bm25', + 'vector': 'near_vector', + 'hybrid': 'hybrid' + } +DEFAULT_WEAVIATE_COLLECTION_NAME = 'MeetingDocument' +DEFAULT_OPENAI_EMBEDDING_MODEL = 'text-embedding-3-small' +DEFAULT_OPENAI_GENERATIVE_MODEL = 'gpt-4o' + + +### COMMON RESOURCES AND INITIALIZATION ### +# LOGGING +# https://docs.python.org/3/library/logging.html +logger = logging.getLogger(__name__) + +# API app = FastAPI( - title="MinuteMate Propmpt & Response API", - description="A simple API that takes a text prompt uses ", - version="1.0.0" - + title="MinuteMate Prompt & Response API", + description="An AI-powered API for processing meeting-related prompts", + version="1.0.0" +).add_middleware( + CORSMiddleware, + allow_origins=["*"], # Allows all origins + allow_credentials=True, + allow_methods=["*"], # Allows all methods + allow_headers=["*"], # Allows all headers ) -# Define the request schema -class PromptRequest(BaseModel): - user_prompt_text: str +# ENVIRONMENTAL VARIABLES +load_dotenv() -# Define the response schema -class PromptResponse(BaseModel): - generated_response: str - error_code : int - -# Takes a prompt from the front end, processes the prompt -# using NLP tools, embedding services, and generative services -# and finally returns the prompt response -def process_prompt(prompt_request: PromptRequest) -> PromptResponse: - - ### 0 - ENVIRONMENT AND CONFIGURATION ### - - # Update environment variables - # not sure if this works - # TODO test and/or look for alternatives - import os - - # Set API keys, endpoint URLs, model versions, and configurations - # Embedding and Generative Models - OPENAI_API_KEY = os.getenv('OPENAI_API_KEY') - # OPENAI_BASE_URL = os.getenv('OPENAI_BASE_URL') - OPENAI_EMBEDDING_URL = os.getenv('OPENAI_EMBEDDING_URL') - OPENAI_GENERATION_URL = os.getenv('OPENAI_GENERATION_URL') - EMBEDDING_MODEL = 'text-embedding-3-small' - ENCODING_FORMAT = 'float' - RESPONDING_GENERATIVE_MODEL = 'gpt-4o' - # TRUSTSAFETY_GENERATIVE_MODEL = llama on modal, probably, but can't be too - - # API key, endpoint URL, and target collection(s) - # for Weaviate vector database - WEAVIATE_URL = os.environ['WEAVIATE_URL'] - WEAVIATE_API_KEY = os.environ['WEAVIATE_API_KEY'] - WEAVIATE_TARGET_COLLECTION = 'MeetingDocument' - # WEAVIATE_TARGET_COLLECTION = "VERBA_Embedding_text_embedding_3_small" +WEAVIATE_ENDPOINT_URL = os.environ.get('WEAVIATE_ENDPOINT_URL') +WEAVIATE_API_KEY = os.environ.get('WEAVIATE_API_KEY') +OPENAI_API_KEY = os.environ.get('OPENAI_API_KEY') +# VECTOR DATABASE +weaviate_client = weaviate.connect_to_weaviate_cloud( + cluster_url=WEAVIATE_ENDPOINT_URL, + auth_credentials=Auth.api_key(WEAVIATE_API_KEY), + additional_config=weaviate.classes.init.AdditionalConfig( + timeout=weaviate.classes.init.Timeout(init=10, query=30))) +# EMBEDDING SERVICE - ### 1- INITIAL TRUST AND SAFETY CHECK ### - # TODO add initial trust & safety check here - # If trust and safety check fails, return the error immediately +embedding_client = OpenAI( + api_key = OPENAI_API_KEY +) +# GENERATIVE SERVICE +generative_client = OpenAI( + api_key = OPENAI_API_KEY +) +# NATURAL LANGUAGE PROCESSING TOOLS +rake = Rake() - ### 2- INFORMATION RETRIEVAL ### - # Set RAG search type - SEARCH_TYPE = 'keyword' - # SEARCH_TYPE = 'vector' - # SEARCH_TYPE = 'hybrid' +### REQUEST AND RESPONSE MODELS +class PromptRequest(BaseModel): + user_prompt_text: str = Field(..., min_length=1, max_length=1000) + +# TODO does this need to be based on BaseModel? +# TODO Can we just return a list of 3-tuples instead? +class ContextSegment(BaseModel): + chunk_id: int + content: str + score: Optional[float] = None + +class PromptResponse(BaseModel): + generated_response: str + context_segments: List[ContextSegment] = [] + keywords: List[str] = [] + error_code: int = 0 + + +# Establish secure socket layer? +# TODO - figure out how this works, +# TODO - ensure that this does work +# TODO - ensure it's in the right place +# try: +# _create_unverified_https_context = ssl._create_unverified_context +# except AttributeError: +# pass +# else: +# ssl._create_default_https_context = _create_unverified_https_context + +# TODO clarify how this works +# TODO clarify whether an object initialization is necessary & eliminate if possible +def extract_keywords(text: str) -> List[str]: + """Extract keywords using RAKE""" + try: + # Extract keywords, return ranked phrases + rake.extract_keywords_from_text(text) + return rake.get_ranked_phrases()[:3] + + except Exception as e: + logger.error(f"Keyword extraction error: {e}") + return [] + +def search_weaviate( + query: str, + search_type: str, + target_collection_name : str = DEFAULT_WEAVIATE_COLLECTION_NAME) -> List[ContextSegment]: + + """ + Search Weaviate database + """ - # Establish connection with Weaviate server - # https://weaviate.io/developers/weaviate - weaviate_client = weaviate.connect_to_weaviate_cloud( - cluster_url=WEAVIATE_URL, - auth_credentials=Auth.api_key(WEAVIATE_API_KEY), - ) - # Look up the appropriate Weviate database collection - db_collection = weaviate_client.collections.get(WEAVIATE_TARGET_COLLECTION) - db_response = None - - # Extract keywords and query database - # TODO - finish and test - if(SEARCH_TYPE == 'keyword'): + # Search Weaviate + try: + collection = weaviate_client.collections.get(target_collection_name) - rake = Rake() - rake.extract_keywords_from_text(prompt_request.user_prompt_text) - keywords = rake.get_ranked_phrases()[:3] - db_response = db_collection.query.bm25( - query=",".join(keywords), - limit=5, - # rerank=Rerank( - # prop="content", - # query="meeting" - # ), - # return_metadata=MetadataQuery(score=True) - ) - - # Vectorize the prompt and query the database - # TODO - test - elif(SEARCH_TYPE == 'vector'): + if search_type == 'keyword': + keywords = extract_keywords(query) + results = collection.query.bm25( + query=",".join(keywords), + limit=5 + ) + # print(keywords) + elif search_type == 'vector': - # Set API Key. Not necessary if you have an - # OPENAI_API_KEY variable in your environment - openai.api_key = OPENAI_API_KEY - embedding_client = OpenAI() - - # Vector-embed the prompt - embedding_response = embedding_client.embeddings.create( - model = EMBEDDING_MODEL, - input = prompt_request.user_prompt_text, - encoding_format = ENCODING_FORMAT - ) - - # Extract the vector embeddings list[float] from the embedding response - query_vector = embedding_response.data[0].embedding + embedding = embedding_client.embeddings.create( + model=DEFAULT_OPENAI_EMBEDDING_MODEL, + input=query + ).data[0].embedding + + results = collection.query.near_vector( + near_vector=embedding, + limit=5 + ) + + else: + raise ValueError(f"Unsupported search type: {search_type}") + + context_segments = [ + ContextSegment( + chunk_id=int(item.properties.get('chunk_id', 0)), + content=item.properties.get('content', ''), + score=getattr(item.metadata, 'distance', None) + ) for item in results.objects + ] - # Send vector query to database and get response - db_response = db_collection.query.near_vector( - near_vector=query_vector, - limit=10, - return_metadata=MetadataQuery(distance=True) - ) + #TODO do we need to return keywords here? + return context_segments, keywords + + except Exception as e: + logger.error(f"Weaviate search error: {e}") + return [] - #TODO support this - #elif(SEARCH_TYPE == 'hybrid'): +def openai_generate_with_context( + prompt: str, + context: List[ContextSegment] = [], + model = DEFAULT_OPENAI_GENERATIVE_MODEL) -> str: + + """Generate response using OpenAI""" + # Merge RAG context + # TODO see if generative models supports specific tokens + # for identifying context segments & if so implement them + context_text = "\n".join([ + f"\n{seg.content}" + for seg in context + ]) - else: - #No RAG search - db_response = None + try: + # Call the generative service to get a chat response + # Provide initial chat prompt as well as added context + response = generative_client.chat.completions.create( + model = model, + messages=[ + { + "role": "system", + "content": f"Use this context if relevant: {context_text}" + }, + { + "role": "user", + "content": prompt + } + ] + ) + return response.choices[0].message.content + + except Exception as e: + logger.error(f"OpenAI generation error: {e}") + return "I'm sorry, but I couldn't generate a response." - # Extract items from database response - # and aggregate into a single string - db_response_text = "" - for item in db_response.objects: - segment = '\n\n' - db_response_text += segment - db_response_text += item.properties.get('content') +def main_response(prompt_request: PromptRequest) -> PromptResponse: + """Main method to process prompts""" - ### 3 - RESPONSE GENERATION ### + ### 1- INITIAL TRUST AND SAFETY CHECK ### + # TODO add initial trust & safety check here + # If trust and safety check fails, return the error immediately + + generated_response = None + ### 2- INFORMATION RETRIEVAL ### + try: + # Search for relevant context + context_segments, keywords = search_weaviate(prompt_request.user_prompt_text) + except Exception as e: + logger.error(f"Response information retrieval error: {e}") + return PromptResponse( + generated_response="An error occurred while processing your request.", + error_code=500) + + ### 3 - RESPONSE GENERATION ### # Generate response to user with OpenAI generative model # https://platform.openai.com/docs/api-reference/chat/create - openai.api_key = OPENAI_API_KEY - generation_client = OpenAI() - generated_response_text = generation_client.chat.completions.create( - model="gpt-4o", - messages=[ - { - "role": "system", - "content": f"You are a helpful assistant who uses this context if appropriate: {db_response_text}" - }, - { - "role": "user", - "content": prompt_request.user_prompt_text - } - ] - ) + + try: + generated_response = openai_generate_with_context( + prompt_request.user_prompt_text, + context_segments, + model = DEFAULT_OPENAI_GENERATIVE_MODEL) + + except Exception as e: + logger.error(f"Response content generation error: {e}") + return PromptResponse( + generated_response="An error occurred while processing your request.", + error_code=500) + ### 4 - FINAL TRUST AND SAFETY CHECK ### # TODO add final trust & safety check here # If trust and safety check fails, return an error - + ### 5 - BUILD & RETURN RESPONSE OBJECT ### # Return chat response to API layer # to be passed along to frontend - prompt_response = PromptResponse() - prompt_response.generated_response = generated_response_text - return prompt_request - - - - + #TODO there aren't necessarily going to be keywords + return PromptResponse( + generated_response=generated_response, + context_segments=context_segments, + keywords = keywords, + error_code=0 + ) -# API endpoint +# API Endpoint @app.post("/process-prompt", response_model=PromptResponse) async def process_prompt_endpoint(prompt_request: PromptRequest): - """ - Process the prompt and return the response - """ - try: - prompt_response = process_prompt(prompt_request) - return PromptResponse(result=prompt_response) - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - \ No newline at end of file + """Process user prompt and return response""" + return main_response(prompt_request) + + +# Cleanup on shutdown +@app.on_event("shutdown") +async def shutdown_event(): + """Close Weaviate connection on app shutdown""" + weaviate_client.close() \ No newline at end of file diff --git a/MinuteMate/back/main02.py b/MinuteMate/back/main02.py deleted file mode 100644 index f87caef7..00000000 --- a/MinuteMate/back/main02.py +++ /dev/null @@ -1,242 +0,0 @@ -import os -import logging -from typing import Optional, List - -from fastapi import FastAPI, HTTPException -from fastapi.middleware.cors import CORSMiddleware -from pydantic import BaseModel, Field - -import weaviate -from weaviate.classes.init import Auth -from weaviate.classes.query import Rerank, MetadataQuery - -import openai -from openai import OpenAI - - -from rake_nltk import Rake -from dotenv import load_dotenv - -import nltk -import ssl - -try: - _create_unverified_https_context = ssl._create_unverified_context -except AttributeError: - pass -else: - ssl._create_default_https_context = _create_unverified_https_context - - -try: - nltk.download('punkt') - nltk.download('punkt_tab') - nltk.download('stopwords') -except Exception as e: - print(f"Error downloading NLTK resources: {e}") - - -# Configure logging -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - -load_dotenv() - - -# Initialize the FastAPI app -app = FastAPI( - title="MinuteMate Prompt & Response API", - description="An AI-powered API for processing meeting-related prompts", - version="1.0.0" -) - -# Add CORS middleware -app.add_middleware( - CORSMiddleware, - allow_origins=["*"], # Allows all origins - allow_credentials=True, - allow_methods=["*"], # Allows all methods - allow_headers=["*"], # Allows all headers -) - -# Define request and response models -class PromptRequest(BaseModel): - user_prompt_text: str = Field(..., min_length=1, max_length=1000) - -class ContextSegment(BaseModel): - chunk_id: int - content: str - score: Optional[float] = None - -class PromptResponse(BaseModel): - generated_response: str - context_segments: List[ContextSegment] = [] - keywords: List[str] = [] - error_code: int = 0 - -class WeaviateConfig: - """Configuration for Weaviate connection and querying""" - SEARCH_TYPES = { - 'keyword': 'bm25', - 'vector': 'near_vector', - 'hybrid': 'hybrid' - } - - @classmethod - def get_weaviate_client(cls, url: str, api_key: str): - """Establish Weaviate connection""" - try: - return weaviate.connect_to_weaviate_cloud( - cluster_url=url, - auth_credentials=Auth.api_key(api_key), - additional_config=weaviate.classes.init.AdditionalConfig( - timeout=weaviate.classes.init.Timeout(init=10, query=30) - ) - ) - except Exception as e: - logger.error(f"Weaviate connection error: {e}") - raise - -class PromptProcessor: - """Main class for processing user prompts""" - def __init__(self): - # Load environment variables - self.load_env_vars() - - # Initialize clients - self.weaviate_client = WeaviateConfig.get_weaviate_client( - self.WEAVIATE_URL, - self.WEAVIATE_API_KEY - ) - self.openai_client = OpenAI(api_key=self.OPENAI_API_KEY) - - def load_env_vars(self): - """Load and validate environment variables""" - required_vars = [ - 'OPENAI_API_KEY', - 'WEAVIATE_URL', - 'WEAVIATE_API_KEY' - ] - - for var in required_vars: - value = os.getenv(var) - print(f"Loading {var}: {value}") - if not value: - raise ValueError(f"Missing environment variable: {var}") - setattr(self, var, value) - - def extract_keywords(self, text: str) -> List[str]: - """Extract keywords using RAKE""" - try: - - rake = Rake() - rake.extract_keywords_from_text(text) - return rake.get_ranked_phrases()[:3] - except Exception as e: - logger.error(f"Keyword extraction error: {e}") - return [] - - def search_weaviate(self, query: str, search_type: str = 'keyword') -> List[ContextSegment]: - """Perform search in Weaviate database""" - try: - collection = self.weaviate_client.collections.get('MeetingDocument') - - if search_type == 'keyword': - keywords = self.extract_keywords(query) - results = collection.query.bm25( - query=",".join(keywords), - limit=5 - ) - print(keywords) - elif search_type == 'vector': - embedding = self.openai_client.embeddings.create( - model='text-embedding-3-small', - input=query - ).data[0].embedding - - results = collection.query.near_vector( - near_vector=embedding, - limit=5 - ) - else: - raise ValueError(f"Unsupported search type: {search_type}") - - context_segments = [ - ContextSegment( - chunk_id=int(item.properties.get('chunk_id', 0)), - content=item.properties.get('content', ''), - score=getattr(item.metadata, 'distance', None) - ) for item in results.objects - ] - return context_segments, keywords - except Exception as e: - logger.error(f"Weaviate search error: {e}") - return [] - - def generate_response(self, prompt: str, context_segments: List[ContextSegment]) -> str: - """Generate response using OpenAI""" - context_text = "\n".join([ - f"\n{seg.content}" - for seg in context_segments - ]) - - try: - response = self.openai_client.chat.completions.create( - model="gpt-4o", - messages=[ - { - "role": "system", - "content": f"Use this context if relevant: {context_text}" - }, - { - "role": "user", - "content": prompt - } - ] - ) - return response.choices[0].message.content - except Exception as e: - logger.error(f"OpenAI generation error: {e}") - return "I'm sorry, but I couldn't generate a response." - - def process_prompt(self, prompt_request: PromptRequest) -> PromptResponse: - """Main method to process user prompt""" - try: - # Search for relevant context - context_segments, keywords = self.search_weaviate(prompt_request.user_prompt_text) - - # Generate response - generated_response = self.generate_response( - prompt_request.user_prompt_text, - context_segments - ) - - return PromptResponse( - generated_response=generated_response, - context_segments=context_segments, - keywords = keywords, - error_code=0 - ) - - except Exception as e: - logger.error(f"Prompt processing error: {e}") - return PromptResponse( - generated_response="An error occurred while processing your request.", - error_code=500 - ) - -# Initialize processor -processor = PromptProcessor() - -# API Endpoint -@app.post("/process-prompt", response_model=PromptResponse) -async def process_prompt_endpoint(prompt_request: PromptRequest): - """Process user prompt and return response""" - return processor.process_prompt(prompt_request) - - -# Cleanup on shutdown -@app.on_event("shutdown") -async def shutdown_event(): - """Close Weaviate connection on app shutdown""" - processor.weaviate_client.close() \ No newline at end of file diff --git a/MinuteMate/back/main_functional.py b/MinuteMate/back/main_functional.py new file mode 100644 index 00000000..bb871a78 --- /dev/null +++ b/MinuteMate/back/main_functional.py @@ -0,0 +1,199 @@ +from fastapi import FastAPI, HTTPException +from pydantic import BaseModel + +import os + +from rake_nltk import Rake + +import weaviate +from weaviate.classes.init import Auth +from weaviate.classes.init import AdditionalConfig +from weaviate.classes.init import Timeout +from weaviate.classes.query import Rerank +from weaviate.classes.query import MetadataQuery + +import openai +from openai import OpenAI + + +# Initialize the FastAPI app +app = FastAPI( + title="MinuteMate Propmpt & Response API", + description="A simple API that takes a text prompt uses ", + version="1.0.0" + +) + +# Define the request schema +class PromptRequest(BaseModel): + user_prompt_text: str + +# Define the response schema +class PromptResponse(BaseModel): + generated_response: str + error_code : int + +# Takes a prompt from the front end, processes the prompt +# using NLP tools, embedding services, and generative services +# and finally returns the prompt response +def process_prompt(prompt_request: PromptRequest) -> PromptResponse: + + ### 0 - ENVIRONMENT AND CONFIGURATION ### + + # Update environment variables + # not sure if this works + # TODO test and/or look for alternatives + import os + + # Set API keys, endpoint URLs, model versions, and configurations + # Embedding and Generative Models + OPENAI_API_KEY = os.getenv('OPENAI_API_KEY') + # OPENAI_BASE_URL = os.getenv('OPENAI_BASE_URL') + OPENAI_EMBEDDING_URL = os.getenv('OPENAI_EMBEDDING_URL') + OPENAI_GENERATION_URL = os.getenv('OPENAI_GENERATION_URL') + EMBEDDING_MODEL = 'text-embedding-3-small' + ENCODING_FORMAT = 'float' + RESPONDING_GENERATIVE_MODEL = 'gpt-4o' + # TRUSTSAFETY_GENERATIVE_MODEL = llama on modal, probably, but can't be too + + # API key, endpoint URL, and target collection(s) + # for Weaviate vector database + WEAVIATE_URL = os.environ['WEAVIATE_URL'] + WEAVIATE_API_KEY = os.environ['WEAVIATE_API_KEY'] + WEAVIATE_TARGET_COLLECTION = 'MeetingDocument' + # WEAVIATE_TARGET_COLLECTION = "VERBA_Embedding_text_embedding_3_small" + + + + ### 1- INITIAL TRUST AND SAFETY CHECK ### + # TODO add initial trust & safety check here + # If trust and safety check fails, return the error immediately + + + + ### 2- INFORMATION RETRIEVAL ### + + # Set RAG search type + SEARCH_TYPE = 'keyword' + # SEARCH_TYPE = 'vector' + # SEARCH_TYPE = 'hybrid' + + # Establish connection with Weaviate server + # https://weaviate.io/developers/weaviate + weaviate_client = weaviate.connect_to_weaviate_cloud( + cluster_url=WEAVIATE_URL, + auth_credentials=Auth.api_key(WEAVIATE_API_KEY), + ) + # Look up the appropriate Weviate database collection + db_collection = weaviate_client.collections.get(WEAVIATE_TARGET_COLLECTION) + db_response = None + + # Extract keywords and query database + # TODO - finish and test + if(SEARCH_TYPE == 'keyword'): + + rake = Rake() + rake.extract_keywords_from_text(prompt_request.user_prompt_text) + keywords = rake.get_ranked_phrases()[:3] + db_response = db_collection.query.bm25( + query=",".join(keywords), + limit=5, + # rerank=Rerank( + # prop="content", + # query="meeting" + # ), + # return_metadata=MetadataQuery(score=True) + ) + + # Vectorize the prompt and query the database + # TODO - test + elif(SEARCH_TYPE == 'vector'): + + + # Set API Key. Not necessary if you have an + # OPENAI_API_KEY variable in your environment + openai.api_key = OPENAI_API_KEY + embedding_client = OpenAI() + + # Vector-embed the prompt + embedding_response = embedding_client.embeddings.create( + model = EMBEDDING_MODEL, + input = prompt_request.user_prompt_text, + encoding_format = ENCODING_FORMAT + ) + + # Extract the vector embeddings list[float] from the embedding response + query_vector = embedding_response.data[0].embedding + + # Send vector query to database and get response + db_response = db_collection.query.near_vector( + near_vector=query_vector, + limit=10, + return_metadata=MetadataQuery(distance=True) + ) + + #TODO support this + #elif(SEARCH_TYPE == 'hybrid'): + + + else: + #No RAG search + db_response = None + + # Extract items from database response + # and aggregate into a single string + db_response_text = "" + for item in db_response.objects: + segment = '\n\n' + db_response_text += segment + db_response_text += item.properties.get('content') + + + ### 3 - RESPONSE GENERATION ### + + # Generate response to user with OpenAI generative model + # https://platform.openai.com/docs/api-reference/chat/create + openai.api_key = OPENAI_API_KEY + generation_client = OpenAI() + generated_response_text = generation_client.chat.completions.create( + model="gpt-4o", + messages=[ + { + "role": "system", + "content": f"You are a helpful assistant who uses this context if appropriate: {db_response_text}" + }, + { + "role": "user", + "content": prompt_request.user_prompt_text + } + ] + ) + + ### 4 - FINAL TRUST AND SAFETY CHECK ### + # TODO add final trust & safety check here + # If trust and safety check fails, return an error + + + ### 5 - BUILD & RETURN RESPONSE OBJECT ### + # Return chat response to API layer + # to be passed along to frontend + prompt_response = PromptResponse() + prompt_response.generated_response = generated_response_text + return prompt_response + + + + + +# API endpoint +@app.post("/process-prompt", response_model=PromptResponse) +async def process_prompt_endpoint(prompt_request: PromptRequest): + """ + Process the prompt and return the response + """ + try: + prompt_response = process_prompt(prompt_request) + return PromptResponse(result=prompt_response) + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + \ No newline at end of file diff --git a/MinuteMate/back/methods/__init__.py b/MinuteMate/back/methods/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/MinuteMate/back/requirements.txt b/MinuteMate/back/requirements.txt index 1cfda1c3..0ea2b09e 100644 --- a/MinuteMate/back/requirements.txt +++ b/MinuteMate/back/requirements.txt @@ -1,18 +1,30 @@ +# SSL - mainly for NLTK at this point +# ssl #==1.16 + + +# NLP stuff +rake_nltk #==1.0.6 +nltk #==3.9.1 + + + + # Necessary for the API -fastapi[standard] +fastapi[standard] #==0.115.5 # Necessary for the API # Handles data validation, parsing, error handling, conversions, type hints -pydantic +pydantic #==2.10.1 # Necessary for web stuff -uvicorn +uvicorn[standard] #==0.32.1 # vector database -weaviate-client==4.7.1 +weaviate-client #==4.7.1 # environmental variables such as API keys and endpoint URLs -python-dotenv==1.0.0 +python-dotenv #==1.0.0 # for running queries -openai==1.54.3 \ No newline at end of file +openai #==1.54.3 + diff --git a/MinuteMate/front/app.py b/MinuteMate/front/app.py index 4e89685b..3e7813c5 100644 --- a/MinuteMate/front/app.py +++ b/MinuteMate/front/app.py @@ -1,4 +1,4 @@ -from st_weaviate_connection import WeaviateConnection +# from st_weaviate_connection import WeaviateConnection import streamlit as st import time import os @@ -20,22 +20,22 @@ # ) -load_dotenv() +# load_dotenv() -ENV_VARS = ["WEAVIATE_URL", "WEAVIATE_API_KEY", "OPENAI_KEY"] +# ENV_VARS = ["WEAVIATE_URL", "WEAVIATE_API_KEY", "OPENAI_KEY"] NUM_IMAGES_PER_ROW = 3 -def get_env_vars(env_vars: list) -> dict: - """Retrieve environment variables.""" - env_vars_dict = {} - for var in ENV_VARS: - value = os.environ.get(var, "") - if value == "": - st.error(f"{var} not set", icon="🚨") - st.stop() - env_vars_dict[var] = value +# def get_env_vars(env_vars: list) -> dict: +# """Retrieve environment variables.""" +# env_vars_dict = {} +# for var in ENV_VARS: +# value = os.environ.get(var, "") +# if value == "": +# st.error(f"{var} not set", icon="🚨") +# st.stop() +# env_vars_dict[var] = value - return env_vars_dict +# return env_vars_dict def display_chat_messages() -> None: @@ -50,20 +50,20 @@ def display_chat_messages() -> None: if i + j < len(message["images"]): cols[j].image(message["images"][i + j], width=200) -env_vars = get_env_vars(ENV_VARS) -url = env_vars["WEAVIATE_URL"] -api_key = env_vars["WEAVIATE_API_KEY"] -openai_key = env_vars["OPENAI_KEY"] +# env_vars = get_env_vars(ENV_VARS) +# url = env_vars["WEAVIATE_URL"] +# api_key = env_vars["WEAVIATE_API_KEY"] +# openai_key = env_vars["OPENAI_KEY"] st.title("📝 Minute Mate") -conn = st.connection( - "weaviate", - type=WeaviateConnection, - url=url, - api_key=api_key, - additional_headers={"X-OpenAI-Api-Key": openai_key}, -) +# conn = st.connection( +# "weaviate", +# type=WeaviateConnection, +# url=url, +# api_key=api_key, +# additional_headers={"X-OpenAI-Api-Key": openai_key}, +# ) with st.sidebar: