# Copyright (c) Microsoft. All rights reserved.

import json
import logging
import operator
import sys
from abc import abstractmethod
from ast import AST, Lambda, NodeVisitor, expr, parse
from collections.abc import AsyncIterable, Callable, Mapping, Sequence
from copy import deepcopy
from dataclasses import dataclass
from enum import Enum
from inspect import Parameter, _empty, getsource, signature
from types import MappingProxyType, NoneType
from typing import Annotated, Any, ClassVar, Final, Generic, Literal, Protocol, TypeVar, overload, runtime_checkable

from pydantic import BaseModel, Field, ValidationError, model_validator
from pydantic.dataclasses import dataclass as pyd_dataclass

from semantic_kernel.connectors.ai.embedding_generator_base import EmbeddingGeneratorBase
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
from semantic_kernel.data._shared import (
    DEFAULT_FUNCTION_NAME,
    DEFAULT_PARAMETER_METADATA,
    DEFAULT_RETURN_PARAMETER_METADATA,
    DynamicFilterFunction,
    KernelSearchResults,
    SearchOptions,
    create_options,
    default_dynamic_filter_function,
)
from semantic_kernel.exceptions import (
    VectorSearchExecutionException,
    VectorSearchOptionsException,
    VectorStoreModelDeserializationException,
    VectorStoreModelException,
    VectorStoreModelSerializationException,
    VectorStoreModelValidationError,
    VectorStoreOperationException,
    VectorStoreOperationNotSupportedException,
)
from semantic_kernel.exceptions.search_exceptions import TextSearchException
from semantic_kernel.functions import kernel_function
from semantic_kernel.functions.kernel_function import KernelFunction
from semantic_kernel.functions.kernel_function_from_method import KernelFunctionFromMethod
from semantic_kernel.functions.kernel_parameter_metadata import KernelParameterMetadata
from semantic_kernel.kernel_pydantic import KernelBaseModel
from semantic_kernel.kernel_types import OneOrList, OneOrMany, OptionalOneOrList, OptionalOneOrMany
from semantic_kernel.utils.feature_stage_decorator import release_candidate
from semantic_kernel.utils.list_handler import desync_list

if sys.version_info >= (3, 11):
    from typing import Self  # pragma: no cover
else:
    from typing_extensions import Self  # pragma: no cover

logger = logging.getLogger(__name__)


TModel = TypeVar("TModel", bound=object)
TKey = TypeVar("TKey")
_T = TypeVar("_T", bound="VectorStoreRecordHandler")
TFilters = TypeVar("TFilters")

DEFAULT_DESCRIPTION: Final[str] = (
    "Perform a vector search for data in a vector store, using the provided search options."
)


# region: Fields and Collection Definitions


@release_candidate
class FieldTypes(str, Enum):
    """Enumeration for field types in vector store models."""

    KEY = "key"
    VECTOR = "vector"
    DATA = "data"

    def __str__(self) -> str:
        """Return the string representation of the enum."""
        return self.value


@runtime_checkable
class SerializeMethodProtocol(Protocol):
    """Data model serialization protocol.

    This can optionally be implemented to allow single step serialization and deserialization
    for using your data model with a specific datastore.
    """

    def serialize(self, **kwargs: Any) -> Any:
        """Serialize the object to the format required by the data store."""
        ...  # pragma: no cover


@runtime_checkable
class ToDictFunctionProtocol(Protocol):
    """Protocol for to_dict function.

    Args:
        record: The record to be serialized.
        **kwargs: Additional keyword arguments.

    Returns:
        A list of dictionaries.
    """

    def __call__(self, record: Any, **kwargs: Any) -> Sequence[dict[str, Any]]: ...  # pragma: no cover


@runtime_checkable
class FromDictFunctionProtocol(Protocol):
    """Protocol for from_dict function.

    Args:
        records: A list of dictionaries.
        **kwargs: Additional keyword arguments.

    Returns:
        A record or list thereof.
    """

    def __call__(self, records: Sequence[dict[str, Any]], **kwargs: Any) -> Any: ...


@runtime_checkable
class SerializeFunctionProtocol(Protocol):
    """Protocol for serialize function.

    Args:
        record: The record to be serialized.
        **kwargs: Additional keyword arguments.

    Returns:
        The serialized record, ready to be consumed by the specific store.

    """

    def __call__(self, record: Any, **kwargs: Any) -> Any: ...


@runtime_checkable
class DeserializeFunctionProtocol(Protocol):
    """Protocol for deserialize function.

    Args:
        records: The serialized record directly from the store.
        **kwargs: Additional keyword arguments.

    Returns:
        The deserialized record in the format expected by the application.

    """

    def __call__(self, records: Any, **kwargs: Any) -> Any: ...


@runtime_checkable
class ToDictMethodProtocol(Protocol):
    """Class used internally to check if a model has a to_dict method."""

    def to_dict(self, *args: Any, **kwargs: Any) -> dict[str, Any]:
        """Serialize the object to the format required by the data store."""
        ...  # pragma: no cover


class IndexKind(str, Enum):
    """Index kinds for similarity search.

    HNSW
        Hierarchical Navigable Small World which performs an approximate nearest neighbor (ANN) search.
        Lower accuracy than exhaustive k nearest neighbor, but faster and more efficient.

    Flat
        Does a brute force search to find the nearest neighbors.
        Calculates the distances between all pairs of data points, so has a linear time complexity,
        that grows directly proportional to the number of points.
        Also referred to as exhaustive k nearest neighbor in some databases.
        High recall accuracy, but slower and more expensive than HNSW.
        Better with smaller datasets.

    IVF Flat
        Inverted File with Flat Compression.
        Designed to enhance search efficiency by narrowing the search area
        through the use of neighbor partitions or clusters.
        Also referred to as approximate nearest neighbor (ANN) search.

    Disk ANN
        Disk-based Approximate Nearest Neighbor algorithm designed for efficiently searching
        for approximate nearest neighbors (ANN) in high-dimensional spaces.
        The primary focus of DiskANN is to handle large-scale datasets that cannot fit entirely
        into memory, leveraging disk storage to store the data while maintaining fast search times.

    Quantized Flat
        Index that compresses vectors using DiskANN-based quantization methods for better efficiency in the kNN search.

    Dynamic
        Dynamic index allows to automatically switch from FLAT to HNSW indexes.

    Default
        Default index type.
        Used when no index type is specified.
        Will differ per vector store.

    """

    HNSW = "hnsw"
    FLAT = "flat"
    IVF_FLAT = "ivf_flat"
    DISK_ANN = "disk_ann"
    QUANTIZED_FLAT = "quantized_flat"
    DYNAMIC = "dynamic"
    DEFAULT = "default"


class DistanceFunction(str, Enum):
    """Distance functions for similarity search.

    Cosine Similarity
        the cosine (angular) similarity between two vectors
        measures only the angle between the two vectors, without taking into account the length of the vectors
        Cosine Similarity = 1 - Cosine Distance
        -1 means vectors are opposite
        0 means vectors are orthogonal
        1 means vectors are identical
    Cosine Distance
        the cosine (angular) distance between two vectors
        measures only the angle between the two vectors, without taking into account the length of the vectors
        Cosine Distance = 1 - Cosine Similarity
        2 means vectors are opposite
        1 means vectors are orthogonal
        0 means vectors are identical
    Dot Product
        measures both the length and angle between two vectors
        same as cosine similarity if the vectors are the same length, but more performant
    Euclidean Distance
        measures the Euclidean distance between two vectors
        also known as l2-norm
    Euclidean Squared Distance
        measures the Euclidean squared distance between two vectors
        also known as l2-squared
    Manhattan
        measures the Manhattan distance between two vectors
    Hamming
        number of differences between vectors at each dimensions
    DEFAULT
        default distance function
        used when no distance function is specified
        will differ per vector store.
    """

    COSINE_SIMILARITY = "cosine_similarity"
    COSINE_DISTANCE = "cosine_distance"
    DOT_PROD = "dot_prod"
    EUCLIDEAN_DISTANCE = "euclidean_distance"
    EUCLIDEAN_SQUARED_DISTANCE = "euclidean_squared_distance"
    MANHATTAN = "manhattan"
    HAMMING = "hamming"
    DEFAULT = "DEFAULT"


DISTANCE_FUNCTION_DIRECTION_HELPER: Final[dict[DistanceFunction, Callable[[int | float, int | float], bool]]] = {
    DistanceFunction.COSINE_SIMILARITY: operator.gt,
    DistanceFunction.COSINE_DISTANCE: operator.le,
    DistanceFunction.DOT_PROD: operator.gt,
    DistanceFunction.EUCLIDEAN_DISTANCE: operator.le,
    DistanceFunction.EUCLIDEAN_SQUARED_DISTANCE: operator.le,
    DistanceFunction.MANHATTAN: operator.le,
    DistanceFunction.HAMMING: operator.le,
}


