dembasowmr commited on
Commit
4d2a44f
·
1 Parent(s): ac5fcf7

Added prompt system to compassia

Browse files
Files changed (2) hide show
  1. .gitignore +1 -0
  2. compassia.py +58 -98
.gitignore CHANGED
@@ -6,3 +6,4 @@ all-libraries.txt
6
  # Ignore ChromaDB persistent storage
7
  chroma_db/
8
 
 
 
6
  # Ignore ChromaDB persistent storage
7
  chroma_db/
8
 
9
+ temp*
compassia.py CHANGED
@@ -1,10 +1,3 @@
1
- import requests
2
- import os
3
- import io
4
- import re
5
- import uuid # For generating unique IDs for ChromaDB
6
- from PIL import Image
7
-
8
  import sys
9
  # IMPORTANT: These lines MUST be at the very top of compassia.py
10
  # They ensure that any subsequent import of 'sqlite3' (even indirectly by chromadb)
@@ -15,6 +8,12 @@ try:
15
  except ImportError:
16
  pass # Fallback if pysqlite3 isn't available, but it should be in Docker
17
 
 
 
 
 
 
 
18
 
19
  # For text extraction from PDFs (non-OCR)
20
  from pdfminer.high_level import extract_text_to_fp
@@ -25,14 +24,14 @@ from pdf2image import convert_from_path
25
  import pytesseract
26
 
27
  # For embeddings and vector search
28
- from FlagEmbedding import BGEM3FlagModel # Using BGEM3FlagModel directly as per your latest code
29
  import chromadb # pip install chromadb
30
 
31
  # --- IMPORTANT: Configure Paths for Tesseract and Poppler ---
32
- # If Tesseract is not in your system's PATH, uncomment and set this:
33
  # pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'
34
 
35
- # If pdf2image gives errors about poppler, uncomment and set this:
36
  # poppler_path = r'C:\path\to\poppler\bin'
37
 
38
  # --- OpenRouter DeepSeek API Configuration ---
@@ -50,21 +49,15 @@ HEADERS = {
50
  }
51
 
52
  # --- Embedding Model Configuration (Local BGE-M3) ---
53
- # IMPORTANT: This assumes you've run 'pip install -U FlagEmbedding'
54
- # BGE-M3 is multilingual, which is good for Turkish PDFs.
55
- # You might need to download the model weights the first time it's initialized.
56
- # Ensure you have enough RAM/VRAM for the model.
57
  print("Loading FlagEmbedding (BGE-M3) model...")
58
  try:
59
- # Initialize BGEM3FlagModel. It will download weights to Hugging Face cache
60
- # the first time, hence the need for disk space.
61
  embedding_model = BGEM3FlagModel('BAAI/bge-m3', use_fp16=True)
62
  print("FlagEmbedding (BGE-M3) model loaded successfully.")
63
  except Exception as e:
64
  print(f"Error loading FlagEmbedding model: {e}")
65
  print("Ensure you have resolved disk space issues for model download and have enough memory.")
66
  print("You might need to adjust 'use_fp16' based on your hardware (e.g., False for CPU/older GPUs).")
67
- exit(1) # Exit if embedding model fails to load
68
 
69
  # --- PDF Processing Functions ---
70
 
@@ -77,11 +70,9 @@ def extract_text_from_pdf(pdf_path: str) -> str:
77
  output_string = io.StringIO()
78
  with open(pdf_path, 'rb') as fp:
79
  try:
80
- # Use LAParams for better layout analysis
81
  extract_text_to_fp(fp, output_string, laparams=LAParams())
82
  text = output_string.getvalue()
83
- # Basic check: if text is very short for a non-empty PDF, it might be image-based
84
- if len(text.strip()) < 100 and os.path.getsize(pdf_path) > 10000: # Check file size as well
85
  print("Direct extraction yielded sparse text. Attempting OCR...")
86
  return ocr_pdf(pdf_path)
87
  return text
