ajmel commited on
Commit
0dc0e59
·
1 Parent(s): d78f487

implemented semantic retrieval pipeline using embeddings and chromadb

Browse files
.gitignore CHANGED
@@ -5,4 +5,4 @@ __pycache__/
5
  instance/
6
  *.log
7
  chroma_db
8
- rag-system/app/loade.py
 
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 RecursiveCharacterTextSplitter
2
 
3
- def chunk_clean_text(text, chunk_size=500, chunk_overlap=100):
4
- text_splitter = RecursiveCharacterTextSplitter(
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 extract_and_clean_pdf(pdf_path):
 
5
  try:
6
- reader = PdfReader(pdf_path)
7
  except Exception as e:
8
- print(f"erro loading PDF: {e}")
9
  return "", 0
10
-
11
- raw_text_pieces = []
12
-
13
- for page_number, page in enumerate(reader.pages):
14
  page_text = page.extract_text()
15
  if page_text:
16
- raw_text_pieces.append(page_text)
17
 
18
- raw_text = "\n".join(raw_text_pieces)
19
- cleaned_text = re.sub(r'[ \t]+', ' ', raw_text)
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 extract_and_clean_pdf
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 = extract_and_clean_pdf(pdf_path)
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: 500, Overlap: 100)...")
21
- chunks = chunk_clean_text(full_text, chunk_size=500, chunk_overlap=100)
22
 
23
  print(f"Success! Created {len(chunks)} total chunks.")
24
 
25
- # 4. Preview the results
26
- print(f"\n=========================================")
27
- print(f"PIPELINE OUTPUT PREVIEW")
28
- print(f"=========================================")
29
-
30
- # Show the first 2 chunks as a test sample
31
- for index, chunk in enumerate(chunks[:2]):
32
- print(f"\n--- CHUNK {index + 1} (Length: {len(chunk)} characters) ---")
33
- print(chunk)
34
- print("-" * 40)
 
 
 
 
 
 
 
 
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
- langchain_huggingface
 
 
 
 
 
 
 
 
 
 
 
 
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