@release_candidate
@dataclass
class VectorStoreField:
    """Vector store fields."""

    field_type: Literal[FieldTypes.DATA, FieldTypes.KEY, FieldTypes.VECTOR] = FieldTypes.DATA
    name: str = ""
    storage_name: str | None = None
    type_: str | None = None
    # data specific fields (all optional)
    is_indexed: bool | None = None
    is_full_text_indexed: bool | None = None
    # vector specific fields (dimensions is mandatory)
    dimensions: int | None = None
    embedding_generator: EmbeddingGeneratorBase | None = None
    # defaults for these fields are not set here, because they are not relevant for data and key types
    index_kind: IndexKind | None = None
    distance_function: DistanceFunction | None = None

    @overload
    def __init__(
        self,
        field_type: Literal[FieldTypes.KEY, "key"] = FieldTypes.KEY,  # type: ignore[assignment]
        *,
        name: str | None = None,
        type: str | None = None,
        storage_name: str | None = None,
    ):
        """Key field of the record.

        When the key will be auto-generated by the store, make sure it has a default, usually None.

        Args:
            field_type: always "key".
            name: The name of the field.
            storage_name: The name of the field in the store, uses the field name by default.
            type: The type of the field.
        """
        ...

    @overload
    def __init__(
        self,
        field_type: Literal[FieldTypes.DATA, "data"] = FieldTypes.DATA,  # type: ignore[assignment]
        *,
        name: str | None = None,
        type: str | None = None,
        storage_name: str | None = None,
        is_indexed: bool | None = None,
        is_full_text_indexed: bool | None = None,
    ):
        """Data field in the record.

        Args:
            field_type: always "data".
            name: The name of the field.
            storage_name: The name of the field in the store, uses the field name by default.
            type: The type of the field.
            is_indexed: Whether the field is indexed.
            is_full_text_indexed: Whether the field is full text indexed.
        """
        ...

    @overload
    def __init__(
        self,
        field_type: Literal[FieldTypes.VECTOR, "vector"] = FieldTypes.VECTOR,  # type: ignore[assignment]
        *,
        name: str | None = None,
        type: str | None = None,
        dimensions: Annotated[int, Field(gt=0)],
        storage_name: str | None = None,
        index_kind: IndexKind | None = None,
        distance_function: DistanceFunction | None = None,
        embedding_generator: EmbeddingGeneratorBase | None = None,
    ):
        """Vector field in the record.

        This field should contain the value you want to use for the vector.
        When passing in the embedding generator, the embedding will be
        generated locally before upserting.
        If this is not set, the store should support generating the embedding for you.
        If you want to retrieve the original content of the vector,
        make sure to set this field twice,
        once with the VectorStoreRecordDataField and once with the VectorStoreRecordVectorField.

        If you want to be able to get the vectors back, make sure the type allows this, especially for pydantic models.
        For instance, if the input is a string, then the type annotation should be `str | list[float] | None`.

        If you want to cast the vector that is returned, you need to set the deserialize_function,
        for instance: `deserialize_function=np.array`, (with `import numpy as np` at the top of your file).
        If you want to set it up with more specific options, use a lambda, a custom function or a partial.

        Args:
            field_type: always "vector".
            name: The name of the field.
            storage_name: The name of the field in the store, uses the field name by default.
            type: Property type.
                For vectors this should be the inner type of the vector.
                By default the vector will be a list of numbers.
                If you want to use a numpy array or some other optimized format,
                set the cast_function with a function
                that takes a list of floats and returns a numpy array.
            dimensions: The number of dimensions of the vector, mandatory.
            index_kind: The index kind to use, uses a default index kind when None.
            distance_function: The distance function to use, uses a default distance function when None.
            embedding_generator: The embedding generator to use.
                If this is set, the embedding will be generated locally before upserting.
        """
        ...

    def __init__(
        self,
        field_type=FieldTypes.DATA,
        *,
        name=None,
        type=None,
        storage_name=None,
        is_indexed=None,
        is_full_text_indexed=None,
        dimensions=None,
        index_kind=None,
        distance_function=None,
        embedding_generator=None,
    ):
        """Vector store field."""
        self.field_type = field_type if isinstance(field_type, FieldTypes) else FieldTypes(field_type)
        # when a field is created, the name can be empty,
        # when a field get's added to a definition, the name needs to be there.
        if name:
            self.name = name
        self.storage_name = storage_name
        self.type_ = type
        self.is_indexed = is_indexed
        self.is_full_text_indexed = is_full_text_indexed
        if field_type == FieldTypes.VECTOR:
            if dimensions is None:
                raise ValidationError("Vector fields must specify 'dimensions'")
            self.dimensions = dimensions
            self.index_kind = index_kind or IndexKind.DEFAULT
            self.distance_function = distance_function or DistanceFunction.DEFAULT
            self.embedding_generator = embedding_generator


@release_candidate
class VectorStoreCollectionDefinition(KernelBaseModel):
    """Collection definition for vector stores.

    Args:
        fields: The fields of the record.
        container_mode: Whether the record is in container mode.
        to_dict: The to_dict function, should take a record and return a list of dicts.
        from_dict: The from_dict function, should take a list of dicts and return a record.
        deserialize: The deserialize function, should take a type specific to a datastore and return a record.

    """

    fields: list[VectorStoreField]
    key_name: str = Field(default="", init=False)
    container_mode: bool = False
    collection_name: str | None = None
    to_dict: ToDictFunctionProtocol | None = None
    from_dict: FromDictFunctionProtocol | None = None
    serialize: SerializeFunctionProtocol | None = None
    deserialize: DeserializeFunctionProtocol | None = None

    @property
    def names(self) -> list[str]:
        """Get the names of the fields."""
        return [field.name for field in self.fields]

    @property
    def storage_names(self) -> list[str]:
        """Get the names of the fields for storage."""
        return [field.storage_name or field.name for field in self.fields]

    @property
    def key_field(self) -> VectorStoreField:
        """Get the key field."""
        return next((field for field in self.fields if field.name == self.key_name), None)  # type: ignore

    @property
    def key_field_storage_name(self) -> str:
        """Get the key field storage name."""
        return self.key_field.storage_name or self.key_field.name

    @property
    def vector_fields(self) -> list[VectorStoreField]:
        """Get the names of the vector fields."""
        return [field for field in self.fields if field.field_type == FieldTypes.VECTOR]

    @property
    def data_fields(self) -> list[VectorStoreField]:
        """Get the names of the data fields."""
        return [field for field in self.fields if field.field_type == FieldTypes.DATA]

    @property
    def vector_field_names(self) -> list[str]:
        """Get the names of the vector fields."""
        return [field.name for field in self.fields if field.field_type == FieldTypes.VECTOR]

    @property
    def data_field_names(self) -> list[str]:
        """Get the names of all the data fields."""
        return [field.name for field in self.fields if field.field_type == FieldTypes.DATA]

    def try_get_vector_field(self, field_name: str | None = None) -> VectorStoreField | None:
        """Try to get the vector field.

        If the field_name is None, then the first vector field is returned.
        If no vector fields are present None is returned.

        Args:
            field_name: The field name.

        Returns:
            VectorStoreRecordVectorField | None: The vector field or None.
        """
        if field_name is None:
            if len(self.vector_fields) == 0:
                return None
            return self.vector_fields[0]
        for field in self.fields:
            if field.name == field_name or field.storage_name == field_name:
                if field.field_type == FieldTypes.VECTOR:
                    return field
                raise VectorStoreModelException(
                    f"Field {field_name} is not a vector field, it is of type {type(field).__name__}."
                )
        raise VectorStoreModelException(f"Field {field_name} not found.")

    def get_storage_names(self, include_vector_fields: bool = True, include_key_field: bool = True) -> list[str]:
        """Get the names of the fields for the storage.

        Args:
            include_vector_fields: Whether to include vector fields.
            include_key_field: Whether to include the key field.

        Returns:
            list[str]: The names of the fields.
        """
        return [
            field.storage_name or field.name
            for field in self.fields
            if field.field_type == FieldTypes.DATA
            or (field.field_type == FieldTypes.VECTOR and include_vector_fields)
            or (field.field_type == FieldTypes.KEY and include_key_field)
        ]

    def get_names(self, include_vector_fields: bool = True, include_key_field: bool = True) -> list[str]:
        """Get the names of the fields.

        Args:
            include_vector_fields: Whether to include vector fields.
            include_key_field: Whether to include the key field.

        Returns:
            list[str]: The names of the fields.
        """
        return [
            field.name
            for field in self.fields
            if field.field_type == FieldTypes.DATA
            or (field.field_type == FieldTypes.VECTOR and include_vector_fields)
            or (field.field_type == FieldTypes.KEY and include_key_field)
        ]

    def model_post_init(self, _: Any):
        """Validate the fields.

        Raises:
            VectorStoreModelException: If there is a field with an embedding property name
                but no corresponding vector field.
            VectorStoreModelException: If there is no key field.
        """
        if len(self.fields) == 0:
            raise VectorStoreModelException(
                "There must be at least one field with a VectorStoreRecordField annotation."
            )
        for field in self.fields:
            if not field.name or field.name == "":
                raise VectorStoreModelException("Field names must not be empty.")
            if field.field_type == FieldTypes.KEY:
                if self.key_name != "":
                    raise VectorStoreModelException("Memory record definition must have exactly one key field.")
                self.key_name = field.name
        if not self.key_name:
            raise VectorStoreModelException("Memory record definition must have exactly one key field.")


# region: Decorator


def _parse_vector_store_record_field_instance(record_field: VectorStoreField, field: Parameter) -> VectorStoreField:
    if not record_field.name or record_field.name != field.name:
        record_field.name = field.name
    if not record_field.type_ and hasattr(field.annotation, "__origin__"):
        property_type = field.annotation.__origin__
        if record_field.field_type == FieldTypes.VECTOR:
            if args := getattr(property_type, "__args__", None):
                if NoneType in args and len(args) > 1:
                    for arg in args:
                        if arg is NoneType:
                            continue

                        if (
                            (inner_args := getattr(arg, "__args__", None))
                            and len(inner_args) == 1
                            and inner_args[0] is not NoneType
                        ):
                            property_type = inner_args[0]
                            break
                        property_type = arg
                        break
                else:
                    property_type = args[0]

        else:
            if (args := getattr(property_type, "__args__", None)) and NoneType in args and len(args) == 2:
                property_type = args[0]

        record_field.type_ = str(property_type) if hasattr(property_type, "__args__") else property_type.__name__

    return record_field


