dnj0 commited on
Commit
292292a
·
1 Parent(s): f84a554
Files changed (4) hide show
  1. src/config.py +9 -9
  2. src/pdf_parser.py +48 -8
  3. src/rag_system.py +191 -62
  4. src/vector_store.py +19 -17
src/config.py CHANGED
@@ -2,8 +2,8 @@ import os
2
  from pathlib import Path
3
 
4
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "")
5
- OPENAI_MODEL = "gpt-4o-mini" # Cheaper model variant
6
- USE_CACHE = True # Enable response caching
7
 
8
  CHROMA_DB_PATH = "./chroma_db"
9
  DOCSTORE_PATH = "./docstore"
@@ -12,10 +12,10 @@ PROCESSED_FILES_LOG = "./processed_files.txt"
12
  EMBEDDING_MODEL = "sentence-transformers/all-mpnet-base-v2"
13
  EMBEDDING_DIM = 768
14
 
15
- MAX_CHUNK_SIZE = 500 # Smaller chunks = fewer tokens
16
- CHUNK_OVERLAP = 50 # Less overlap = fewer chunks
17
- TEMPERATURE = 0.3 # Lower = faster, cheaper
18
- MAX_TOKENS = 500 # Limit response size (vs 1500)
19
 
20
  LANGUAGE = "russian"
21
 
@@ -26,6 +26,6 @@ UPLOAD_FOLDER = "./uploaded_pdfs"
26
  Path(UPLOAD_FOLDER).mkdir(exist_ok=True)
27
  MAX_PDF_SIZE_MB = 50
28
 
29
- BATCH_SEARCH_RESULTS = 3
30
- CACHE_RESPONSES = True
31
- SUMMARIZE_FIRST = True
 
2
  from pathlib import Path
3
 
4
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "")
5
+ OPENAI_MODEL = "gpt-4o-mini"
6
+ USE_CACHE = True
7
 
8
  CHROMA_DB_PATH = "./chroma_db"
9
  DOCSTORE_PATH = "./docstore"
 
12
  EMBEDDING_MODEL = "sentence-transformers/all-mpnet-base-v2"
13
  EMBEDDING_DIM = 768
14
 
15
+ MAX_CHUNK_SIZE = 500
16
+ CHUNK_OVERLAP = 50
17
+ TEMPERATURE = 0.3
18
+ MAX_TOKENS = 500
19
 
20
  LANGUAGE = "russian"
21
 
 
26
  Path(UPLOAD_FOLDER).mkdir(exist_ok=True)
27
  MAX_PDF_SIZE_MB = 50
28
 
29
+ BATCH_SEARCH_RESULTS = 3
30
+ CACHE_RESPONSES = True
31
+ SUMMARIZE_FIRST = True
src/pdf_parser.py CHANGED
@@ -9,13 +9,16 @@ from PIL import Image
9
  import pytesseract
10
  from config import DOCSTORE_PATH, PROCESSED_FILES_LOG
11
 
 
12
  class PDFParser:
13
  def __init__(self, debug: bool = True):
14
  self.docstore_path = Path(DOCSTORE_PATH)
15
  self.docstore_path.mkdir(exist_ok=True)
16
  self.processed_files = self._load_processed_files()
17
  self.debug = debug
 
18
  self._configure_tesseract()
 
19
  if self.debug:
20
  print("PDFParser initialized")
21
 
@@ -31,13 +34,13 @@ class PDFParser:
31
  print(f"[PDF Parser] {label}")
32
  if isinstance(data, dict):
33
  for key, val in data.items():
34
- print(f" {key}: {val}")
35
  elif isinstance(data, (list, tuple)):
36
- print(f" Count: {len(data)}")
37
  for i, item in enumerate(data[:3]):
38
- print(f" [{i}]: {str(item)[:100]}")
39
  else:
40
- print(f" {data}")
41
 
42
  def _load_processed_files(self) -> Dict[str, str]:
43
  if os.path.exists(PROCESSED_FILES_LOG):
@@ -66,12 +69,14 @@ class PDFParser:
66
  reader = PyPDF2.PdfReader(file)
67
  page_count = len(reader.pages)
68
  self._debug_print("PDF Text Extraction", f"Total pages: {page_count}")
 
69
  for page_num, page in enumerate(reader.pages):
70
  page_text = page.extract_text()
71
  text += page_text + "\n"
72
  self._debug_print(f"Page {page_num+1} Text Length", len(page_text))
73
  except Exception as e:
74
  self._debug_print("ERROR extracting text", str(e))
 
75
  self._debug_print("Total Text Extracted", len(text))
76
  return text
77
 
@@ -79,24 +84,33 @@ class PDFParser:
79
  images_data = []
80
  try:
81
  self._debug_print("Image Extraction Started", f"File: {pdf_path}")
 
82
  images = convert_from_path(pdf_path, dpi=150)
83
- self._debug_print("PDF to Images Conversion", f"Total images: {len(images)}")
 
84
  for idx, image in enumerate(images):
85
  self._debug_print(f"Processing Image {idx}", f"Size: {image.size}")
 
86
  image_path = self.docstore_path / f"{doc_id}_image_{idx}.png"
87
  image.save(image_path)
88
  self._debug_print(f"Image {idx} Saved", str(image_path))
 
89
  self._debug_print(f"Image {idx} OCR")
 
90
  try:
91
  ocr_text = pytesseract.image_to_string(image, lang='rus')
 
92
  ocr_text = ocr_text.strip()
 
93
  if not ocr_text or len(ocr_text) < 5:
94
- self._debug_print(f"Image {idx} OCR Result", f"EMPTY ({len(ocr_text)} chars)")
95
  else:
96
  self._debug_print(f"Image {idx} OCR Result", f"Success - {len(ocr_text)} chars: {ocr_text[:150]}")
 
97
  except Exception as ocr_error:
98
  self._debug_print(f"Image {idx} OCR ERROR", str(ocr_error))
99
- ocr_text = f"Image {idx}: OCR failed - {str(ocr_error)}"
 
100
  images_data.append({
101
  'page': idx,
102
  'path': str(image_path),
@@ -105,6 +119,7 @@ class PDFParser:
105
  })
106
  except Exception as e:
107
  self._debug_print("ERROR extracting images", str(e))
 
108
  self._debug_print("Image Extraction Complete", f"Total: {len(images_data)}")
109
  return images_data
110
 
@@ -113,7 +128,9 @@ class PDFParser:
113
  try:
114
  text = self._extract_text_from_pdf(pdf_path)
115
  lines = text.split('\n')
 
116
  self._debug_print("Table Detection", f"Scanning {len(lines)} lines")
 
117
  current_table = []
118
  for line in lines:
119
  if '|' in line or '\t' in line:
@@ -125,37 +142,48 @@ class PDFParser:
125
  'description': f"Table {len(tables_data) + 1}"
126
  })