@@ -96,17 +87,15 @@ def ocr_pdf(pdf_path: str) -> str:
96
  """
97
  all_text = []
98
  try:
99
- # Convert PDF pages to images. Higher DPI for better OCR.
100
- # Pass poppler_path=poppler_path if it's not in your system's PATH
101
- images = convert_from_path(pdf_path, dpi=300) # You can adjust dpi for quality vs. speed
102
 
103
  print(f" Performing OCR on {len(images)} pages...")
104
  for i, img in enumerate(images):
105
- # Optional: Basic image preprocessing for better OCR
106
- # img = img.convert('L') # Convert to grayscale
107
- # img = img.point(lambda x: 0 if x < 128 else 255, '1') # Binarize
108
-
109
- # Perform OCR (lang='eng+tur' for English and Turkish support)
110
  page_text = pytesseract.image_to_string(img, lang='eng+tur')
111
  all_text.append(page_text)
112
  print(f" Page {i+1} OCR complete.")
@@ -114,7 +103,7 @@ def ocr_pdf(pdf_path: str) -> str:
114
  except Exception as e:
115
  print(f"OCR process failed: {e}")
116
  print("Please ensure Tesseract OCR and Poppler are correctly installed and their executables are in your system's PATH.")
117
- return "" # Return empty string if OCR fails
118
 
119
  return "\n".join(all_text)
120
 
@@ -122,12 +111,10 @@ def chunk_text(text: str, max_chunk_size: int = 700, overlap: int = 100) -> list
122
  """
123
  Splits text into chunks of a maximum size with optional overlap.
124
  Aims to split by paragraphs/sentences first, then by word.
