norjala commited on
Commit
79f1ae1
1 Parent(s): 5cb128c

Updated code

Browse files
Files changed (4) hide show
  1. Dockerfile +20 -6
  2. app.py +97 -109
  3. chainlit.md +1 -1
  4. requirements.txt +8 -10
Dockerfile CHANGED
@@ -1,13 +1,27 @@
1
  FROM python:3.9
 
 
 
 
2
  RUN useradd -m -u 1000 user
3
  USER user
 
4
  ENV HOME=/home/user \
5
  PATH=/home/user/.local/bin:$PATH
 
6
  WORKDIR $HOME/app
 
 
 
 
 
 
 
 
 
 
 
7
  COPY --chown=user . $HOME/app
8
- COPY ./requirements.txt ~/app/requirements.txt
9
- RUN pip install --upgrade pip
10
- RUN pip install -r requirements.txt
11
- RUN mkdir -p $HOME/app/data/vectorstore && chown -R user:user $HOME/app/data
12
- COPY . .
13
- CMD ["chainlit", "run", "app.py", "--port", "7860"]
 
1
  FROM python:3.9
2
+
3
+ RUN pip install --upgrade pip
4
+
5
+ # Create a user and set up the environment
6
  RUN useradd -m -u 1000 user
7
  USER user
8
+
9
  ENV HOME=/home/user \
10
  PATH=/home/user/.local/bin:$PATH
11
+
12
  WORKDIR $HOME/app
13
+
14
+ # Add this line to copy the data directory
15
+ COPY ./data /home/user/app/data
16
+
17
+ # Copy only requirements.txt first to leverage Docker cache
18
+ COPY --chown=user requirements.txt $HOME/app/requirements.txt
19
+
20
+ # Install dependencies
21
+ RUN pip install --no-cache-dir -r requirements.txt
22
+
23
+ # Copy the rest of the application code
24
  COPY --chown=user . $HOME/app
25
+
26
+ # Run the application
27
+ CMD ["chainlit", "run", "app.py", "--port", "7860"]
 
 
 
app.py CHANGED
@@ -1,144 +1,132 @@
 
1
  import os
2
- import chainlit as cl
 
3
  import openai
 
 
 
 
 
 
4
  import tiktoken
5
- from dotenv import load_dotenv
 
 
 
 
 
 
 
6
  from operator import itemgetter
7
- from langchain_community.document_loaders import PyMuPDFLoader
8
- from langchain_text_splitters import RecursiveCharacterTextSplitter
9
- from langchain_community.vectorstores import FAISS
10
- from langchain_openai.embeddings import OpenAIEmbeddings
11
- from langchain_core.prompts import PromptTemplate
12
- from langchain_core.runnables import RunnableConfig, RunnablePassthrough
13
- from langchain_openai import ChatOpenAI
14
-
15
- # Load environment variables from .env file
16
  load_dotenv()
17
 
18
- # Environment variables
19
- openai.api_key = os.environ.get("OPENAI_API_KEY")
20
- if not openai.api_key:
21
- raise ValueError("OPENAI_API_KEY environment variable not set")
 
22
 
23
- # Set vector store path
24
- VECTOR_STORE_PATH = "./data/vectorstore"
 
25
 
26
- # Document loader
27
- document_loader = PyMuPDFLoader("./data/Airbnb-10k.pdf")
28
- documents = document_loader.load()
29
 
30
  def tiktoken_len(text):
31
  tokens = tiktoken.encoding_for_model("gpt-4o").encode(text)
32
  return len(tokens)
33
 