def _parse_parameter_to_field(field: Parameter) -> VectorStoreField | None:
    # first check if there are any annotations
    if field.annotation is not _empty and hasattr(field.annotation, "__metadata__"):
        for field_annotation in field.annotation.__metadata__:
            if isinstance(field_annotation, VectorStoreField):
                return _parse_vector_store_record_field_instance(field_annotation, field)
    # This means there are no annotations or that all annotations are of other types.
    # we will check if there is a default, otherwise this will cause a runtime error.
    # because it will not be stored, and retrieving this object will fail without a default for this field.
    if field.default is _empty:
        raise VectorStoreModelException(
            "Fields that do not have a VectorStoreField annotation must have a default value."
        )
    logger.debug(f'Field "{field.name}" does not have a VectorStoreField annotation, will not be part of the record.')
    return None


def _parse_signature_to_definition(
    parameters: MappingProxyType[str, Parameter], collection_name: str | None = None
) -> VectorStoreCollectionDefinition:
    if len(parameters) == 0:
        raise VectorStoreModelException(
            "There must be at least one field in the datamodel. If you are using this with a @dataclass, "
            "you might have inverted the order of the decorators, the vectorstoremodel decorator should be the top one."
        )
    fields = []
    for param in parameters.values():
        field = _parse_parameter_to_field(param)
        if field:
            fields.append(field)

    return VectorStoreCollectionDefinition(
        fields=fields,
        collection_name=collection_name,
    )


@release_candidate
def vectorstoremodel(
    cls: type[TModel] | None = None,
    collection_name: str | None = None,
) -> type[TModel]:
    """Returns the class as a vector store model.

    This decorator makes a class a vector store model.
    There are three things being checked:
    - The class must have at least one field with a annotation,
        of type VectorStoreField.
    - The class must have exactly one field with the field_type `key`.
    - When creating a Vector Field, either supply the property type directly,
    or make sure to set the property that you want the index to use first.


    Args:
        cls: The class to be decorated.
        collection_name: The name of the collection to be used.
            This is used to set the collection name in the VectorStoreCollectionDefinition.

    Raises:
        VectorStoreModelException: If there are no fields with a VectorStoreField annotation.
        VectorStoreModelException: If there are fields with no name.
        VectorStoreModelException: If there is no key field.
    """

    def wrap(cls: type[TModel]) -> type[TModel]:
        # get fields and annotations
        cls_sig = signature(cls)
        setattr(cls, "__kernel_vectorstoremodel__", True)
        setattr(
            cls,
            "__kernel_vectorstoremodel_definition__",
            _parse_signature_to_definition(cls_sig.parameters, collection_name),
        )

        return cls  # type: ignore

    # See if we're being called as @vectorstoremodel or @vectorstoremodel().
    if cls is None:
        # We're called with parens.
        return wrap  # type: ignore

    # We're called as @vectorstoremodel without parens.
    return wrap(cls)


# region: VectorSearch Helpers


def _get_collection_name_from_model(
    record_type: type[TModel],
    definition: VectorStoreCollectionDefinition | None = None,
) -> str | None:
    """Get the collection name from the data model type or definition."""
    if record_type and not definition:
        definition = getattr(record_type, "__kernel_vectorstoremodel_definition__", None)
    if definition and definition.collection_name:
        return definition.collection_name
    return None


@pyd_dataclass
class GetFilteredRecordOptions:
    """Options for filtering records.

    Args:
        top: The maximum number of records to return.
        skip: The number of records to skip.
        order_by: A dictionary with fields names and a bool, True means ascending, False means descending.
    """

    top: int = 10
    skip: int = 0
    order_by: Mapping[str, bool] | None = None


class LambdaVisitor(NodeVisitor, Generic[TFilters]):
    """Visitor class to visit the AST nodes."""

    def __init__(self, lambda_parser: Callable[[expr], TFilters], output_filters: list[TFilters] | None = None) -> None:
        """Initialize the visitor with a lambda parser and output filters."""
        self.lambda_parser = lambda_parser
        self.output_filters = output_filters if output_filters is not None else []

    def visit_Lambda(self, node: Lambda) -> None:
        """This method is called when a lambda expression is found."""
        self.output_filters.append(self.lambda_parser(node.body))


@release_candidate
class SearchType(str, Enum):
    """Enumeration for search types.

    Contains: vector and keyword_hybrid.
    """

    VECTOR = "vector"
    KEYWORD_HYBRID = "keyword_hybrid"


@release_candidate
class VectorSearchOptions(SearchOptions):
    """Options for vector search, builds on TextSearchOptions.

    When multiple filters are used, they are combined with an AND operator.
    """

    vector_property_name: str | None = None
    additional_property_name: str | None = None
    top: Annotated[int, Field(gt=0)] = 3
    include_vectors: bool = False


@release_candidate
class VectorSearchResult(KernelBaseModel, Generic[TModel]):
    """The result of a vector search."""

    record: TModel
    score: float | None = None


# region: VectorStoreRecordHandler