127
  current_table = []
 
128
  if current_table and len(current_table) > 1:
129
  tables_data.append({
130
  'content': '\n'.join(current_table),
131
  'description': f"Table {len(tables_data) + 1}"
132
  })
 
133
  self._debug_print("Tables Found", len(tables_data))
134
  except Exception as e:
135
  self._debug_print("ERROR extracting tables", str(e))
 
136
  return tables_data
137
 
138
  def parse_pdf(self, pdf_path: str) -> Tuple[str, List[Dict], List[Dict]]:
139
  file_hash = self._get_file_hash(pdf_path)
140
  doc_id = Path(pdf_path).stem
 
141
  self._debug_print("PDF Parsing Started", f"File: {doc_id}")
 
142
  if doc_id in self.processed_files:
143
  if self.processed_files[doc_id] == file_hash:
144
  self._debug_print("Status", f"File {doc_id} already processed")
145
  return self._load_extracted_data(doc_id)
 
146
  print(f"Processing PDF: {doc_id}")
 
147
  text = self._extract_text_from_pdf(pdf_path)
148
  images = self._extract_images_from_pdf(pdf_path, doc_id)
149
  tables = self._extract_tables_from_pdf(pdf_path, doc_id)
 
150
  self._debug_print("Extraction Summary", {
151
  'text_length': len(text),
152
  'images_count': len(images),
153
  'tables_count': len(tables),
154
  'images_with_ocr': sum(1 for img in images if img.get('ocr_text', '').strip())
155
  })
 
156
  self._save_extracted_data(doc_id, text, images, tables)
 
157
  self.processed_files[doc_id] = file_hash
158
  self._save_processed_files()
 
159
  return text, images, tables
160
 
161
  def _save_extracted_data(self, doc_id: str, text: str, images: List[Dict], tables: List[Dict]):
@@ -167,6 +195,7 @@ class PDFParser:
167
  data_path = self.docstore_path / f"{doc_id}_data.json"
168
  with open(data_path, 'w', encoding='utf-8') as f:
169
  json.dump(data, f, ensure_ascii=False, indent=2)
 
170
  self._debug_print("Data Saved", str(data_path))
171
 
172
  def _load_extracted_data(self, doc_id: str) -> Tuple[str, List[Dict], List[Dict]]:
@@ -176,4 +205,15 @@ class PDFParser:
176
  data = json.load(f)
177
  return data['text'], data['images'], data['tables']
178
  except:
179
- return "", [], []
 
 
 
 
 
 
 
 
 
 
 
 
9
  import pytesseract
10
  from config import DOCSTORE_PATH, PROCESSED_FILES_LOG
11
 
12
+
13
  class PDFParser:
14
  def __init__(self, debug: bool = True):
15
  self.docstore_path = Path(DOCSTORE_PATH)
16
  self.docstore_path.mkdir(exist_ok=True)
17
  self.processed_files = self._load_processed_files()
18
  self.debug = debug
19
+
20
  self._configure_tesseract()
21
+
22
  if self.debug:
23
  print("PDFParser initialized")
24
 
 
34
  print(f"[PDF Parser] {label}")
35
  if isinstance(data, dict):
36
  for key, val in data.items():
37
+ print(f" {key}: {val}")
38
  elif isinstance(data, (list, tuple)):
39
+ print(f" Count: {len(data)}")
40
  for i, item in enumerate(data[:3]):
41
+ print(f" [{i}]: {str(item)[:100]}")
42
  else:
43
+ print(f" {data}")
44
 
45
  def _load_processed_files(self) -> Dict[str, str]:
46
  if os.path.exists(PROCESSED_FILES_LOG):
 
69
  reader = PyPDF2.PdfReader(file)
70
  page_count = len(reader.pages)
71
  self._debug_print("PDF Text Extraction", f"Total pages: {page_count}")
72
+
73
  for page_num, page in enumerate(reader.pages):
74
  page_text = page.extract_text()
75
  text += page_text + "\n"
76
  self._debug_print(f"Page {page_num+1} Text Length", len(page_text))
77
  except Exception as e:
78
  self._debug_print("ERROR extracting text", str(e))
79
+
80
  self._debug_print("Total Text Extracted", len(text))
81
  return text
82
 
 
84
  images_data = []
85
  try:
86
  self._debug_print("Image Extraction Started", f"File: {pdf_path}")
87
+
88
  images = convert_from_path(pdf_path, dpi=150)
89
+ self._debug_print("PDF to Images", f"Total images: {len(images)}")
90
+
91
  for idx, image in enumerate(images):
92
  self._debug_print(f"Processing Image {idx}", f"Size: {image.size}")
93
+
94
  image_path = self.docstore_path / f"{doc_id}_image_{idx}.png"
95
  image.save(image_path)
96
  self._debug_print(f"Image {idx} Saved", str(image_path))
97
+
98
  self._debug_print(f"Image {idx} OCR")
99
+
100
  try:
101
  ocr_text = pytesseract.image_to_string(image, lang='rus')
102
+
103
  ocr_text = ocr_text.strip()
104
+
105
  if not ocr_text or len(ocr_text) < 5:
106
+ self._debug_print(f"Image {idx} OCR Result", f"EMPTY or very short ({len(ocr_text)} chars)")
107
  else:
108
  self._debug_print(f"Image {idx} OCR Result", f"Success - {len(ocr_text)} chars: {ocr_text[:150]}")
109
+
110
  except Exception as ocr_error:
111
  self._debug_print(f"Image {idx} OCR ERROR", str(ocr_error))
112
+ ocr_text = f"[Image {idx}: OCR failed - {str(ocr_error)}]"
113
+
114
  images_data.append({
115
  'page': idx,
116
  'path': str(image_path),
 
119
  })
120
  except Exception as e:
121
  self._debug_print("ERROR extracting images", str(e))
122
+
123
  self._debug_print("Image Extraction Complete", f"Total: {len(images_data)}")
124
  return images_data
125
 
 
128
  try:
129
  text = self._extract_text_from_pdf(pdf_path)
130
  lines = text.split('\n')
131
+
132
  self._debug_print("Table Detection", f"Scanning {len(lines)} lines")
133
+
134
  current_table = []
135
  for line in lines:
136
  if '|' in line or '\t' in line:
 
142
  'description': f"Table {len(tables_data) + 1}"
143
  })
144
  current_table = []
145
+
146
  if current_table and len(current_table) > 1:
147
  tables_data.append({
148
  'content': '\n'.join(current_table),
149
  'description': f"Table {len(tables_data) + 1}"
150
  })
151
+
152
  self._debug_print("Tables Found", len(tables_data))
153
  except Exception as e:
154
  self._debug_print("ERROR extracting tables", str(e))
155
+
156
  return tables_data
157
 
158
  def parse_pdf(self, pdf_path: str) -> Tuple[str, List[Dict], List[Dict]]:
