Repository: langchain-ai/langchain (Python)
Date: 2023-01-21 16:51:48+00:00
Task type: Bug Fix

Issue:
test_faiss_with_metadatas: key mismatch in assert
https://github.com/hwchase17/langchain/blob/236ae93610a8538d3d0044fc29379c481acc6789/tests/integration_tests/vectorstores/test_faiss.py#L54

This test will fail because `FAISS.from_texts` will assign uuid4s as keys in its docstore, while `expected_docstore`  has string numbers as keys.
test_faiss_with_metadatas: key mismatch in assert
https://github.com/hwchase17/langchain/blob/236ae93610a8538d3d0044fc29379c481acc6789/tests/integration_tests/vectorstores/test_faiss.py#L54

This test will fail because `FAISS.from_texts` will assign uuid4s as keys in its docstore, while `expected_docstore`  has string numbers as keys.


Issue discussion:

Solution patch:
diff --git a/langchain/vectorstores/faiss.py b/langchain/vectorstores/faiss.py
--- a/langchain/vectorstores/faiss.py
+++ b/langchain/vectorstores/faiss.py
@@ -14,6 +14,19 @@
 from langchain.vectorstores.utils import maximal_marginal_relevance
 
 
+def dependable_faiss_import() -> Any:
+    """Import faiss if available, otherwise raise error."""
+    try:
+        import faiss
+    except ImportError:
+        raise ValueError(
+            "Could not import faiss python package. "
+            "Please it install it with `pip install faiss` "
+            "or `pip install faiss-cpu` (depending on Python version)."
+        )
+    return faiss
+
+
 class FAISS(VectorStore):
     """Wrapper around FAISS vector database.
 
@@ -174,14 +187,7 @@ def from_texts(
                 embeddings = OpenAIEmbeddings()
                 faiss = FAISS.from_texts(texts, embeddings)
         """
-        try:
-            import faiss
-        except ImportError:
-            raise ValueError(
-                "Could not import faiss python package. "
-                "Please it install it with `pip install faiss` "
-                "or `pip install faiss-cpu` (depending on Python version)."
-            )
+        faiss = dependable_faiss_import()
         embeddings = embedding.embed_documents(texts)
         index = faiss.IndexFlatL2(len(embeddings[0]))
         index.add(np.array(embeddings, dtype=np.float32))
@@ -194,3 +200,21 @@ def from_texts(
             {index_to_id[i]: doc for i, doc in enumerate(documents)}
         )
         return cls(embedding.embed_query, index, docstore, index_to_id)
+
+    def save_local(self, path: str) -> None:
+        """Save FAISS index to disk.
+
+        Args:
+            path: Path to save FAISS index to.
+        """
+        faiss = dependable_faiss_import()
+        faiss.write_index(self.index, path)
+
+    def load_local(self, path: str) -> None:
+        """Load FAISS index from disk.
+
+        Args:
+            path: Path to load FAISS index from.
+        """
+        faiss = dependable_faiss_import()
+        self.index = faiss.read_index(path)


Test patch:
diff --git a/tests/integration_tests/vectorstores/test_faiss.py b/tests/integration_tests/vectorstores/test_faiss.py
--- a/tests/integration_tests/vectorstores/test_faiss.py
+++ b/tests/integration_tests/vectorstores/test_faiss.py
@@ -1,4 +1,5 @@
 """Test FAISS functionality."""
+import tempfile
 from typing import List
 
 import pytest
@@ -46,9 +47,15 @@ def test_faiss_with_metadatas() -> None:
     docsearch = FAISS.from_texts(texts, FakeEmbeddings(), metadatas=metadatas)
     expected_docstore = InMemoryDocstore(
         {
-            "0": Document(page_content="foo", metadata={"page": 0}),
-            "1": Document(page_content="bar", metadata={"page": 1}),
-            "2": Document(page_content="baz", metadata={"page": 2}),
+            docsearch.index_to_docstore_id[0]: Document(
+                page_content="foo", metadata={"page": 0}
+            ),
+            docsearch.index_to_docstore_id[1]: Document(
+                page_content="bar", metadata={"page": 1}
+            ),
+            docsearch.index_to_docstore_id[2]: Document(
+                page_content="baz", metadata={"page": 2}
+            ),
         }
     )
     assert docsearch.docstore.__dict__ == expected_docstore.__dict__
@@ -82,3 +89,15 @@ def test_faiss_add_texts_not_supported() -> None:
     docsearch = FAISS(FakeEmbeddings().embed_query, None, Wikipedia(), {})
     with pytest.raises(ValueError):
         docsearch.add_texts(["foo"])
+
+
+def test_faiss_local_save_load() -> None:
+    """Test end to end serialization."""
+    texts = ["foo", "bar", "baz"]
+    docsearch = FAISS.from_texts(texts, FakeEmbeddings())
+
+    with tempfile.NamedTemporaryFile() as temp_file:
+        docsearch.save_local(temp_file.name)
+        docsearch.index = None
+        docsearch.load_local(temp_file.name)
+    assert docsearch.index is not None