@release_candidate
class VectorStoreRecordHandler(KernelBaseModel, Generic[TKey, TModel]):
    """Vector Store Record Handler class.

    This class is used to serialize and deserialize records to and from a vector store.
    As well as validating the data model against the vector store.
    It is subclassed by VectorStoreRecordCollection and VectorSearchBase.
    """

    record_type: type[TModel]
    definition: VectorStoreCollectionDefinition
    supported_key_types: ClassVar[set[str] | None] = None
    supported_vector_types: ClassVar[set[str] | None] = None
    embedding_generator: EmbeddingGeneratorBase | None = None

    @property
    def _key_field_name(self) -> str:
        return self.definition.key_name

    @property
    def _key_field_storage_name(self) -> str:
        return self.definition.key_field.storage_name or self.definition.key_name

    @property
    def _container_mode(self) -> bool:
        return self.definition.container_mode

    @model_validator(mode="before")
    @classmethod
    def _ensure_definition(cls: type[_T], data: Any) -> dict[str, Any]:
        """Ensure there is a  data model definition, if it isn't passed, try to get it from the data model type."""
        if isinstance(data, dict) and not data.get("definition"):
            data["definition"] = getattr(data["record_type"], "__kernel_vectorstoremodel_definition__", None)
        return data

    def model_post_init(self, __context: object | None = None):
        """Post init function that sets the key field and container mode values, and validates the datamodel."""
        self._validate_data_model()

    def _validate_data_model(self):
        """Internal function that can be overloaded by child classes to validate datatypes, etc.

        This should take the VectorStoreRecordDefinition from the item_type and validate it against the store.

        Checks can include, allowed naming of parameters, allowed data types, allowed vector dimensions.

        Default checks are that the key field is in the allowed key types and the vector fields
        are in the allowed vector types.

        Raises:
            VectorStoreModelValidationError: If the key field is not in the allowed key types.
            VectorStoreModelValidationError: If the vector fields are not in the allowed vector types.

        """
        if (
            self.supported_key_types
            and self.definition.key_field.type_
            and self.definition.key_field.type_ not in self.supported_key_types
        ):
            raise VectorStoreModelValidationError(
                f"Key field must be one of {self.supported_key_types}, got {self.definition.key_field.type_}"
            )
        if not self.supported_vector_types:
            return
        for field in self.definition.vector_fields:
            if field.type_ and field.type_ not in self.supported_vector_types:
                raise VectorStoreModelValidationError(
                    f"Vector field {field.name} must be one of {self.supported_vector_types}, got {field.type_}"
                )

    @abstractmethod
    def _serialize_dicts_to_store_models(self, records: Sequence[dict[str, Any]], **kwargs: Any) -> Sequence[Any]:
        """Serialize a list of dicts of the data to the store model.

        This method should be overridden by the child class to convert the dict to the store model.
        """
        ...  # pragma: no cover

    @abstractmethod
    def _deserialize_store_models_to_dicts(self, records: Sequence[Any], **kwargs: Any) -> Sequence[dict[str, Any]]:
        """Deserialize the store models to a list of dicts.

        This method should be overridden by the child class to convert the store model to a list of dicts.
        """
        ...  # pragma: no cover

    # region Serialization methods

    async def serialize(self, records: OneOrMany[TModel], **kwargs: Any) -> OneOrMany[Any]:
        """Serialize the data model to the store model.

        This method follows the following steps:
        1. Check if the data model has a serialize method.
            Use that method to serialize and return the result.
        2. Serialize the records into a dict, using the data model specific method.
        3. Convert the dict to the store model, using the store specific method.

        If overriding this method, make sure to first try to serialize the data model to the store model,
        before doing the store specific version,
        the user supplied version should have precedence.

        Raises:
            VectorStoreModelSerializationException: If an error occurs during serialization.

        """
        try:
            if serialized := self._serialize_data_model_to_store_model(records):
                return serialized
        except VectorStoreModelSerializationException:
            raise  # pragma: no cover
        except Exception as exc:
            raise VectorStoreModelSerializationException(f"Error serializing records: {exc}") from exc

        try:
            dict_records: list[dict[str, Any]] = []
            if not isinstance(records, list):
                records = [records]  # type: ignore
            for rec in records:
                dict_rec = self._serialize_data_model_to_dict(rec)
                if isinstance(dict_rec, list):
                    dict_records.extend(dict_rec)
                else:
                    dict_records.append(dict_rec)
        except VectorStoreModelSerializationException:
            raise  # pragma: no cover
        except Exception as exc:
            raise VectorStoreModelSerializationException(f"Error serializing records: {exc}") from exc

        # add vectors
        try:
            dict_records = await self._add_vectors_to_records(dict_records)  # type: ignore
        except (VectorStoreModelException, VectorStoreOperationException):
            raise
        except Exception as exc:
            raise VectorStoreOperationException(
                "Exception occurred while trying to add the vectors to the records."
            ) from exc

        try:
            return self._serialize_dicts_to_store_models(dict_records, **kwargs)  # type: ignore
        except VectorStoreModelSerializationException:
            raise  # pragma: no cover
        except Exception as exc:
            raise VectorStoreModelSerializationException(f"Error serializing records: {exc}") from exc

    def _serialize_data_model_to_store_model(self, record: OneOrMany[TModel], **kwargs: Any) -> OneOrMany[Any] | None:
        """Serialize the data model to the store model.

        This works when the data model has supplied a serialize method, specific to a data source.
        This is a method called 'serialize()' on the data model or part of the vector store record definition.

        The developer is responsible for correctly serializing for the specific data source.
        """
        if isinstance(record, Sequence):
            result = [self._serialize_data_model_to_store_model(rec, **kwargs) for rec in record]
            if not all(result):
                return None
            return result
        if self.definition.serialize:
            return self.definition.serialize(record, **kwargs)
        if isinstance(record, SerializeMethodProtocol):
            return record.serialize(**kwargs)
        return None

    def _serialize_data_model_to_dict(self, record: TModel, **kwargs: Any) -> OneOrList[dict[str, Any]]:
        """This function is used if no serialize method is found on the data model.

        This will generally serialize the data model to a dict, should not be overridden by child classes.

        The output of this should be passed to the serialize_dict_to_store_model method.
        """
        if self.definition.to_dict:
            return self.definition.to_dict(record, **kwargs)  # type: ignore
        if isinstance(record, BaseModel):
            return record.model_dump()

        store_model = {}
        for field in self.definition.fields:
            store_model[field.storage_name or field.name] = (
                record.get(field.name, None) if isinstance(record, Mapping) else getattr(record, field.name)
            )
        return store_model

    # region Deserialization methods

    def deserialize(self, records: OneOrMany[Any | dict[str, Any]], **kwargs: Any) -> OneOrMany[TModel] | None:
        """Deserialize the store model to the data model.

        This method follows the following steps:
        1. Check if the data model has a deserialize method.
            Use that method to deserialize and return the result.
        2. Deserialize the store model to a dict, using the store specific method.
        3. Convert the dict to the data model, using the data model specific method.

        Raises:
            VectorStoreModelDeserializationException: If an error occurs during deserialization.
        """
        try:
            if not records:
                return None
            if deserialized := self._deserialize_store_model_to_data_model(records, **kwargs):
                return deserialized

            if isinstance(records, Sequence):
                dict_records = self._deserialize_store_models_to_dicts(records, **kwargs)
                return (
                    self._deserialize_dict_to_data_model(dict_records, **kwargs)
                    if self._container_mode
                    else [self._deserialize_dict_to_data_model(rec, **kwargs) for rec in dict_records]
                )

            dict_record = self._deserialize_store_models_to_dicts([records], **kwargs)[0]
            # regardless of mode, only 1 object is returned.
            return self._deserialize_dict_to_data_model(dict_record, **kwargs)
        except VectorStoreModelDeserializationException:
            raise  # pragma: no cover
        except Exception as exc:
            raise VectorStoreModelDeserializationException(f"Error deserializing records: {exc}") from exc

    def _deserialize_store_model_to_data_model(self, record: OneOrMany[Any], **kwargs: Any) -> OneOrMany[TModel] | None:
        """Deserialize the store model to the data model.

        This works when the data model has supplied a deserialize method, specific to a data source.
        This uses a method called 'deserialize()' on the data model or part of the vector store record definition.

        The developer is responsible for correctly deserializing for the specific data source.
        """
        if self.definition.deserialize:
            if isinstance(record, Sequence):
                return self.definition.deserialize(record, **kwargs)
            return self.definition.deserialize([record], **kwargs)
        if func := getattr(self.record_type, "deserialize", None):
            if isinstance(record, Sequence):
                return [func(rec, **kwargs) for rec in record]
            return func(record, **kwargs)
        return None

    def _deserialize_dict_to_data_model(self, record: OneOrMany[dict[str, Any]], **kwargs: Any) -> TModel:
        """This function is used if no deserialize method is found on the data model.

        This method is the second step and will deserialize a dict to the data model,
        should not be overridden by child classes.

        The input of this should come from the _deserialized_store_model_to_dict function.
        """
        if self.definition.from_dict:
            if isinstance(record, Sequence):
                return self.definition.from_dict(record, **kwargs)
            ret = self.definition.from_dict([record], **kwargs)
            return ret if self._container_mode else ret[0]
        if isinstance(record, Sequence):
            if len(record) > 1:
                raise VectorStoreModelDeserializationException(
                    "Cannot deserialize multiple records to a single record unless you are using a container."
                )
            record = record[0]
        if func := getattr(self.record_type, "from_dict", None):
            return func(record)
        if issubclass(self.record_type, BaseModel):
            for field in self.definition.fields:
                if field.storage_name and field.storage_name in record:
                    record[field.name] = record.pop(field.storage_name)
            return self.record_type.model_validate(record)  # type: ignore
        data_model_dict: dict[str, Any] = {}
        for field in self.definition.fields:
            value = record.get(field.storage_name or field.name, None)
            if field.field_type == FieldTypes.VECTOR and not kwargs.get("include_vectors"):
                continue
            data_model_dict[field.name] = value
        if self.record_type is dict:
            return data_model_dict  # type: ignore
        return self.record_type(**data_model_dict)

    async def _add_vectors_to_records(
        self,
        records: OneOrMany[dict[str, Any]],
        **kwargs,
    ) -> OneOrMany[dict[str, Any]]:
        """Vectorize the vector record.

        This function can be passed to upsert or upsert batch of a VectorStoreRecordCollection.

        Loops through the fields of the data model definition,
        looks at data fields, if they have a vector field,
        looks up that vector field and checks if is a local embedding.

        If so adds that to a list of embeddings to make.

        Finally calls Kernel add_embedding_to_object with the list of embeddings to make.

        Optional arguments are passed onto the Kernel add_embedding_to_object call.
        """
        # dict of embedding_field.name and tuple of record, settings, field_name
        embeddings_to_make: list[tuple[str, int, EmbeddingGeneratorBase]] = []

        for field in self.definition.vector_fields:
            embedding_generator = field.embedding_generator or self.embedding_generator
            if not embedding_generator:
                continue
            if field.dimensions is None:
                raise VectorStoreModelException(
                    f"Field {field.name} has no dimensions, cannot create embedding for field."
                )
            embeddings_to_make.append((
                field.storage_name or field.name,
                field.dimensions,
                embedding_generator,
            ))

        for field_name, dimensions, embedder in embeddings_to_make:
            await self._add_embedding_to_object(
                inputs=records,
                field_name=field_name,
                dimensions=dimensions,
                embedding_generator=embedder,
                container_mode=self.definition.container_mode,
                **kwargs,
            )
        return records

    async def _add_embedding_to_object(
        self,
        inputs: OneOrMany[Any],
        field_name: str,
        dimensions: int,
        embedding_generator: EmbeddingGeneratorBase,
        container_mode: bool = False,
        **kwargs: Any,
    ):
        """Gather all fields to embed, batch the embedding generation and store."""
        contents: list[Any] = []
        dict_like = (getter := getattr(inputs, "get", False)) and callable(getter)
        list_of_dicts: bool = False
        if isinstance(inputs, list):
            list_of_dicts = (getter := getattr(inputs[0], "get", False)) and callable(getter)
            for record in inputs:
                if list_of_dicts:
                    contents.append(record.get(field_name))  # type: ignore
                else:
                    contents.append(getattr(record, field_name))
        else:
            if dict_like:
                contents.append(inputs.get(field_name))  # type: ignore
            else:
                contents.append(getattr(inputs, field_name))

        vectors = await embedding_generator.generate_raw_embeddings(
            texts=contents, settings=PromptExecutionSettings(dimensions=dimensions), **kwargs
        )  # type: ignore
        if vectors is None:
            raise VectorStoreOperationException("No vectors were generated.")
        if isinstance(inputs, list):
            for record, vector in zip(inputs, vectors):
                if list_of_dicts:
                    record[field_name] = vector  # type: ignore
                else:
                    setattr(record, field_name, vector)
            return
        if dict_like:
            inputs[field_name] = vectors[0]  # type: ignore
            return
        setattr(inputs, field_name, vectors[0])


# region: VectorStoreRecordCollection


