From 0a7860b0fff8daeae9a27f8eddc32a154cffeb0d Mon Sep 17 00:00:00 2001 From: Cole Medin <47287758+coleam00@users.noreply.github.com> Date: Sat, 27 Jul 2024 17:03:04 -0500 Subject: [PATCH] AI Agents Masterclass #5 - RAG --- .gitignore | 2 +- 5-rag-agent/.env.example | 17 +++ 5-rag-agent/local-rag-agent.py | 128 +++++++++++++++++++++++ 5-rag-agent/meeting_notes/2024-07-18.pdf | Bin 0 -> 4309 bytes 5-rag-agent/meeting_notes/2024-07-19.pdf | Bin 0 -> 4395 bytes 5-rag-agent/meeting_notes/2024-07-20.txt | 71 +++++++++++++ 5-rag-agent/meeting_notes/2024-07-21.txt | 71 +++++++++++++ 5-rag-agent/meeting_notes/2024-07-22.txt | 78 ++++++++++++++ 5-rag-agent/requirements.txt | 11 ++ 9 files changed, 377 insertions(+), 1 deletion(-) create mode 100644 5-rag-agent/.env.example create mode 100644 5-rag-agent/local-rag-agent.py create mode 100644 5-rag-agent/meeting_notes/2024-07-18.pdf create mode 100644 5-rag-agent/meeting_notes/2024-07-19.pdf create mode 100644 5-rag-agent/meeting_notes/2024-07-20.txt create mode 100644 5-rag-agent/meeting_notes/2024-07-21.txt create mode 100644 5-rag-agent/meeting_notes/2024-07-22.txt create mode 100644 5-rag-agent/requirements.txt diff --git a/.gitignore b/.gitignore index 6ab75ac..dad015d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ __pycache__ prep -.env +.env \ No newline at end of file diff --git a/5-rag-agent/.env.example b/5-rag-agent/.env.example new file mode 100644 index 0000000..42d19f6 --- /dev/null +++ b/5-rag-agent/.env.example @@ -0,0 +1,17 @@ +# Rename this file to .env once you have filled in the below environment variables! + +# Get your Hugging Face API token here: https://huggingface.co/settings/tokens +# After creating an account with Hugging Face +# Then run huggingface-cli login and enter the token in there too after installing Hugging Face +HUGGINGFACEHUB_API_TOKEN= + +# The local LLM to use from Hugging Face +# A good default to go with here is meta-llama/Meta-Llama-3.1-405B-Instruct +# Note that at the time of recording you need a pro HuggingFace account for the Llama 3.1 models! +# I don't mention that in the video because usually the new models are available to everyone +# after only a couple weeks. Feel free to use meta-llama/Meta-Llama-3-70B-Instruct as an alternative. +LLM_MODEL=meta-llama/Meta-Llama-3.1-405B-Instruct + +# The absolute or relative path to the folder that has all the files for retrieval +# If using a relative path, the path is relative to the directoy containing this .env.example file +DIRECTORY= \ No newline at end of file diff --git a/5-rag-agent/local-rag-agent.py b/5-rag-agent/local-rag-agent.py new file mode 100644 index 0000000..44f0535 --- /dev/null +++ b/5-rag-agent/local-rag-agent.py @@ -0,0 +1,128 @@ +from langchain_chroma import Chroma +from langchain_huggingface import HuggingFacePipeline, HuggingFaceEndpoint, ChatHuggingFace +from langchain_community.embeddings.sentence_transformer import SentenceTransformerEmbeddings +from langchain_core.messages import SystemMessage, AIMessage, HumanMessage +from langchain_community.document_loaders import DirectoryLoader +from langchain_text_splitters import CharacterTextSplitter +from dotenv import load_dotenv +from datetime import datetime +import streamlit as st +import json +import os + +load_dotenv() + +model = os.getenv('LLM_MODEL', 'meta-llama/Meta-Llama-3.1-405B-Instruct') +rag_directory = os.getenv('DIRECTORY', 'meeting_notes') + +@st.cache_resource +def get_local_model(): + return HuggingFaceEndpoint( + repo_id=model, + task="text-generation", + max_new_tokens=1024, + do_sample=False + ) + + # If you want to run the model absolutely locally - VERY resource intense! + # return HuggingFacePipeline.from_model_id( + # model_id=model, + # task="text-generation", + # pipeline_kwargs={ + # "max_new_tokens": 1024, + # "top_k": 50, + # "temperature": 0.4 + # }, + # ) + +llm = get_local_model() + +def load_documents(directory): + # Load the PDF or txt documents from the directory + loader = DirectoryLoader(directory) + documents = loader.load() + + # Split the documents into chunks + text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0) + docs = text_splitter.split_documents(documents) + + return docs + +@st.cache_resource +def get_chroma_instance(): + # Get the documents split into chunks + docs = load_documents(rag_directory) + + # create the open-sourc e embedding function + embedding_function = SentenceTransformerEmbeddings(model_name="all-MiniLM-L6-v2") + + # load it into Chroma + return Chroma.from_documents(docs, embedding_function) + +db = get_chroma_instance() + +def query_documents(question): + """ + Uses RAG to query documents for information to answer a question + + Example call: + + query_documents("What are the action items from the meeting on the 20th?") + Args: + question (str): The question the user asked that might be answerable from the searchable documents + Returns: + str: The list of texts (and their sources) that matched with the question the closest using RAG + """ + similar_docs = db.similarity_search(question, k=5) + docs_formatted = list(map(lambda doc: f"Source: {doc.metadata.get('source', 'NA')}\nContent: {doc.page_content}", similar_docs)) + + return docs_formatted + +def prompt_ai(messages): + # Fetch the relevant documents for the query + user_prompt = messages[-1].content + retrieved_context = query_documents(user_prompt) + formatted_prompt = f"Context for answering the question:\n{retrieved_context}\nQuestion/user input:\n{user_prompt}" + + # Prompt the AI with the latest user message + doc_chatbot = ChatHuggingFace(llm=llm) + ai_response = doc_chatbot.invoke(messages[:-1] + [HumanMessage(content=formatted_prompt)]) + + return ai_response + +def main(): + st.title("Chat with Local Documents") + + # Initialize chat history + if "messages" not in st.session_state: + st.session_state.messages = [ + SystemMessage(content=f"You are a personal assistant who answers questions based on the context provided if the provided context can answer the question. You only provide the answer to the question/user input and nothing else. The current date is: {datetime.now().date()}") + ] + + # Display chat messages from history on app rerun + for message in st.session_state.messages: + message_json = json.loads(message.json()) + message_type = message_json["type"] + if message_type in ["human", "ai", "system"]: + with st.chat_message(message_type): + st.markdown(message_json["content"]) + + # React to user input + # Example question: What's included in the wellness program Emily proposed? + # Example question 2: What were the results of the team survey? + # Example question 3: What was discussed in the meeting on the 22nd? + if prompt := st.chat_input("What questions do you have?"): + # Display user message in chat message container + st.chat_message("user").markdown(prompt) + # Add user message to chat history + st.session_state.messages.append(HumanMessage(content=prompt)) + + # Display assistant response in chat message container + with st.chat_message("assistant"): + ai_response = prompt_ai(st.session_state.messages) + st.markdown(ai_response.content) + + st.session_state.messages.append(ai_response) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/5-rag-agent/meeting_notes/2024-07-18.pdf b/5-rag-agent/meeting_notes/2024-07-18.pdf new file mode 100644 index 0000000000000000000000000000000000000000..fdf0900ebdaf1b10679258a4c6df63e40bb6cfd2 GIT binary patch literal 4309 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 la%`zF;~Z5-;wrPlrSul!_N{cN+jy*KWPLdX6 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