Spaces:
Sleeping
Sleeping
implemented semantic retrieval pipeline using embeddings and chromadb
Browse files- .gitignore +1 -1
- rag-system/app/chunker.py +4 -6
- rag-system/app/embeding.py +4 -0
- rag-system/app/loader.py +12 -28
- rag-system/app/main.py +25 -15
- rag-system/app/retriever.py +14 -0
- rag-system/app/tempCodeRunnerFile.py +12 -1
- rag-system/app/vectore_store.py +13 -0
- rag-system/notebooks/__init__.py +0 -0
- rag-system/notebooks/chunk_inspect.ipynb +24 -0
- rag-system/requirements.txt +1 -0
- rag-system/test/inspect_chunk.py +63 -0
- rag-system/{app → test}/testdb_load.py +0 -0
- requirements.txt +2 -1
.gitignore
CHANGED
|
@@ -5,4 +5,4 @@ __pycache__/
|
|
| 5 |
instance/
|
| 6 |
*.log
|
| 7 |
chroma_db
|
| 8 |
-
rag-system/
|
|
|
|
| 5 |
instance/
|
| 6 |
*.log
|
| 7 |
chroma_db
|
| 8 |
+
rag-system/test/loade.py
|
rag-system/app/chunker.py
CHANGED
|
@@ -1,11 +1,9 @@
|
|
| 1 |
-
from langchain_text_splitters import
|
| 2 |
|
| 3 |
-
def chunk_clean_text(text, chunk_size=
|
| 4 |
-
text_splitter =
|
| 5 |
chunk_size=chunk_size,
|
| 6 |
-
chunk_overlap=chunk_overlap
|
| 7 |
-
length_function=len,
|
| 8 |
-
separators=["\n\n", "\n", " ", ""]
|
| 9 |
)
|
| 10 |
|
| 11 |
chunk_list = text_splitter.split_text(text)
|
|
|
|
| 1 |
+
from langchain_text_splitters import NLTKTextSplitter
|
| 2 |
|
| 3 |
+
def chunk_clean_text(text, chunk_size=800, chunk_overlap=150):
|
| 4 |
+
text_splitter = NLTKTextSplitter(
|
| 5 |
chunk_size=chunk_size,
|
| 6 |
+
chunk_overlap=chunk_overlap
|
|
|
|
|
|
|
| 7 |
)
|
| 8 |
|
| 9 |
chunk_list = text_splitter.split_text(text)
|
rag-system/app/embeding.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from langchain_community.embeddings import HuggingFaceEmbeddings
|
| 2 |
+
|
| 3 |
+
def create_embedding():
|
| 4 |
+
return HuggingFaceEmbeddings(model_name= "all-MiniLM-L6-v2")
|
rag-system/app/loader.py
CHANGED
|
@@ -1,42 +1,26 @@
|
|
| 1 |
import re
|
|
|
|
| 2 |
from pypdf import PdfReader
|
| 3 |
|
| 4 |
-
def
|
|
|
|
| 5 |
try:
|
| 6 |
-
|
| 7 |
except Exception as e:
|
| 8 |
-
print(f"
|
| 9 |
return "", 0
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
for page_number, page in enumerate(reader.pages):
|
| 14 |
page_text = page.extract_text()
|
| 15 |
if page_text:
|
| 16 |
-
|
| 17 |
|
| 18 |
-
raw_text = "\n".join(
|
| 19 |
-
cleaned_text = re.sub(r'[
|
| 20 |
cleaned_text = re.sub(r'\n{3,}', '\n\n', cleaned_text)
|
| 21 |
cleaned_text = "\n".join([line.strip() for line in cleaned_text.splitlines()])
|
| 22 |
|
| 23 |
total_length = len(cleaned_text)
|
| 24 |
-
first_500_chars = cleaned_text[:500]
|
| 25 |
-
|
| 26 |
-
return cleaned_text, total_length
|
| 27 |
-
|
| 28 |
-
# if __name__ == "__main__":
|
| 29 |
-
# pdf_path = r"C:/Users/ajmel/desktop/internship-projects/rag-system/data/ArtificiaL_.pdf"
|
| 30 |
-
# print(f"Reading and cleaning: {pdf_path}...\n")
|
| 31 |
-
# preview, length = extract_and_clean_pdf(pdf_path)
|
| 32 |
-
|
| 33 |
-
# # PRINT REQUIRED METRICS
|
| 34 |
-
# print(f"=========================================")
|
| 35 |
-
# print(f"TOTAL TEXT LENGTH: {length} characters")
|
| 36 |
-
# print(f"=========================================\n")
|
| 37 |
-
# print("FIRST 500 CHARACTERS PREVIEW:")
|
| 38 |
-
# print("-" * 40)
|
| 39 |
-
# print(preview)
|
| 40 |
-
# print("-" * 40)
|
| 41 |
-
|
| 42 |
|
|
|
|
|
|
| 1 |
import re
|
| 2 |
+
import os
|
| 3 |
from pypdf import PdfReader
|
| 4 |
|
| 5 |
+
def extract_clean_text_pdf(pdf_path):
|
| 6 |
+
|
| 7 |
try:
|
| 8 |
+
loader = PdfReader(pdf_path)
|
| 9 |
except Exception as e:
|
| 10 |
+
print(f"Error loading PDF: {e}")
|
| 11 |
return "", 0
|
| 12 |
+
|
| 13 |
+
full_text = []
|
| 14 |
+
for page_num, page in enumerate(loader.pages):
|
|
|
|
| 15 |
page_text = page.extract_text()
|
| 16 |
if page_text:
|
| 17 |
+
full_text.append(page_text)
|
| 18 |
|
| 19 |
+
raw_text = "\n".join(full_text)
|
| 20 |
+
cleaned_text = re.sub(r'[\t]+', ' ', raw_text)
|
| 21 |
cleaned_text = re.sub(r'\n{3,}', '\n\n', cleaned_text)
|
| 22 |
cleaned_text = "\n".join([line.strip() for line in cleaned_text.splitlines()])
|
| 23 |
|
| 24 |
total_length = len(cleaned_text)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
+
return cleaned_text, total_length
|
rag-system/app/main.py
CHANGED
|
@@ -1,13 +1,15 @@
|
|
| 1 |
# 1. Bring in the tools from your other files
|
| 2 |
-
from loader import
|
| 3 |
from chunker import chunk_clean_text
|
| 4 |
-
|
|
|
|
|
|
|
| 5 |
def run_pipeline(pdf_path):
|
| 6 |
print("=== STARTING RAG DATA PIPELINE ===")
|
| 7 |
|
| 8 |
# 2. Load and Clean the PDF
|
| 9 |
print(f"\nStep 1: Reading and cleaning text from: {pdf_path}...")
|
| 10 |
-
full_text, total_length =
|
| 11 |
|
| 12 |
# Safety check if the PDF failed to load
|
| 13 |
if not full_text:
|
|
@@ -17,21 +19,29 @@ def run_pipeline(pdf_path):
|
|
| 17 |
print(f"Success! Cleaned {total_length} characters from the document.")
|
| 18 |
|
| 19 |
# 3. Chunk the Cleaned Text
|
| 20 |
-
print(f"\nStep 2: Splitting text into chunks (Size:
|
| 21 |
-
chunks = chunk_clean_text(full_text, chunk_size=
|
| 22 |
|
| 23 |
print(f"Success! Created {len(chunks)} total chunks.")
|
| 24 |
|
| 25 |
-
#
|
| 26 |
-
print(f"\n
|
| 27 |
-
|
| 28 |
-
print(
|
| 29 |
-
|
| 30 |
-
#
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
if __name__ == "__main__":
|
| 37 |
# The path to your actual PDF file
|
|
|
|
| 1 |
# 1. Bring in the tools from your other files
|
| 2 |
+
from loader import extract_clean_text_pdf
|
| 3 |
from chunker import chunk_clean_text
|
| 4 |
+
from embeding import create_embedding
|
| 5 |
+
from vectore_store import create_vector_store
|
| 6 |
+
from retriever import create_retriever
|
| 7 |
def run_pipeline(pdf_path):
|
| 8 |
print("=== STARTING RAG DATA PIPELINE ===")
|
| 9 |
|
| 10 |
# 2. Load and Clean the PDF
|
| 11 |
print(f"\nStep 1: Reading and cleaning text from: {pdf_path}...")
|
| 12 |
+
full_text, total_length = extract_clean_text_pdf(pdf_path)
|
| 13 |
|
| 14 |
# Safety check if the PDF failed to load
|
| 15 |
if not full_text:
|
|
|
|
| 19 |
print(f"Success! Cleaned {total_length} characters from the document.")
|
| 20 |
|
| 21 |
# 3. Chunk the Cleaned Text
|
| 22 |
+
print(f"\nStep 2: Splitting text into chunks (Size: 800, Overlap: 150)...")
|
| 23 |
+
chunks = chunk_clean_text(full_text, chunk_size=800, chunk_overlap=150)
|
| 24 |
|
| 25 |
print(f"Success! Created {len(chunks)} total chunks.")
|
| 26 |
|
| 27 |
+
# create embedding
|
| 28 |
+
print(f"\n step3: craete embedding for chunks...")
|
| 29 |
+
embeddings = create_embedding()
|
| 30 |
+
print("sucess! embedding craeted")
|
| 31 |
+
|
| 32 |
+
#4. stor vectore in db
|
| 33 |
+
print(f"creating vectore store")
|
| 34 |
+
vectore_store = create_vector_store(chunks, embeddings)
|
| 35 |
+
print("success! vectore store created and stored in chroma db")
|
| 36 |
+
|
| 37 |
+
#5 retrive similar chunks
|
| 38 |
+
print("retrive similar chunks from vectore store")
|
| 39 |
+
retriver = create_retriever(vectore_store)
|
| 40 |
+
print("sucessfully retrived similar chunks from vectore store")
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
|
| 45 |
|
| 46 |
if __name__ == "__main__":
|
| 47 |
# The path to your actual PDF file
|
rag-system/app/retriever.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from vectore_store import create_vector_store
|
| 2 |
+
|
| 3 |
+
def create_retriever(vector_store):
|
| 4 |
+
query = input("enter your query")
|
| 5 |
+
retriver = vector_store.as_retriever(
|
| 6 |
+
search_type = "mmr",
|
| 7 |
+
search_kwargs = {"k": 3}
|
| 8 |
+
)
|
| 9 |
+
similar_docs = retriver.invoke(query)
|
| 10 |
+
|
| 11 |
+
print("top 3: similar chunks recived from vector store")
|
| 12 |
+
|
| 13 |
+
for docs in similar_docs:
|
| 14 |
+
print(f"-- {docs.page_content[:100]}")
|
rag-system/app/tempCodeRunnerFile.py
CHANGED
|
@@ -1 +1,12 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
|
| 3 |
+
# 6. Preview the results
|
| 4 |
+
# print(f"\n=========================================")
|
| 5 |
+
# print(f"PIPELINE OUTPUT PREVIEW")
|
| 6 |
+
# print(f"=========================================")
|
| 7 |
+
|
| 8 |
+
# # Show the first 2 chunks as a test sample
|
| 9 |
+
# for index, chunk in enumerate(chunks[:2]):
|
| 10 |
+
# print(f"\n--- CHUNK {index + 1} (Length: {len(chunk)} characters) ---")
|
| 11 |
+
# print(chunk)
|
| 12 |
+
# print("-" * 40)
|
rag-system/app/vectore_store.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from langchain_community.vectorstores import Chroma
|
| 2 |
+
import chromadb
|
| 3 |
+
|
| 4 |
+
def create_vector_store(chunk_list, embedding_model):
|
| 5 |
+
perssisten_client = chromadb.PersistentClient("./chroma_db")
|
| 6 |
+
|
| 7 |
+
vectore_store = Chroma.from_texts(
|
| 8 |
+
texts = chunk_list,
|
| 9 |
+
embedding = embedding_model,
|
| 10 |
+
collection_name = "rag_chunks",
|
| 11 |
+
client = perssisten_client
|
| 12 |
+
)
|
| 13 |
+
return vectore_store
|
rag-system/notebooks/__init__.py
ADDED
|
File without changes
|
rag-system/notebooks/chunk_inspect.ipynb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [],
|
| 3 |
+
"metadata": {
|
| 4 |
+
"kernelspec": {
|
| 5 |
+
"display_name": "venv (3.12.7)",
|
| 6 |
+
"language": "python",
|
| 7 |
+
"name": "python3"
|
| 8 |
+
},
|
| 9 |
+
"language_info": {
|
| 10 |
+
"codemirror_mode": {
|
| 11 |
+
"name": "ipython",
|
| 12 |
+
"version": 3
|
| 13 |
+
},
|
| 14 |
+
"file_extension": ".py",
|
| 15 |
+
"mimetype": "text/x-python",
|
| 16 |
+
"name": "python",
|
| 17 |
+
"nbconvert_exporter": "python",
|
| 18 |
+
"pygments_lexer": "ipython3",
|
| 19 |
+
"version": "3.12.7"
|
| 20 |
+
}
|
| 21 |
+
},
|
| 22 |
+
"nbformat": 4,
|
| 23 |
+
"nbformat_minor": 5
|
| 24 |
+
}
|
rag-system/requirements.txt
CHANGED
|
@@ -10,3 +10,4 @@ chromadb>=0.4.24
|
|
| 10 |
streamlit>=1.32.0
|
| 11 |
pypdf>=6.12.0
|
| 12 |
sentence_transformers>=5.5.1
|
|
|
|
|
|
| 10 |
streamlit>=1.32.0
|
| 11 |
pypdf>=6.12.0
|
| 12 |
sentence_transformers>=5.5.1
|
| 13 |
+
nltk>=3.9.4
|
rag-system/test/inspect_chunk.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
# Ensure the parent `rag-system` directory is on sys.path so `app` package imports work
|
| 4 |
+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
| 5 |
+
|
| 6 |
+
from app.loader import extract_clean_text_pdf
|
| 7 |
+
from app.chunker import chunk_clean_text
|
| 8 |
+
|
| 9 |
+
def inspect_chunk(pdf_path):
|
| 10 |
+
|
| 11 |
+
print("--------starting debuging chunking quality--------------")
|
| 12 |
+
|
| 13 |
+
cleaned_text, total_length = extract_clean_text_pdf(pdf_path)
|
| 14 |
+
chunks = chunk_clean_text(cleaned_text, chunk_size=800, chunk_overlap=150)
|
| 15 |
+
|
| 16 |
+
too_short_count = 0
|
| 17 |
+
broken_sentence_count = 0
|
| 18 |
+
total_chars = 0
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
min_len_treshold = 40
|
| 22 |
+
valid_endings = (".", "!", "?", '"', "'", "”", "’")
|
| 23 |
+
|
| 24 |
+
for idx, chunk in enumerate(chunks):
|
| 25 |
+
chunk_len = len(chunk)
|
| 26 |
+
total_chars += chunk_len
|
| 27 |
+
|
| 28 |
+
is_too_short = chunk_len < min_len_treshold
|
| 29 |
+
is_broken_sentence = not chunk.endswith(valid_endings)
|
| 30 |
+
|
| 31 |
+
if is_too_short or is_broken_sentence:
|
| 32 |
+
print(f"issues found in chunk {idx + 1}: Length={chunk_len}, Ends with valid punctuation: {not is_broken_sentence}")
|
| 33 |
+
|
| 34 |
+
if is_too_short:
|
| 35 |
+
print(f"[Critical]: chunks fells below the minimum semanic length threshold")
|
| 36 |
+
if is_broken_sentence:
|
| 37 |
+
print(f"[Critical]: chunk ends with a broken sentence, which may cause loss of context for the LLM")
|
| 38 |
+
|
| 39 |
+
if is_too_short: too_short_count += 1
|
| 40 |
+
if is_broken_sentence: broken_sentence_count += 1
|
| 41 |
+
|
| 42 |
+
avg_chunk = total_chars / len(chunks) if chunks else 0
|
| 43 |
+
broken_ratio = (broken_sentence_count / len(chunks)) * 100 if chunks else 0
|
| 44 |
+
|
| 45 |
+
print("\n--------chunking quality report--------------")
|
| 46 |
+
print(f"Total chunks: {len(chunks)}")
|
| 47 |
+
print(f"Too short chunks: {too_short_count}")
|
| 48 |
+
print(f"Broken sentence chunks: {broken_sentence_count}")
|
| 49 |
+
print(f"Average chunk length: {avg_chunk:.2f}")
|
| 50 |
+
print(f"Broken sentence ratio: {broken_ratio:.2f}%")
|
| 51 |
+
|
| 52 |
+
#
|
| 53 |
+
print("\n architectural recommendations:")
|
| 54 |
+
|
| 55 |
+
if broken_ratio > 30:
|
| 56 |
+
print(f"[Critical]: A high percentage of chunks end with broken sentences. Consider adjusting chunking parameters or implementing smarter sentence-aware chunking to preserve context for the LLM.")
|
| 57 |
+
elif avg_chunk < 150:
|
| 58 |
+
print(f"[Warning]: The average chunk length is quite low, which may lead to inefficient use of the LLM's context window. Consider increasing the chunk size or reducing overlap to create more meaningful chunks.")
|
| 59 |
+
else:
|
| 60 |
+
print(f"[Success]: The chunking quality appears to be good with a reasonable average length and low broken sentence ratio. You can proceed with this configuration for your RAG system.")
|
| 61 |
+
if __name__ == "__main__":
|
| 62 |
+
pdf_path = r"C:/Users/ajmel/desktop/internship-projects/rag-system/data/ArtificiaL_.pdf"
|
| 63 |
+
inspect_chunk(pdf_path)
|
rag-system/{app → test}/testdb_load.py
RENAMED
|
File without changes
|
requirements.txt
CHANGED
|
@@ -9,4 +9,5 @@ langchain-google-genai>=1.0.0
|
|
| 9 |
chromadb>=0.4.24
|
| 10 |
streamlit>=1.32.0
|
| 11 |
pypdf>=6.12.0
|
| 12 |
-
sentence_transformers>=5.5.1
|
|
|
|
|
|
| 9 |
chromadb>=0.4.24
|
| 10 |
streamlit>=1.32.0
|
| 11 |
pypdf>=6.12.0
|
| 12 |
+
sentence_transformers>=5.5.1
|
| 13 |
+
nltk>=3.9.4
|