From c03346c0b342d4b1b433445437b0c799a753dea2 Mon Sep 17 00:00:00 2001
From: Will Tai <wtaisen@gmail.com>
Date: Tue, 28 May 2024 13:59:02 +0100
Subject: [PATCH 1/7] Add custom exceptions

---
 src/neo4j_genai/exceptions.py        | 42 ++++++++++++++++++++++++++++
 src/neo4j_genai/retrievers/hybrid.py | 21 ++++++++++----
 src/neo4j_genai/retrievers/vector.py | 29 ++++++++++++-------
 tests/unit/retrievers/test_hybrid.py | 13 ++++++---
 tests/unit/retrievers/test_vector.py | 26 ++++++++++++-----
 5 files changed, 104 insertions(+), 27 deletions(-)
 create mode 100644 src/neo4j_genai/exceptions.py

diff --git a/src/neo4j_genai/exceptions.py b/src/neo4j_genai/exceptions.py
new file mode 100644
index 000000000..d2cecd747
--- /dev/null
+++ b/src/neo4j_genai/exceptions.py
@@ -0,0 +1,42 @@
+#  Copyright (c) "Neo4j"
+#  Neo4j Sweden AB [https://neo4j.com]
+#  #
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#  #
+#      https://www.apache.org/licenses/LICENSE-2.0
+#  #
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+
+class RetrieverInitializationError(Exception):
+    """Exception raised when initialization of a retriever fails."""
+
+    def __init__(self, errors):
+        super().__init__(f"Initialization failed: {errors}")
+        self.errors = errors
+
+
+class SearchValidationError(Exception):
+    """Exception raised for validation errors during search."""
+
+    def __init__(self, errors):
+        super().__init__(f"Search validation failed: {errors}")
+        self.errors = errors
+
+
+class EmbeddingRequiredError(Exception):
+    """Exception raised when an embedding method is required but not provided."""
+
+    pass
+
+
+class RecordCreationError(Exception):
+    """Exception raised when valid Record fails to be created."""
+
+    pass
diff --git a/src/neo4j_genai/retrievers/hybrid.py b/src/neo4j_genai/retrievers/hybrid.py
index 2f7108afd..e46d3f0b0 100644
--- a/src/neo4j_genai/retrievers/hybrid.py
+++ b/src/neo4j_genai/retrievers/hybrid.py
@@ -18,6 +18,11 @@
 from pydantic import ValidationError
 
 from neo4j_genai.embedder import Embedder