@release_candidate
class VectorStoreCollection(VectorStoreRecordHandler[TKey, TModel], Generic[TKey, TModel]):
    """Base class for a vector store record collection."""

    collection_name: str = ""
    managed_client: bool = True

    @model_validator(mode="before")
    @classmethod
    def _ensure_collection_name(cls: type[_T], data: Any) -> dict[str, Any]:
        """Ensure there is a collection name, if it isn't passed, try to get it from the data model type."""
        if (
            isinstance(data, dict)
            and not data.get("collection_name")
            and (collection_name := _get_collection_name_from_model(data["record_type"], data.get("definition")))
        ):
            data["collection_name"] = collection_name
        return data

    async def __aenter__(self) -> Self:
        """Enter the context manager."""
        return self

    async def __aexit__(self, exc_type, exc_value, traceback) -> None:
        """Exit the context manager.

        Should be overridden by subclasses, if necessary.

        If the client is passed in the constructor, it should not be closed,
        in that case the managed_client should be set to False.

        If the store supplied the managed client, it is responsible for closing it,
        and it should not be closed here and so managed_client should be False.

        Some services use two clients, one for the store and one for the collection,
        in that case, the collection client should be closed here,
        but the store client should only be closed when it is created in the collection.
        A additional flag might be needed for that.
        """
        pass

    @abstractmethod
    async def _inner_upsert(
        self,
        records: Sequence[Any],
        **kwargs: Any,
    ) -> Sequence[TKey]:
        """Upsert the records, this should be overridden by the child class.

        Args:
            records: The records, the format is specific to the store.
            **kwargs (Any): Additional arguments, to be passed to the store.

        Returns:
            The keys of the upserted records.

        Raises:
            Exception: If an error occurs during the upsert.
                There is no need to catch and parse exceptions in the inner functions,
                they are handled by the public methods.
                The only exception is raises exceptions yourself, such as a ValueError.
                This is then caught and turned into the relevant exception by the public method.
                This setup promotes a limited depth of the stack trace.

        """
        ...  # pragma: no cover

    @abstractmethod
    async def _inner_get(
        self,
        keys: Sequence[TKey] | None = None,
        options: GetFilteredRecordOptions | None = None,
        **kwargs: Any,
    ) -> OneOrMany[Any] | None:
        """Get the records, this should be overridden by the child class.

        Args:
            keys: The keys to get.
            options: the options to use for the get.
            **kwargs: Additional arguments.

        Returns:
            The records from the store, not deserialized.

        Raises:
            Exception: If an error occurs during the upsert.
                There is no need to catch and parse exceptions in the inner functions,
                they are handled by the public methods.
                The only exception is raises exceptions yourself, such as a ValueError.
                This is then caught and turned into the relevant exception by the public method.
                This setup promotes a limited depth of the stack trace.
        """
        ...  # pragma: no cover

    @abstractmethod
    async def _inner_delete(self, keys: Sequence[TKey], **kwargs: Any) -> None:
        """Delete the records, this should be overridden by the child class.

        Args:
            keys: The keys.
            **kwargs: Additional arguments.

        Raises:
            Exception: If an error occurs during the upsert.
                There is no need to catch and parse exceptions in the inner functions,
                they are handled by the public methods.
                The only exception is raises exceptions yourself, such as a ValueError.
                This is then caught and turned into the relevant exception by the public method.
                This setup promotes a limited depth of the stack trace.
        """
        ...  # pragma: no cover

    @abstractmethod
    async def ensure_collection_exists(self, **kwargs: Any) -> None:
        """Create the collection in the service.

        This should be overridden by the child class. Should first check if the collection exists,
        if it does not, it should create the collection.

        Raises:
            Make sure the implementation of this function raises relevant exceptions with good descriptions.
            This is different then the `_inner_x` methods, as this is a public method.

        """
        ...  # pragma: no cover

    @abstractmethod
    async def collection_exists(self, **kwargs: Any) -> bool:
        """Check if the collection exists.

        This should be overridden by the child class.

        Raises:
            Make sure the implementation of this function raises relevant exceptions with good descriptions.
            This is different then the `_inner_x` methods, as this is a public method.
        """
        ...  # pragma: no cover

    @abstractmethod
    async def ensure_collection_deleted(self, **kwargs: Any) -> None:
        """Delete the collection.

        This should be overridden by the child class.

        Raises:
            Make sure the implementation of this function raises relevant exceptions with good descriptions.
            This is different then the `_inner_x` methods, as this is a public method.
        """
        ...  # pragma: no cover

    async def upsert(
        self,
        records: OneOrMany[TModel],
        **kwargs,
    ) -> OneOrMany[TKey]:
        """Upsert one or more records.

        If the key of the record already exists, the existing record will be updated.
        If the key does not exist, a new record will be created.

        Args:
            records: The records to upsert, can be a single record, a list of records, or a single container.
                If a single record is passed, a single key is returned, instead of a list of keys.
            **kwargs: Additional arguments.

        Returns:
            OneOrMany[TKey]: The keys of the upserted records.

        Raises:
            VectorStoreModelSerializationException: If an error occurs during serialization.
            VectorStoreOperationException: If an error occurs during upserting.
        """
        batch = True
        if not isinstance(records, list) and not self._container_mode:
            batch = False
        if records is None:
            raise VectorStoreOperationException("Either record or records must be provided.")

        try:
            data = await self.serialize(records)
        # the serialize method will parse any exception into a VectorStoreModelSerializationException
        except VectorStoreModelSerializationException:
            raise

        try:
            results = await self._inner_upsert(data if isinstance(data, list) else [data], **kwargs)  # type: ignore
        except Exception as exc:
            raise VectorStoreOperationException(
                f"Error upserting record(s) into collection '{self.collection_name}': {exc}"
            ) from exc
        if batch or self._container_mode:
            return results
        return results[0]

    @overload
    async def get(
        self,
        top: int = ...,
        skip: int = ...,
        order_by: OneOrMany[str] | dict[str, bool] | None = None,
        include_vectors: bool = False,
        **kwargs: Any,
    ) -> Sequence[TModel] | None:
        """Get records based on the ordering and selection criteria.

        Args:
            include_vectors: Include the vectors in the response. Default is True.
                Some vector stores do not support retrieving without vectors, even when set to false.
                Some vector stores have specific parameters to control that behavior, when
                that parameter is set, include_vectors is ignored.
            top: The number of records to return.
                Only used if keys are not provided.
            skip: The number of records to skip.
                Only used if keys are not provided.
            order_by: The order by clause,
                this can be a string, a list of strings or a dict,
                when passing strings, they are assumed to be ascending.
                Otherwise, use the value in the dict to set ascending (True) or descending (False).
                example: {"field_name": True} or ["field_name", {"field_name2": False}].
            **kwargs: Additional arguments.

        Returns:
            The records, either a list of TModel or the container type.

        Raises:
            VectorStoreOperationException: If an error occurs during the get.
            VectorStoreModelDeserializationException: If an error occurs during deserialization.
        """
        ...

    @overload
    async def get(
        self,
        key: TKey = ...,
        include_vectors: bool = False,
        **kwargs: Any,
    ) -> TModel | None:
        """Get a record if it exists.

        Args:
            key: The key to get.
            include_vectors: Include the vectors in the response. Default is True.
                Some vector stores do not support retrieving without vectors, even when set to false.
                Some vector stores have specific parameters to control that behavior, when
                that parameter is set, include_vectors is ignored.
            **kwargs: Additional arguments.

        Returns:
            The records, either a list of TModel or the container type.

        Raises:
            VectorStoreOperationException: If an error occurs during the get.
            VectorStoreModelDeserializationException: If an error occurs during deserialization.
        """
        ...

    @overload
    async def get(
        self,
        keys: Sequence[TKey] = ...,
        include_vectors: bool = False,
        **kwargs: Any,
    ) -> OneOrMany[TModel] | None:
        """Get a batch of records whose keys exist in the collection, i.e. keys that do not exist are ignored.

        Args:
            keys: The keys to get, if keys are provided, key is ignored.
            include_vectors: Include the vectors in the response. Default is True.
                Some vector stores do not support retrieving without vectors, even when set to false.
                Some vector stores have specific parameters to control that behavior, when
                that parameter is set, include_vectors is ignored.
            **kwargs: Additional arguments.

        Returns:
            The records, either a list of TModel or the container type.

        Raises:
            VectorStoreOperationException: If an error occurs during the get.
            VectorStoreModelDeserializationException: If an error occurs during deserialization.
        """
        ...

    async def get(
        self,
        key=None,
        keys=None,
        include_vectors=False,
        **kwargs,
    ):
        """Get a batch of records whose keys exist in the collection, i.e. keys that do not exist are ignored.

        Args:
            key: The key to get.
            keys: The keys to get, if keys are provided, key is ignored.
            include_vectors: Include the vectors in the response. Default is True.
                Some vector stores do not support retrieving without vectors, even when set to false.
                Some vector stores have specific parameters to control that behavior, when
                that parameter is set, include_vectors is ignored.
            top: The number of records to return.
                Only used if keys are not provided.
            skip: The number of records to skip.
                Only used if keys are not provided.
            order_by: The order by clause, this is a list of dicts with the field name and ascending flag,
                (default is True, which means ascending).
                Only used if keys are not provided.
            **kwargs: Additional arguments.

        Returns:
            The records, either a list of TModel or the container type.

        Raises:
            VectorStoreOperationException: If an error occurs during the get.
            VectorStoreModelDeserializationException: If an error occurs during deserialization.
        """
        batch = True
        options = None
        if not keys and key:
            if not isinstance(key, list):
                keys = [key]
                batch = False
            else:
                keys = key
        if not keys:
            if kwargs:
                get_args = {}
                kw_order_by: OneOrList[str] | dict[str, bool] | None = kwargs.pop("order_by", None)  # type: ignore
                if "top" in kwargs:
                    get_args["top"] = kwargs.pop("top", None)
                if "skip" in kwargs:
                    get_args["skip"] = kwargs.pop("skip", None)
                order_by: dict[str, bool] | None = None  # type: ignore
                if kw_order_by is not None:
                    order_by = {}
                    if isinstance(kw_order_by, str):
                        order_by[kw_order_by] = True
                    elif isinstance(kw_order_by, dict):
                        order_by = kw_order_by
                    elif isinstance(kw_order_by, list):
                        for item in kw_order_by:
                            if isinstance(item, str):
                                order_by[item] = True
                            else:
                                order_by.update(item)
                    else:
                        raise VectorStoreOperationException(
                            f"Invalid order_by type: {type(order_by)}, expected str, dict or list."
                        )
                    get_args["order_by"] = order_by
                try:
                    options = GetFilteredRecordOptions(**get_args)
                except Exception as exc:
                    raise VectorStoreOperationException(f"Error creating options: {exc}") from exc
            else:
                raise VectorStoreOperationException("Either key, keys or options must be provided.")
        try:
            records = await self._inner_get(keys, include_vectors=include_vectors, options=options, **kwargs)
        except Exception as exc:
            raise VectorStoreOperationException(f"Error getting record(s): {exc}") from exc

        if not records:
            return None

        try:
            model_records = self.deserialize(
                records if batch else records[0], include_vectors=include_vectors, **kwargs
            )
        # the deserialize method will parse any exception into a VectorStoreModelDeserializationException
        except VectorStoreModelDeserializationException:
            raise

        # there are many code paths within the deserialize method, some supplied by the developer,
        # and so depending on what is used,
        # it might return a sequence, so we just return the first element,
        # there should never be multiple elements (this is not a batch get),
        # hence a raise if there are.
        if batch:
            return model_records
        if not isinstance(model_records, Sequence):
            return model_records
        if len(model_records) == 1:
            return model_records[0]
        raise VectorStoreModelDeserializationException(
            f"Error deserializing record, multiple records returned: {model_records}"
        )

    async def delete(self, keys: OneOrMany[TKey], **kwargs):
        """Delete one or more records by key.

        An exception will be raised at the end if any record does not exist.

        Args:
            keys: The key or keys to be deleted.
            **kwargs: Additional arguments.
        Exceptions:
            VectorStoreOperationException: If an error occurs during deletion or a record does not exist.
        """
        if not isinstance(keys, list):
            keys = [keys]  # type: ignore
        try:
            await self._inner_delete(keys, **kwargs)  # type: ignore
        except Exception as exc:
            raise VectorStoreOperationException(f"Error deleting record(s): {exc}") from exc