125
- Note: Increased max_chunk_size to 700 to match your previous code's `chunk_size` for RAG.
126
  """
127
  if not text:
128
  return []
129
 
130
- # Simple paragraph-based chunking
131
  paragraphs = re.split(r'\n\s*\n', text)
132
  chunks = []
133
  current_chunk = []
@@ -137,16 +124,12 @@ def chunk_text(text: str, max_chunk_size: int = 700, overlap: int = 100) -> list
137
  if not para.strip():
138
  continue
139
 
140
- # If adding paragraph plus a separator exceeds max_chunk_size,
141
- # or if the current_chunk is already substantial and adding this makes it too big,
142
- # then finalize the current chunk.
143
  if current_chunk_len + len(para) + len('\n\n') > max_chunk_size:
144
- if current_chunk: # Only append if current_chunk is not empty
145
  chunks.append("\n\n".join(current_chunk))
146
  current_chunk = []
147
  current_chunk_len = 0
148
 
149
- # If a single paragraph is larger than max_chunk_size, split it by words
150
  if len(para) > max_chunk_size:
151
  words = para.split(' ')
152
  sub_chunk = []
@@ -159,32 +142,28 @@ def chunk_text(text: str, max_chunk_size: int = 700, overlap: int = 100) -> list
159
  else:
160
  sub_chunk.append(word)
161
  sub_chunk_len += len(word) + len(' ')
162
- if sub_chunk: # Add remaining sub-chunk
163
  chunks.append(" ".join(sub_chunk))
164
- else: # Paragraph fits into a new chunk
165
  current_chunk.append(para)
166
  current_chunk_len += len(para) + len('\n\n')
167
- else: # Paragraph fits into the current chunk
168
  current_chunk.append(para)
169
  current_chunk_len += len(para) + len('\n\n')
170
 
171
- if current_chunk: # Add any remaining text
172
  chunks.append("\n\n".join(current_chunk))
173
-
174
- # Apply overlap: This is a simplistic overlap implementation.
175
- # For more robust RAG, consider sentence-window retrieval or more advanced chunking libraries.
176
  final_chunks_with_overlap = []
177
  for i in range(len(chunks)):
178
  chunk = chunks[i]
179
  if i > 0 and overlap > 0:
180
- # Take a portion of the previous chunk to overlap
181
  prev_chunk_part = chunks[i-1][-overlap:]
182
  chunk = prev_chunk_part + "\n" + chunk
183
  final_chunks_with_overlap.append(chunk)
184
 
185
  return final_chunks_with_overlap
186
 
187
-
188
  # --- RAG Core Functions with ChromaDB ---
189
 
190
  class DocumentRAG:
@@ -195,30 +174,21 @@ class DocumentRAG:
195
  self.persist_directory = persist_directory
196
  self.collection_name = collection_name
197
 
198
- # Initialize ChromaDB client and collection
199
  print(f"Initializing ChromaDB at: {self.persist_directory}")
200
  self.client = chromadb.PersistentClient(path=self.persist_directory)
201
 
202
- # Get or create the collection
203
  self.collection = self.client.get_or_create_collection(
204
  name=self.collection_name,
205
- # Genkit uses 'cosine' by default. 'l2' (Euclidean) or 'ip' (Inner Product)
206
- # are also common. BGE-M3 generally uses cosine.
207
  metadata={"hnsw:space": "cosine"}
208
  )
209
  print(f"ChromaDB collection '{self.collection_name}' ready.")
210
 
211
  def _generate_chunk_id(self, pdf_path: str, chunk_idx: int) -> str:
212
- """Generates a unique ID for each chunk based on file path and index."""
213
- # Use UUID to ensure uniqueness even if paths are similar or contain problematic chars
214
  return f"{os.path.basename(pdf_path)}_{chunk_idx}_{uuid.uuid4().hex}"
215
 
216
  def add_document(self, pdf_path: str):
217
  print(f"Adding document: {pdf_path}")
218
 
219
- # Check if the document has already been indexed in ChromaDB
220
- # We'll use the file path as a simple way to check if _any_ chunk from this PDF exists.
221
- # A more robust check might involve hashing the file content or checking specific metadata.
222
  results = self.collection.get(
223
  where={"source": pdf_path},
224
  limit=1
@@ -237,43 +207,31 @@ class DocumentRAG:
237
  print(f"Warning: No chunks generated for {pdf_path}. Skipping.")
238
  return
239
 
240
- # Prepare data for ChromaDB
241
  documents_to_add = []
242
  metadatas_to_add = []
243
  ids_to_add = []
244
 
245
  print(f" Generating embeddings for {len(chunks)} chunks and preparing for ChromaDB...")
246
 
247
- # BGE-M3's encode method returns a dictionary for dense, sparse, etc.
248
- # We need the 'dense_vecs' for standard vector search.
249
  encoded_results = self.embedding_model.encode(
250
  chunks,
251
- batch_size=32, # Adjust batch_size if out of memory
252
  return_dense=True,
253
  return_sparse=False,
254
  return_colbert_vecs=False
255
  )
256
 
257
- # Extract only the dense vectors for ChromaDB
258
  chunk_embeddings = encoded_results["dense_vecs"]
259
 
260
- # Ensure embeddings are normalized if using cosine similarity with IP index,
261
- # but ChromaDB's 'cosine' space handles this internally.
262
- # If using FAISS with IP, you'd normalize here:
263
- # from numpy.linalg import norm
264
- # chunk_embeddings = chunk_embeddings / norm(chunk_embeddings, axis=1, keepdims=True)
265
-
266
-
267
  for i, chunk in enumerate(chunks):
268
  unique_id = self._generate_chunk_id(pdf_path, i)
269
  documents_to_add.append(chunk)
270
  metadatas_to_add.append({"source": pdf_path, "chunk_id": i})
271
  ids_to_add.append(unique_id)
272
 
273
- # Add to ChromaDB collection
274
  self.collection.add(
275
  documents=documents_to_add,
276
- embeddings=chunk_embeddings.tolist(), # Convert numpy array to list of lists
277
  metadatas=metadatas_to_add,
278
  ids=ids_to_add
279
  )
@@ -291,7 +249,6 @@ class DocumentRAG:
291
 
292
  print(f"Retrieving context for query: '{query}'")
293
 
294
- # Encode the query using the embedding model
295
  query_embedding_result = self.embedding_model.encode(
296
  [query],
297
  batch_size=1,
@@ -299,9 +256,8 @@ class DocumentRAG:
299
  return_sparse=False,
300
  return_colbert_vecs=False
301
  )
302
- query_embedding = query_embedding_result["dense_vecs"].tolist() # Get dense vector and convert to list
303
 
304
- # Query ChromaDB
305
  results = self.collection.query(
306
  query_embeddings=query_embedding,
307
  n_results=top_k,
@@ -313,7 +269,7 @@ class DocumentRAG:
313
  for i, doc_text in enumerate(results['documents'][0]):
314
  source_info = results['metadatas'][0][i].get('source', 'Unknown Source')
315
  chunk_id_info = results['metadatas'][0][i].get('chunk_id', 'N/A')
316
- distance_info = results['distances'][0][i] # Smaller distance is more similar for cosine
317
 
318
  retrieved_chunks_texts.append(doc_text)
319
  print(f" Retrieved chunk {i+1} (distance: {distance_info:.4f}) from '{source_info}' (chunk {chunk_id_info}).")
@@ -327,12 +283,9 @@ class DocumentRAG:
327
  Answers a question by ensuring PDFs are indexed, retrieving context,
328
  and querying DeepSeek.
329
  """