+from neo4j_genai.exceptions import (
+    RetrieverInitializationError,
+    SearchValidationError,
+    EmbeddingRequiredError,
+)
 from neo4j_genai.retrievers.base import Retriever
 from neo4j_genai.types import (
     HybridSearchModel,
@@ -81,7 +86,7 @@ def __init__(
                 return_properties=return_properties,
             )
         except ValidationError as e:
-            raise ValueError(f"Validation failed: {e.errors()}")
+            raise RetrieverInitializationError(e.errors())
 
         super().__init__(validated_data.driver_model.driver)
         self.vector_index_name = validated_data.vector_index_name
@@ -131,13 +136,15 @@ def search(
                 query_text=query_text,
             )
         except ValidationError as e:
-            raise ValueError(f"Validation failed: {e.errors()}")
+            raise SearchValidationError(e.errors())
 
         parameters = validated_data.model_dump(exclude_none=True)
 
         if query_text and not query_vector:
             if not self.embedder:
-                raise ValueError("Embedding method required for text query.")
+                raise EmbeddingRequiredError(
+                    "Embedding method required for text query."
+                )
             query_vector = self.embedder.embed_query(query_text)
             parameters["query_vector"] = query_vector
 
@@ -199,7 +206,7 @@ def __init__(
                 embedder_model=embedder_model,
             )
         except ValidationError as e:
-            raise ValueError(f"Validation failed: {e.errors()}")
+            raise RetrieverInitializationError(e.errors())
 
         super().__init__(validated_data.driver_model.driver)
         self.vector_index_name = validated_data.vector_index_name
@@ -252,13 +259,15 @@ def search(
                 query_params=query_params,
             )
         except ValidationError as e:
-            raise ValueError(f"Validation failed: {e.errors()}")
+            raise SearchValidationError(e.errors())
 
         parameters = validated_data.model_dump(exclude_none=True)
 
         if query_text and not query_vector:
             if not self.embedder:
-                raise ValueError("Embedding method required for text query.")
+                raise EmbeddingRequiredError(
+                    "Embedding method required for text query."
+                )
             query_vector = self.embedder.embed_query(query_text)
             parameters["query_vector"] = query_vector
 
diff --git a/src/neo4j_genai/retrievers/vector.py b/src/neo4j_genai/retrievers/vector.py
index 8770f70fe..07950b051 100644
--- a/src/neo4j_genai/retrievers/vector.py
+++ b/src/neo4j_genai/retrievers/vector.py
@@ -15,6 +15,13 @@
 from typing import Optional, Any
 
 import neo4j
+
+from neo4j_genai.exceptions import (
+    RetrieverInitializationError,
+    SearchValidationError,
+    EmbeddingRequiredError,
+    RecordCreationError,
+)
 from neo4j_genai.retrievers.base import Retriever
 from pydantic import ValidationError
 
@@ -76,7 +83,7 @@ def __init__(
                 return_properties=return_properties,
             )
         except ValidationError as e:
-            raise ValueError(f"Validation failed: {e.errors()}")
+            raise RetrieverInitializationError(e.errors())
 
         super().__init__(driver)
         self.index_name = validated_data.index_name
@@ -125,14 +132,15 @@ def search(
                 query_text=query_text,
             )
         except ValidationError as e:
-            error_details = e.errors()
-            raise ValueError(f"Validation failed: {error_details}")
+            raise SearchValidationError(e.errors())
 
         parameters = validated_data.model_dump(exclude_none=True)
 
         if query_text:
             if not self.embedder:
-                raise ValueError("Embedding method required for text query.")
+                raise EmbeddingRequiredError(
+                    "Embedding method required for text query."
+                )
             query_vector = self.embedder.embed_query(query_text)
             parameters["query_vector"] = query_vector
             del parameters["query_text"]
@@ -158,9 +166,8 @@ def search(
                 for record in records
             ]
         except ValidationError as e:
-            error_details = e.errors()
-            raise ValueError(
-                f"Validation failed while constructing output: {error_details}"
+            raise RecordCreationError(
+                f"Failed constructing VectorSearchRecord output: {e.errors()}"
             )
 
 
@@ -209,7 +216,7 @@ def __init__(
                 embedder_model=embedder_model,
             )
         except ValidationError as e:
-            raise ValueError(f"Validation failed: {e.errors()}")
+            raise RetrieverInitializationError(e.errors())
 
         super().__init__(driver)
         self.index_name = validated_data.index_name
@@ -261,13 +268,15 @@ def search(
                 query_params=query_params,
             )
         except ValidationError as e:
-            raise ValueError(f"Validation failed: {e.errors()}")
+            raise SearchValidationError(e.errors())
 
         parameters = validated_data.model_dump(exclude_none=True)
 
         if query_text:
             if not self.embedder:
-                raise ValueError("Embedding method required for text query.")
+                raise EmbeddingRequiredError(
+                    "Embedding method required for text query."
+                )
             parameters["query_vector"] = self.embedder.embed_query(query_text)
             del parameters["query_text"]
 
diff --git a/tests/unit/retrievers/test_hybrid.py b/tests/unit/retrievers/test_hybrid.py
index 6e0688c3c..aaf69e994 100644
--- a/tests/unit/retrievers/test_hybrid.py
+++ b/tests/unit/retrievers/test_hybrid.py
@@ -18,6 +18,7 @@
 import pytest
 
 from neo4j_genai import HybridRetriever, HybridCypherRetriever
+from neo4j_genai.exceptions import RetrieverInitializationError, EmbeddingRequiredError
 from neo4j_genai.neo4j_queries import get_search_query
 from neo4j_genai.types import SearchType
 
@@ -45,7 +46,7 @@ def test_vector_cypher_retriever_initialization(driver):
 
 @patch("neo4j_genai.HybridRetriever._verify_version")
 def test_hybrid_retriever_invalid_fulltext_index_name(_verify_version_mock, driver):
-    with pytest.raises(ValueError) as exc_info:
+    with pytest.raises(RetrieverInitializationError) as exc_info:
         HybridRetriever(
             driver=driver, vector_index_name="my-index", fulltext_index_name=42
         )
@@ -56,7 +57,7 @@ def test_hybrid_retriever_invalid_fulltext_index_name(_verify_version_mock, driv
 
 @patch("neo4j_genai.HybridCypherRetriever._verify_version")
 def test_hybrid_cypher_retriever_invalid_retrieval_query(_verify_version_mock, driver):
-    with pytest.raises(ValueError) as exc_info:
+    with pytest.raises(RetrieverInitializationError) as exc_info:
         HybridCypherRetriever(
             driver=driver,
             vector_index_name="my-index",
@@ -143,7 +144,9 @@ def test_error_when_hybrid_search_only_text_no_embedder(hybrid_retriever):
     query_text = "may thy knife chip and shatter"
     top_k = 5
 
-    with pytest.raises(ValueError, match="Embedding method required for text query."):
+    with pytest.raises(
+        EmbeddingRequiredError, match="Embedding method required for text query."
+    ):
         hybrid_retriever.search(
             query_text=query_text,
             top_k=top_k,
@@ -156,7 +159,9 @@ def test_hybrid_search_retriever_search_missing_embedder_for_text(
     query_text = "may thy knife chip and shatter"
     top_k = 5
 
-    with pytest.raises(ValueError, match="Embedding method required for text query"):
+    with pytest.raises(
+        EmbeddingRequiredError, match="Embedding method required for text query"
+    ):
         hybrid_retriever.search(
             query_text=query_text,
             top_k=top_k,
diff --git a/tests/unit/retrievers/test_vector.py b/tests/unit/retrievers/test_vector.py
index 3755d8a60..6ff16cb33 100644
--- a/tests/unit/retrievers/test_vector.py
+++ b/tests/unit/retrievers/test_vector.py
@@ -18,6 +18,12 @@
 from neo4j.exceptions import CypherSyntaxError
 
 from neo4j_genai import VectorRetriever, VectorCypherRetriever
+from neo4j_genai.exceptions import (
+    RetrieverInitializationError,
+    EmbeddingRequiredError,
+    SearchValidationError,
+    RecordCreationError,
+)
 from neo4j_genai.neo4j_queries import get_search_query
 from neo4j_genai.types import SearchType, VectorSearchRecord
 
@@ -30,7 +36,7 @@ def test_vector_retriever_initialization(driver):
 
 @patch("neo4j_genai.VectorRetriever._verify_version")
 def test_vector_retriever_invalid_index_name(_verify_version_mock, driver):
-    with pytest.raises(ValueError) as exc_info:
+    with pytest.raises(RetrieverInitializationError) as exc_info:
         VectorRetriever(driver=driver, index_name=42)
 
     assert "index_name" in str(exc_info.value)
@@ -39,7 +45,7 @@ def test_vector_retriever_invalid_index_name(_verify_version_mock, driver):
 
 @patch("neo4j_genai.VectorCypherRetriever._verify_version")
 def test_vector_cypher_retriever_invalid_retrieval_query(_verify_version_mock, driver):
-    with pytest.raises(ValueError) as exc_info:
+    with pytest.raises(RetrieverInitializationError) as exc_info:
         VectorCypherRetriever(driver=driver, index_name="my-index", retrieval_query=42)
 
         assert "retrieval_query" in str(exc_info.value)
@@ -156,7 +162,9 @@ def test_vector_retriever_search_missing_embedder_for_text(vector_retriever):
     query_text = "may thy knife chip and shatter"
     top_k = 5
 
-    with pytest.raises(ValueError, match="Embedding method required for text query"):
+    with pytest.raises(
+        EmbeddingRequiredError, match="Embedding method required for text query"
+    ):
         vector_retriever.search(query_text=query_text, top_k=top_k)
 
 
@@ -166,7 +174,8 @@ def test_vector_retriever_search_both_text_and_vector(vector_retriever):
     top_k = 5
 
     with pytest.raises(
-        ValueError, match="You must provide exactly one of query_vector or query_text."
+        SearchValidationError,
+        match="You must provide exactly one of query_vector or query_text.",
     ):
         vector_retriever.search(
             query_text=query_text,
@@ -181,7 +190,9 @@ def test_vector_cypher_retriever_search_missing_embedder_for_text(
     query_text = "may thy knife chip and shatter"
     top_k = 5
 
-    with pytest.raises(ValueError, match="Embedding method required for text query"):
+    with pytest.raises(
+        EmbeddingRequiredError, match="Embedding method required for text query"
+    ):
         vector_cypher_retriever.search(query_text=query_text, top_k=top_k)
 
 
@@ -191,7 +202,8 @@ def test_vector_cypher_retriever_search_both_text_and_vector(vector_cypher_retri
     top_k = 5
 
     with pytest.raises(
-        ValueError, match="You must provide exactly one of query_vector or query_text."
+        SearchValidationError,
+        match="You must provide exactly one of query_vector or query_text.",
     ):
         vector_cypher_retriever.search(
             query_text=query_text,
@@ -217,7 +229,7 @@ def test_similarity_search_vector_bad_results(
     ]
     search_query, _ = get_search_query(SearchType.VECTOR)
 
-    with pytest.raises(ValueError):
+    with pytest.raises(RecordCreationError):
         retriever.search(query_vector=query_vector, top_k=top_k)
 
     retriever.driver.execute_query.assert_called_once_with(

From ccb74516d26d6a08a444364bd7c895d7969331b0 Mon Sep 17 00:00:00 2001
From: Will Tai <wtaisen@gmail.com>
Date: Tue, 28 May 2024 17:19:57 +0100
Subject: [PATCH 2/7] Add exceptions for Weaviate retriever and index methods,
 added types for Weaviate retriever

---
 .gitignore                                    |  1 +
 src/neo4j_genai/exceptions.py                 | 19 +++++
 src/neo4j_genai/filters.py                    | 19 +++--
 src/neo4j_genai/indexes.py                    | 15 ++--
 src/neo4j_genai/retrievers/base.py            |  8 +-
 .../retrievers/external/weaviate/types.py     | 77 +++++++++++++++++++
 .../retrievers/external/weaviate/weaviate.py  | 63 ++++++++++++---
 src/neo4j_genai/retrievers/hybrid.py          |  8 +-
 src/neo4j_genai/retrievers/vector.py          |  8 +-
 .../unit/retrievers/external/test_weaviate.py |  5 +-
 tests/unit/retrievers/test_base.py            |  5 +-
 tests/unit/test_filters.py                    | 21 ++---
 tests/unit/test_indexes.py                    | 13 ++--
 13 files changed, 205 insertions(+), 57 deletions(-)
 create mode 100644 src/neo4j_genai/retrievers/external/weaviate/types.py

diff --git a/.gitignore b/.gitignore
index fd9b584f5..ff6cab99c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,4 @@ htmlcov/
 docs/build/
 .vscode/
 .python-version
+.DS_Store
diff --git a/src/neo4j_genai/exceptions.py b/src/neo4j_genai/exceptions.py
index d2cecd747..359b42efc 100644
--- a/src/neo4j_genai/exceptions.py
+++ b/src/neo4j_genai/exceptions.py
@@ -30,6 +30,12 @@ def __init__(self, errors):
         self.errors = errors
 
 
+class FilterValidationError(Exception):
+    """Exception raised when an embedding method is required but not provided."""
+
+    pass
+
+
 class EmbeddingRequiredError(Exception):
     """Exception raised when an embedding method is required but not provided."""
 
@@ -40,3 +46,16 @@ class RecordCreationError(Exception):
     """Exception raised when valid Record fails to be created."""
 
     pass
+
+
+class Neo4jIndexError(Exception):
+    """Exception raised when handling Neo4j indexes fails."""
+
+    pass
+
+
+class Neo4jVersionError(Exception):
+    """Exception raised when Neo4j version does not meet minimum requirements."""
+
+    def __init__(self):
+        super().__init__("This package only supports Neo4j version 5.18.1 or greater")
diff --git a/src/neo4j_genai/filters.py b/src/neo4j_genai/filters.py
index ec80ac8c6..3fa0a33cc 100644
--- a/src/neo4j_genai/filters.py
+++ b/src/neo4j_genai/filters.py
@@ -16,6 +16,7 @@
 from typing import Any, Type
 from collections import Counter
 
+from neo4j_genai.exceptions import FilterValidationError
 
 DEFAULT_NODE_ALIAS = "node"
 
@@ -244,12 +245,12 @@ def _handle_field_filter(
     """
     # first, perform some sanity checks
     if not isinstance(field, str):
-        raise ValueError(
+        raise FilterValidationError(
             f"Field should be a string but got: {type(field)} with value: {field}"
         )
 
     if field.startswith(OPERATOR_PREFIX):
-        raise ValueError(
+        raise FilterValidationError(
             f"Invalid filter condition. Expected a field but got an operator: "
             f"{field}"
         )
@@ -257,7 +258,7 @@ def _handle_field_filter(
     if isinstance(value, dict):
         # This is a filter specification e.g. {"$gte": 0}
         if len(value) != 1:
-            raise ValueError(
+            raise FilterValidationError(
                 "Invalid filter condition. Expected a value which "
                 "is a dictionary with a single key that corresponds to an operator "
                 f"but got a dictionary with {len(value)} keys. The first few "
@@ -267,7 +268,7 @@ def _handle_field_filter(
         operator = operator.lower()
         # Verify that that operator is an operator
         if operator not in SUPPORTED_OPERATORS:
-            raise ValueError(
+            raise FilterValidationError(
                 f"Invalid operator: {operator}. "
                 f"Expected one of {SUPPORTED_OPERATORS}"
             )
@@ -280,7 +281,7 @@ def _handle_field_filter(
     # two tests (lower_bound <= value <= higher_bound)
     if operator == OPERATOR_BETWEEN:
         if len(filter_value) != 2:
-            raise ValueError(
+            raise FilterValidationError(
                 f"Expected lower and upper bounds in a list, got {filter_value}"
             )
         low, high = filter_value
@@ -312,7 +313,7 @@ def _construct_metadata_filter(
     """
 
     if not isinstance(filter, dict):
-        raise ValueError(f"Filter must be a dictionary, got {type(filter)}")
+        raise FilterValidationError(f"Filter must be a dictionary, got {type(filter)}")
     # if we have more than one entry, this is an implicit "AND" filter
     if len(filter) > 1:
         return _construct_metadata_filter(
@@ -329,13 +330,15 @@ def _construct_metadata_filter(
 
     # Here we handle the $and and $or operators
     if not isinstance(value, list):
-        raise ValueError(f"Expected a list, but got {type(value)} for value: {value}")
+        raise FilterValidationError(
+            f"Expected a list, but got {type(value)} for value: {value}"
+        )
     if key.lower() == OPERATOR_AND:
         cypher_operator = " AND "
     elif key.lower() == OPERATOR_OR:
         cypher_operator = " OR "
     else:
-        raise ValueError(f"Unsupported operator: {key}")
+        raise FilterValidationError(f"Unsupported operator: {key}")
     query = cypher_operator.join(
         [
             f"({ _construct_metadata_filter(el, param_store, node_alias)})"
diff --git a/src/neo4j_genai/indexes.py b/src/neo4j_genai/indexes.py
index 32ace5784..528983669 100644
--- a/src/neo4j_genai/indexes.py
+++ b/src/neo4j_genai/indexes.py
@@ -15,6 +15,8 @@
 
 import neo4j
 from pydantic import ValidationError
+
+from .exceptions import Neo4jIndexError
 from .types import VectorIndexModel, FulltextIndexModel
 import logging
 
@@ -64,7 +66,7 @@ def create_vector_index(
             }
         )
     except ValidationError as e:
-        raise ValueError(f"Error for inputs to create_vector_index {str(e)}")
+        raise Neo4jIndexError(f"Error for inputs to create_vector_index {str(e)}")
 
     try:
         query = (
@@ -77,8 +79,7 @@ def create_vector_index(
             {"name": name, "dimensions": dimensions, "similarity_fn": similarity_fn},
         )
     except neo4j.exceptions.ClientError as e:
-        logger.error(f"Neo4j vector index creation failed {e}")
-        raise
+        raise Neo4jIndexError(f"Neo4j vector index creation failed: {e}")
 
 
 def create_fulltext_index(
@@ -113,7 +114,7 @@ def create_fulltext_index(
             }
         )
     except ValidationError as e:
-        raise ValueError(f"Error for inputs to create_fulltext_index {str(e)}")
+        raise Neo4jIndexError(f"Error for inputs to create_fulltext_index: {str(e)}")
 
     try:
         query = (
@@ -124,8 +125,7 @@ def create_fulltext_index(
         logger.info(f"Creating fulltext index named '{name}'")
         driver.execute_query(query, {"name": name})
     except neo4j.exceptions.ClientError as e:
-        logger.error(f"Neo4j fulltext index creation failed {e}")
-        raise
+        raise Neo4jIndexError(f"Neo4j fulltext index creation failed {e}")
 
 
 def drop_index_if_exists(driver: neo4j.Driver, name: str) -> None:
@@ -149,5 +149,4 @@ def drop_index_if_exists(driver: neo4j.Driver, name: str) -> None:
         logger.info(f"Dropping index named '{name}'")
         driver.execute_query(query, parameters)
     except neo4j.exceptions.ClientError as e:
-        logger.error(f"Dropping Neo4j index failed {e}")
-        raise
+        raise Neo4jIndexError(f"Dropping Neo4j index failed: {e}")
diff --git a/src/neo4j_genai/retrievers/base.py b/src/neo4j_genai/retrievers/base.py
index 217fbe980..f1dd713fb 100644
--- a/src/neo4j_genai/retrievers/base.py
+++ b/src/neo4j_genai/retrievers/base.py
@@ -16,6 +16,8 @@
 from typing import Optional, Any
 import neo4j
 
+from neo4j_genai.exceptions import Neo4jVersionError
+
 
 class Retriever(ABC):
     """
@@ -32,7 +34,7 @@ def _verify_version(self) -> None:
 
         Queries the Neo4j database to retrieve its version and compares it
         against a target version (5.18.1) that is known to support vector
-        indexing. Raises a ValueError if the connected Neo4j version is
+        indexing. Raises a Neo4jMinVersionError if the connected Neo4j version is
         not supported.
         """
         records, _, _ = self.driver.execute_query("CALL dbms.components()")
@@ -49,9 +51,7 @@ def _verify_version(self) -> None:
             target_version = (5, 18, 1)
 
         if version_tuple < target_version:
-            raise ValueError(
-                "This package only supports Neo4j version 5.18.1 or greater"
-            )
+            raise Neo4jVersionError()
 
     @abstractmethod
     def search(self, *args, **kwargs) -> Any:
diff --git a/src/neo4j_genai/retrievers/external/weaviate/types.py b/src/neo4j_genai/retrievers/external/weaviate/types.py
new file mode 100644
index 000000000..00ef3cd84
--- /dev/null
+++ b/src/neo4j_genai/retrievers/external/weaviate/types.py
@@ -0,0 +1,77 @@
+#  Copyright (c) "Neo4j"
+#  Neo4j Sweden AB [https://neo4j.com]
+#  #
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#  #
+#      https://www.apache.org/licenses/LICENSE-2.0
+#  #
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+from typing import Optional
+
+from pydantic import (
+    field_validator,
+    BaseModel,
+    PositiveInt,
+    model_validator,
+    ConfigDict,
+)
+from weaviate.client import WeaviateClient
+from weaviate.collections.classes.filters import _Filters
+
+from neo4j_genai.retrievers.utils import validate_search_query_input
+from neo4j_genai.types import Neo4jDriverModel, EmbedderModel
+
+
+class WeaviateModel(BaseModel):
+    client: WeaviateClient
+    model_config = ConfigDict(arbitrary_types_allowed=True)
+
+    @field_validator("client")
+    def check_client(cls, value):
+        if not isinstance(value, WeaviateClient):
+            raise TypeError(
+                "Provided client needs to be of type weaviate.client.WeaviateClient"
+            )
+        return value
+
+
+class WeaviateNeo4jRetrieverModel(BaseModel):
+    driver_model: Neo4jDriverModel
+    client_model: WeaviateModel
+    collection: str
+    id_property_external: str
+    id_property_neo4j: str
+    embedder_model: Optional[EmbedderModel]
+    return_properties: Optional[list[str]] = None
+    retrieval_query: Optional[str] = None
+
+
+class WeaviateNeo4jSearchModel(BaseModel):
+    top_k: PositiveInt = 5
+    query_vector: Optional[list[float]] = None
+    query_text: Optional[str] = None
+    weaviate_filters: Optional[_Filters] = (None,)
+    model_config = ConfigDict(arbitrary_types_allowed=True)
+
+    @field_validator("weaviate_filters")
+    def check_weaviate_filters(cls, value):
+        if value and not isinstance(value, _Filters):
+            raise TypeError(
+                "Provided filters need to be of type weaviate.collections.classes.filters._Filters"
+            )
+        return value
+
+    @model_validator(mode="before")
+    def check_query(cls, values):
+        """
+        Validates that one of either query_vector or query_text is provided exclusively.
+        """
+        query_vector, query_text = values.get("query_vector"), values.get("query_text")
+        validate_search_query_input(query_text, query_vector)
+        return values
diff --git a/src/neo4j_genai/retrievers/external/weaviate/weaviate.py b/src/neo4j_genai/retrievers/external/weaviate/weaviate.py
index 3db774827..9af8f04a6 100644
--- a/src/neo4j_genai/retrievers/external/weaviate/weaviate.py
+++ b/src/neo4j_genai/retrievers/external/weaviate/weaviate.py
@@ -14,15 +14,24 @@
 #  limitations under the License.
 
 from typing import Optional
+
+from pydantic import ValidationError
+
+from neo4j_genai.exceptions import RetrieverInitializationError, SearchValidationError
 from neo4j_genai.retrievers.base import ExternalRetriever
 from neo4j_genai.embedder import Embedder
-from neo4j_genai.retrievers.utils import validate_search_query_input
+from neo4j_genai.retrievers.external.weaviate.types import (
+    WeaviateModel,
+    WeaviateNeo4jRetrieverModel,
+    WeaviateNeo4jSearchModel,
+)
 import weaviate.classes as wvc
 from weaviate.client import WeaviateClient
 from weaviate.collections.classes.filters import _Filters
 import neo4j
 import logging
 from neo4j_genai.neo4j_queries import get_query_tail
+from neo4j_genai.types import Neo4jDriverModel, EmbedderModel
 
 logger = logging.getLogger(__name__)
 
@@ -39,13 +48,36 @@ def __init__(
         return_properties: Optional[list[str]] = None,
         retrieval_query: Optional[str] = None,
     ):
+        try:
+            driver_model = Neo4jDriverModel(driver=driver)
+            weaviate_model = WeaviateModel(client=client)
+            embedder_model = EmbedderModel(embedder=embedder) if embedder else None
+            print("embedder_model", embedder_model)
+            validated_data = WeaviateNeo4jRetrieverModel(
+                driver_model=driver_model,
+                client_model=weaviate_model,
+                collection=collection,
+                id_property_external=id_property_external,
+                id_property_neo4j=id_property_neo4j,
+                embedder_model=embedder_model,
+                return_properties=return_properties,
+                retrieval_query=retrieval_query,
+            )
+        except ValidationError as e:
+            raise RetrieverInitializationError(e.errors())
+
         super().__init__(id_property_external, id_property_neo4j)
-        self.driver = driver
-        self.client = client
-        self.search_collection = client.collections.get(collection)
-        self.embedder = embedder
-        self.return_properties = return_properties
-        self.retrieval_query = retrieval_query
+        self.driver = validated_data.driver_model.driver
+        self.client = validated_data.client_model.client
+        collection = validated_data.collection
+        self.search_collection = self.client.collections.get(collection)
+        self.embedder = (
+            validated_data.embedder_model.embedder
+            if validated_data.embedder_model
+            else None
+        )
+        self.return_properties = validated_data.return_properties
+        self.retrieval_query = validated_data.retrieval_query
 
     def search(
         self,
@@ -71,12 +103,23 @@ def search(
             top_k (int, optional): The number of neighbors to return. Defaults to 5.
             weaviate_filters (Optional[_Filters], optional): The filters to apply to the search query in Weaviate. Defaults to None.
         Raises:
-            ValueError: If validation of the input arguments fail.
+            SearchValidationError: If validation of the input arguments fail.
         Returns:
             list[neo4j.Record]: The results of the search query
         """
-
-        validate_search_query_input(query_text=query_text, query_vector=query_vector)
+        try:
+            validated_data = WeaviateNeo4jSearchModel(
+                top_k=top_k,
+                query_vector=query_vector,
+                query_text=query_text,
+                weaviate_filters=weaviate_filters,
+            )
+            query_text = validated_data.query_text
+            query_vector = validated_data.query_vector
+            top_k = validated_data.top_k
+            weaviate_filters = validated_data.weaviate_filters
+        except ValidationError as e:
+            raise SearchValidationError(e.errors())
 
         # If we want to use a local embedder, we still want to call the near_vector method
         # so we want to create the vector as early as possible here
diff --git a/src/neo4j_genai/retrievers/hybrid.py b/src/neo4j_genai/retrievers/hybrid.py
index e46d3f0b0..22c66fd2a 100644
--- a/src/neo4j_genai/retrievers/hybrid.py
+++ b/src/neo4j_genai/retrievers/hybrid.py
@@ -121,8 +121,8 @@ def search(
             top_k (int, optional): The number of neighbors to return. Defaults to 5.
 
         Raises:
-            ValueError: If validation of the input arguments fail.
-            ValueError: If no embedder is provided.
+            EmbeddingRequiredError: If validation of the input arguments fail.
+            EmbeddingRequiredError: If no embedder is provided.
 
         Returns:
             list[neo4j.Record]: The results of the search query
@@ -243,8 +243,8 @@ def search(
             query_params (Optional[dict[str, Any]]): Parameters for the Cypher query. Defaults to None.
 
         Raises:
-            ValueError: If validation of the input arguments fail.
-            ValueError: If no embedder is provided.
+            SearchValidationError: If validation of the input arguments fail.
+            EmbeddingRequiredError: If no embedder is provided.
 
         Returns:
             list[neo4j.Record]: The results of the search query
diff --git a/src/neo4j_genai/retrievers/vector.py b/src/neo4j_genai/retrievers/vector.py
index 07950b051..dbbf1fd49 100644
--- a/src/neo4j_genai/retrievers/vector.py
+++ b/src/neo4j_genai/retrievers/vector.py
@@ -118,8 +118,8 @@ def search(
             filters (Optional[dict[str, Any]]): Filters for metadata pre-filtering. Defaults to None.
 
         Raises:
-            ValueError: If validation of the input arguments fail.
-            ValueError: If no embedder is provided.
+            SearchValidationError: If validation of the input arguments fail.
+            EmbeddingRequiredError: If no embedder is provided.
 
         Returns:
             list[VectorSearchRecord]: The `top_k` neighbors found in vector search with their nodes and scores.
@@ -253,8 +253,8 @@ def search(
             filters (Optional[dict[str, Any]]): Filters for metadata pre-filtering. Defaults to None.
 
         Raises:
-            ValueError: If validation of the input arguments fail.
-            ValueError: If no embedder is provided.
+            SearchValidationError: If validation of the input arguments fail.
+            EmbeddingRequiredError: If no embedder is provided.
 
         Returns:
             list[VectorSearchRecord]: The `top_k` neighbors found in vector search with their nodes and scores.
diff --git a/tests/unit/retrievers/external/test_weaviate.py b/tests/unit/retrievers/external/test_weaviate.py
index 6d3af46b1..07ccb716b 100644
--- a/tests/unit/retrievers/external/test_weaviate.py
+++ b/tests/unit/retrievers/external/test_weaviate.py
@@ -15,6 +15,9 @@
 
 from unittest.mock import MagicMock
 from types import SimpleNamespace
+
+from weaviate import WeaviateClient
+
 from neo4j_genai.retrievers.external.weaviate import (
     WeaviateNeo4jRetriever,
     get_match_query,
@@ -22,7 +25,7 @@
 
 
 # Weaviate class with fake methods
-class WClient:
+class WClient(WeaviateClient):
     def __init__(self, node_id_value=None, node_match_score=None):
         self.collections = MagicMock()
         self.collections.get = MagicMock()
diff --git a/tests/unit/retrievers/test_base.py b/tests/unit/retrievers/test_base.py
index 0386b81b6..3667d922b 100644
--- a/tests/unit/retrievers/test_base.py
+++ b/tests/unit/retrievers/test_base.py
@@ -14,6 +14,7 @@
 #  limitations under the License.
 import pytest
 
+from neo4j_genai.exceptions import Neo4jVersionError
 from neo4j_genai.retrievers.base import Retriever
 
 
@@ -21,9 +22,9 @@
     "db_version,expected_exception",
     [
         (["5.18-aura"], None),
-        (["5.3-aura"], ValueError),
+        (["5.3-aura"], Neo4jVersionError),
         (["5.19.0"], None),
-        (["4.3.5"], ValueError),
+        (["4.3.5"], Neo4jVersionError),
     ],
 )
 def test_retriever_version_support(driver, db_version, expected_exception):
diff --git a/tests/unit/test_filters.py b/tests/unit/test_filters.py
index 66d3d6c8b..3086460c9 100644
--- a/tests/unit/test_filters.py
+++ b/tests/unit/test_filters.py
@@ -16,6 +16,7 @@
 
 import pytest
 
+from neo4j_genai.exceptions import FilterValidationError
 from neo4j_genai.filters import (
     get_metadata_filter,
     _single_condition_cypher,
@@ -194,7 +195,7 @@ def test_single_condition_cypher_escaped_field_name(param_store_empty):
 
 
 def test_handle_field_filter_not_a_string(param_store_empty):
-    with pytest.raises(ValueError) as excinfo:
+    with pytest.raises(FilterValidationError) as excinfo:
         _handle_field_filter(1, "value", param_store=param_store_empty)
     assert "Field should be a string but got: <class 'int'> with value: 1" in str(
         excinfo
@@ -202,7 +203,7 @@ def test_handle_field_filter_not_a_string(param_store_empty):
 
 
 def test_handle_field_filter_field_start_with_dollar_sign(param_store_empty):
-    with pytest.raises(ValueError) as excinfo:
+    with pytest.raises(FilterValidationError) as excinfo:
         _handle_field_filter("$field_name", "value", param_store=param_store_empty)
     assert (
         "Invalid filter condition. Expected a field but got an operator: $field_name"
@@ -211,7 +212,7 @@ def test_handle_field_filter_field_start_with_dollar_sign(param_store_empty):
 
 
 def test_handle_field_filter_bad_value(param_store_empty):
-    with pytest.raises(ValueError) as excinfo:
+    with pytest.raises(FilterValidationError) as excinfo:
         _handle_field_filter(
             "field",
             value={"operator1": "value1", "operator2": "value2"},
@@ -221,7 +222,7 @@ def test_handle_field_filter_bad_value(param_store_empty):
 
 
 def test_handle_field_filter_bad_operator_name(param_store_empty):
-    with pytest.raises(ValueError) as excinfo:
+    with pytest.raises(FilterValidationError) as excinfo:
         _handle_field_filter(
             "field", value={"$invalid": "value"}, param_store=param_store_empty
         )
@@ -237,7 +238,7 @@ def test_handle_field_filter_operator_between(param_store_empty):
 
 
 def test_handle_field_filter_operator_between_not_enough_parameters(param_store_empty):
-    with pytest.raises(ValueError) as excinfo:
+    with pytest.raises(FilterValidationError) as excinfo:
         _handle_field_filter(
             "field",
             value={
@@ -355,7 +356,7 @@ def test_handle_field_filter_ilike(_single_condition_cypher_mocked, param_store_
 def test_construct_metadata_filter_filter_is_not_a_dict(
     _handle_field_filter_mock, param_store_empty
 ):
-    with pytest.raises(ValueError) as excinfo:
+    with pytest.raises(FilterValidationError) as excinfo:
         _construct_metadata_filter([], param_store_empty, node_alias="n")
     assert "Filter must be a dictionary, got <class 'list'>" in str(excinfo)
 
@@ -429,7 +430,7 @@ def test_construct_metadata_filter_or(
 
 
 def test_construct_metadata_filter_invalid_operator(param_store_empty):
-    with pytest.raises(ValueError) as excinfo:
+    with pytest.raises(FilterValidationError) as excinfo:
         _construct_metadata_filter(
             {"$invalid": [{}, {}]}, param_store_empty, node_alias="n"
         )
@@ -582,17 +583,17 @@ def test_get_metadata_filter_and_or_combined():
 # now testing bad filters
 def test_get_metadata_filter_field_name_with_dollar_sign():
     filters = {"$field": "value"}
-    with pytest.raises(ValueError):
+    with pytest.raises(FilterValidationError):
         get_metadata_filter(filters)
 
 
 def test_get_metadata_filter_and_no_list():
     filters = {"$and": {}}
-    with pytest.raises(ValueError):
+    with pytest.raises(FilterValidationError):
         get_metadata_filter(filters)
 
 
 def test_get_metadata_filter_unsupported_operator():
     filters = {"field": {"$unsupported": "value"}}
-    with pytest.raises(ValueError):
+    with pytest.raises(FilterValidationError):
         get_metadata_filter(filters)
diff --git a/tests/unit/test_indexes.py b/tests/unit/test_indexes.py
index c5509da9a..4adaa4190 100644
--- a/tests/unit/test_indexes.py
+++ b/tests/unit/test_indexes.py
@@ -15,6 +15,7 @@
 import neo4j.exceptions
 import pytest
 
+from neo4j_genai.exceptions import Neo4jIndexError
 from neo4j_genai.indexes import (
     create_vector_index,
     drop_index_if_exists,
@@ -57,25 +58,25 @@ def test_create_vector_index_ensure_escaping(driver):
 
 
 def test_create_vector_index_negative_dimension(driver):
-    with pytest.raises(ValueError) as excinfo:
+    with pytest.raises(Neo4jIndexError) as excinfo:
         create_vector_index(driver, "my-index", "People", "name", -5, "cosine")
     assert "Error for inputs to create_vector_index" in str(excinfo)
 
 
 def test_create_vector_index_validation_error_dimensions(driver):
-    with pytest.raises(ValueError) as excinfo:
+    with pytest.raises(Neo4jIndexError) as excinfo:
         create_vector_index(driver, "my-index", "People", "name", "no-dim", "cosine")
     assert "Error for inputs to create_vector_index" in str(excinfo)
 
 
 def test_create_vector_index_raises_error_with_neo4j_client_error(driver):
     driver.execute_query.side_effect = neo4j.exceptions.ClientError
-    with pytest.raises(neo4j.exceptions.ClientError):
+    with pytest.raises(Neo4jIndexError):
         create_vector_index(driver, "my-index", "People", "name", 2048, "cosine")
 
 
 def test_create_vector_index_validation_error_similarity_fn(driver):
-    with pytest.raises(ValueError) as excinfo:
+    with pytest.raises(Neo4jIndexError) as excinfo:
         create_vector_index(driver, "my-index", "People", "name", 1536, "algebra")
     assert "Error for inputs to create_vector_index" in str(excinfo)
 
@@ -124,7 +125,7 @@ def test_create_fulltext_index_raises_error_with_neo4j_client_error(driver):
     text_node_properties = ["property-1", "property-2"]
     driver.execute_query.side_effect = neo4j.exceptions.ClientError
 
-    with pytest.raises(neo4j.exceptions.ClientError):
+    with pytest.raises(Neo4jIndexError):
         create_fulltext_index(driver, "my-index", label, text_node_properties)
 
 
@@ -132,7 +133,7 @@ def test_create_fulltext_index_empty_node_properties(driver):
     label = "node-label"
     node_properties = []
 
-    with pytest.raises(ValueError) as excinfo:
+    with pytest.raises(Neo4jIndexError) as excinfo:
         create_fulltext_index(driver, "my-index", label, node_properties)
 
     assert "Error for inputs to create_fulltext_index" in str(excinfo)

From a035b00a835b0a27dd38948c5bdd5576469871ca Mon Sep 17 00:00:00 2001
From: Will Tai <wtaisen@gmail.com>
Date: Wed, 29 May 2024 11:04:47 +0100
Subject: [PATCH 3/7] Add errors to documentation

---
 docs/source/api.rst                  | 54 ++++++++++++++++++++++++++++
 src/neo4j_genai/exceptions.py        |  4 +--
 src/neo4j_genai/retrievers/hybrid.py |  2 +-
 3 files changed, 57 insertions(+), 3 deletions(-)

diff --git a/docs/source/api.rst b/docs/source/api.rst
index 4e010932c..3af86e4ac 100644
--- a/docs/source/api.rst
+++ b/docs/source/api.rst
@@ -40,3 +40,57 @@ HybridCypherRetriever
 
 .. autoclass:: neo4j_genai.retrievers.hybrid.HybridCypherRetriever
    :members:
+
+
+******
+Errors
+******
+
+
+RetrieverInitializationError
+============================
+
+.. autoclass:: neo4j_genai.exceptions.RetrieverInitializationError
+   :members:
+
+
+SearchValidationError
+=====================
+
+.. autoclass:: neo4j_genai.exceptions.SearchValidationError
+   :members:
+
+
+FilterValidationError
+=====================
+
+.. autoclass:: neo4j_genai.exceptions.FilterValidationError
+   :members:
+
+
+EmbeddingRequiredError
+======================
+
+.. autoclass:: neo4j_genai.exceptions.EmbeddingRequiredError
+   :members:
+
+
+RecordCreationError
+===================
+
+.. autoclass:: neo4j_genai.exceptions.RecordCreationError
+   :members:
+
+
+Neo4jIndexError
+===============
+
+.. autoclass:: neo4j_genai.exceptions.Neo4jIndexError
+   :members:
+
+
+Neo4jVersionError
+=================
+
+.. autoclass:: neo4j_genai.exceptions.Neo4jVersionError
+   :members:
diff --git a/src/neo4j_genai/exceptions.py b/src/neo4j_genai/exceptions.py
index 359b42efc..9baa526ca 100644
--- a/src/neo4j_genai/exceptions.py
+++ b/src/neo4j_genai/exceptions.py
@@ -17,7 +17,7 @@
 class RetrieverInitializationError(Exception):
     """Exception raised when initialization of a retriever fails."""
 
-    def __init__(self, errors):
+    def __init__(self, errors: str):
         super().__init__(f"Initialization failed: {errors}")
         self.errors = errors
 
@@ -31,7 +31,7 @@ def __init__(self, errors):
 
 
 class FilterValidationError(Exception):
-    """Exception raised when an embedding method is required but not provided."""
+    """Exception raised when input validation for metadata filtering fails."""
 
     pass
 
diff --git a/src/neo4j_genai/retrievers/hybrid.py b/src/neo4j_genai/retrievers/hybrid.py
index 22c66fd2a..b735b1f1c 100644
--- a/src/neo4j_genai/retrievers/hybrid.py
+++ b/src/neo4j_genai/retrievers/hybrid.py
@@ -121,7 +121,7 @@ def search(
             top_k (int, optional): The number of neighbors to return. Defaults to 5.
 
         Raises:
-            EmbeddingRequiredError: If validation of the input arguments fail.
+            SearchValidationError: If validation of the input arguments fail.
             EmbeddingRequiredError: If no embedder is provided.
 
         Returns:

From 2adf48d00e2c6291698e1dd2441140ad47b87429 Mon Sep 17 00:00:00 2001
From: Will Tai <wtaisen@gmail.com>
Date: Thu, 30 May 2024 15:48:28 +0100
Subject: [PATCH 4/7] Update exception names

---
 src/neo4j_genai/exceptions.py        | 24 +++++++++++++++---------
 src/neo4j_genai/retrievers/vector.py |  4 ++--
 tests/unit/retrievers/test_vector.py |  4 ++--
 3 files changed, 19 insertions(+), 13 deletions(-)

diff --git a/src/neo4j_genai/exceptions.py b/src/neo4j_genai/exceptions.py
index 9baa526ca..286cb7030 100644
--- a/src/neo4j_genai/exceptions.py
+++ b/src/neo4j_genai/exceptions.py
@@ -14,7 +14,13 @@
 #  limitations under the License.
 
 
-class RetrieverInitializationError(Exception):
+class Neo4jGenAiError(Exception):
+    """Global exception used for the neo4j-genai package"""
+
+    pass
+
+
+class RetrieverInitializationError(Neo4jGenAiError):
     """Exception raised when initialization of a retriever fails."""
 
     def __init__(self, errors: str):
@@ -22,7 +28,7 @@ def __init__(self, errors: str):
         self.errors = errors
 
 
-class SearchValidationError(Exception):
+class SearchValidationError(Neo4jGenAiError):
     """Exception raised for validation errors during search."""
 
     def __init__(self, errors):
@@ -30,31 +36,31 @@ def __init__(self, errors):
         self.errors = errors
 
 
-class FilterValidationError(Exception):
+class FilterValidationError(Neo4jGenAiError):
     """Exception raised when input validation for metadata filtering fails."""
 
     pass
 
 
-class EmbeddingRequiredError(Exception):
+class EmbeddingRequiredError(Neo4jGenAiError):
     """Exception raised when an embedding method is required but not provided."""
 
     pass
 
 
-class RecordCreationError(Exception):
-    """Exception raised when valid Record fails to be created."""
+class InvalidRetrieverResultError(Neo4jGenAiError):
+    """Exception raised when the Retriever fails to return a result."""
 
     pass
 
 
-class Neo4jIndexError(Exception):
-    """Exception raised when handling Neo4j indexes fails."""
+class Neo4jIndexError(Neo4jGenAiError):
+    """Exception raised when handling Neo4j indeails."""
 
     pass
 
 
-class Neo4jVersionError(Exception):
+class Neo4jVersionError(Neo4jGenAiError):
     """Exception raised when Neo4j version does not meet minimum requirements."""
 
     def __init__(self):
diff --git a/src/neo4j_genai/retrievers/vector.py b/src/neo4j_genai/retrievers/vector.py
index dbbf1fd49..b3a3ebac6 100644
--- a/src/neo4j_genai/retrievers/vector.py
+++ b/src/neo4j_genai/retrievers/vector.py
@@ -20,7 +20,7 @@
     RetrieverInitializationError,
     SearchValidationError,
     EmbeddingRequiredError,
-    RecordCreationError,
+    InvalidRetrieverResultError,
 )
 from neo4j_genai.retrievers.base import Retriever
 from pydantic import ValidationError
@@ -166,7 +166,7 @@ def search(
                 for record in records
             ]
         except ValidationError as e:
-            raise RecordCreationError(
+            raise InvalidRetrieverResultError(
                 f"Failed constructing VectorSearchRecord output: {e.errors()}"
             )
 
diff --git a/tests/unit/retrievers/test_vector.py b/tests/unit/retrievers/test_vector.py
index 6ff16cb33..ca3dbe634 100644
--- a/tests/unit/retrievers/test_vector.py
+++ b/tests/unit/retrievers/test_vector.py
@@ -22,7 +22,7 @@
     RetrieverInitializationError,
     EmbeddingRequiredError,
     SearchValidationError,
-    RecordCreationError,
+    InvalidRetrieverResultError,
 )
 from neo4j_genai.neo4j_queries import get_search_query
 from neo4j_genai.types import SearchType, VectorSearchRecord
@@ -229,7 +229,7 @@ def test_similarity_search_vector_bad_results(
     ]
     search_query, _ = get_search_query(SearchType.VECTOR)
 
-    with pytest.raises(RecordCreationError):
+    with pytest.raises(InvalidRetrieverResultError):
         retriever.search(query_vector=query_vector, top_k=top_k)
 
     retriever.driver.execute_query.assert_called_once_with(

From 6503a23be90c4103a4f420d38b201318c37cf2d6 Mon Sep 17 00:00:00 2001
From: Will Tai <wtaisen@gmail.com>
Date: Thu, 30 May 2024 16:00:46 +0100
Subject: [PATCH 5/7] Update documentation

---
 docs/source/api.rst | 44 ++++++++++++++++++++++++++++++++++----------
 1 file changed, 34 insertions(+), 10 deletions(-)

diff --git a/docs/source/api.rst b/docs/source/api.rst
index 3af86e4ac..740a5373d 100644
--- a/docs/source/api.rst
+++ b/docs/source/api.rst
@@ -47,50 +47,74 @@ Errors
 ******
 
 
+* :class:`neo4j_genai.exceptions.Neo4jGenAiError`
+
+  * :class:`neo4j_genai.exceptions.RetrieverInitializationError`
+
+  * :class:`neo4j_genai.exceptions.SearchValidationError`
+
+  * :class:`neo4j_genai.exceptions.FilterValidationError`
+
+  * :class:`neo4j_genai.exceptions.EmbeddingRequiredError`
+
+  * :class:`neo4j_genai.exceptions.InvalidRetrieverResultError`
+
+  * :class:`neo4j_genai.exceptions.Neo4jIndexError`
+
+  * :class:`neo4j_genai.exceptions.Neo4jVersionError`
+
+
+Neo4jGenAiError
+===============
+
+.. autoclass:: neo4j_genai.exceptions.Neo4jGenAiError
+   :show-inheritance:
+
+
 RetrieverInitializationError
 ============================
 
 .. autoclass:: neo4j_genai.exceptions.RetrieverInitializationError
-   :members:
+   :show-inheritance:
 
 
 SearchValidationError
 =====================
 
 .. autoclass:: neo4j_genai.exceptions.SearchValidationError
-   :members:
+   :show-inheritance:
 
 
 FilterValidationError
 =====================
 
 .. autoclass:: neo4j_genai.exceptions.FilterValidationError
-   :members:
+   :show-inheritance:
 
 
 EmbeddingRequiredError
 ======================
 
 .. autoclass:: neo4j_genai.exceptions.EmbeddingRequiredError
-   :members:
+   :show-inheritance:
 
 
-RecordCreationError
-===================
+InvalidRetrieverResultError
+===========================
 
-.. autoclass:: neo4j_genai.exceptions.RecordCreationError
-   :members:
+.. autoclass:: neo4j_genai.exceptions.InvalidRetrieverResultError
+   :show-inheritance:
 
 
 Neo4jIndexError
 ===============
 
 .. autoclass:: neo4j_genai.exceptions.Neo4jIndexError
-   :members:
+   :show-inheritance:
 
 
 Neo4jVersionError
 =================
 
 .. autoclass:: neo4j_genai.exceptions.Neo4jVersionError
-   :members:
+   :show-inheritance:

From 13350526f81d02212dd3f82c8bacbf480941d61a Mon Sep 17 00:00:00 2001
From: Will Tai <wtaisen@gmail.com>
Date: Thu, 30 May 2024 17:00:09 +0100
Subject: [PATCH 6/7] Fixed docstrings

---
 src/neo4j_genai/exceptions.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/neo4j_genai/exceptions.py b/src/neo4j_genai/exceptions.py
index 286cb7030..96373b091 100644
--- a/src/neo4j_genai/exceptions.py
+++ b/src/neo4j_genai/exceptions.py
@@ -15,7 +15,7 @@
 
 
 class Neo4jGenAiError(Exception):
-    """Global exception used for the neo4j-genai package"""
+    """Global exception used for the neo4j-genai package."""
 
     pass
 
@@ -55,7 +55,7 @@ class InvalidRetrieverResultError(Neo4jGenAiError):
 
 
 class Neo4jIndexError(Neo4jGenAiError):
-    """Exception raised when handling Neo4j indeails."""
+    """Exception raised when handling Neo4j index fails."""
 
     pass
 

From a32886900bd0621bd655d8f9104d3e19bba8b439 Mon Sep 17 00:00:00 2001
From: Will Tai <wtaisen@gmail.com>
Date: Fri, 31 May 2024 11:42:27 +0100
Subject: [PATCH 7/7] Addressed comments

---
 src/neo4j_genai/retrievers/external/weaviate/types.py    | 2 +-
 src/neo4j_genai/retrievers/external/weaviate/weaviate.py | 1 -
 2 files changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/neo4j_genai/retrievers/external/weaviate/types.py b/src/neo4j_genai/retrievers/external/weaviate/types.py
index 00ef3cd84..1400c249f 100644
--- a/src/neo4j_genai/retrievers/external/weaviate/types.py
+++ b/src/neo4j_genai/retrievers/external/weaviate/types.py
@@ -56,7 +56,7 @@ class WeaviateNeo4jSearchModel(BaseModel):
     top_k: PositiveInt = 5
     query_vector: Optional[list[float]] = None
     query_text: Optional[str] = None
-    weaviate_filters: Optional[_Filters] = (None,)
+    weaviate_filters: Optional[_Filters] = None
     model_config = ConfigDict(arbitrary_types_allowed=True)
 
     @field_validator("weaviate_filters")
diff --git a/src/neo4j_genai/retrievers/external/weaviate/weaviate.py b/src/neo4j_genai/retrievers/external/weaviate/weaviate.py
index 9af8f04a6..3682acf12 100644
--- a/src/neo4j_genai/retrievers/external/weaviate/weaviate.py
+++ b/src/neo4j_genai/retrievers/external/weaviate/weaviate.py
@@ -52,7 +52,6 @@ def __init__(
             driver_model = Neo4jDriverModel(driver=driver)
             weaviate_model = WeaviateModel(client=client)
             embedder_model = EmbedderModel(embedder=embedder) if embedder else None
-            print("embedder_model", embedder_model)
             validated_data = WeaviateNeo4jRetrieverModel(
                 driver_model=driver_model,
                 client_model=weaviate_model,