# region: VectorStore


@release_candidate
class VectorStore(KernelBaseModel):
    """Base class for vector stores."""

    managed_client: bool = True
    embedding_generator: EmbeddingGeneratorBase | None = None

    @abstractmethod
    def get_collection(
        self,
        record_type: type[TModel],
        *,
        definition: VectorStoreCollectionDefinition | None = None,
        collection_name: str | None = None,
        embedding_generator: EmbeddingGeneratorBase | None = None,
        **kwargs: Any,
    ) -> "VectorStoreCollection":
        """Get a vector store record collection instance tied to this store.

        Args:
            record_type: The type of the records that will be used.
            definition: The data model definition.
            collection_name: The name of the collection.
            embedding_generator: The embedding generator to use.
            **kwargs: Additional arguments.

        Returns:
            A vector store record collection instance tied to this store.

        """
        ...  # pragma: no cover

    @abstractmethod
    async def list_collection_names(self, **kwargs) -> Sequence[str]:
        """Get the names of all collections."""
        ...  # pragma: no cover

    async def collection_exists(self, collection_name: str) -> bool:
        """Check if a collection exists.

        This is a wrapper around the get_collection method of a collection,
        to check if the collection exists.
        """
        try:
            data_model = VectorStoreCollectionDefinition(fields=[VectorStoreField("key", name="id")])
            collection = self.get_collection(record_type=dict, definition=data_model, collection_name=collection_name)
            return await collection.collection_exists()
        except VectorStoreOperationException:
            return False

    async def ensure_collection_deleted(self, collection_name: str) -> None:
        """Delete a collection.

        This is a wrapper around the get_collection method of a collection,
        to delete the collection.
        """
        try:
            data_model = VectorStoreCollectionDefinition(fields=[VectorStoreField("key", name="id")])
            collection = self.get_collection(record_type=dict, definition=data_model, collection_name=collection_name)
            await collection.ensure_collection_deleted()
        except VectorStoreOperationException:
            pass

    async def __aenter__(self) -> Self:
        """Enter the context manager."""
        return self

    async def __aexit__(self, exc_type, exc_value, traceback) -> None:
        """Exit the context manager.

        Should be overridden by subclasses, if necessary.

        If the client is passed in the constructor, it should not be closed,
        in that case the managed_client should be set to False.
        """
        pass  # pragma: no cover


# region: Vector Search