330
- # Ensure documents are added for specified paths.
331
- # This will now intelligently skip already indexed documents.
332
  for path in pdf_paths:
333
  self.add_document(path)
334
 
335
- # Get relevant context from ChromaDB
336
  context_chunks = self.retrieve_context(question)
337
  context = "\n\n".join(context_chunks)
338
 
@@ -342,19 +295,36 @@ class DocumentRAG:
342
  else:
343
  context_prompt = f"Using the following context:\n\n{context}\n\n"
344
 
345
- # Construct prompt for DeepSeek
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
346
  messages = [
347
- {"role": "system", "content": "You are an AI assistant specialized in answering questions based on provided context. If the answer is not in the context, state that explicitly. If you cannot answer based *solely* on the context, politely indicate that the information is not available in the provided documents."},
348
  {"role": "user", "content": f"{context_prompt}Question: {question}"}
349
  ]
350
 
351
- # Call DeepSeek API via OpenRouter
352
  print("\nSending request to DeepSeek API...")
353
  data = {
354
- "model": "deepseek/deepseek-chat:free", # Using the specified free model
355
  "messages": messages,
356
- "temperature": 0.5, # Adjust for creativity vs. factualness
357
- "max_tokens": 500, # Limit response length
358
  }
359
 
360
  response = requests.post(API_URL, json=data, headers=HEADERS)
@@ -372,30 +342,22 @@ class DocumentRAG:
372
 
373
  # --- Main execution logic ---
374
  if __name__ == "__main__":
375
- # Initialize the RAG system with ChromaDB persistence
376
- # The 'chroma_db' directory will be created in your project root.
377
  rag_system = DocumentRAG(
378
  embedding_model=embedding_model,
379
- persist_directory="./chroma_db", # This is where your vector DB will be saved
380
- collection_name="pdf_documents_collection", # A unique name for your collection
381
  chunk_size=700,
382
  overlap=100
383
  )
384
 
385
- # --- Define your PDF documents ---
386
- # Replace with the actual paths to your PDF files.
387
- # For testing, ensure 'documents' directory exists and contains your PDFs.
388
  pdf_document_paths = [
389
- "documents/heracles_tr.pdf", # Heracles TR PDF path
390
- "documents/heracles_en.pdf", # Heracles EN PDF path
391
- # Add more PDF paths here if you have them
392
  "documents/ogrenci_katki_payi_ogrenim_ucretleri.pdf",
393
  "documents/Ogrenci_Liderligi_Burs_Programi_Sozlesme_Metni_2024-2025.pdf",
394
  "documents/tmv-bursluluk-yonergesi.pdf"
395
  ]
396
 
397
- # --- Add PDFs to the RAG system for indexing ---
398
- # This will now process only new or unindexed documents.
399
  print("\n--- Indexing Documents ---")
400
  for pdf_path in pdf_document_paths:
401
  if os.path.exists(pdf_path):
@@ -403,13 +365,11 @@ if __name__ == "__main__":
403
  else:
404
  print(f"Error: PDF file not found at {pdf_path}. Please check the path.")
405
 
406
- # --- Start Chat Loop ---
407
- print("\n--- PDF Chat with DeepSeek (Type 'quit' to exit) ---")
408
  while True:
409
- user_question = input("\nYour question about the PDF(s): ")
410
  if user_question.lower() == 'quit':