34
- # Load embeddings
35
- openai_embeddings = OpenAIEmbeddings(model="text-embedding-ada-002")
36
-
37
- # Text splitter
38
- text_splitter = RecursiveCharacterTextSplitter(chunk_size=600, chunk_overlap=100)
39
- split_documents = text_splitter.split_documents(documents)
40
-
41
- # Create or load vector store
42
- if os.path.exists(os.path.join(VECTOR_STORE_PATH, "index.faiss")):
43
- print("Loading existing vectorstore from disk.")
44
- vectorstore = FAISS.load_local(
45
- VECTOR_STORE_PATH,
46
- openai_embeddings,
47
- allow_dangerous_deserialization=True
48
- )
49
- retriever = vectorstore.as_retriever()
50
- print("Loaded Vectorstore")
51
- else:
52
- print("Indexing Files")
53
- os.makedirs(VECTOR_STORE_PATH, exist_ok=True)
54
- vectorstore = FAISS.from_documents(split_documents[:32], openai_embeddings)
55
- for i in range(32, len(split_documents), 32):
56
- vectorstore.add_documents(split_documents[i:i+32])
57
- vectorstore.save_local(VECTOR_STORE_PATH)
58
- print("Vectorstore created and documents indexed.")
59
-
60
- # Create retriever
61
- retriever = vectorstore.as_retriever()
62
 
63
- # Define the prompt template
64
- RAG_PROMPT_TEMPLATE = """\
65
- system
66
- You are a helpful assistant. You answer user questions based on provided context. If you can't answer the question with the provided context, say you don't know.
67
 
68
- user
69
- User Query:
70
- {query}
71
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  Context:
73
  {context}
74
-
75
- assistant
76
  """
77
 
78
- rag_prompt = PromptTemplate.from_template(RAG_PROMPT_TEMPLATE)
 
 
 
79
 
80
- # Create ChatOpenAI instance
81
- llm = ChatOpenAI(model_name="gpt-4o", temperature=0)
 
 
 
82
 
83
  retrieval_augmented_qa_chain = (
 
 
 
84
  {"context": itemgetter("question") | retriever, "question": itemgetter("question")}
 
 
85
  | RunnablePassthrough.assign(context=itemgetter("context"))
86
- | {"response": rag_prompt | llm, "context": itemgetter("context")}
 
 
 
87
  )
88
 
89
- # Chainlit
90
- @cl.on_chat_start
 
91
  async def start_chat():
92
- """
93
- This function will be called at the start of every user session.
94
- We will build our LCEL RAG chain here and store it in the user session.
95
- The user session is a dictionary that is unique to each user session and is stored in the memory of the server.
96
- """
97
  settings = {
98
  "model": "gpt-4o",
99
  "temperature": 0,
100
- "max_tokens": 64,
101
  "top_p": 1,
102
  "frequency_penalty": 0,
103
  "presence_penalty": 0,
104
  }
105
- try:
106
- lcel_rag_chain = ({"context": itemgetter("query") | retriever, "query": itemgetter("query")}
107
- | rag_prompt | llm)
108
-
109
- cl.user_session.set("lcel_rag_chain", lcel_rag_chain)
110
- print("Chat session started and LCEL RAG chain set.")
111
- except Exception as e:
112
- print(f"Error in start_chat: {e}")
113
-
114
- @cl.on_message
115
- async def main(message: cl.Message):
116
- """
117
- This function will be called every time a message is received from a session.
118
- We will use the LCEL RAG chain to generate a response to the user query.
119
- The LCEL RAG chain is stored in the user session and is unique to each user session - this is why we can access it here.
120
- """
121
- try:
122
- lcel_rag_chain = cl.user_session.get("lcel_rag_chain")
123
- print(f"Received message: {message.content}")
124
- print("Using LCEL RAG chain to generate response...")
125
-
126
- msg = cl.Message(content="")
127
-
128
- async for chunk in lcel_rag_chain.astream(
129
- {"query": message.content},
130
- config=RunnableConfig(callbacks=[cl.LangchainCallbackHandler()]),
131
- ):
132
- chunk_text = chunk.content if hasattr(chunk, 'content') else str(chunk)
133
- print(f"Streaming chunk: {chunk_text}")
134
- await msg.stream_token(chunk_text)
135
-
136
- print("Sending final message...")
137
- await msg.send()
138
- print("Message sent.")
139
- except KeyError as e:
140
- print(f"Session error: {e}")
141
- await message.send("Session error occurred. Please try again.")
142
- except Exception as e:
143
- print(f"Error: {e}")
144
- await message.send("An error occurred. Please try again.")
 