@release_candidate
class VectorSearch(VectorStoreRecordHandler[TKey, TModel], Generic[TKey, TModel]):
    """Base class for searching vectors."""

    supported_search_types: ClassVar[set[SearchType]] = Field(default_factory=set)

    @property
    def options_class(self) -> type[SearchOptions]:
        """The options class for the search."""
        return VectorSearchOptions

    @abstractmethod
    async def _inner_search(
        self,
        search_type: SearchType,
        options: VectorSearchOptions,
        values: Any | None = None,
        vector: Sequence[float | int] | None = None,
        **kwargs: Any,
    ) -> KernelSearchResults[VectorSearchResult[TModel]]:
        """Inner search method.

        This is the main search method that should be implemented, and will be called by the public search methods.
        Currently, at least one of the three search contents will be provided
        (through the public interface mixin functions), in the future, this may be expanded to allow multiple of them.

        This method should return a KernelSearchResults object with the results of the search.
        The inner "results" object of the KernelSearchResults should be a async iterator that yields the search results,
        this allows things like paging to be implemented.

        There is a default helper method "_get_vector_search_results_from_results" to convert
        the results to a async iterable VectorSearchResults, but this can be overridden if necessary.

        Options might be a object of type VectorSearchOptions, or a subclass of it.

        The implementation of this method must deal with the possibility that multiple search contents are provided,
        and should handle them in a way that makes sense for that particular store.

        The public methods will catch and reraise the three exceptions mentioned below, others are caught and turned
        into a VectorSearchExecutionException.

        Args:
            search_type: The type of search to perform.
            options: The search options, can be None.
            values: The values to search for, optional.
            vector: The vector to search for, optional.
            **kwargs: Additional arguments that might be needed.

        Returns:
            The search results, wrapped in a KernelSearchResults object.

        Raises:
            VectorSearchExecutionException: If an error occurs during the search.
            VectorStoreModelDeserializationException: If an error occurs during deserialization.
            VectorSearchOptionsException: If the search options are invalid.
            VectorStoreOperationNotSupportedException: If the search type is not supported.

        """
        ...

    @abstractmethod
    def _get_record_from_result(self, result: Any) -> Any:
        """Get the record from the returned search result.

        Does any unpacking or processing of the result to get just the record.

        If the underlying SDK of the store returns a particular type that might include something
        like a score or other metadata, this method should be overridden to extract just the record.

        Likely returns a dict, but in some cases could return the record in the form of a SDK specific object.

        This method is used as part of the _get_vector_search_results_from_results method,
        the output of it is passed to the deserializer.
        """
        ...

    @abstractmethod
    def _get_score_from_result(self, result: Any) -> float | None:
        """Get the score from the result.

        Does any unpacking or processing of the result to get just the score.

        If the underlying SDK of the store returns a particular type with a score or other metadata,
        this method extracts it.
        """
        ...

    async def _get_vector_search_results_from_results(
        self, results: AsyncIterable[Any] | Sequence[Any], options: VectorSearchOptions | None = None
    ) -> AsyncIterable[VectorSearchResult[TModel]]:
        if isinstance(results, Sequence):
            results = desync_list(results)
        async for result in results:
            if not result:
                continue
            try:
                record = self.deserialize(
                    self._get_record_from_result(result), include_vectors=options.include_vectors if options else True
                )
            except VectorStoreModelDeserializationException:
                raise
            except Exception as exc:
                raise VectorStoreModelDeserializationException(
                    f"An error occurred while deserializing the record: {exc}"
                ) from exc
            score = self._get_score_from_result(result)
            if record is not None:
                # single records are always returned as single records by the deserializer
                yield VectorSearchResult(record=record, score=score)  # type: ignore

    @overload
    async def search(
        self,
        values: Any,
        *,
        vector_field_name: str | None = None,
        filter: OptionalOneOrList[Callable | str] = None,
        top: int = 3,
        skip: int = 0,
        include_total_count: bool = False,
        include_vectors: bool = False,
        **kwargs: Any,
    ) -> KernelSearchResults[VectorSearchResult[TModel]]:
        """Search the vector store with Vector search for records that match the given value and filter.

        Args:
            values: The values to search for. These will be vectorized,
                either by the store or using the provided generator.
            vector_field_name: The name of the vector field to use for the search.
            filter: The filter to apply to the search.
            top: The number of results to return.
            skip: The number of results to skip.
            include_total_count: Whether to include the total count of results.
            include_vectors: Whether to include the vectors in the results.
            kwargs: If options are not set, this is used to create them.
                they are passed on to the inner search method.

        Raises:
            VectorSearchExecutionException: If an error occurs during the search.
            VectorStoreModelDeserializationException: If an error occurs during deserialization.
            VectorSearchOptionsException: If the search options are invalid.
            VectorStoreOperationNotSupportedException: If the search type is not supported.

        """
        ...

    @overload
    async def search(
        self,
        *,
        vector: Sequence[float | int],
        vector_field_name: str | None = None,
        filter: OptionalOneOrList[Callable | str] = None,
        top: int = 3,
        skip: int = 0,
        include_total_count: bool = False,
        include_vectors: bool = False,
        **kwargs: Any,
    ) -> KernelSearchResults[VectorSearchResult[TModel]]:
        """Search the vector store with Vector search for records that match the given vector and filter.

        Args:
            vector: The vector to search for
            vector_field_name: The name of the vector field to use for the search.
            filter: The filter to apply to the search.
            top: The number of results to return.
            skip: The number of results to skip.
            include_total_count: Whether to include the total count of results.
            include_vectors: Whether to include the vectors in the results.
            kwargs: If options are not set, this is used to create them.
                they are passed on to the inner search method.

        Raises:
            VectorSearchExecutionException: If an error occurs during the search.
            VectorStoreModelDeserializationException: If an error occurs during deserialization.
            VectorSearchOptionsException: If the search options are invalid.
            VectorStoreOperationNotSupportedException: If the search type is not supported.

        """
        ...

    async def search(
        self,
        values=None,
        *,
        vector=None,
        vector_property_name=None,
        filter=None,
        top=3,
        skip=0,
        include_total_count=False,
        include_vectors=False,
        **kwargs,
    ):
        """Search the vector store for records that match the given value and filter.

        Args:
            values: The values to search for.
            vector: The vector to search for, if not provided, the values will be used to generate a vector.
            vector_property_name: The name of the vector property to use for the search.
            filter: The filter to apply to the search.
            top: The number of results to return.
            skip: The number of results to skip.
            include_total_count: Whether to include the total count of results.
            include_vectors: Whether to include the vectors in the results.
            kwargs: If options are not set, this is used to create them.
                they are passed on to the inner search method.

        Raises:
            VectorSearchExecutionException: If an error occurs during the search.
            VectorStoreModelDeserializationException: If an error occurs during deserialization.
            VectorSearchOptionsException: If the search options are invalid.
            VectorStoreOperationNotSupportedException: If the search type is not supported.

        """
        if SearchType.VECTOR not in self.supported_search_types:
            raise VectorStoreOperationNotSupportedException(
                f"Vector search is not supported by this vector store: {self.__class__.__name__}"
            )
        options = VectorSearchOptions(
            filter=filter,
            vector_property_name=vector_property_name,
            top=top,
            skip=skip,
            include_total_count=include_total_count,
            include_vectors=include_vectors,
        )
        try:
            return await self._inner_search(
                search_type=SearchType.VECTOR,
                values=values,
                options=options,
                vector=vector,
                **kwargs,
            )
        except (
            VectorStoreModelDeserializationException,
            VectorSearchOptionsException,
            VectorSearchExecutionException,
            VectorStoreOperationNotSupportedException,
            VectorStoreOperationException,
        ):
            raise  # pragma: no cover
        except Exception as exc:
            raise VectorSearchExecutionException(f"An error occurred during the search: {exc}") from exc

    async def hybrid_search(
        self,
        values: Any,
        *,
        vector: list[float | int] | None = None,
        vector_property_name: str | None = None,
        additional_property_name: str | None = None,
        filter: OptionalOneOrList[Callable | str] = None,
        top: int = 3,
        skip: int = 0,
        include_total_count: bool = False,
        include_vectors: bool = False,
        **kwargs: Any,
    ) -> KernelSearchResults[VectorSearchResult[TModel]]:
        """Search the vector store for records that match the given values and filter.

        Args:
            values: The values to search for.
            vector: The vector to search for, if not provided, the values will be used to generate a vector.
            vector_property_name: The name of the vector field to use for the search.
            additional_property_name: The name of the additional property field to use for the search.
            filter: The filter to apply to the search.
            top: The number of results to return.
            skip: The number of results to skip.
            include_total_count: Whether to include the total count of results.
            include_vectors: Whether to include the vectors in the results.
            kwargs: If options are not set, this is used to create them.
                they are passed on to the inner search method.

        Raises:
            VectorSearchExecutionException: If an error occurs during the search.
            VectorStoreModelDeserializationException: If an error occurs during deserialization.
            VectorSearchOptionsException: If the search options are invalid.
            VectorStoreOperationNotSupportedException: If the search type is not supported.

        """
        if SearchType.KEYWORD_HYBRID not in self.supported_search_types:
            raise VectorStoreOperationNotSupportedException(
                f"Keyword hybrid search is not supported by this vector store: {self.__class__.__name__}"
            )
        options = VectorSearchOptions(
            filter=filter,
            vector_property_name=vector_property_name,
            additional_property_name=additional_property_name,
            top=top,
            skip=skip,
            include_total_count=include_total_count,
            include_vectors=include_vectors,
        )
        try:
            return await self._inner_search(
                search_type=SearchType.KEYWORD_HYBRID,
                values=values,
                vector=vector,
                options=options,
                **kwargs,
            )
        except (
            VectorStoreModelDeserializationException,
            VectorSearchOptionsException,
            VectorSearchExecutionException,
            VectorStoreOperationNotSupportedException,
            VectorStoreOperationException,
        ):
            raise  # pragma: no cover
        except Exception as exc:
            raise VectorSearchExecutionException(f"An error occurred during the search: {exc}") from exc

    async def _generate_vector_from_values(
        self,
        values: Any | None,
        options: VectorSearchOptions,
    ) -> Sequence[float | int] | None:
        """Generate a vector from the given keywords."""
        if values is None:
            return None
        vector_field = self.definition.try_get_vector_field(options.vector_property_name)
        if not vector_field:
            raise VectorSearchOptionsException(
                f"Vector field '{options.vector_property_name}' not found in data model definition."
            )
        embedding_generator = (
            vector_field.embedding_generator if vector_field.embedding_generator else self.embedding_generator
        )
        if not embedding_generator:
            raise VectorSearchOptionsException(
                f"Embedding generator not found for vector field '{options.vector_property_name}'."
            )

        return (
            await embedding_generator.generate_embeddings(
                # TODO (eavanvalkenburg): this only deals with string values, should support other types as well
                # but that requires work on the embedding generators first.
                texts=[values if isinstance(values, str) else json.dumps(values)],
                settings=PromptExecutionSettings(dimensions=vector_field.dimensions),
            )
        )[0].tolist()

    def _build_filter(self, search_filter: OptionalOneOrMany[Callable | str] | None) -> OptionalOneOrMany[Any]:
        """Create the filter based on the filters.

        This function returns None, a single filter, or a list of filters.
        If a single filter is passed, a single filter is returned.

        It takes the filters, which can be a Callable (lambda) or a string, and parses them into a filter object,
        using the _lambda_parser method that is specific to each vector store.

        If a list of filters, is passed, the parsed filters are also returned as a list, so the caller needs to
        combine them in the appropriate way.

        Often called like this (when filters are strings):
        ```python
        if filter := self._build_filter(options.filter):
            search_args["filter"] = filter if isinstance(filter, str) else " and ".join(filter)
        ```
        """
        if not search_filter:
            return None

        filters = search_filter if isinstance(search_filter, list) else [search_filter]

        created_filters: list[Any] = []

        visitor = LambdaVisitor(self._lambda_parser)
        for filter_ in filters:
            # parse lambda expression with AST
            tree = parse(filter_ if isinstance(filter_, str) else getsource(filter_).strip())
            visitor.visit(tree)
        created_filters = visitor.output_filters
        if len(created_filters) == 0:
            raise VectorStoreOperationException("No filter strings found.")
        if len(created_filters) == 1:
            return created_filters[0]
        return created_filters

    @abstractmethod
    def _lambda_parser(self, node: AST) -> Any:
        """Parse the lambda expression and return the filter string.

        This follows from the ast specs: https://docs.python.org/3/library/ast.html
        """
        # This method should be implemented in the derived class
        # to parse the lambda expression and return the filter string.
        pass

    def create_search_function(
        self,
        function_name: str = DEFAULT_FUNCTION_NAME,
        description: str = DEFAULT_DESCRIPTION,
        *,
        search_type: Literal["vector", "keyword_hybrid"] = "vector",
        parameters: list[KernelParameterMetadata] | None = None,
        return_parameter: KernelParameterMetadata | None = None,
        filter: OptionalOneOrList[Callable | str] = None,
        top: int = 5,
        skip: int = 0,
        vector_property_name: str | None = None,
        additional_property_name: str | None = None,
        include_vectors: bool = False,
        include_total_count: bool = False,
        filter_update_function: DynamicFilterFunction | None = None,
        string_mapper: Callable[[VectorSearchResult[TModel]], str] | None = None,
    ) -> KernelFunction:
        """Create a kernel function from a search function.

        Args:
            function_name: The name of the function, to be used in the kernel, default is "search".
            description: The description of the function, a default is provided.
            search_type: The type of search to perform, can be 'vector' or 'keyword_hybrid'.
            parameters: The parameters for the function,
                use an empty list for a function without parameters,
                use None for the default set, which is "query", "top", and "skip".
            return_parameter: The return parameter for the function.
            filter: The filter to apply to the search.
            top: The number of results to return.
            skip: The number of results to skip.
            vector_property_name: The name of the vector property to use for the search.
            additional_property_name: The name of the additional property field to use for the search.
            include_vectors: Whether to include the vectors in the results.
            include_total_count: Whether to include the total count of results.
            filter_update_function: A function to update the filters.
                The function should return the updated filter.
                The default function uses the parameters and the kwargs to update the filters, it
                adds equal to filters to the options for all parameters that are not "query".
                As well as adding equal to filters for parameters that have a default value.
            string_mapper: The function to map the search results to strings.
        """
        search_types = SearchType(search_type)
        if search_types not in self.supported_search_types:
            raise VectorStoreOperationNotSupportedException(
                f"Search type '{search_types.value}' is not supported by this vector store: {self.__class__.__name__}"
            )
        options = VectorSearchOptions(
            filter=filter,
            skip=skip,
            top=top,
            include_total_count=include_total_count,
            include_vectors=include_vectors,
            vector_property_name=vector_property_name,
            additional_property_name=additional_property_name,
        )
        return self._create_kernel_function(
            search_type=search_types,
            options=options,
            parameters=parameters,
            filter_update_function=filter_update_function,
            return_parameter=return_parameter,
            function_name=function_name,
            description=description,
            string_mapper=string_mapper,
        )

    def _create_kernel_function(
        self,
        search_type: SearchType,
        options: SearchOptions | None = None,
        parameters: list[KernelParameterMetadata] | None = None,
        filter_update_function: DynamicFilterFunction | None = None,
        return_parameter: KernelParameterMetadata | None = None,
        function_name: str = DEFAULT_FUNCTION_NAME,
        description: str = DEFAULT_DESCRIPTION,
        string_mapper: Callable[[VectorSearchResult[TModel]], str] | None = None,
    ) -> KernelFunction:
        """Create a kernel function from a search function."""
        update_func = filter_update_function or default_dynamic_filter_function

        @kernel_function(name=function_name, description=description)
        async def search_wrapper(**kwargs: Any) -> Sequence[str]:
            query = kwargs.pop("query", "")
            try:
                inner_options = create_options(self.options_class, deepcopy(options), **kwargs)
            except ValidationError:
                # this usually only happens when the kwargs are invalid, so blank options in this case.
                inner_options = self.options_class()
            inner_options.filter = update_func(filter=inner_options.filter, parameters=parameters, **kwargs)
            match search_type:
                case SearchType.VECTOR:
                    try:
                        results = await self.search(
                            values=query,
                            **inner_options.model_dump(exclude_defaults=True, exclude_none=True),
                        )
                    except Exception as e:
                        msg = f"Exception in search function: {e}"
                        logger.error(msg)
                        raise TextSearchException(msg) from e
                case SearchType.KEYWORD_HYBRID:
                    try:
                        results = await self.hybrid_search(
                            values=query,
                            **inner_options.model_dump(exclude_defaults=True, exclude_none=True),
                        )
                    except Exception as e:
                        msg = f"Exception in hybrid search function: {e}"
                        logger.error(msg)
                        raise TextSearchException(msg) from e
                case _:
                    raise VectorStoreOperationNotSupportedException(
                        f"Search type '{search_type}' is not supported by this vector store: {self.__class__.__name__}"
                    )
            if string_mapper:
                return [string_mapper(result) async for result in results.results]
            return [result.model_dump_json(exclude_none=True) async for result in results.results]

        return KernelFunctionFromMethod(
            method=search_wrapper,
            parameters=DEFAULT_PARAMETER_METADATA if parameters is None else parameters,
            return_parameter=return_parameter or DEFAULT_RETURN_PARAMETER_METADATA,
        )