411
  print("Exiting chat.")
412
  break
413
 
414
- # No need to pass pdf_document_paths here; documents are already in ChromaDB
415
- rag_system.answer_question(user_question, []) # Pass an empty list, as documents are in DB
 
 
 
 
 
 
 
 
1
  import sys
2
  # IMPORTANT: These lines MUST be at the very top of compassia.py
3
  # They ensure that any subsequent import of 'sqlite3' (even indirectly by chromadb)
 
8
  except ImportError:
9
  pass # Fallback if pysqlite3 isn't available, but it should be in Docker
10
 
11
+ import requests
12
+ import os
13
+ import io
14
+ import re
15
+ import uuid # For generating unique IDs for ChromaDB
16
+ from PIL import Image
17
 
18
  # For text extraction from PDFs (non-OCR)
19
  from pdfminer.high_level import extract_text_to_fp
 
24
  import pytesseract
25
 
26
  # For embeddings and vector search
27
+ from FlagEmbedding import BGEM3FlagModel
28
  import chromadb # pip install chromadb
29
 
30
  # --- IMPORTANT: Configure Paths for Tesseract and Poppler ---
31
+ # If Tesseract is not in your system's PATH, uncomment and set this locally:
32
  # pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'
33
 
34
+ # If pdf2image gives errors about poppler, uncomment and set this locally:
35
  # poppler_path = r'C:\path\to\poppler\bin'
36
 
37
  # --- OpenRouter DeepSeek API Configuration ---
 
49
  }
50
 
51
  # --- Embedding Model Configuration (Local BGE-M3) ---
 
 
 
 
52
  print("Loading FlagEmbedding (BGE-M3) model...")
53
  try:
 
 
54
  embedding_model = BGEM3FlagModel('BAAI/bge-m3', use_fp16=True)
55
  print("FlagEmbedding (BGE-M3) model loaded successfully.")
56
  except Exception as e:
57
  print(f"Error loading FlagEmbedding model: {e}")
58
  print("Ensure you have resolved disk space issues for model download and have enough memory.")
59
  print("You might need to adjust 'use_fp16' based on your hardware (e.g., False for CPU/older GPUs).")
60
+ exit(1)
61
 
62
  # --- PDF Processing Functions ---
63
 
 
70
  output_string = io.StringIO()
71
  with open(pdf_path, 'rb') as fp:
72
  try:
 
73
  extract_text_to_fp(fp, output_string, laparams=LAParams())
74
  text = output_string.getvalue()
75
+ if len(text.strip()) < 100 and os.path.getsize(pdf_path) > 10000:
 
76
  print("Direct extraction yielded sparse text. Attempting OCR...")
77
  return ocr_pdf(pdf_path)
78
  return text
 
87
  """
88
  all_text = []
89
  try:
90
+ images = convert_from_path(pdf_path, dpi=300)
 
 
91
 
92
  print(f" Performing OCR on {len(images)} pages...")
93
  for i, img in enumerate(images):
94
+ # Tesseract language packs:
95
+ # 'eng' for English, 'tur' for Turkish
96
+ # If you have scanned PDFs in Arabic or French, you MUST install
97
+ # 'tesseract-ocr-ara' and 'tesseract-ocr-fra' in your Dockerfile
98
+ # and change 'lang' to 'eng+tur+ara+fra'.
99
  page_text = pytesseract.image_to_string(img, lang='eng+tur')
100
  all_text.append(page_text)
101
  print(f" Page {i+1} OCR complete.")
 
103
  except Exception as e:
104
  print(f"OCR process failed: {e}")
105
  print("Please ensure Tesseract OCR and Poppler are correctly installed and their executables are in your system's PATH.")
106
+ return ""
107
 
108
  return "\n".join(all_text)
109
 
 
111
  """
112
  Splits text into chunks of a maximum size with optional overlap.
113
  Aims to split by paragraphs/sentences first, then by word.
 