159
  file_hash = self._get_file_hash(pdf_path)
160
  doc_id = Path(pdf_path).stem
161
+
162
  self._debug_print("PDF Parsing Started", f"File: {doc_id}")
163
+
164
  if doc_id in self.processed_files:
165
  if self.processed_files[doc_id] == file_hash:
166
  self._debug_print("Status", f"File {doc_id} already processed")
167
  return self._load_extracted_data(doc_id)
168
+
169
  print(f"Processing PDF: {doc_id}")
170
+
171
  text = self._extract_text_from_pdf(pdf_path)
172
  images = self._extract_images_from_pdf(pdf_path, doc_id)
173
  tables = self._extract_tables_from_pdf(pdf_path, doc_id)
174
+
175
  self._debug_print("Extraction Summary", {
176
  'text_length': len(text),
177
  'images_count': len(images),
178
  'tables_count': len(tables),
179
  'images_with_ocr': sum(1 for img in images if img.get('ocr_text', '').strip())
180
  })
181
+
182
  self._save_extracted_data(doc_id, text, images, tables)
183
+
184
  self.processed_files[doc_id] = file_hash
185
  self._save_processed_files()
186
+
187
  return text, images, tables
188
 
189
  def _save_extracted_data(self, doc_id: str, text: str, images: List[Dict], tables: List[Dict]):
 
195
  data_path = self.docstore_path / f"{doc_id}_data.json"
196
  with open(data_path, 'w', encoding='utf-8') as f:
197
  json.dump(data, f, ensure_ascii=False, indent=2)
198
+
199
  self._debug_print("Data Saved", str(data_path))
200
 
201
  def _load_extracted_data(self, doc_id: str) -> Tuple[str, List[Dict], List[Dict]]:
 
205
  data = json.load(f)
206
  return data['text'], data['images'], data['tables']
207
  except:
208
+ return "", [], []
209
+
210
+ def get_all_documents(self) -> Dict:
211
+ all_docs = {}
212
+ for json_file in self.docstore_path.glob("*_data.json"):
213
+ doc_id = json_file.stem.replace("_data", "")
214
+ try:
215
+ with open(json_file, 'r', encoding='utf-8') as f:
216
+ all_docs[doc_id] = json.load(f)
217
+ except:
218
+ pass
219
+ return all_docs
src/rag_system.py CHANGED
@@ -1,25 +1,32 @@
1
  from typing import List, Dict
2
  from langchain_openai import ChatOpenAI
3
- from langchain_core.messages import HumanMessage
4
  import base64
5
  import os
6
  from pathlib import Path
7
  from config import (
8
- OPENAI_API_KEY, OPENAI_MODEL, TEMPERATURE, MAX_TOKENS,
9
  LANGUAGE, CHROMA_DB_PATH
10
  )
11
 
 
12
  class VisualMultimodalRAG:
 
13
  def __init__(self, api_key: str = None, debug: bool = True):
14
  api_key = api_key or OPENAI_API_KEY
15
  self.debug = debug
 
 
16
  self.llm = ChatOpenAI(
17
- model_name="gpt-4o-mini",
18
  api_key=api_key,
19
  temperature=TEMPERATURE,
20
  max_tokens=MAX_TOKENS,
21
  )
 
22
  self.language = LANGUAGE
 
 
23
  if self.debug:
24
  print("VisualMultimodalRAG initialized")
25
 
@@ -27,10 +34,10 @@ class VisualMultimodalRAG:
27
  if self.debug:
28
  print(f"DEBUG [{label}]:")
29
  if isinstance(data, (list, dict)):
30
- print(f" Type: {type(data).__name__}")
31
- print(f" Content: {str(data)[:300]}...")
32
  else:
33
- print(f" {data}")
34
 
35
  def _image_to_base64(self, image_path: str) -> str:
36
  try:
@@ -43,11 +50,13 @@ class VisualMultimodalRAG:
43
 
44
  def analyze_image_visually(self, image_path: str, image_idx: int) -> str:
45
  if not os.path.exists(image_path):
46
- return f"Image {image_idx}: File not found - {image_path}"
 
47
  try:
48
  image_base64 = self._image_to_base64(image_path)
49
  if not image_base64:
50
- return f"Image {image_idx}: Could not convert to base64"
 
51
  file_ext = Path(image_path).suffix.lower()
52
  media_type_map = {
53
  '.jpg': 'image/jpeg',
@@ -57,7 +66,9 @@ class VisualMultimodalRAG:
57
  '.webp': 'image/webp'
58
  }
59
  media_type = media_type_map.get(file_ext, 'image/png')
