brags 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
brags/__init__.py ADDED
File without changes
@@ -0,0 +1,79 @@
1
+ from pydantic import BaseModel, Field
2
+ from typing import Any, Optional, Dict
3
+
4
+
5
+ class LLMConfig(BaseModel):
6
+ provider: str
7
+ model_name: str
8
+ temperature: float
9
+ max_tokens: int
10
+ api_keys: Optional[Dict[str, str]] = None
11
+ huggingface_api_token: Optional[str] = None
12
+ ollama_host: Optional[str] = None
13
+
14
+
15
+ class EmbeddingConfig(BaseModel):
16
+ provider: str
17
+ model_name: str
18
+ dimensions: int
19
+ normalize: bool
20
+ ensemble_weights: Optional[Dict[str, float]] = None
21
+ cache_dir: Optional[str] = "./embedding_cache"
22
+ tfidf_config: Optional[Dict[str, Any]] = None
23
+ lda_config: Optional[Dict[str, Any]] = None
24
+ bm25_enabled: Optional[bool] = True
25
+
26
+ class VectorStoreConfig(BaseModel):
27
+ type: str
28
+ persist_path: str
29
+ similarity_metric: str
30
+ top_k: int
31
+ allow_dangerous_deserialization: Optional[bool] = False
32
+ save_if_not_local: Optional[bool] = False
33
+
34
+
35
+ class ChunkingConfig(BaseModel):
36
+ chunk_size: int
37
+ chunk_overlap: int
38
+ splitter: str
39
+
40
+
41
+ class RerankingConfig(BaseModel):
42
+ enabled: bool
43
+ model_name: Optional[str] = None
44
+ top_k: Optional[int] = None
45
+
46
+
47
+ class HallucinationCheckerConfig(BaseModel):
48
+ enabled: bool
49
+ same_as_retriever: bool
50
+ method: str
51
+ threshold: Optional[float] = None
52
+ provider: Optional[str] = None
53
+ model_name: Optional[str] = None
54
+ temperature: Optional[float] = None
55
+ max_tokens: Optional[int] = None
56
+ api_keys: Optional[Dict[str, str]] = None
57
+ huggingface_api_token: Optional[str] = None
58
+ ollama_host: Optional[str] = None
59
+ prompt_template: Optional[str] = None
60
+
61
+
62
+ class LoggingConfig(BaseModel):
63
+ level: str
64
+ log_to_file: bool
65
+ log_file_path: Optional[str] = None
66
+
67
+
68
+ class RAGConfig(BaseModel):
69
+ llm: LLMConfig
70
+ embedding: EmbeddingConfig
71
+ vector_store: VectorStoreConfig
72
+ chunking: ChunkingConfig
73
+ reranking: RerankingConfig
74
+ hallucination_checker: HallucinationCheckerConfig
75
+ logging: LoggingConfig
76
+ rag_mode: str
77
+ streaming: bool
78
+ use_cache: bool
79
+ debug: bool
@@ -0,0 +1,10 @@
1
+ import yaml
2
+ from pathlib import Path
3
+
4
+ from .data_types import RAGConfig
5
+
6
+ # Load YAML config
7
+ def load_config(path: str) -> RAGConfig:
8
+ with open(path, "r") as f:
9
+ data = yaml.safe_load(f)
10
+ return RAGConfig(**data)
@@ -0,0 +1,8 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Any
3
+
4
+ class BaseEmbedding(ABC):
5
+ @abstractmethod
6
+ def create(self) -> Any:
7
+ """Return a LangChain Embedding instance"""
8
+ pass
@@ -0,0 +1,8 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Any
3
+
4
+ class BaseLLM(ABC):
5
+ @abstractmethod
6
+ def create(self) -> Any:
7
+ """Return a LangChain LLM instance"""
8
+ pass
@@ -0,0 +1,12 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Any
3
+
4
+ class BaseVectorStore(ABC):
5
+ @abstractmethod
6
+ def create(self, embedder: Any, documents: list = None, save_if_not_local=False) -> Any:
7
+ """Return a LangChain VectorStore instance"""
8
+ pass
9
+
10
+ @abstractmethod
11
+ def remove_by_path(self, embedder, path: str) -> Any:
12
+ pass
@@ -0,0 +1,14 @@
1
+ from brags.config_parser.data_types import EmbeddingConfig
2
+ from brags.factories.baseclasses.baseembedding import BaseEmbedding
3
+ from .implementations.huggingFaceEmbedding import HuggingFaceEmbedding
4
+ from .implementations.ensembleEmbedding import EnsembleEmbedding
5
+
6
+
7
+ class EmbeddingFactory:
8
+ @staticmethod
9
+ def create(config: EmbeddingConfig) -> BaseEmbedding:
10
+ if config.provider == "huggingface":
11
+ return HuggingFaceEmbedding(config)
12
+ elif config.provider == "ensemble":
13
+ return EnsembleEmbedding(config)
14
+ raise ValueError(f"Unsupported embedding provider: {config.provider}")
@@ -0,0 +1,381 @@
1
+ from ....config_parser.data_types import EmbeddingConfig
2
+ import numpy as np
3
+ import os
4
+ import pickle
5
+ from typing import List, Dict, Any
6
+ from langchain_community.embeddings import HuggingFaceEmbeddings
7
+ from langchain.schema import Document
8
+ from sklearn.feature_extraction.text import TfidfVectorizer
9
+ from gensim import corpora
10
+ from gensim.models import LdaModel
11
+
12
+ from ....factories.baseclasses.baseembedding import BaseEmbedding
13
+
14
+
15
+ class EnsembleEmbedding(BaseEmbedding):
16
+ """
17
+ Ensemble embedding that combines multiple retrieval methods:
18
+ - Dense embeddings (HuggingFace)
19
+ - TF-IDF (sparse representations)
20
+ - LDA topic modeling
21
+ - BM25 (optional)
22
+ """
23
+
24
+ def __init__(self, config: EmbeddingConfig):
25
+ self.config = config
26
+ self.cache_dir = config.cache_dir or "./embedding_cache"
27
+
28
+ # Default ensemble weights
29
+ self.weights = config.ensemble_weights or {
30
+ 'vector': 0.4,
31
+ 'tfidf': 0.3,
32
+ 'lda': 0.2,
33
+ 'bm25': 0.1
34
+ }
35
+
36
+ # Configuration for individual components
37
+ self.tfidf_config = config.tfidf_config or {
38
+ 'max_features': 10000,
39
+ 'stop_words': 'english',
40
+ 'ngram_range': (1, 2),
41
+ 'min_df': 2,
42
+ 'max_df': 0.8
43
+ }
44
+
45
+ self.lda_config = config.lda_config or {
46
+ 'num_topics': 20,
47
+ 'passes': 10,
48
+ 'random_state': 42,
49
+ 'alpha': 'auto'
50
+ }
51
+
52
+ self.bm25_enabled = config.bm25_enabled if config.bm25_enabled is not None else True
53
+
54
+ # Will be initialized when create() is called
55
+ self._embedding_instance = None
56
+ self._initialized = False
57
+
58
+ def create(self):
59
+ """Create the ensemble embedding instance"""
60
+ if self._embedding_instance is None:
61
+ self._embedding_instance = EnsembleEmbeddingInstance(
62
+ config=self.config,
63
+ weights=self.weights,
64
+ cache_dir=self.cache_dir,
65
+ tfidf_config=self.tfidf_config,
66
+ lda_config=self.lda_config,
67
+ bm25_enabled=self.bm25_enabled
68
+ )
69
+ return self._embedding_instance
70
+
71
+
72
+ class EnsembleEmbeddingInstance:
73
+ """
74
+ The actual embedding instance that handles ensemble operations
75
+ Compatible with LangChain's embedding interface
76
+ """
77
+
78
+ def __init__(
79
+ self,
80
+ config: EmbeddingConfig,
81
+ weights: Dict[str, float],
82
+ cache_dir: str,
83
+ tfidf_config: Dict[str, Any],
84
+ lda_config: Dict[str, Any],
85
+ bm25_enabled: bool
86
+ ):
87
+ self.config = config
88
+ self.weights = weights
89
+ self.cache_dir = cache_dir
90
+ self.tfidf_config = tfidf_config
91
+ self.lda_config = lda_config
92
+ self.bm25_enabled = bm25_enabled
93
+
94
+ # Initialize base vector embedding
95
+ self.vector_embedding = HuggingFaceEmbeddings(
96
+ model_name=config.model_name,
97
+ encode_kwargs={'normalize_embeddings': config.normalize}
98
+ )
99
+
100
+ # Components will be initialized when documents are provided
101
+ self.tfidf_vectorizer = None
102
+ self.tfidf_matrix = None
103
+ self.lda_model = None
104
+ self.doc_topic_matrix = None
105
+ self.dictionary = None
106
+ self.bm25 = None
107
+ self.documents = None
108
+ self.document_texts = None
109
+
110
+ os.makedirs(self.cache_dir, exist_ok=True)
111
+
112
+ def initialize_with_documents(self, documents: List[Document]):
113
+ """Initialize the ensemble components with a document corpus"""
114
+ if self.documents is not None:
115
+ # Already initialized
116
+ return
117
+
118
+ self.documents = documents
119
+ self.document_texts = [doc.page_content for doc in documents]
120
+
121
+ print(f"Initializing ensemble embedding with {len(documents)} documents...")
122
+
123
+ # Initialize all components
124
+ self._init_tfidf()
125
+ self._init_lda()
126
+ if self.bm25_enabled:
127
+ self._init_bm25()
128
+
129
+ print("Ensemble embedding initialized")
130
+
131
+ def _init_tfidf(self):
132
+ """Initialize TF-IDF component"""
133
+ cache_path = os.path.join(self.cache_dir, "ensemble_tfidf.pkl")
134
+
135
+ try:
136
+ if os.path.exists(cache_path):
137
+ with open(cache_path, 'rb') as f:
138
+ self.tfidf_vectorizer, self.tfidf_matrix = pickle.load(f)
139
+ print("Loaded cached TF-IDF components")
140
+ else:
141
+ raise FileNotFoundError("Cache not found")
142
+
143
+ except (FileNotFoundError, EOFError):
144
+ print("Building TF-IDF components...")
145
+ self.tfidf_vectorizer = TfidfVectorizer(**self.tfidf_config)
146
+ self.tfidf_matrix = self.tfidf_vectorizer.fit_transform(self.document_texts)
147
+
148
+ with open(cache_path, 'wb') as f:
149
+ pickle.dump((self.tfidf_vectorizer, self.tfidf_matrix), f)
150
+ print("TF-IDF components cached")
151
+
152
+ def _init_lda(self):
153
+ """Initialize LDA component"""
154
+ cache_path = os.path.join(self.cache_dir, "ensemble_lda.pkl")
155
+
156
+ try:
157
+ if os.path.exists(cache_path):
158
+ with open(cache_path, 'rb') as f:
159
+ self.lda_model, self.doc_topic_matrix, self.dictionary = pickle.load(f)
160
+ print("Loaded cached LDA components")
161
+ else:
162
+ raise FileNotFoundError("Cache not found")
163
+
164
+ except (FileNotFoundError, EOFError):
165
+ print("Building LDA components...")
166
+
167
+ # Preprocess texts
168
+ processed_texts = []
169
+ for text in self.document_texts:
170
+ words = text.lower().split()
171
+ words = [w for w in words if len(w) > 3 and w.isalpha()]
172
+ processed_texts.append(words)
173
+
174
+ # Create dictionary and corpus
175
+ self.dictionary = corpora.Dictionary(processed_texts)
176
+ self.dictionary.filter_extremes(no_below=2, no_above=0.8)
177
+ corpus = [self.dictionary.doc2bow(text) for text in processed_texts]
178
+
179
+ # Dynamic topic count
180
+ num_topics = min(self.lda_config['num_topics'], len(self.documents) // 5)
181
+
182
+ # Train LDA model
183
+ self.lda_model = LdaModel(
184
+ corpus=corpus,
185
+ id2word=self.dictionary,
186
+ num_topics=num_topics,
187
+ passes=self.lda_config['passes'],
188
+ random_state=self.lda_config['random_state'],
189
+ alpha=self.lda_config['alpha'],
190
+ per_word_topics=True
191
+ )
192
+
193
+ # Get document-topic distributions
194
+ self.doc_topic_matrix = np.zeros((len(self.documents), num_topics))
195
+ for i, doc_bow in enumerate(corpus):
196
+ topic_dist = self.lda_model.get_document_topics(doc_bow, minimum_probability=0)
197
+ for topic_id, prob in topic_dist:
198
+ self.doc_topic_matrix[i, topic_id] = prob
199
+
200
+ with open(cache_path, 'wb') as f:
201
+ pickle.dump((self.lda_model, self.doc_topic_matrix, self.dictionary), f)
202
+ print("LDA components cached")
203
+
204
+ def _init_bm25(self):
205
+ """Initialize BM25 component"""
206
+ try:
207
+ from rank_bm25 import BM25Okapi
208
+
209
+ cache_path = os.path.join(self.cache_dir, "ensemble_bm25.pkl")
210
+
211
+ try:
212
+ if os.path.exists(cache_path):
213
+ with open(cache_path, 'rb') as f:
214
+ self.bm25 = pickle.load(f)
215
+ print(" Loaded cached BM25 index")
216
+ else:
217
+ raise FileNotFoundError("Cache not found")
218
+
219
+ except (FileNotFoundError, EOFError):
220
+ print(" Building BM25 index...")
221
+ tokenized_docs = [doc.lower().split() for doc in self.document_texts]
222
+ self.bm25 = BM25Okapi(tokenized_docs)
223
+
224
+ with open(cache_path, 'wb') as f:
225
+ pickle.dump(self.bm25, f)
226
+ print("BM25 index cached")
227
+
228
+ except ImportError:
229
+ print("BM25 not available. Install rank_bm25 to enable BM25 scoring.")
230
+ self.bm25 = None
231
+ self.weights['bm25'] = 0
232
+ # Renormalize weights
233
+ total = sum(v for k, v in self.weights.items() if k != 'bm25')
234
+ if total > 0:
235
+ for k in ['vector', 'tfidf', 'lda']:
236
+ if k in self.weights:
237
+ self.weights[k] /= total
238
+
239
+ def embed_documents(self, texts: List[str]) -> List[List[float]]:
240
+ """
241
+ Embed documents using ensemble approach
242
+ Returns combined embeddings that incorporate multiple similarity methods
243
+ """
244
+ # For document embedding, we primarily use the vector embedding
245
+ # The ensemble scoring happens at retrieval time
246
+ return self.vector_embedding.embed_documents(texts)
247
+
248
+ def embed_query(self, text: str) -> List[float]:
249
+ """
250
+ Embed query using ensemble approach
251
+ Returns combined embedding for query
252
+ """
253
+ # For query embedding, we primarily use the vector embedding
254
+ # The ensemble scoring happens at retrieval time
255
+ return self.vector_embedding.embed_query(text)
256
+
257
+ def get_ensemble_similarities(self, query: str, top_k: int = 10) -> List[Dict[str, Any]]:
258
+ """
259
+ Get document similarities using ensemble approach
260
+ Returns list of documents with ensemble scores
261
+ """
262
+ if self.documents is None:
263
+ raise ValueError("Ensemble embedding not initialized with documents. Call initialize_with_documents() first.")
264
+
265
+ # Get query representations for different methods
266
+ query_representations = self._get_query_representations(query)
267
+
268
+ # Score documents using all methods
269
+ method_scores = self._score_documents(query, query_representations)
270
+
271
+ # Combine scores
272
+ final_scores = self._ensemble_scoring(method_scores)
273
+
274
+ # Get top-k results
275
+ top_indices = np.argsort(final_scores)[::-1][:top_k]
276
+
277
+ results = []
278
+ for idx in top_indices:
279
+ results.append({
280
+ 'document_index': idx,
281
+ 'document': self.documents[idx],
282
+ 'ensemble_score': float(final_scores[idx]),
283
+ 'method_scores': {
284
+ 'vector': float(method_scores['vector'][idx]),
285
+ 'tfidf': float(method_scores['tfidf'][idx]),
286
+ 'lda': float(method_scores['lda'][idx]),
287
+ 'bm25': float(method_scores['bm25'][idx]) if self.bm25 else 0.0
288
+ }
289
+ })
290
+
291
+ return results
292
+
293
+ def _get_query_representations(self, query: str) -> Dict[str, Any]:
294
+ """Get query representations for different methods"""
295
+ representations = {}
296
+
297
+ # Vector representation
298
+ representations['vector'] = np.array(self.vector_embedding.embed_query(query))
299
+
300
+ # TF-IDF representation
301
+ if self.tfidf_vectorizer:
302
+ representations['tfidf'] = self.tfidf_vectorizer.transform([query])
303
+
304
+ # LDA representation
305
+ if self.lda_model and self.dictionary:
306
+ query_words = [w for w in query.lower().split() if len(w) > 3 and w.isalpha()]
307
+ query_bow = self.dictionary.doc2bow(query_words)
308
+ query_topics = self.lda_model.get_document_topics(query_bow, minimum_probability=0)
309
+
310
+ query_topic_vec = np.zeros(self.lda_model.num_topics)
311
+ for topic_id, prob in query_topics:
312
+ query_topic_vec[topic_id] = prob
313
+ representations['lda'] = query_topic_vec
314
+
315
+ # BM25 representation
316
+ if self.bm25:
317
+ representations['bm25'] = query.lower().split()
318
+
319
+ return representations
320
+
321
+ def _score_documents(self, query: str, query_representations: Dict[str, Any]) -> Dict[str, np.ndarray]:
322
+ """Score all documents using different methods"""
323
+ scores = {}
324
+ num_docs = len(self.documents)
325
+
326
+ # Vector similarity scores
327
+ if 'vector' in query_representations:
328
+ doc_embeddings = np.array(self.embed_documents(self.document_texts))
329
+ query_vec = query_representations['vector'].reshape(1, -1)
330
+ from sklearn.metrics.pairwise import cosine_similarity
331
+ vector_similarities = cosine_similarity(query_vec, doc_embeddings).flatten()
332
+ scores['vector'] = vector_similarities
333
+ else:
334
+ scores['vector'] = np.zeros(num_docs)
335
+
336
+ # TF-IDF similarity scores
337
+ if 'tfidf' in query_representations and self.tfidf_matrix is not None:
338
+ from sklearn.metrics.pairwise import cosine_similarity
339
+ tfidf_similarities = cosine_similarity(
340
+ query_representations['tfidf'],
341
+ self.tfidf_matrix
342
+ ).flatten()
343
+ scores['tfidf'] = tfidf_similarities
344
+ else:
345
+ scores['tfidf'] = np.zeros(num_docs)
346
+
347
+ # LDA similarity scores
348
+ if 'lda' in query_representations and self.doc_topic_matrix is not None:
349
+ from sklearn.metrics.pairwise import cosine_similarity
350
+ query_topic_vec = query_representations['lda'].reshape(1, -1)
351
+ lda_similarities = cosine_similarity(query_topic_vec, self.doc_topic_matrix).flatten()
352
+ scores['lda'] = lda_similarities
353
+ else:
354
+ scores['lda'] = np.zeros(num_docs)
355
+
356
+ # BM25 scores
357
+ if 'bm25' in query_representations and self.bm25:
358
+ bm25_scores = np.array(self.bm25.get_scores(query_representations['bm25']))
359
+ scores['bm25'] = bm25_scores
360
+ else:
361
+ scores['bm25'] = np.zeros(num_docs)
362
+
363
+ return scores
364
+
365
+ def _ensemble_scoring(self, scores: Dict[str, np.ndarray]) -> np.ndarray:
366
+ """Combine scores from different methods"""
367
+ # Normalize scores to [0, 1] range
368
+ normalized_scores = {}
369
+ for method, score_array in scores.items():
370
+ if len(score_array) > 0 and score_array.max() > 0:
371
+ normalized_scores[method] = score_array / score_array.max()
372
+ else:
373
+ normalized_scores[method] = score_array
374
+
375
+ # Weighted combination
376
+ ensemble_scores = np.zeros(len(self.documents))
377
+ for method, weight in self.weights.items():
378
+ if method in normalized_scores:
379
+ ensemble_scores += weight * normalized_scores[method]
380
+
381
+ return ensemble_scores
@@ -0,0 +1,10 @@
1
+ from langchain_community.embeddings import HuggingFaceEmbeddings
2
+
3
+ from brags.factories.baseclasses.baseembedding import BaseEmbedding
4
+
5
+ class HuggingFaceEmbedding(BaseEmbedding):
6
+ def __init__(self, config):
7
+ self.config = config
8
+
9
+ def create(self):
10
+ return HuggingFaceEmbeddings(model_name=self.config.model_name)
@@ -0,0 +1,15 @@
1
+ from langchain_google_genai import ChatGoogleGenerativeAI
2
+
3
+ from brags.factories.baseclasses.basellm import BaseLLM
4
+
5
+
6
+ class GoogleGenAILLM(BaseLLM):
7
+ def __init__(self, config):
8
+ self.config = config
9
+
10
+ def create(self):
11
+ return ChatGoogleGenerativeAI(
12
+ model=self.config.model_name,
13
+ temperature=self.config.temperature,
14
+ google_api_key=self.config.api_keys.get("gemini_api_key"),
15
+ )
@@ -0,0 +1,14 @@
1
+ from langchain_community.llms import Ollama
2
+
3
+ from brags.config_parser.data_types import LLMConfig
4
+ from brags.factories.baseclasses.basellm import BaseLLM
5
+
6
+ class OllamaLLM(BaseLLM):
7
+ def __init__(self, config: LLMConfig):
8
+ self.config = config
9
+
10
+ def create(self):
11
+ return Ollama(
12
+ model=self.config.model_name,
13
+ base_url=self.config.ollama_host or "http://localhost:11434",
14
+ )
@@ -0,0 +1,14 @@
1
+ from brags.config_parser.data_types import LLMConfig
2
+ from brags.factories.baseclasses.basellm import BaseLLM
3
+ from .implementations.googleGenAILLM import GoogleGenAILLM
4
+ from .implementations.ollamaLLM import OllamaLLM
5
+
6
+
7
+ class LLMFactory:
8
+ @staticmethod
9
+ def create(config: LLMConfig) -> BaseLLM:
10
+ if config.provider == "gemini":
11
+ return GoogleGenAILLM(config)
12
+ elif config.provider == "ollama":
13
+ return OllamaLLM(config)
14
+ raise ValueError(f"Unsupported LLM provider: {config.provider}")
@@ -0,0 +1,67 @@
1
+ import os
2
+ import logging
3
+
4
+ from langchain_community.vectorstores import Chroma
5
+
6
+ from ....config_parser.data_types import VectorStoreConfig
7
+ from ...baseclasses.basevectorstore import BaseVectorStore
8
+
9
+
10
+ class ChromaVectorStore(BaseVectorStore):
11
+ def __init__(self, config: VectorStoreConfig):
12
+ self.config = config
13
+ self.logger = logging.getLogger("Adora")
14
+
15
+ def create(self, embedder, documents=None, save_if_not_local=False):
16
+ persist_path = self.config.persist_path
17
+ os.makedirs(persist_path, exist_ok=True)
18
+
19
+ if documents:
20
+ self.logger.info("ChromaVectorStore: Adding documents to vector store")
21
+
22
+ if os.path.exists(os.path.join(persist_path, "chroma.sqlite3")):
23
+ # Load existing store
24
+ store = Chroma(
25
+ persist_directory=persist_path,
26
+ embedding_function=embedder,
27
+ )
28
+ store.add_documents(documents)
29
+ self.logger.info("ChromaVectorStore: Added documents to existing DB")
30
+ else:
31
+ # Create a new store
32
+ store = Chroma.from_documents(
33
+ documents=documents,
34
+ embedding=embedder,
35
+ persist_directory=persist_path,
36
+ )
37
+ self.logger.info("ChromaVectorStore: Created new DB with provided documents")
38
+
39
+ if save_if_not_local:
40
+ self.logger.info("Saving Chroma DB to disk")
41
+ store.persist()
42
+ self.logger.info("ChromaVectorStore: Save complete")
43
+
44
+ return store
45
+
46
+ else:
47
+ self.logger.info("ChromaVectorStore: Documents not provided, loading from disk")
48
+ return Chroma(
49
+ persist_directory=persist_path,
50
+ embedding_function=embedder,
51
+ )
52
+
53
+ def remove_by_path(self, embedder, path: str):
54
+ """Remove all documents with metadata['source'] == path from the Chroma store."""
55
+ self.logger.info(f"ChromaVectorStore: Removing documents with source={path}")
56
+
57
+ store = Chroma(
58
+ persist_directory=self.config.persist_path,
59
+ embedding_function=embedder,
60
+ )
61
+
62
+ # Delete by metadata
63
+ store.delete(where={"source": path})
64
+ store.persist()
65
+
66
+ self.logger.info(f"ChromaVectorStore: Removed documents with source={path}")
67
+ return store