114
  """
115
  if not text:
116
  return []
117
 
 
118
  paragraphs = re.split(r'\n\s*\n', text)
119
  chunks = []
120
  current_chunk = []
 
124
  if not para.strip():
125
  continue
126
 
 
 
 
127
  if current_chunk_len + len(para) + len('\n\n') > max_chunk_size:
128
+ if current_chunk:
129
  chunks.append("\n\n".join(current_chunk))
130
  current_chunk = []
131
  current_chunk_len = 0
132
 
 
133
  if len(para) > max_chunk_size:
134
  words = para.split(' ')
135
  sub_chunk = []
 
142
  else:
143
  sub_chunk.append(word)
144
  sub_chunk_len += len(word) + len(' ')
145
+ if sub_chunk:
146
  chunks.append(" ".join(sub_chunk))
147
+ else:
148
  current_chunk.append(para)
149
  current_chunk_len += len(para) + len('\n\n')
150
+ else:
151
  current_chunk.append(para)
152
  current_chunk_len += len(para) + len('\n\n')
153
 
154
+ if current_chunk:
155
  chunks.append("\n\n".join(current_chunk))
156
+
 
 
157
  final_chunks_with_overlap = []
158
  for i in range(len(chunks)):
159
  chunk = chunks[i]
160
  if i > 0 and overlap > 0:
 
161
  prev_chunk_part = chunks[i-1][-overlap:]
162
  chunk = prev_chunk_part + "\n" + chunk
163
  final_chunks_with_overlap.append(chunk)
164
 
165
  return final_chunks_with_overlap
166
 
 
167
  # --- RAG Core Functions with ChromaDB ---
168
 
169
  class DocumentRAG:
 
174
  self.persist_directory = persist_directory
175
  self.collection_name = collection_name
176
 
 
177
  print(f"Initializing ChromaDB at: {self.persist_directory}")
178
  self.client = chromadb.PersistentClient(path=self.persist_directory)
179
 
 
180
  self.collection = self.client.get_or_create_collection(
181
  name=self.collection_name,
 
 
182
  metadata={"hnsw:space": "cosine"}
183
  )
184
  print(f"ChromaDB collection '{self.collection_name}' ready.")
185
 
186
  def _generate_chunk_id(self, pdf_path: str, chunk_idx: int) -> str:
 
 
187
  return f"{os.path.basename(pdf_path)}_{chunk_idx}_{uuid.uuid4().hex}"
188
 
189
  def add_document(self, pdf_path: str):
190
  print(f"Adding document: {pdf_path}")
191
 
 
 
 
192
  results = self.collection.get(
193
  where={"source": pdf_path},
194
  limit=1
 
207
  print(f"Warning: No chunks generated for {pdf_path}. Skipping.")
208
  return
209
 
 
210
  documents_to_add = []
211
  metadatas_to_add = []
212
  ids_to_add = []
213
 
214
  print(f" Generating embeddings for {len(chunks)} chunks and preparing for ChromaDB...")
215
 
 
 
216
  encoded_results = self.embedding_model.encode(
217
  chunks,
218
+ batch_size=32,
219
  return_dense=True,
220
  return_sparse=False,
221
  return_colbert_vecs=False
222
  )
223
 
 
224
  chunk_embeddings = encoded_results["dense_vecs"]
225
 
 
 
 
 
 
 
 
226
  for i, chunk in enumerate(chunks):
227
  unique_id = self._generate_chunk_id(pdf_path, i)
228
  documents_to_add.append(chunk)
229
  metadatas_to_add.append({"source": pdf_path, "chunk_id": i})
230
  ids_to_add.append(unique_id)
231
 
 
232
  self.collection.add(
233
  documents=documents_to_add,
234
+ embeddings=chunk_embeddings.tolist(),
235
  metadatas=metadatas_to_add,
236
  ids=ids_to_add
237
  )
 
249
 
250
  print(f"Retrieving context for query: '{query}'")
251
 
 
252
  query_embedding_result = self.embedding_model.encode(
253
  [query],
254
  batch_size=1,
 
256
  return_sparse=False,
257
  return_colbert_vecs=False
258
  )
259
+ query_embedding = query_embedding_result["dense_vecs"].tolist()
260
 
 
261
  results = self.collection.query(
262
  query_embeddings=query_embedding,
263
  n_results=top_k,
 
269
  for i, doc_text in enumerate(results['documents'][0]):
270
  source_info = results['metadatas'][0][i].get('source', 'Unknown Source')
271
  chunk_id_info = results['metadatas'][0][i].get('chunk_id', 'N/A')
272
+ distance_info = results['distances'][0][i]
273
 
274
  retrieved_chunks_texts.append(doc_text)
275
  print(f" Retrieved chunk {i+1} (distance: {distance_info:.4f}) from '{source_info}' (chunk {chunk_id_info}).")
 
283
  Answers a question by ensuring PDFs are indexed, retrieving context,
284
  and querying DeepSeek.
285
  """
 
 