1
+ #-----Import Required Libraries-----#
2
  import os
3
+ from dotenv import load_dotenv
4
+
5
  import openai
6
+ import fitz # PyMuPDF
7
+ import pandas as pd
8
+ from transformers import pipeline
9
+ from qdrant_client import QdrantClient
10
+ from qdrant_client.http import models as qdrant_models
11
+ import chainlit as cl
12
  import tiktoken
13
+
14
+ # Specific imports from the libraries
15
+ from langchain.document_loaders import PyMuPDFLoader
16
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
17
+ from langchain.embeddings import OpenAIEmbeddings #Note: Old import was - from langchain_openai import OpenAIEmbeddings
18
+ from langchain_community.vectorstores import Qdrant
19
+ from langchain.prompts import ChatPromptTemplate
20
+ from langchain.chat_models import ChatOpenAI #Note: Old import was - from langchain_openai import ChatOpenAI
21
  from operator import itemgetter
22
+ from langchain.schema.output_parser import StrOutputParser
23
+ from langchain.schema.runnable import RunnablePassthrough
24
+
25
+ #-----Set Environment Variables-----#
 
 
 
 
 
26
  load_dotenv()
27
 
28
+ # Load environment variables
29
+ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
30
+
31
+ # Initialize OpenAI client after loading the environment variables
32
+ openai.api_key = OPENAI_API_KEY
33
 
34
+ #-----Document Loading and Processing -----#
35
+ loader = PyMuPDFLoader("./data/Airbnb-10k.pdf")
36
+ documents = loader.load()
37
 
38
+ #Note: I changed the loader file path from one that worked locally only to one that worked with Docker. The old file path is loader = PyMuPDFLoader("/Users/sampazar/AIE3-Midterm/data/airbnb_q1_2024.pdf")
 
 
39
 
40
  def tiktoken_len(text):
41
  tokens = tiktoken.encoding_for_model("gpt-4o").encode(text)
42
  return len(tokens)
43
 
44
+ text_splitter = RecursiveCharacterTextSplitter(
45
+ chunk_size=500,
46
+ chunk_overlap=100,
47
+ length_function = tiktoken_len
48
+ )
49
+
50
+ split_chunks = text_splitter.split_documents(documents)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
+ #-----Embedding and Vector Store Setup-----#
 
 
 
53
 
54
+ # Load OpenAI Embeddings Model
55
+ embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
 
56
 
57
+ # Creating a Qdrant Vector Store
58
+ qdrant_vector_store = Qdrant.from_documents(
59
+ split_chunks,
60
+ embeddings,
61
+ location=":memory:",
62
+ collection_name="Airbnb_Q1_2024",
63
+ )
64
+
65
+ # Create a Retriever
66
+ retriever = qdrant_vector_store.as_retriever()
67
+
68
+ #-----Prompt Template and Language Model Setup-----#
69
+ # Define the prompt template
70
+ template = """Answer the question based only on the following context. If you cannot answer the question with the context, please respond with 'I don't know':
71
  Context:
72
  {context}
73
+ Question:
74
+ {question}
75
  """
76
 
77
+ prompt = ChatPromptTemplate.from_template(template)
78
+
79
+ # Define the primary LLM
80
+ primary_llm = ChatOpenAI(model_name="gpt-4o", temperature=0)
81
 
82
+ #-----Creating a Retrieval Augmented Generation (RAG) Chain-----#
83
+ # The RAG chain:
84
+ # (1) Takes the user question and retrieves relevant context,
85
+ # (2) Passes the context through unchanged,
86
+ # (3) Formats the prompt with context and question, then send it to the LLM to generate a response
87
 