@runtime_checkable
class VectorStoreCollectionProtocol(Protocol):  # noqa: D101
    collection_name: str
    record_type: object
    definition: VectorStoreCollectionDefinition
    supported_key_types: ClassVar[set[str]]
    supported_vector_types: ClassVar[set[str]]
    embedding_generator: EmbeddingGeneratorBase | None = None

    async def ensure_collection_exists(self, **kwargs: Any) -> bool:
        """Create the collection in the service if it does not exists.

        First uses does_collection_exist to check if it exists, if it does returns False.
        Otherwise, creates the collection and returns True.

        Args:
            **kwargs: Additional arguments.

        Returns:
            bool: True if the collection was created, False if it already exists.
        """
        ...

    async def collection_exists(self, **kwargs: Any) -> bool:
        """Check if the collection exists.

        Args:
            **kwargs: Additional arguments.

        Returns:
            bool: True if the collection exists, False otherwise.

        Raises:
            Make sure the implementation of this function raises relevant exceptions with good descriptions.
        """
        ...

    async def ensure_collection_deleted(self, **kwargs: Any) -> None:
        """Delete the collection.

        Args:
            **kwargs: Additional arguments.
        """
        ...

    async def get(
        self,
        key: Any = None,
        keys: Sequence[Any] | None = None,
        include_vectors: bool = False,
        top: int | None = None,
        skip: int | None = None,
        order_by: OneOrMany[str] | dict[str, bool] | None = None,
        **kwargs: Any,
    ) -> OptionalOneOrList[Any]:
        """Get a batch of records whose keys exist in the collection, i.e. keys that do not exist are ignored.

        Args:
            key: The key to get.
            keys: The keys to get, if keys are provided, key is ignored.
            include_vectors: Include the vectors in the response. Default is False.
                Some vector stores do not support retrieving without vectors, even when set to false.
                Some vector stores have specific parameters to control that behavior, when
                that parameter is set, include_vectors is ignored.
            top: The number of records to return.
                Only used if keys are not provided.
            skip: The number of records to skip.
                Only used if keys are not provided.
            order_by: The order by clause,
                this can be a string, a list of strings or a dict,
                when passing strings, they are assumed to be ascending.
                Otherwise, use the value in the dict to set ascending (True) or descending (False).
                example: {"field_name": True} or ["field_name", {"field_name2": False}].
            **kwargs: Additional arguments.

        Returns:
            The records, either a list of TModel or the container type.

        Raises:
            VectorStoreOperationException: If an error occurs during the get.
            VectorStoreModelDeserializationException: If an error occurs during deserialization.
        """
        ...

    async def upsert(
        self,
        records: OneOrMany[Any],
        **kwargs: Any,
    ) -> OneOrMany[Any]:
        """Upsert one or more records.

        If the key of the record already exists, the existing record will be updated.
        If the key does not exist, a new record will be created.

        Args:
            records: The records to upsert, can be a single record, a list of records, or a single container.
                If a single record is passed, a single key is returned, instead of a list of keys.
            **kwargs: Additional arguments.

        Returns:
            OneOrMany[Any]: The keys of the upserted records.

        Raises:
            VectorStoreModelSerializationException: If an error occurs during serialization.
            VectorStoreOperationException: If an error occurs during upserting.
        """
        ...

    async def delete(self, keys: OneOrMany[Any], **kwargs: Any) -> None:
        """Delete one or more records by key.

        An exception will be raised at the end if any record does not exist.

        Args:
            keys: The key or keys to be deleted.
            **kwargs: Additional arguments.

        Raises:
            VectorStoreOperationException: If an error occurs during deletion or a record does not exist.
        """
        ...


@runtime_checkable
class VectorSearchProtocol(VectorStoreCollectionProtocol, Protocol):
    """Protocol to check that a collection supports vector search."""

    supported_search_types: ClassVar[set[SearchType]]

    async def search(
        self,
        values: Any = None,
        *,
        vector: Sequence[float | int] | None = None,
        vector_property_name: str | None = None,
        filter: OptionalOneOrList[Callable | str] = None,
        top: int = 3,
        skip: int = 0,
        include_total_count: bool = False,
        include_vectors: bool = False,
        **kwargs: Any,
    ) -> KernelSearchResults[VectorSearchResult]:
        """Search the vector store for records that match the given value and filter.

        Args:
            values: The values to search for. These will be vectorized,
                either by the store or using the provided generator.
            vector: The vector to search for, if not provided, the values will be used to generate a vector.
            vector_property_name: The name of the vector property to use for the search.
            filter: The filter to apply to the search.
            top: The number of results to return.
            skip: The number of results to skip.
            include_total_count: Whether to include the total count of results.
            include_vectors: Whether to include the vectors in the results.
            kwargs: If options are not set, this is used to create them.
                they are passed on to the inner search method.

        Returns:
            The search results.

        Raises:
            VectorSearchExecutionException: If an error occurs during the search.
            VectorStoreModelDeserializationException: If an error occurs during deserialization.
            VectorSearchOptionsException: If the search options are invalid.
            VectorStoreOperationNotSupportedException: If the search type is not supported.
        """
        ...

    async def hybrid_search(
        self,
        values: Any,
        *,
        vector: list[float | int] | None = None,
        vector_property_name: str | None = None,
        additional_property_name: str | None = None,
        filter: OptionalOneOrList[Callable | str] = None,
        top: int = 3,
        skip: int = 0,
        include_total_count: bool = False,
        include_vectors: bool = False,
        **kwargs: Any,
    ) -> KernelSearchResults[VectorSearchResult]:
        """Search the vector store for records that match the given values and filter using hybrid search.

        Args:
            values: The values to search for.
            vector: The vector to search for, if not provided, the values will be used to generate a vector.
            vector_property_name: The name of the vector field to use for the search.
            additional_property_name: The name of the additional property field to use for the search.
            filter: The filter to apply to the search.
            top: The number of results to return.
            skip: The number of results to skip.
            include_total_count: Whether to include the total count of results.
            include_vectors: Whether to include the vectors in the results.
            kwargs: If options are not set, this is used to create them.
                they are passed on to the inner search method.

        Returns:
            The search results.

        Raises:
            VectorSearchExecutionException: If an error occurs during the search.
            VectorStoreModelDeserializationException: If an error occurs during deserialization.
            VectorSearchOptionsException: If the search options are invalid.
            VectorStoreOperationNotSupportedException: If the search type is not supported.
        """
        ...


__all__ = [
    "DEFAULT_DESCRIPTION",
    "DEFAULT_FUNCTION_NAME",
    "DEFAULT_PARAMETER_METADATA",
    "DEFAULT_RETURN_PARAMETER_METADATA",
    "DISTANCE_FUNCTION_DIRECTION_HELPER",
    "DistanceFunction",
    "DynamicFilterFunction",
    "FieldTypes",
    "IndexKind",
    "KernelSearchResults",
    "SearchType",
    "VectorSearch",
    "VectorSearchProtocol",
    "VectorSearchResult",
    "VectorStore",
    "VectorStoreCollection",
    "VectorStoreCollectionDefinition",
    "VectorStoreCollectionProtocol",
    "VectorStoreField",
    "create_options",
    "default_dynamic_filter_function",
    "vectorstoremodel",
]