286
  for path in pdf_paths:
287
  self.add_document(path)
288
 
 
289
  context_chunks = self.retrieve_context(question)
290
  context = "\n\n".join(context_chunks)
291
 
 
295
  else:
296
  context_prompt = f"Using the following context:\n\n{context}\n\n"
297
 
298
+ # --- UPDATED SYSTEM PROMPT FOR COMPASSIA AI ---
299
+ system_prompt = """
300
+ You are CompassIA, the intelligent assistant for MaarifCompass, committed to supporting Turkiye Maarif Foundation graduates residing in Turkiye.
301
+
302
+ Your core function is to deliver precise, document-backed information concerning their needs, primarily focusing on:
303
+ - University application procedures, requirements, tuition fees, and scholarship opportunities
304
+ - Accommodation and housing resources
305
+ - Career networking and professional development
306
+ - Relevant administrative and support services.
307
+ - Information related to Turkiye Maarif Foundation, Turkiye Scholarship and more.
308
+
309
+ You operate exclusively with data from a designated Document Center. **It is imperative that every piece of information you provide is directly sourced and verifiable from these internal documents.**
310
+
311
+ **Should a query fall outside the scope of the provided documents or lack a direct answer within them, you are required to politely inform the user that the specific information is not available in your current knowledge base, without offering any external insights or assumptions.**
312
+
313
+ Your answers should be highly accurate, directly relevant, easy to understand, and always prioritize the user's query based strictly on documented facts.
314
+ **Remember, you always answer the user with the language of the question.**
315
+ """
316
+
317
  messages = [
318
+ {"role": "system", "content": system_prompt},
319
  {"role": "user", "content": f"{context_prompt}Question: {question}"}
320
  ]
321
 
 
322
  print("\nSending request to DeepSeek API...")
323
  data = {
324
+ "model": "deepseek/deepseek-chat:free",
325
  "messages": messages,
326
+ "temperature": 0.5,
327
+ "max_tokens": 500,
328
  }
329
 
330
  response = requests.post(API_URL, json=data, headers=HEADERS)
 
342
 
343
  # --- Main execution logic ---
344
  if __name__ == "__main__":
 
 
345
  rag_system = DocumentRAG(
346
  embedding_model=embedding_model,
347
+ persist_directory="./chroma_db",
348
+ collection_name="pdf_documents_collection",
349
  chunk_size=700,
350
  overlap=100
351
  )
352
 
 
 
 
353
  pdf_document_paths = [
354
+ "documents/heracles_tr.pdf",
355
+ "documents/heracles_en.pdf",
 
356
  "documents/ogrenci_katki_payi_ogrenim_ucretleri.pdf",
357
  "documents/Ogrenci_Liderligi_Burs_Programi_Sozlesme_Metni_2024-2025.pdf",
358
  "documents/tmv-bursluluk-yonergesi.pdf"
359
  ]
360
 
 
 
361
  print("\n--- Indexing Documents ---")
362
  for pdf_path in pdf_document_paths:
363
  if os.path.exists(pdf_path):
 
365
  else:
366
  print(f"Error: PDF file not found at {pdf_path}. Please check the path.")
367
 
368
+ print("\n--- Chat With CompassIA (Type 'quit' to exit) ---")
 
369
  while True:
370
+ user_question = input("\nHow can I help you? ")
371
  if user_question.lower() == 'quit':
372
  print("Exiting chat.")
373
  break
374
 
375
+ rag_system.answer_question(user_question, pdf_document_paths)