88
  retrieval_augmented_qa_chain = (
89
+ # INVOKE CHAIN WITH: {"question" : "<>"}
90
+ # "question" : populated by getting the value of the "question" key
91
+ # "context" : populated by getting the value of the "question" key and chaining it into the base_retriever
92
  {"context": itemgetter("question") | retriever, "question": itemgetter("question")}
93
+ # "context" : is assigned to a RunnablePassthrough object (will not be called or considered in the next step)
94
+ # by getting the value of the "context" key from the previous step
95
  | RunnablePassthrough.assign(context=itemgetter("context"))
96
+ # "response" : the "context" and "question" values are used to format our prompt object and then piped
97
+ # into the LLM and stored in a key called "response"
98
+ # "context" : populated by getting the value of the "context" key from the previous step
99
+ | {"response": prompt | primary_llm, "context": itemgetter("context")}
100
  )
101
 
102
+ #-----Chainlit Integration-----#
103
+ # Sets initial chat settings at the start of a user session
104
+ @cl.on_chat_start
105
  async def start_chat():
 
 
 
 
 
106
  settings = {
107
  "model": "gpt-4o",
108
  "temperature": 0,
109
+ "max_tokens": 500,
110
  "top_p": 1,
111
  "frequency_penalty": 0,
112
  "presence_penalty": 0,
113
  }
114
+ cl.user_session.set("settings", settings)
115
+
116
+ # Processes incoming messages from the user and sends a response through a series of steps:
117
+ # (1) Retrieves the user's settings
118
+ # (2) Invokes the RAG chain with the user's message
119
+ # (3) Extracts the content from the response and sends it back to the user
120
+
121
+ @cl.on_message
122
+ async def handle_message(message: cl.Message):
123
+ settings = cl.user_session.get("settings")
124
+
125
+ response = retrieval_augmented_qa_chain.invoke({"question": message.content})
126
+
127
+
128
+ # Extracting and sending just the content
129
+ content = response["response"].content
130
+ pretty_content = content.strip() # Remove any leading/trailing whitespace
131
+
132
+ await cl.Message(content=pretty_content).send()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
chainlit.md CHANGED
@@ -1,4 +1,4 @@
1
- # Airbnb 10k 2024 RAG Application
2
 
3
  Welcome to the Airbnb 10k 2024 RAG application!
4
 
 
1
+ ## Airbnb 10k 2024 RAG Application
2
 
3
  Welcome to the Airbnb 10k 2024 RAG application!
4
 
requirements.txt CHANGED
@@ -1,14 +1,12 @@
1
- chainlit>=0.7.700
2
  langchain==0.2.5
3
  langchain_community==0.2.5
4
  langchain_core==0.2.9
5
- langchain_huggingface==0.0.3
6
  langchain_text_splitters==0.2.1
7
- langchain_openai==0.1.9
8
- python-dotenv==1.0.0
9
- pymupdf==1.24.5
10
- faiss-cpu==1.8.0.post1
11
- openai==1.35.3
12
- tiktoken<1,>=0.7
13
- uvicorn<0.26.0,>=0.25.0
14
- gunicorn==20.1.0
 
1
+ chainlit==0.7.700
2
  langchain==0.2.5
3
  langchain_community==0.2.5
4
  langchain_core==0.2.9
 
5
  langchain_text_splitters==0.2.1
6
+ python-dotenv==1.0.1
7
+ openai==1.35.3 #Be sure to use the latest version 'pip show openai'
8
+ qdrant-client==1.9.2 #Be sure to use the latest version 'pip show qdrant-client'
9
+ PyMuPDF==1.24.5 #Be sure to use the latest version 'pip show pymupdf'
10
+ tiktoken==0.7.0
11
+ transformers==4.37.0
12
+ pandas==2.0.3