Upload 14 files
Browse files- .gitattributes +2 -0
- app.py +72 -0
- chroma/360ffad6-f56e-46e1-8b3c-e88a73c8a70d/data_level0.bin +3 -0
- chroma/360ffad6-f56e-46e1-8b3c-e88a73c8a70d/header.bin +3 -0
- chroma/360ffad6-f56e-46e1-8b3c-e88a73c8a70d/index_metadata.pickle +3 -0
- chroma/360ffad6-f56e-46e1-8b3c-e88a73c8a70d/length.bin +3 -0
- chroma/360ffad6-f56e-46e1-8b3c-e88a73c8a70d/link_lists.bin +3 -0
- chroma/chroma.sqlite3 +3 -0
- data/goog-10-k-2023 (1).pdf +0 -0
- data/tsla-20231231-gen.pdf +0 -0
- data/uber-10-k-2023.pdf +3 -0
- database.py +103 -0
- embedding_function.py +6 -0
- query.py +61 -0
- requirements.txt +0 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
chroma/chroma.sqlite3 filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
data/uber-10-k-2023.pdf filter=lfs diff=lfs merge=lfs -text
|
app.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
from database import main as populate_db
|
| 3 |
+
from query import query_rag
|
| 4 |
+
from langchain.schema.document import Document
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
# Set the page configuration for Streamlit
|
| 8 |
+
st.set_page_config(page_title='Content Engine')
|
| 9 |
+
|
| 10 |
+
# Function to load and populate the database
|
| 11 |
+
def load_and_populate_database():
|
| 12 |
+
st.header("Database Population")
|
| 13 |
+
st.write("Use this section to populate the database.")
|
| 14 |
+
if st.button("Populate Database"):
|
| 15 |
+
populate_db()
|
| 16 |
+
|
| 17 |
+
st.markdown("<hr style='border: 1px dashed #e0e0e0;'>", unsafe_allow_html=True)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
# Function to display the scrolling chat box
|
| 21 |
+
def display_chat_box():
|
| 22 |
+
st.header("Chat Box")
|
| 23 |
+
st.write("Enter your questions below and get responses generated from the context.")
|
| 24 |
+
|
| 25 |
+
# Setup for holding old messages
|
| 26 |
+
if 'messages' not in st.session_state:
|
| 27 |
+
st.session_state.messages = []
|
| 28 |
+
|
| 29 |
+
# Display all the history in a scrollable chat box
|
| 30 |
+
with st.container():
|
| 31 |
+
for message in st.session_state.messages:
|
| 32 |
+
st.chat_message(message['role']).markdown(message['content'])
|
| 33 |
+
|
| 34 |
+
# Input field for user query
|
| 35 |
+
user_query = st.chat_input("Your Query:", key="query_input")
|
| 36 |
+
|
| 37 |
+
if user_query:
|
| 38 |
+
# Display user prompt
|
| 39 |
+
st.chat_message('user').markdown(user_query)
|
| 40 |
+
|
| 41 |
+
# Store the user prompt in state
|
| 42 |
+
st.session_state.messages.append({'role': 'user', 'content': user_query})
|
| 43 |
+
|
| 44 |
+
# Buffering message while waiting for response
|
| 45 |
+
with st.spinner('Waiting for response...'):
|
| 46 |
+
# Query processing
|
| 47 |
+
response_text = query_rag(user_query)
|
| 48 |
+
|
| 49 |
+
# Show the LLM response
|
| 50 |
+
st.chat_message('assistant').markdown(response_text)
|
| 51 |
+
|
| 52 |
+
# Store the LLM response
|
| 53 |
+
st.session_state.messages.append({'role': 'assistant', 'content': response_text})
|
| 54 |
+
|
| 55 |
+
# Main function to run the Streamlit app
|
| 56 |
+
def main():
|
| 57 |
+
# Title and description
|
| 58 |
+
st.markdown("<h1 style='font-size: 60px; text-align: center;'>🧠Content Engine🤖</h1>", unsafe_allow_html=True)
|
| 59 |
+
# st.markdown("<hr style='border: 1px dashed #e0e0e0; '>", unsafe_allow_html=True)
|
| 60 |
+
# st.write("This system analyzes and compares multiple PDF documents, specifically identifying and highlighting their differences. The system will utilize Retrieval Augmented Generation (RAG) techniques to effectively retrieve, assess, and generate insights from the documents.")
|
| 61 |
+
st.markdown("<hr style='border: 1px dashed #e0e0e0;'>", unsafe_allow_html=True)
|
| 62 |
+
|
| 63 |
+
# To load and populate the database
|
| 64 |
+
# load_and_populate_database()
|
| 65 |
+
|
| 66 |
+
# Display chat box
|
| 67 |
+
display_chat_box()
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
if __name__ == "__main__":
|
| 72 |
+
main()
|
chroma/360ffad6-f56e-46e1-8b3c-e88a73c8a70d/data_level0.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2cfeb32effd3397c9d0aae0a629aa165a2dffe3321940cf0abc5e07b9c5b3466
|
| 3 |
+
size 6424000
|
chroma/360ffad6-f56e-46e1-8b3c-e88a73c8a70d/header.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b56eb1d0439027f400d145f9eff65f500b8c5d1afb597348ce4bc35443d3bd60
|
| 3 |
+
size 100
|
chroma/360ffad6-f56e-46e1-8b3c-e88a73c8a70d/index_metadata.pickle
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:827b4e3f7d764e876cfb1337993477583d5f230f7748ef0352e04963f97f8265
|
| 3 |
+
size 102041
|
chroma/360ffad6-f56e-46e1-8b3c-e88a73c8a70d/length.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5d44dbe88b0e3a2b082ab89d9f9f05498dd3633b19061c8a82c774f2d80a8041
|
| 3 |
+
size 8000
|
chroma/360ffad6-f56e-46e1-8b3c-e88a73c8a70d/link_lists.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:561ae3ffa1abbf2695cb93002ce011c1bf0eca135b6521c77ff6c4782b2ea122
|
| 3 |
+
size 17044
|
chroma/chroma.sqlite3
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:334e569b55a158a70f6b2c1c410c2326719a1f8808215ccc4f21a38fa9cd7cea
|
| 3 |
+
size 19922944
|
data/goog-10-k-2023 (1).pdf
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/tsla-20231231-gen.pdf
ADDED
|
Binary file (985 kB). View file
|
|
|
data/uber-10-k-2023.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9fc1d0c5f2e2095798c99de2d892a9e785b9ed8329090c0c376e39a22e71ad06
|
| 3 |
+
size 1835051
|
database.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
import os
|
| 3 |
+
import shutil
|
| 4 |
+
from langchain_text_splitters import RecursiveCharacterTextSplitter
|
| 5 |
+
from langchain.schema.document import Document
|
| 6 |
+
from embedding_function import get_embedding
|
| 7 |
+
from langchain_community.document_loaders import PyPDFDirectoryLoader
|
| 8 |
+
from langchain_community.vectorstores import Chroma
|
| 9 |
+
|
| 10 |
+
# Define paths for the Chroma database and the data directory
|
| 11 |
+
CHROMA_PATH = "chroma"
|
| 12 |
+
DATA_PATH = "data"
|
| 13 |
+
|
| 14 |
+
def main():
|
| 15 |
+
# Set up command-line argument parsing
|
| 16 |
+
parser = argparse.ArgumentParser()
|
| 17 |
+
parser.add_argument("--reset", action="store_true", help="Reset the database.")
|
| 18 |
+
args = parser.parse_args()
|
| 19 |
+
|
| 20 |
+
# Clear the database if the reset flag is set
|
| 21 |
+
if args.reset:
|
| 22 |
+
print("✨ Clearing Database")
|
| 23 |
+
clear_database()
|
| 24 |
+
|
| 25 |
+
# Load documents, split them into chunks, and add them to Chroma
|
| 26 |
+
documents = load_documents()
|
| 27 |
+
chunks = split_documents(documents)
|
| 28 |
+
add_to_chroma(chunks)
|
| 29 |
+
|
| 30 |
+
def load_documents():
|
| 31 |
+
# Load PDF documents from the specified directory
|
| 32 |
+
document_loader = PyPDFDirectoryLoader(DATA_PATH)
|
| 33 |
+
return document_loader.load()
|
| 34 |
+
|
| 35 |
+
def split_documents(documents: list[Document]):
|
| 36 |
+
# Split documents into smaller chunks using a character-based splitter
|
| 37 |
+
text_splitter = RecursiveCharacterTextSplitter(
|
| 38 |
+
chunk_size=800,
|
| 39 |
+
chunk_overlap=80,
|
| 40 |
+
length_function=len,
|
| 41 |
+
is_separator_regex=False,
|
| 42 |
+
)
|
| 43 |
+
return text_splitter.split_documents(documents)
|
| 44 |
+
|
| 45 |
+
def add_to_chroma(chunks: list[Document]):
|
| 46 |
+
# Initialize the Chroma vector store with the embedding function
|
| 47 |
+
db = Chroma(
|
| 48 |
+
persist_directory=CHROMA_PATH, embedding_function=get_embedding()
|
| 49 |
+
)
|
| 50 |
+
|
| 51 |
+
# Calculate unique IDs for each chunk
|
| 52 |
+
chunks_with_ids = calculate_chunk_ids(chunks)
|
| 53 |
+
|
| 54 |
+
# Get the existing documents from the database
|
| 55 |
+
existing_items = db.get(include=[]) # IDs are always included by default
|
| 56 |
+
existing_ids = set(existing_items["ids"])
|
| 57 |
+
print(f"Number of existing documents in DB: {len(existing_ids)}")
|
| 58 |
+
|
| 59 |
+
# Add only new chunks that don't exist in the database
|
| 60 |
+
new_chunks = []
|
| 61 |
+
for chunk in chunks_with_ids:
|
| 62 |
+
if chunk.metadata["id"] not in existing_ids:
|
| 63 |
+
new_chunks.append(chunk)
|
| 64 |
+
|
| 65 |
+
# Add new documents to the database and persist the changes
|
| 66 |
+
if len(new_chunks):
|
| 67 |
+
print(f"----->>> Adding new documents: {len(new_chunks)}")
|
| 68 |
+
new_chunk_ids = [chunk.metadata["id"] for chunk in new_chunks]
|
| 69 |
+
db.add_documents(new_chunks, ids=new_chunk_ids)
|
| 70 |
+
db.persist()
|
| 71 |
+
else:
|
| 72 |
+
print("----->>> No new documents to add")
|
| 73 |
+
|
| 74 |
+
def calculate_chunk_ids(chunks):
|
| 75 |
+
# Create unique IDs for each chunk based on the source, page, and chunk index
|
| 76 |
+
last_page_id = None
|
| 77 |
+
current_chunk_index = 0
|
| 78 |
+
|
| 79 |
+
for chunk in chunks:
|
| 80 |
+
source = chunk.metadata.get("source")
|
| 81 |
+
page = chunk.metadata.get("page")
|
| 82 |
+
current_page_id = f"{source}:{page}"
|
| 83 |
+
|
| 84 |
+
# Increment the chunk index if it's the same page
|
| 85 |
+
if current_page_id == last_page_id:
|
| 86 |
+
current_chunk_index += 1
|
| 87 |
+
else:
|
| 88 |
+
current_chunk_index = 0
|
| 89 |
+
|
| 90 |
+
# Assign a unique ID to each chunk
|
| 91 |
+
chunk_id = f"{current_page_id}:{current_chunk_index}"
|
| 92 |
+
last_page_id = current_page_id
|
| 93 |
+
chunk.metadata["id"] = chunk_id
|
| 94 |
+
|
| 95 |
+
return chunks
|
| 96 |
+
|
| 97 |
+
def clear_database():
|
| 98 |
+
# Remove the Chroma directory to clear the database
|
| 99 |
+
if os.path.exists(CHROMA_PATH):
|
| 100 |
+
shutil.rmtree(CHROMA_PATH)
|
| 101 |
+
|
| 102 |
+
if __name__ == "__main__":
|
| 103 |
+
main()
|
embedding_function.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from langchain_community.embeddings.ollama import OllamaEmbeddings
|
| 2 |
+
|
| 3 |
+
def get_embedding():
|
| 4 |
+
# Initialize embeddings with the specified model
|
| 5 |
+
embeddings = OllamaEmbeddings(model="nomic-embed-text")
|
| 6 |
+
return embeddings # Return the embedding function
|
query.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
from langchain.vectorstores.chroma import Chroma
|
| 3 |
+
from langchain.prompts import ChatPromptTemplate
|
| 4 |
+
from langchain_community.llms.ollama import Ollama
|
| 5 |
+
|
| 6 |
+
from embedding_function import get_embedding
|
| 7 |
+
|
| 8 |
+
# Path to the Chroma database
|
| 9 |
+
CHROMA_PATH = "chroma"
|
| 10 |
+
|
| 11 |
+
# Template for the prompt to be used by the LLM
|
| 12 |
+
PROMPT_TEMPLATE = """
|
| 13 |
+
Answer the question based only on the following context:
|
| 14 |
+
|
| 15 |
+
{context}
|
| 16 |
+
|
| 17 |
+
---
|
| 18 |
+
|
| 19 |
+
Answer the question based on the above context: {question}
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
def main():
|
| 23 |
+
# Create a command-line interface to accept the query text
|
| 24 |
+
parser = argparse.ArgumentParser()
|
| 25 |
+
parser.add_argument("query_text", type=str, help="The query text.")
|
| 26 |
+
args = parser.parse_args()
|
| 27 |
+
query_text = args.query_text
|
| 28 |
+
query_rag(query_text)
|
| 29 |
+
|
| 30 |
+
def query_rag(query_text: str):
|
| 31 |
+
# Initialize the embedding function
|
| 32 |
+
embedding_function = get_embedding()
|
| 33 |
+
|
| 34 |
+
# Load the Chroma vector store with the embedding function
|
| 35 |
+
db = Chroma(persist_directory=CHROMA_PATH, embedding_function=embedding_function)
|
| 36 |
+
|
| 37 |
+
# Perform a similarity search on the database with the query text
|
| 38 |
+
results = db.similarity_search_with_score(query_text, k=5)
|
| 39 |
+
|
| 40 |
+
# Combine the search results into a single context string
|
| 41 |
+
context_text = "\n\n---\n\n".join([doc.page_content for doc, _score in results])
|
| 42 |
+
|
| 43 |
+
# Format the prompt using the context and the query
|
| 44 |
+
prompt_template = ChatPromptTemplate.from_template(PROMPT_TEMPLATE)
|
| 45 |
+
prompt = prompt_template.format(context=context_text, question=query_text)
|
| 46 |
+
print(prompt)
|
| 47 |
+
|
| 48 |
+
# Initialize the model and generate a response
|
| 49 |
+
model = Ollama(model="mistral")
|
| 50 |
+
response_text = model.invoke(prompt)
|
| 51 |
+
|
| 52 |
+
# Extract source IDs from the search results
|
| 53 |
+
sources = [doc.metadata.get("id", None) for doc, _score in results]
|
| 54 |
+
|
| 55 |
+
# Format and print the response with the sources
|
| 56 |
+
formatted_response = f"Response: {response_text}\nSources: {sources}"
|
| 57 |
+
print(formatted_response)
|
| 58 |
+
return response_text
|
| 59 |
+
|
| 60 |
+
if __name__ == "__main__":
|
| 61 |
+
main()
|
requirements.txt
ADDED
|
Binary file (150 Bytes). View file
|
|
|