60
- print(f"Analyzing image {image_idx} visually (as {media_type})...")
 
 
61
  message = HumanMessage(
62
  content=[
63
  {
@@ -80,25 +91,34 @@ Analysis:"""
80
  }
81
  ],
82
  )
 
83
  response = self.llm.invoke([message])
84
  analysis = response.content.strip()
 
85
  if self.debug:
86
  self._debug_print(f"Image {image_idx} Visual Analysis", analysis)
 
87
  print(f"Image {image_idx} analyzed successfully")
88
  return analysis
 
89
  except Exception as e:
90
- error_msg = f"Image {image_idx}: Vision analysis failed - {str(e)}"
91
  print(f"Error analyzing image {image_idx}: {e}")
92
  return error_msg
93
 
94
  def analyze_images_visually(self, images: List[Dict]) -> List[Dict]:
 
95
  visual_analyses = []
 
96
  for idx, image in enumerate(images):
97
  image_path = image.get('path', '')
 
98
  if not image_path:
99
  print(f"Image {idx}: No path provided")
100
  continue
 
101
  visual_analysis = self.analyze_image_visually(image_path, idx)
 
102
  visual_analyses.append({
103
  'type': 'image_visual',
104
  'image_index': idx,
@@ -106,24 +126,32 @@ Analysis:"""
106
  'visual_analysis': visual_analysis,
107
  'ocr_text': image.get('ocr_text', '')
108
  })
 
109
  return visual_analyses
110
 
111
  def summarize_text_chunks(self, text: str, chunk_size: int = 1500) -> List[Dict]:
112
  chunks = []
113
  text_chunks = self._chunk_text(text, chunk_size=chunk_size, overlap=300)
 
114
  self._debug_print("Text Chunking", f"Created {len(text_chunks)} chunks")
 
115
  for idx, chunk in enumerate(text_chunks):
116
  if len(chunk.strip()) < 50:
117
  continue
 
118
  try:
119
  prompt = f"""Summarize this text chunk in {self.language}.
120
  Be brief and meaningful. Extract key points, facts, and main ideas.
 
121
  Text Chunk:
122
  {chunk}
 
123
  Summary:"""
 
124
  message = HumanMessage(content=prompt)
125
  response = self.llm.invoke([message])
126
  summary = response.content.strip()
 
127
  chunks.append({
128
  'type': 'text_chunk',
129
  'chunk_index': len(chunks),
@@ -131,27 +159,37 @@ Summary:"""
131
  'summary': summary,
132
  'chunk_length': len(chunk)
133
  })
 
134
  if self.debug:
135
  self._debug_print(f"Text Chunk {len(chunks)-1} Summary", summary)
 
136
  except Exception as e:
137
  print(f"Error summarizing text chunk: {e}")
 
138
  return chunks
139
 
140
  def summarize_tables(self, tables: List[Dict]) -> List[Dict]:
141
  summaries = []
 
142
  for idx, table in enumerate(tables):
143
  table_content = table.get('content', '')
 
144
  if not table_content or len(table_content.strip()) < 10:
145
  continue
 
146
  try:
147
  prompt = f"""Analyze and summarize this table/structured data in {self.language}.
148
  Extract key insights, row/column meanings, and important figures. Be brief and meaningful.
 
149
  Table Content:
150
  {table_content}
 
151
  Summary:"""
 
152
  message = HumanMessage(content=prompt)
153
  response = self.llm.invoke([message])
154
  summary = response.content.strip()
 
155
  summaries.append({
156
  'type': 'table',
157
  'table_index': idx,
@@ -159,21 +197,27 @@ Summary:"""
159
  'summary': summary,
160
  'table_length': len(table_content)
161
  })
 
162
  if self.debug:
163
  self._debug_print(f"Table {idx} Summary", summary)
 
164
  except Exception as e:
165
  print(f"Error summarizing table {idx}: {e}")
 
166
  return summaries
167
 
168
  def process_and_store_document(
169
- self,
170
- text: str,
171
  images: List[Dict],
172
  tables: List[Dict],
173
  vector_store,
174
  doc_id: str
175
  ) -> Dict:
176
- print("PROCESSING WITH VISUAL IMAGE ANALYSIS: " + doc_id)
 
 
 
177
  results = {
178
  'doc_id': doc_id,
179
  'image_visual_analyses': [],
@@ -181,42 +225,54 @@ Summary:"""
181
  'table_summaries': [],
182
  'total_stored': 0
183
  }
184
- print("VISUAL IMAGE ANALYSIS (" + str(len(images)) + " total)")
 
 
 
 
185
  image_analyses = self.analyze_images_visually(images)
186
  results['image_visual_analyses'] = image_analyses
 
187
  image_docs = {
188
  'text': ' | '.join([
189
- f"Image {a['image_index']}: {a['visual_analysis']}"
190
  for a in image_analyses
191
  ]),
192
  'images': [],
193
  'tables': []
194
  }
 
195
  for analysis in image_analyses:
196
- print(f" Image {analysis['image_index']} (visual analysis)")
197
- print(f" Path: {analysis['image_path']}")
198
- print(f" Analysis: {analysis['visual_analysis'][:100]}...")
 
199
  if image_analyses:
200
  try:
201
  vector_store.add_documents(
202
- image_docs,
203
  f"{doc_id}_images_visual"
204
  )
205
  results['total_stored'] += len(image_analyses)
206
- print(f"Stored {len(image_analyses)} image visual analyses")
207
  except Exception as e:
208
- print(f"Error storing image analyses: {e}")
209
- print("TEXT CHUNK SUMMARIZATION")
 
 
210
  text_summaries = self.summarize_text_chunks(text)
211
  results['text_summaries'] = text_summaries
 
212
  text_docs = {
213
- 'text': ' | '.join([f"Chunk {s['chunk_index']}: {s['summary']}"
214
- for s in text_summaries]),
215
  'images': [],
216
  'tables': []
217
  }
 
218
  for summary in text_summaries:
219
- print(f" Chunk {summary['chunk_index']}: {summary['summary'][:50]}...")
 
220
  if text_summaries:
221
  try:
222
  vector_store.add_documents(
@@ -224,20 +280,25 @@ Summary:"""
224
  f"{doc_id}_text_chunks"
225
  )
226
  results['total_stored'] += len(text_summaries)
227
- print(f"Stored {len(text_summaries)} text chunk summaries")
228
  except Exception as e:
229
- print(f"Error storing text summaries: {e}")
230
- print("TABLE SUMMARIZATION (" + str(len(tables)) + " total)")
 
 
231
  table_summaries = self.summarize_tables(tables)
232
  results['table_summaries'] = table_summaries
 
233
  table_docs = {
234
- 'text': ' | '.join([f"Table {s['table_index']}: {s['summary']}"
235
- for s in table_summaries]),
236
  'images': [],
237
  'tables': []
238
  }
 
239
  for summary in table_summaries:
240
- print(f" Table {summary['table_index']}: {summary['summary'][:50]}...")
 
241
  if table_summaries:
242
  try:
243
  vector_store.add_documents(
@@ -245,17 +306,21 @@ Summary:"""
245
  f"{doc_id}_tables"
246
  )
247
  results['total_stored'] += len(table_summaries)
248
- print(f"Stored {len(table_summaries)} table summaries")
249
  except Exception as e:
250
- print(f"Error storing table summaries: {e}")
251
- print("STORAGE SUMMARY")
252
- print(f" Images analyzed and stored: {len(image_analyses)}")
253
- print(f" Text chunks summarized and stored: {len(text_summaries)}")
254
- print(f" Tables summarized and stored: {len(table_summaries)}")
255
- print(f" Total items stored in vector: {results['total_stored']}")
 
 
 
256
  return results
257
 
258
  def _chunk_text(self, text: str, chunk_size: int = 1500, overlap: int = 300) -> List[str]:
 
259
  chunks = []
260
  start = 0
261
  while start < len(text):
@@ -264,41 +329,56 @@ Summary:"""
264
  start = end - overlap
265
  return chunks
266
 
 
 
 
 
 
267
  class AnsweringRAG:
 
268
  def __init__(self, api_key: str = None, debug: bool = True):
269
  api_key = api_key or OPENAI_API_KEY
270
  self.debug = debug
 
271
  self.llm = ChatOpenAI(
272
- model_name="gpt-4o-mini",
273
  api_key=api_key,
274
  temperature=TEMPERATURE,
275
  max_tokens=MAX_TOKENS,
276
  )
 
277
  self.language = LANGUAGE
 
 
278
  if self.debug:
279
- print("AnsweringRAG initialized")
280
 
281
  def _debug_print(self, label: str, data: any):
282
  if self.debug:
283
- print(f"DEBUG [{label}]:")
284
  if isinstance(data, (list, dict)):
285
- print(f" Type: {type(data).__name__}")
286
- print(f" Content: {str(data)[:300]}...")
287
  else:
288
- print(f" {data}")
289
 
290
  def analyze_and_answer(
291
- self,
292
- question: str,
293
  search_results: List[Dict]
294
  ) -> Dict:
295
- print("ANALYZING QUESTION & GENERATING ANSWER")
 
 
 
296
  print(f"Question: {question}")
297
  print(f"Search Results Found: {len(search_results)}")
 
298
  if not search_results:
299
- print("No search results found!")
300
- answer = f"""No relevant information in the document to answer question: "{question}"
301
  """
 
302
  result = {
303
  'question': question,
304
  'answer': answer,
@@ -306,23 +386,33 @@ class AnsweringRAG:
306
  'confidence': 'low',
307
  'search_results': []
308
  }
 
309
  return result
 
310
  context_parts = []
311
  for idx, result in enumerate(search_results, 1):
312
  content = result.get('content', '')
 
313
  content_type = result.get('type', 'unknown')
314
  distance = result.get('distance', 0)
315
  relevance = 1 - distance if distance else 0
 
316
  context_parts.append(f"""
317
  [Source {idx} - {content_type.upper()} (relevance: {relevance:.1%})]
318
  {content}""")
 
319
  full_context = "\n".join(context_parts)
 
320
  self._debug_print("Context Prepared", f"{len(context_parts)} sources, {len(full_context)} chars")
 
321
  analysis_prompt = f"""You are a helpful assistant analyzing document content to answer user questions.
 
322
  USER QUESTION:
323
  "{question}"
 
324
  RELEVANT CONTENT FROM DOCUMENT:
325
  {full_context}
 
326
  INSTRUCTIONS:
327
  1. Analyze the provided content carefully
328
  2. Extract information relevant to the question
@@ -331,29 +421,37 @@ INSTRUCTIONS:
331
  5. Be specific and cite the content when relevant
332
  6. Structure your answer clearly with key points
333
  ANSWER:"""
334
- print("Analyzing search results...")
335
- print(f" Context size: {len(full_context)} characters")
336
- print(f" Sources: {len(search_results)}")
 
 
337
  try:
338
  message = HumanMessage(content=analysis_prompt)
339
  response = self.llm.invoke([message])
340
  answer = response.content.strip()
 
341
  confidence = self._estimate_confidence(len(search_results), answer)
342
- print("Answer generated successfully")
343
- print(f" Confidence: {confidence}")
344
- print(f" Answer length: {len(answer)} characters")
 
 
345
  result = {
346
  'question': question,
347
  'answer': answer,
348
  'sources_used': len(search_results),
349
  'confidence': confidence,
350
- 'search_results': search_results,
351
- 'formatted_sources': self._format_sources(search_results)
352
  }
 
 
353
  return result
 
354
  except Exception as e:
355
- print(f"Error generating answer: {e}")
356
- answer = "Error while analyzing the search results"
 
357
  result = {
358
  'question': question,
359
  'answer': answer,
@@ -362,24 +460,55 @@ ANSWER:"""
362
  'error': str(e),
363
  'search_results': search_results
364
  }
 
 
365
  return result
366
 
367
  def _estimate_confidence(self, sources_count: int, answer: str) -> str:
368
  answer_length = len(answer)
 
369
  if sources_count >= 3 and answer_length > 500:
370
  return "high"
 
371
  elif sources_count >= 2 and answer_length > 200:
372
  return "medium"
 
373
  else:
374
  return "low"
375
 
376
- def _format_sources(self, search_results: List[Dict]) -> List[Dict]:
 
 
 
 
 
 
 
377
  formatted_sources = []
378
- for idx, source in enumerate(search_results, 1):
379
  formatted_sources.append({
380
  'index': idx,
381
  'type': source.get('type', 'unknown'),
382
  'content': source.get('content', ''),
383
  'relevance': 1 - source.get('distance', 0) if source.get('distance') else 0
384
  })
385
- return formatted_sources
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from typing import List, Dict
2
  from langchain_openai import ChatOpenAI
3
+ from langchain_core.messages import HumanMessage, SystemMessage
4
  import base64
5
  import os
6
  from pathlib import Path
7
  from config import (
8
+ OPENAI_API_KEY, OPENAI_MODEL, TEMPERATURE, MAX_TOKENS,
9
  LANGUAGE, CHROMA_DB_PATH
10
  )
11
 
12
+
13
  class VisualMultimodalRAG:
14
+
15
  def __init__(self, api_key: str = None, debug: bool = True):
16
  api_key = api_key or OPENAI_API_KEY
17
  self.debug = debug
18
+
19
+
20
  self.llm = ChatOpenAI(
21
+ model_name="gpt-4o-mini",
22
  api_key=api_key,
23
  temperature=TEMPERATURE,
24
  max_tokens=MAX_TOKENS,
25
  )
26
+
27
  self.language = LANGUAGE
28
+ self.visual_summaries_log = []
29
+
30
  if self.debug:
31
  print("VisualMultimodalRAG initialized")
32
 
 
34
  if self.debug:
35
  print(f"DEBUG [{label}]:")
36
  if isinstance(data, (list, dict)):
37
+ print(f" Type: {type(data).__name__}")
38
+ print(f" Content: {str(data)[:300]}...")
39
  else:
40
+ print(f" {data}")
41
 
42
  def _image_to_base64(self, image_path: str) -> str:
43
  try:
 
50
 
51
  def analyze_image_visually(self, image_path: str, image_idx: int) -> str:
52
  if not os.path.exists(image_path):
53
+ return f"[Image {image_idx}: File not found - {image_path}]"
54
+
55
  try:
56
  image_base64 = self._image_to_base64(image_path)
57
  if not image_base64:
58
+ return f"[Image {image_idx}: Could not convert to base64]"
59
+
60
  file_ext = Path(image_path).suffix.lower()
61
  media_type_map = {
62
  '.jpg': 'image/jpeg',
 
66
  '.webp': 'image/webp'
67
  }
68
  media_type = media_type_map.get(file_ext, 'image/png')
69
+
70
+ print(f"Analyzing image {image_idx}...")
71
+
72
  message = HumanMessage(
73
  content=[
74
  {
 
91
  }
92
  ],
93
  )
94
+
95
  response = self.llm.invoke([message])
96
  analysis = response.content.strip()
97
+
98
  if self.debug:
99
  self._debug_print(f"Image {image_idx} Visual Analysis", analysis)
100
+
101
  print(f"Image {image_idx} analyzed successfully")
102
  return analysis
103
+
104
  except Exception as e:
105
+ error_msg = f"[Image {image_idx}: Vision analysis failed - {str(e)}]"
106
  print(f"Error analyzing image {image_idx}: {e}")
107
  return error_msg
108
 
109
  def analyze_images_visually(self, images: List[Dict]) -> List[Dict]:
110
+
111
  visual_analyses = []
112
+
113
  for idx, image in enumerate(images):
114
  image_path = image.get('path', '')
115
+
116
  if not image_path:
117
  print(f"Image {idx}: No path provided")
118
  continue
119
+
120
  visual_analysis = self.analyze_image_visually(image_path, idx)
121
+
122
  visual_analyses.append({
123
  'type': 'image_visual',
124
  'image_index': idx,
 
126
  'visual_analysis': visual_analysis,
127
  'ocr_text': image.get('ocr_text', '')
128
  })
129
+
130
  return visual_analyses
131
 
132
  def summarize_text_chunks(self, text: str, chunk_size: int = 1500) -> List[Dict]:
133
  chunks = []
134
  text_chunks = self._chunk_text(text, chunk_size=chunk_size, overlap=300)
135
+
136
  self._debug_print("Text Chunking", f"Created {len(text_chunks)} chunks")
137
+
138
  for idx, chunk in enumerate(text_chunks):
139
  if len(chunk.strip()) < 50:
140
  continue
141
+
142
  try:
143
  prompt = f"""Summarize this text chunk in {self.language}.
144
  Be brief and meaningful. Extract key points, facts, and main ideas.
145
+
146
  Text Chunk:
147
  {chunk}
148
+
149
  Summary:"""
150
+
151
  message = HumanMessage(content=prompt)
152
  response = self.llm.invoke([message])
153
  summary = response.content.strip()
154
+
155
  chunks.append({
156
  'type': 'text_chunk',
157
  'chunk_index': len(chunks),
 
159
  'summary': summary,
160
  'chunk_length': len(chunk)
161
  })
162
+
163
  if self.debug:
164
  self._debug_print(f"Text Chunk {len(chunks)-1} Summary", summary)
165
+
166
  except Exception as e:
167
  print(f"Error summarizing text chunk: {e}")
168
+
169
  return chunks
170
 
171
  def summarize_tables(self, tables: List[Dict]) -> List[Dict]:
172
  summaries = []
173
+
174
  for idx, table in enumerate(tables):
175
  table_content = table.get('content', '')
176
+
177
  if not table_content or len(table_content.strip()) < 10:
178
  continue
179
+
180
  try:
181
  prompt = f"""Analyze and summarize this table/structured data in {self.language}.
182
  Extract key insights, row/column meanings, and important figures. Be brief and meaningful.
183
+
184
  Table Content:
185
  {table_content}
186
+
187
  Summary:"""
188
+
189
  message = HumanMessage(content=prompt)
190
  response = self.llm.invoke([message])
191
  summary = response.content.strip()
192
+
193
  summaries.append({
194
  'type': 'table',
195
  'table_index': idx,
 
197
  'summary': summary,
198
  'table_length': len(table_content)
199
  })
200
+
201
  if self.debug:
202
  self._debug_print(f"Table {idx} Summary", summary)
203
+
204
  except Exception as e:
205
  print(f"Error summarizing table {idx}: {e}")
206
+
207
  return summaries
208
 
209
  def process_and_store_document(
210
+ self,
211
+ text: str,
212
  images: List[Dict],
213
  tables: List[Dict],
214
  vector_store,
215
  doc_id: str
216
  ) -> Dict:
217
+
218
+ print(f"PROCESSING WITH VISUAL IMAGE ANALYSIS: {doc_id}")
219
+
220
+
221
  results = {
222
  'doc_id': doc_id,
223
  'image_visual_analyses': [],
 
225
  'table_summaries': [],
226
  'total_stored': 0
227
  }
228
+
229
+
230
+ print(f"VISUAL IMAGE ANALYSIS ({len(images)} total)")
231
+
232
+
233
  image_analyses = self.analyze_images_visually(images)
234
  results['image_visual_analyses'] = image_analyses
235
+
236
  image_docs = {
237
  'text': ' | '.join([
238
+ f"Image {a['image_index']}: {a['visual_analysis']}"
239
  for a in image_analyses
240
  ]),
241
  'images': [],
242
  'tables': []
243
  }
244
+
245
  for analysis in image_analyses:
246
+ print(f" Image {analysis['image_index']} (visual analysis)")
247
+ print(f" Path: {analysis['image_path']}")
248
+ print(f" Analysis: {analysis['visual_analysis'][:100]}...")
249
+
250
  if image_analyses:
251
  try:
252
  vector_store.add_documents(
253
+ image_docs,
254
  f"{doc_id}_images_visual"
255
  )
256
  results['total_stored'] += len(image_analyses)
257
+ print(f" Stored {len(image_analyses)} image visual analyses")
258
  except Exception as e:
259
+ print(f" Error storing image analyses: {e}")
260
+
261
+ print(f" TEXT CHUNK SUMMARIZATION")
262
+
263
  text_summaries = self.summarize_text_chunks(text)
264
  results['text_summaries'] = text_summaries
265
+
266
  text_docs = {
267
+ 'text': ' | '.join([f"Chunk {s['chunk_index']}: {s['summary']}"
268
+ for s in text_summaries]),
269
  'images': [],
270
  'tables': []
271
  }
272
+
273
  for summary in text_summaries:
274
+ print(f" Chunk {summary['chunk_index']}: {summary['summary'][:50]}...")
275
+
276
  if text_summaries:
277
  try:
278
  vector_store.add_documents(
 
280
  f"{doc_id}_text_chunks"
281
  )
282
  results['total_stored'] += len(text_summaries)
283
+ print(f" Stored {len(text_summaries)} text chunk summaries")
284
  except Exception as e:
285
+ print(f" Error storing text summaries: {e}")
286
+
287
+ print(f" TABLE SUMMARIZATION ({len(tables)} total)")
288
+
289
  table_summaries = self.summarize_tables(tables)
290
  results['table_summaries'] = table_summaries
291
+
292
  table_docs = {
293
+ 'text': ' | '.join([f"Table {s['table_index']}: {s['summary']}"
294
+ for s in table_summaries]),
295
  'images': [],
296
  'tables': []
297
  }
298
+
299
  for summary in table_summaries:
300
+ print(f" Table {summary['table_index']}: {summary['summary'][:50]}...")
301
+
302
  if table_summaries:
303
  try:
304
  vector_store.add_documents(
 
306
  f"{doc_id}_tables"
307
  )
308
  results['total_stored'] += len(table_summaries)
309
+ print(f" Stored {len(table_summaries)} table summaries")
310
  except Exception as e:
311
+ print(f" Error storing table summaries: {e}")
312
+
313
+ print(f" STORAGE SUMMARY")
314
+ print(f" Images analyzed visually & stored: {len(image_analyses)}")
315
+ print(f" Text chunks summarized & stored: {len(text_summaries)}")
316
+ print(f" Tables summarized & stored: {len(table_summaries)}")
317
+ print(f" Total items stored in vector: {results['total_stored']}")
318
+
319
+ self.visual_summaries_log.append(results)
320
  return results
321
 
322
  def _chunk_text(self, text: str, chunk_size: int = 1500, overlap: int = 300) -> List[str]:
323
+ """Split text into overlapping chunks"""
324
  chunks = []
325
  start = 0
326
  while start < len(text):
 
329
  start = end - overlap
330
  return chunks
331
 
332
+ def get_visual_summaries_log(self) -> List[Dict]:
333
+ """Get all visual analysis logs"""
334
+ return self.visual_summaries_log
335
+
336
+
337
  class AnsweringRAG:
338
+
339
  def __init__(self, api_key: str = None, debug: bool = True):
340
  api_key = api_key or OPENAI_API_KEY
341
  self.debug = debug
342
+
343
  self.llm = ChatOpenAI(
344
+ model_name="gpt-4o-mini",
345
  api_key=api_key,
346
  temperature=TEMPERATURE,
347
  max_tokens=MAX_TOKENS,
348
  )
349
+
350
  self.language = LANGUAGE
351
+ self.answer_log = []
352
+
353
  if self.debug:
354
+ print("AnsweringRAG initialized ")
355
 
356
  def _debug_print(self, label: str, data: any):
357
  if self.debug:
358
+ print(f" DEBUG [{label}]:")
359
  if isinstance(data, (list, dict)):
360
+ print(f" Type: {type(data).__name__}")
361
+ print(f" Content: {str(data)[:300]}...")
362
  else:
363
+ print(f" {data}")
364
 
365
  def analyze_and_answer(
366
+ self,
367
+ question: str,
368
  search_results: List[Dict]
369
  ) -> Dict:
370
+
371
+
372
+ print(f"ANALYZING QUESTION & GENERATING ANSWER")
373
+
374
  print(f"Question: {question}")
375
  print(f"Search Results Found: {len(search_results)}")
376
+
377
  if not search_results:
378
+ print(f"No search results found!")
379
+ answer = f"""No relevant information in the document to answer your question: "{question}"
380
  """
381
+
382
  result = {
383
  'question': question,
384
  'answer': answer,
 
386
  'confidence': 'low',
387
  'search_results': []
388
  }
389
+ self.answer_log.append(result)
390
  return result
391
+
392
  context_parts = []
393
  for idx, result in enumerate(search_results, 1):
394
  content = result.get('content', '')
395
+ metadata = result.get('metadata', {})
396
  content_type = result.get('type', 'unknown')
397
  distance = result.get('distance', 0)
398
  relevance = 1 - distance if distance else 0
399
+
400
  context_parts.append(f"""
401
  [Source {idx} - {content_type.upper()} (relevance: {relevance:.1%})]
402
  {content}""")
403
+
404
  full_context = "\n".join(context_parts)
405
+
406
  self._debug_print("Context Prepared", f"{len(context_parts)} sources, {len(full_context)} chars")
407
+
408
  analysis_prompt = f"""You are a helpful assistant analyzing document content to answer user questions.
409
+
410
  USER QUESTION:
411
  "{question}"
412
+
413
  RELEVANT CONTENT FROM DOCUMENT:
414
  {full_context}
415
+
416
  INSTRUCTIONS:
417
  1. Analyze the provided content carefully
418
  2. Extract information relevant to the question
 
421
  5. Be specific and cite the content when relevant
422
  6. Structure your answer clearly with key points
423
  ANSWER:"""
424
+
425
+ print(f"Analyzing search results...")
426
+ print(f" Context size: {len(full_context)} characters")
427
+ print(f" Sources: {len(search_results)}")
428
+
429
  try:
430
  message = HumanMessage(content=analysis_prompt)
431
  response = self.llm.invoke([message])
432
  answer = response.content.strip()
433
+
434
  confidence = self._estimate_confidence(len(search_results), answer)
435
+
436
+ print(f" Answer generated successfully")
437
+ print(f" Confidence: {confidence}")
438
+ print(f" Answer length: {len(answer)} characters")
439
+
440
  result = {
441
  'question': question,
442
  'answer': answer,
443
  'sources_used': len(search_results),
444
  'confidence': confidence,
445
+ 'search_results': search_results
 
446
  }
447
+
448
+ self.answer_log.append(result)
449
  return result
450
+
451
  except Exception as e:
452
+ print(f" Error generating answer: {e}")
453
+ answer = f"I encountered an error while analyzing the search results. Please try again."
454
+
455
  result = {
456
  'question': question,
457
  'answer': answer,
 
460
  'error': str(e),
461
  'search_results': search_results
462
  }
463
+
464
+ self.answer_log.append(result)
465
  return result
466
 
467
  def _estimate_confidence(self, sources_count: int, answer: str) -> str:
468
  answer_length = len(answer)
469
+
470
  if sources_count >= 3 and answer_length > 500:
471
  return "high"
472
+
473
  elif sources_count >= 2 and answer_length > 200:
474
  return "medium"
475
+
476
  else:
477
  return "low"
478
 
479
+ def get_answer_with_sources(
480
+ self,
481
+ question: str,
482
+ search_results: List[Dict]
483
+ ) -> Dict:
484
+
485
+ result = self.analyze_and_answer(question, search_results)
486
+
487
  formatted_sources = []
488
+ for idx, source in enumerate(result['search_results'], 1):
489
  formatted_sources.append({
490
  'index': idx,
491
  'type': source.get('type', 'unknown'),
492
  'content': source.get('content', ''),
493
  'relevance': 1 - source.get('distance', 0) if source.get('distance') else 0
494
  })
495
+
496
+ result['formatted_sources'] = formatted_sources
497
+ return result
498
+
499
+ def get_answer_log(self) -> List[Dict]:
500
+ return self.answer_log
501
+
502
+ def print_answer_with_sources(self, result: Dict, max_source_length: int = 300):
503
+
504
+ print(f"ANSWER TO: {result['question']}")
505
+
506
+ print(f"ANSWER (Confidence: {result['confidence'].upper()}):")
507
+ print(result['answer'])
508
+
509
+ if result.get('formatted_sources'):
510
+ print(f"SOURCES USED ({len(result['formatted_sources'])} total):")
511
+ for source in result['formatted_sources']:
512
+ print(f"\n[Source {source['index']} - {source['type'].upper()} ({source['relevance']:.0%} relevant)]")
513
+ print(f"{source['content'][:max_source_length]}...")
514
+
src/vector_store.py CHANGED
@@ -9,9 +9,9 @@ from config import CHROMA_DB_PATH, EMBEDDING_MODEL, EMBEDDING_DIM
9
 
10
  class CLIPEmbedder:
11
  def __init__(self, model_name: str = EMBEDDING_MODEL):
12
- print(f"Loading embedding model: {model_name}")
13
  self.model = SentenceTransformer(model_name)
14
- print(f"Model loaded successfully")
15
 
16
  def embed(self, text: str) -> List[float]:
17
  try:
@@ -35,15 +35,15 @@ class VectorStore:
35
  self.persist_directory = CHROMA_DB_PATH
36
  self.embedder = CLIPEmbedder()
37
 
38
- print(f"Initializing ChromaDB at: {self.persist_directory}")
39
 
40
  try:
41
  self.client = chromadb.PersistentClient(
42
  path=self.persist_directory
43
  )
44
- print(f"ChromaDB initialized")
45
  except Exception as e:
46
- print(f"Error initializing ChromaDB: {e}")
47
  self.client = chromadb.PersistentClient(
48
  path=self.persist_directory
49
  )
@@ -54,7 +54,7 @@ class VectorStore:
54
  metadata={"hnsw:space": "cosine"}
55
  )
56
  count = self.collection.count()
57
- print(f"Collection loaded: {count} items in store")
58
  except Exception as e:
59
  print(f"Error with collection: {e}")
60
  self.collection = self.client.get_or_create_collection(
@@ -66,7 +66,7 @@ class VectorStore:
66
  metadatas = []
67
  ids = []
68
 
69
- print(f"Adding documents for: {doc_id}")
70
 
71
  if 'text' in documents and documents['text']:
72
  chunks = self._chunk_text(documents['text'], chunk_size=1000, overlap=200)
@@ -78,7 +78,7 @@ class VectorStore:
78
  'chunk_idx': str(idx)
79
  })
80
  ids.append(f"{doc_id}_text_{idx}")
81
- print(f"Text: {len(chunks)} chunks")
82
 
83
  if 'images' in documents:
84
  image_count = 0
@@ -94,7 +94,7 @@ class VectorStore:
94
  ids.append(f"{doc_id}_image_{idx}")
95
  image_count += 1
96
  if image_count > 0:
97
- print(f"Images: {image_count} with OCR text")
98
 
99
  if 'tables' in documents:
100
  table_count = 0
@@ -109,10 +109,10 @@ class VectorStore:
109
  ids.append(f"{doc_id}_table_{idx}")
110
  table_count += 1
111
  if table_count > 0:
112
- print(f"Tables: {table_count}")
113
 
114
  if texts:
115
- print(f"Generating {len(texts)} embeddings...")
116
  embeddings = self.embedder.embed_batch(texts)
117
 
118
  try:
@@ -122,11 +122,13 @@ class VectorStore:
122
  embeddings=embeddings,
123
  metadatas=metadatas
124
  )
125
- print(f"Successfully added {len(texts)} items to vector store")
 
126
  except Exception as e:
127
- print(f"Error adding to collection: {e}")
128
 
129
  def search(self, query: str, n_results: int = 5) -> List[Dict]:
 
130
  try:
131
  query_embedding = self.embedder.embed(query)
132
 
@@ -180,13 +182,13 @@ class VectorStore:
180
  results = self.collection.get(where={'doc_id': doc_id})
181
  if results['ids']:
182
  self.collection.delete(ids=results['ids'])
183
- print(f"Deleted {len(results['ids'])} documents for {doc_id}")
184
- print(f"Changes persisted automatically")
185
  except Exception as e:
186
  print(f"Error deleting documents: {e}")
187
 
188
  def persist(self):
189
- print("Vector store is using auto-persist")
 
190
 
191
  def clear_all(self):
192
  try:
@@ -195,6 +197,6 @@ class VectorStore:
195
  name="multimodal_rag",
196
  metadata={"hnsw:space": "cosine"}
197
  )
198
- print("Collection cleared and reset")
199
  except Exception as e:
200
  print(f"Error clearing collection: {e}")
 
9
 
10
  class CLIPEmbedder:
11
  def __init__(self, model_name: str = EMBEDDING_MODEL):
12
+ print(f" Loading embedding model: {model_name}")
13
  self.model = SentenceTransformer(model_name)
14
+ print(f" Model loaded successfully")
15
 
16
  def embed(self, text: str) -> List[float]:
17
  try:
 
35
  self.persist_directory = CHROMA_DB_PATH
36
  self.embedder = CLIPEmbedder()
37
 
38
+ print(f" Initializing ChromaDB at: {self.persist_directory}")
39
 
40
  try:
41
  self.client = chromadb.PersistentClient(
42
  path=self.persist_directory
43
  )
44
+ print(f" ChromaDB initialized")
45
  except Exception as e:
46
+ print(f" Error initializing ChromaDB: {e}")
47
  self.client = chromadb.PersistentClient(
48
  path=self.persist_directory
49
  )
 
54
  metadata={"hnsw:space": "cosine"}
55
  )
56
  count = self.collection.count()
57
+ print(f" Collection loaded: {count} items in store")
58
  except Exception as e:
59
  print(f"Error with collection: {e}")
60
  self.collection = self.client.get_or_create_collection(
 
66
  metadatas = []
67
  ids = []
68
 
69
+ print(f" Adding documents for: {doc_id}")
70
 
71
  if 'text' in documents and documents['text']:
72
  chunks = self._chunk_text(documents['text'], chunk_size=1000, overlap=200)
 
78
  'chunk_idx': str(idx)
79
  })
80
  ids.append(f"{doc_id}_text_{idx}")
81
+ print(f" Text: {len(chunks)} chunks")
82
 
83
  if 'images' in documents:
84
  image_count = 0
 
94
  ids.append(f"{doc_id}_image_{idx}")
95
  image_count += 1
96
  if image_count > 0:
97
+ print(f" Images: {image_count} with OCR text")
98
 
99
  if 'tables' in documents:
100
  table_count = 0
 
109
  ids.append(f"{doc_id}_table_{idx}")
110
  table_count += 1
111
  if table_count > 0:
112
+ print(f" Tables: {table_count}")
113
 
114
  if texts:
115
+ print(f" Generating {len(texts)} embeddings...")
116
  embeddings = self.embedder.embed_batch(texts)
117
 
118
  try:
 
122
  embeddings=embeddings,
123
  metadatas=metadatas
124
  )
125
+ print(f" Successfully added {len(texts)} items to vector store")
126
+ print(f" Data persisted automatically to: {self.persist_directory}")
127
  except Exception as e:
128
+ print(f" Error adding to collection: {e}")
129
 
130
  def search(self, query: str, n_results: int = 5) -> List[Dict]:
131
+ """Search vector store for similar documents"""
132
  try:
133
  query_embedding = self.embedder.embed(query)
134
 
 
182
  results = self.collection.get(where={'doc_id': doc_id})
183
  if results['ids']:
184
  self.collection.delete(ids=results['ids'])
185
+ print(f" Deleted {len(results['ids'])} documents for {doc_id}")
 
186
  except Exception as e:
187
  print(f"Error deleting documents: {e}")
188
 
189
  def persist(self):
190
+
191
+ print(" Vector store is using auto-persist")
192
 
193
  def clear_all(self):
194
  try:
 
197
  name="multimodal_rag",
198
  metadata={"hnsw:space": "cosine"}
199
  )
200
+ print(" Collection cleared and reset")
201
  except Exception as e:
202
  print(f"Error clearing collection: {e}")