99from pathlib import Path
1010from typing import Any
1111
12- import faiss
12+ import faiss # type: ignore[import-untyped]
1313import numpy as np
1414from haystack import default_from_dict , default_to_dict
1515from haystack .dataclasses import Document
@@ -40,6 +40,8 @@ def __init__(
4040 :param index_path: Path to save/load the index and documents. If None, the store is in-memory only.
4141 :param index_string: The FAISS index factory string. Default is "Flat".
4242 :param embedding_dim: The dimension of the embeddings. Default is 768.
43+ :raises DocumentStoreError: If the FAISS index cannot be initialized.
44+ :raises ValueError: If `index_path` points to a missing `.faiss` file when loading persisted data.
4345 """
4446 self .index_path = index_path
4547 self .embedding_dim = embedding_dim
@@ -68,6 +70,13 @@ def _create_new_index(self):
6870 msg = f"Could not create FAISS index with factory string '{ self .index_string } ': { e } "
6971 raise DocumentStoreError (msg ) from e
7072
73+ def _get_index_or_raise (self ) -> Any :
74+ """Return the FAISS index or raise if it is unexpectedly missing."""
75+ if self .index is None :
76+ msg = "FAISS index has not been initialized."
77+ raise DocumentStoreError (msg )
78+ return self .index
79+
7180 def count_documents (self ) -> int :
7281 """
7382 Returns the number of documents in the store.
@@ -80,6 +89,7 @@ def filter_documents(self, filters: dict[str, Any] | None = None) -> list[Docume
8089
8190 :param filters: A dictionary of filters to apply.
8291 :return: A list of matching Documents.
92+ :raises FilterError: If the filter structure is invalid.
8393 """
8494 if not filters :
8595 return list (self .documents .values ())
@@ -120,6 +130,9 @@ def write_documents(self, documents: list[Document], policy: DuplicatePolicy = D
120130 :param documents: The list of documents to write.
121131 :param policy: The policy to handle duplicate documents.
122132 :return: The number of documents written.
133+ :raises ValueError: If `documents` is not an iterable of `Document` objects.
134+ :raises DuplicateDocumentError: If a duplicate document is found and `policy` is `DuplicatePolicy.FAIL`.
135+ :raises DocumentStoreError: If the FAISS index is unexpectedly unavailable when adding embeddings.
123136 """
124137 if not isinstance (documents , Iterable ) or isinstance (documents , (str , bytes )):
125138 msg = "param 'documents' must contain an iterable of objects of type Document."
@@ -175,13 +188,16 @@ def write_documents(self, documents: list[Document], policy: DuplicatePolicy = D
175188 if vectors_to_add :
176189 vectors = np .array (vectors_to_add , dtype = "float32" )
177190 ids = np .array (ids_to_add_to_index , dtype = "int64" )
178- self .index .add_with_ids (vectors , ids )
191+ index = self ._get_index_or_raise ()
192+ index .add_with_ids (vectors , ids )
179193
180194 return docs_written
181195
182196 def delete_documents (self , document_ids : list [str ]) -> None :
183197 """
184198 Deletes documents from the store.
199+
200+ :raises DocumentStoreError: If the FAISS index is unexpectedly unavailable when removing embeddings.
185201 """
186202 if not document_ids :
187203 return
@@ -197,9 +213,10 @@ def delete_documents(self, document_ids: list[str]) -> None:
197213 del self .id_map [int_id ]
198214 ids_to_remove_from_index .append (int_id )
199215
200- if ids_to_remove_from_index and self .index .ntotal > 0 :
216+ index = self ._get_index_or_raise ()
217+ if ids_to_remove_from_index and index .ntotal > 0 :
201218 ids_array = np .array (ids_to_remove_from_index , dtype = "int64" )
202- self . index .remove_ids (ids_array )
219+ index .remove_ids (ids_array )
203220
204221 def delete_all_documents (self ) -> None :
205222 """
@@ -221,6 +238,7 @@ def search(
221238 :param top_k: The number of results to return.
222239 :param filters: Filters to apply.
223240 :return: A list of matching Documents.
241+ :raises FilterError: If the filter structure is invalid.
224242 """
225243 if not self .index or self .index .ntotal == 0 :
226244 return []
@@ -301,6 +319,9 @@ def _check_condition(self, doc: Document, condition: dict[str, Any]) -> bool:
301319 msg = "Missing 'field' in filter condition"
302320 raise FilterError (msg )
303321 field = condition .get ("field" )
322+ if not isinstance (field , str ):
323+ msg = "'field' in filter condition must be a string"
324+ raise FilterError (msg )
304325 if "value" not in condition :
305326 msg = "Missing 'value' in filter condition"
306327 raise FilterError (msg )
@@ -370,6 +391,8 @@ def delete_by_filter(self, filters: dict[str, Any]) -> int:
370391
371392 :param filters: A dictionary of filters to apply to find documents to delete.
372393 :returns: The number of documents deleted.
394+ :raises FilterError: If the filter structure is invalid.
395+ :raises DocumentStoreError: If the FAISS index is unexpectedly unavailable when removing embeddings.
373396 """
374397 docs_to_delete = self .filter_documents (filters )
375398 ids = [doc .id for doc in docs_to_delete ]
@@ -382,6 +405,7 @@ def count_documents_by_filter(self, filters: dict[str, Any]) -> int:
382405
383406 :param filters: A dictionary of filters to apply.
384407 :returns: The number of matching documents.
408+ :raises FilterError: If the filter structure is invalid.
385409 """
386410 return len (self .filter_documents (filters ))
387411
@@ -395,6 +419,7 @@ def update_by_filter(self, filters: dict[str, Any], meta: dict[str, Any]) -> int
395419 :param filters: A dictionary of filters to apply to find documents to update.
396420 :param meta: A dictionary of metadata key-value pairs to update in the matching documents.
397421 :returns: The number of documents updated.
422+ :raises FilterError: If the filter structure is invalid.
398423 """
399424 docs_to_update = self .filter_documents (filters )
400425 for doc in docs_to_update :
@@ -505,9 +530,11 @@ def from_dict(cls, data: dict[str, Any]) -> "FAISSDocumentStore":
505530 def save (self , index_path : str | Path ) -> None :
506531 """
507532 Saves the index and documents to disk.
533+
534+ :raises DocumentStoreError: If the FAISS index is unexpectedly unavailable.
508535 """
509536 path = Path (index_path )
510- faiss .write_index (self .index , str (path .with_suffix (".faiss" )))
537+ faiss .write_index (self ._get_index_or_raise () , str (path .with_suffix (".faiss" )))
511538
512539 # Save documents and ID mapping
513540 data = {
@@ -523,6 +550,8 @@ def save(self, index_path: str | Path) -> None:
523550 def load (self , index_path : str | Path ) -> None :
524551 """
525552 Loads the index and documents from disk.
553+
554+ :raises ValueError: If the `.faiss` file does not exist.
526555 """
527556 path = Path (index_path )
528557 if not path .with_suffix (".faiss" ).exists ():
0 commit comments