codekingpro/portable-devtools
114k
1"""VectorStore wrapper around a Postgres-TimescaleVector database."""2 3from __future__ import annotations4 5import enum6import logging7import uuid8from datetime import timedelta9from typing import (10 TYPE_CHECKING,11 Any,12 Callable,13 Dict,14 Iterable,15 List,16 Optional,17 Tuple,18 Type,19 Union,20)21 22from langchain_core.documents import Document23from langchain_core.embeddings import Embeddings24from langchain_core.utils import get_from_dict_or_env25from langchain_core.vectorstores import VectorStore26 27from langchain_community.vectorstores.utils import DistanceStrategy28 29if TYPE_CHECKING:30 from timescale_vector import Predicates31 32 33DEFAULT_DISTANCE_STRATEGY = DistanceStrategy.COSINE34 35ADA_TOKEN_COUNT = 153636 37_LANGCHAIN_DEFAULT_COLLECTION_NAME = "langchain_store"38 39 40class TimescaleVector(VectorStore):41 """Timescale Postgres vector store42 43 To use, you should have the ``timescale_vector`` python package installed.44 45 Args:46 service_url: Service url on timescale cloud.47 embedding: Any embedding function implementing48 `langchain.embeddings.base.Embeddings` interface.49 collection_name: The name of the collection to use. (default: langchain_store)50 This will become the table name used for the collection.51 distance_strategy: The distance strategy to use. (default: COSINE)52 pre_delete_collection: If True, will delete the collection if it exists.53 (default: False). Useful for testing.54 55 Example:56 .. code-block:: python57 58 from langchain_community.vectorstores import TimescaleVector59 from langchain_community.embeddings.openai import OpenAIEmbeddings60 61 SERVICE_URL = "postgres://tsdbadmin:<password>@<id>.tsdb.cloud.timescale.com:<port>/tsdb?sslmode=require"62 COLLECTION_NAME = "state_of_the_union_test"63 embeddings = OpenAIEmbeddings()64 vectorestore = TimescaleVector.from_documents(65 embedding=embeddings,66 documents=docs,67 collection_name=COLLECTION_NAME,68 service_url=SERVICE_URL,69 )70 """71 72 def __init__(73 self,74 service_url: str,75 embedding: Embeddings,76 collection_name: str = _LANGCHAIN_DEFAULT_COLLECTION_NAME,77 num_dimensions: int = ADA_TOKEN_COUNT,78 distance_strategy: DistanceStrategy = DEFAULT_DISTANCE_STRATEGY,79 pre_delete_collection: bool = False,80 logger: Optional[logging.Logger] = None,81 relevance_score_fn: Optional[Callable[[float], float]] = None,82 time_partition_interval: Optional[timedelta] = None,83 **kwargs: Any,84 ) -> None:85 try:86 from timescale_vector import client87 except ImportError:88 raise ImportError(89 "Could not import timescale_vector python package. "90 "Please install it with `pip install timescale-vector`."91 )92 93 self.service_url = service_url94 self.embedding = embedding95 self.collection_name = collection_name96 self.num_dimensions = num_dimensions97 self._distance_strategy = distance_strategy98 self.pre_delete_collection = pre_delete_collection99 self.logger = logger or logging.getLogger(__name__)100 self.override_relevance_score_fn = relevance_score_fn101 self._time_partition_interval = time_partition_interval102 self.sync_client = client.Sync(103 self.service_url,104 self.collection_name,105 self.num_dimensions,106 self._distance_strategy.value.lower(),107 time_partition_interval=self._time_partition_interval,108 **kwargs,109 )110 self.async_client = client.Async(111 self.service_url,112 self.collection_name,113 self.num_dimensions,114 self._distance_strategy.value.lower(),115 time_partition_interval=self._time_partition_interval,116 **kwargs,117 )118 self.__post_init__()119 120 def __post_init__(121 self,122 ) -> None:123 """124 Initialize the store.125 """126 self.sync_client.create_tables()127 if self.pre_delete_collection:128 self.sync_client.delete_all()129 130 @property131 def embeddings(self) -> Embeddings:132 return self.embedding133 134 def drop_tables(self) -> None:135 self.sync_client.drop_table()136 137 @classmethod138 def __from(139 cls,140 texts: List[str],141 embeddings: List[List[float]],142 embedding: Embeddings,143 metadatas: Optional[List[dict]] = None,144 ids: Optional[List[str]] = None,145 collection_name: str = _LANGCHAIN_DEFAULT_COLLECTION_NAME,146 distance_strategy: DistanceStrategy = DEFAULT_DISTANCE_STRATEGY,147 service_url: Optional[str] = None,148 pre_delete_collection: bool = False,149 **kwargs: Any,150 ) -> TimescaleVector:151 num_dimensions = len(embeddings[0])152 153 if ids is None:154 ids = [str(uuid.uuid4()) for _ in texts]155 156 if not metadatas:157 metadatas = [{} for _ in texts]158 159 if service_url is None:160 service_url = cls.get_service_url(kwargs)161 162 store = cls(163 service_url=service_url,164 num_dimensions=num_dimensions,165 collection_name=collection_name,166 embedding=embedding,167 distance_strategy=distance_strategy,168 pre_delete_collection=pre_delete_collection,169 **kwargs,170 )171 172 store.add_embeddings(173 texts=texts, embeddings=embeddings, metadatas=metadatas, ids=ids, **kwargs174 )175 176 return store177 178 @classmethod179 async def __afrom(180 cls,181 texts: List[str],182 embeddings: List[List[float]],183 embedding: Embeddings,184 metadatas: Optional[List[dict]] = None,185 ids: Optional[List[str]] = None,186 collection_name: str = _LANGCHAIN_DEFAULT_COLLECTION_NAME,187 distance_strategy: DistanceStrategy = DEFAULT_DISTANCE_STRATEGY,188 service_url: Optional[str] = None,189 pre_delete_collection: bool = False,190 **kwargs: Any,191 ) -> TimescaleVector:192 num_dimensions = len(embeddings[0])193 194 if ids is None:195 ids = [str(uuid.uuid4()) for _ in texts]196 197 if not metadatas:198 metadatas = [{} for _ in texts]199 200 if service_url is None:201 service_url = cls.get_service_url(kwargs)202 203 store = cls(204 service_url=service_url,205 num_dimensions=num_dimensions,206 collection_name=collection_name,207 embedding=embedding,208 distance_strategy=distance_strategy,209 pre_delete_collection=pre_delete_collection,210 **kwargs,211 )212 213 await store.aadd_embeddings(214 texts=texts, embeddings=embeddings, metadatas=metadatas, ids=ids, **kwargs215 )216 217 return store218 219 def add_embeddings(220 self,221 texts: Iterable[str],222 embeddings: List[List[float]],223 metadatas: Optional[List[dict]] = None,224 ids: Optional[List[str]] = None,225 **kwargs: Any,226 ) -> List[str]:227 """Add embeddings to the vectorstore.228 229 Args:230 texts: Iterable of strings to add to the vectorstore.231 embeddings: List of list of embedding vectors.232 metadatas: List of metadatas associated with the texts.233 kwargs: vectorstore specific parameters234 """235 if ids is None:236 ids = [str(uuid.uuid4()) for _ in texts]237 238 if not metadatas:239 metadatas = [{} for _ in texts]240 241 records = list(zip(ids, metadatas, texts, embeddings))242 self.sync_client.upsert(records)243 244 return ids245 246 async def aadd_embeddings(247 self,248 texts: Iterable[str],249 embeddings: List[List[float]],250 metadatas: Optional[List[dict]] = None,251 ids: Optional[List[str]] = None,252 **kwargs: Any,253 ) -> List[str]:254 """Add embeddings to the vectorstore.255 256 Args:257 texts: Iterable of strings to add to the vectorstore.258 embeddings: List of list of embedding vectors.259 metadatas: List of metadatas associated with the texts.260 kwargs: vectorstore specific parameters261 """262 if ids is None:263 ids = [str(uuid.uuid4()) for _ in texts]264 265 if not metadatas:266 metadatas = [{} for _ in texts]267 268 records = list(zip(ids, metadatas, texts, embeddings))269 await self.async_client.upsert(records)270 271 return ids272 273 def add_texts(274 self,275 texts: Iterable[str],276 metadatas: Optional[List[dict]] = None,277 ids: Optional[List[str]] = None,278 **kwargs: Any,279 ) -> List[str]:280 """Run more texts through the embeddings and add to the vectorstore.281 282 Args:283 texts: Iterable of strings to add to the vectorstore.284 metadatas: Optional list of metadatas associated with the texts.285 kwargs: vectorstore specific parameters286 287 Returns:288 List of ids from adding the texts into the vectorstore.289 """290 embeddings = self.embedding.embed_documents(list(texts))291 return self.add_embeddings(292 texts=texts, embeddings=embeddings, metadatas=metadatas, ids=ids, **kwargs293 )294 295 async def aadd_texts(296 self,297 texts: Iterable[str],298 metadatas: Optional[List[dict]] = None,299 ids: Optional[List[str]] = None,300 **kwargs: Any,301 ) -> List[str]:302 """Run more texts through the embeddings and add to the vectorstore.303 304 Args:305 texts: Iterable of strings to add to the vectorstore.306 metadatas: Optional list of metadatas associated with the texts.307 kwargs: vectorstore specific parameters308 309 Returns:310 List of ids from adding the texts into the vectorstore.311 """312 embeddings = self.embedding.embed_documents(list(texts))313 return await self.aadd_embeddings(314 texts=texts, embeddings=embeddings, metadatas=metadatas, ids=ids, **kwargs315 )316 317 def _embed_query(self, query: str) -> Optional[List[float]]:318 # an empty query should not be embedded319 if query is None or query == "" or query.isspace():320 return None321 else:322 return self.embedding.embed_query(query)323 324 def similarity_search(325 self,326 query: str,327 k: int = 4,328 filter: Optional[Union[dict, list]] = None,329 predicates: Optional[Predicates] = None,330 **kwargs: Any,331 ) -> List[Document]:332 """Run similarity search with TimescaleVector with distance.333 334 Args:335 query (str): Query text to search for.336 k (int): Number of results to return. Defaults to 4.337 filter (Optional[Dict[str, str]]): Filter by metadata. Defaults to None.338 339 Returns:340 List of Documents most similar to the query.341 """342 embedding = self._embed_query(query)343 return self.similarity_search_by_vector(344 embedding=embedding,345 k=k,346 filter=filter,347 predicates=predicates,348 **kwargs,349 )350 351 async def asimilarity_search(352 self,353 query: str,354 k: int = 4,355 filter: Optional[Union[dict, list]] = None,356 predicates: Optional[Predicates] = None,357 **kwargs: Any,358 ) -> List[Document]:359 """Run similarity search with TimescaleVector with distance.360 361 Args:362 query (str): Query text to search for.363 k (int): Number of results to return. Defaults to 4.364 filter (Optional[Dict[str, str]]): Filter by metadata. Defaults to None.365 366 Returns:367 List of Documents most similar to the query.368 """369 embedding = self._embed_query(query)370 return await self.asimilarity_search_by_vector(371 embedding=embedding,372 k=k,373 filter=filter,374 predicates=predicates,375 **kwargs,376 )377 378 def similarity_search_with_score(379 self,380 query: str,381 k: int = 4,382 filter: Optional[Union[dict, list]] = None,383 predicates: Optional[Predicates] = None,384 **kwargs: Any,385 ) -> List[Tuple[Document, float]]:386 """Return docs most similar to query.387 388 Args:389 query: Text to look up documents similar to.390 k: Number of Documents to return. Defaults to 4.391 filter (Optional[Dict[str, str]]): Filter by metadata. Defaults to None.392 393 Returns:394 List of Documents most similar to the query and score for each395 """396 embedding = self._embed_query(query)397 docs = self.similarity_search_with_score_by_vector(398 embedding=embedding,399 k=k,400 filter=filter,401 predicates=predicates,402 **kwargs,403 )404 return docs405 406 async def asimilarity_search_with_score(407 self,408 query: str,409 k: int = 4,410 filter: Optional[Union[dict, list]] = None,411 predicates: Optional[Predicates] = None,412 **kwargs: Any,413 ) -> List[Tuple[Document, float]]:414 """Return docs most similar to query.415 416 Args:417 query: Text to look up documents similar to.418 k: Number of Documents to return. Defaults to 4.419 filter (Optional[Dict[str, str]]): Filter by metadata. Defaults to None.420 421 Returns:422 List of Documents most similar to the query and score for each423 """424 425 embedding = self._embed_query(query)426 return await self.asimilarity_search_with_score_by_vector(427 embedding=embedding,428 k=k,429 filter=filter,430 predicates=predicates,431 **kwargs,432 )433 434 def date_to_range_filter(self, **kwargs: Any) -> Any:435 constructor_args = {436 key: kwargs[key]437 for key in [438 "start_date",439 "end_date",440 "time_delta",441 "start_inclusive",442 "end_inclusive",443 ]444 if key in kwargs445 }446 if not constructor_args or len(constructor_args) == 0:447 return None448 449 try:450 from timescale_vector import client451 except ImportError:452 raise ImportError(453 "Could not import timescale_vector python package. "454 "Please install it with `pip install timescale-vector`."455 )456 return client.UUIDTimeRange(**constructor_args)457 458 def similarity_search_with_score_by_vector(459 self,460 embedding: Optional[List[float]],461 k: int = 4,462 filter: Optional[Union[dict, list]] = None,463 predicates: Optional[Predicates] = None,464 **kwargs: Any,465 ) -> List[Tuple[Document, float]]:466 try:467 from timescale_vector import client468 except ImportError:469 raise ImportError(470 "Could not import timescale_vector python package. "471 "Please install it with `pip install timescale-vector`."472 )473 474 results = self.sync_client.search(475 embedding,476 limit=k,477 filter=filter,478 predicates=predicates,479 uuid_time_filter=self.date_to_range_filter(**kwargs),480 )481 482 docs = [483 (484 Document(485 page_content=result[client.SEARCH_RESULT_CONTENTS_IDX],486 metadata=result[client.SEARCH_RESULT_METADATA_IDX],487 ),488 result[client.SEARCH_RESULT_DISTANCE_IDX],489 )490 for result in results491 ]492 return docs493 494 async def asimilarity_search_with_score_by_vector(495 self,496 embedding: Optional[List[float]],497 k: int = 4,498 filter: Optional[Union[dict, list]] = None,499 predicates: Optional[Predicates] = None,500 **kwargs: Any,501 ) -> List[Tuple[Document, float]]:502 try:503 from timescale_vector import client504 except ImportError:505 raise ImportError(506 "Could not import timescale_vector python package. "507 "Please install it with `pip install timescale-vector`."508 )509 510 results = await self.async_client.search(511 embedding,512 limit=k,513 filter=filter,514 predicates=predicates,515 uuid_time_filter=self.date_to_range_filter(**kwargs),516 )517 518 docs = [519 (520 Document(521 page_content=result[client.SEARCH_RESULT_CONTENTS_IDX],522 metadata=result[client.SEARCH_RESULT_METADATA_IDX],523 ),524 result[client.SEARCH_RESULT_DISTANCE_IDX],525 )526 for result in results527 ]528 return docs529 530 def similarity_search_by_vector(531 self,532 embedding: Optional[List[float]],533 k: int = 4,534 filter: Optional[Union[dict, list]] = None,535 predicates: Optional[Predicates] = None,536 **kwargs: Any,537 ) -> List[Document]:538 """Return docs most similar to embedding vector.539 540 Args:541 embedding: Embedding to look up documents similar to.542 k: Number of Documents to return. Defaults to 4.543 filter (Optional[Dict[str, str]]): Filter by metadata. Defaults to None.544 545 Returns:546 List of Documents most similar to the query vector.547 """548 docs_and_scores = self.similarity_search_with_score_by_vector(549 embedding=embedding, k=k, filter=filter, predicates=predicates, **kwargs550 )551 return [doc for doc, _ in docs_and_scores]552 553 async def asimilarity_search_by_vector(554 self,555 embedding: Optional[List[float]],556 k: int = 4,557 filter: Optional[Union[dict, list]] = None,558 predicates: Optional[Predicates] = None,559 **kwargs: Any,560 ) -> List[Document]:561 """Return docs most similar to embedding vector.562 563 Args:564 embedding: Embedding to look up documents similar to.565 k: Number of Documents to return. Defaults to 4.566 filter (Optional[Dict[str, str]]): Filter by metadata. Defaults to None.567 568 Returns:569 List of Documents most similar to the query vector.570 """571 docs_and_scores = await self.asimilarity_search_with_score_by_vector(572 embedding=embedding, k=k, filter=filter, predicates=predicates, **kwargs573 )574 return [doc for doc, _ in docs_and_scores]575 576 @classmethod577 def from_texts(578 cls: Type[TimescaleVector],579 texts: List[str],580 embedding: Embeddings,581 metadatas: Optional[List[dict]] = None,582 collection_name: str = _LANGCHAIN_DEFAULT_COLLECTION_NAME,583 distance_strategy: DistanceStrategy = DEFAULT_DISTANCE_STRATEGY,584 ids: Optional[List[str]] = None,585 pre_delete_collection: bool = False,586 **kwargs: Any,587 ) -> TimescaleVector:588 """589 Return VectorStore initialized from texts and embeddings.590 Postgres connection string is required591 "Either pass it as a parameter592 or set the TIMESCALE_SERVICE_URL environment variable.593 """594 embeddings = embedding.embed_documents(list(texts))595 596 return cls.__from(597 texts,598 embeddings,599 embedding,600 metadatas=metadatas,601 ids=ids,602 collection_name=collection_name,603 distance_strategy=distance_strategy,604 pre_delete_collection=pre_delete_collection,605 **kwargs,606 )607 608 @classmethod609 async def afrom_texts(610 cls: Type[TimescaleVector],611 texts: List[str],612 embedding: Embeddings,613 metadatas: Optional[List[dict]] = None,614 collection_name: str = _LANGCHAIN_DEFAULT_COLLECTION_NAME,615 distance_strategy: DistanceStrategy = DEFAULT_DISTANCE_STRATEGY,616 ids: Optional[List[str]] = None,617 pre_delete_collection: bool = False,618 **kwargs: Any,619 ) -> TimescaleVector:620 """621 Return VectorStore initialized from texts and embeddings.622 Postgres connection string is required623 "Either pass it as a parameter624 or set the TIMESCALE_SERVICE_URL environment variable.625 """626 embeddings = embedding.embed_documents(list(texts))627 628 return await cls.__afrom(629 texts,630 embeddings,631 embedding,632 metadatas=metadatas,633 ids=ids,634 collection_name=collection_name,635 distance_strategy=distance_strategy,636 pre_delete_collection=pre_delete_collection,637 **kwargs,638 )639 640 @classmethod641 def from_embeddings(642 cls,643 text_embeddings: List[Tuple[str, List[float]]],644 embedding: Embeddings,645 metadatas: Optional[List[dict]] = None,646 collection_name: str = _LANGCHAIN_DEFAULT_COLLECTION_NAME,647 distance_strategy: DistanceStrategy = DEFAULT_DISTANCE_STRATEGY,648 ids: Optional[List[str]] = None,649 pre_delete_collection: bool = False,650 **kwargs: Any,651 ) -> TimescaleVector:652 """Construct TimescaleVector wrapper from raw documents and pre-653 generated embeddings.654 655 Return VectorStore initialized from documents and embeddings.656 Postgres connection string is required657 "Either pass it as a parameter658 or set the TIMESCALE_SERVICE_URL environment variable.659 660 Example:661 .. code-block:: python662 663 from langchain_community.vectorstores import TimescaleVector664 from langchain_community.embeddings import OpenAIEmbeddings665 embeddings = OpenAIEmbeddings()666 text_embeddings = embeddings.embed_documents(texts)667 text_embedding_pairs = list(zip(texts, text_embeddings))668 tvs = TimescaleVector.from_embeddings(text_embedding_pairs, embeddings)669 """670 texts = [t[0] for t in text_embeddings]671 embeddings = [t[1] for t in text_embeddings]672 673 return cls.__from(674 texts,675 embeddings,676 embedding,677 metadatas=metadatas,678 ids=ids,679 collection_name=collection_name,680 distance_strategy=distance_strategy,681 pre_delete_collection=pre_delete_collection,682 **kwargs,683 )684 685 @classmethod686 async def afrom_embeddings(687 cls,688 text_embeddings: List[Tuple[str, List[float]]],689 embedding: Embeddings,690 metadatas: Optional[List[dict]] = None,691 collection_name: str = _LANGCHAIN_DEFAULT_COLLECTION_NAME,692 distance_strategy: DistanceStrategy = DEFAULT_DISTANCE_STRATEGY,693 ids: Optional[List[str]] = None,694 pre_delete_collection: bool = False,695 **kwargs: Any,696 ) -> TimescaleVector:697 """Construct TimescaleVector wrapper from raw documents and pre-698 generated embeddings.699 700 Return VectorStore initialized from documents and embeddings.701 Postgres connection string is required702 "Either pass it as a parameter703 or set the TIMESCALE_SERVICE_URL environment variable.704 705 Example:706 .. code-block:: python707 708 from langchain_community.vectorstores import TimescaleVector709 from langchain_community.embeddings import OpenAIEmbeddings710 embeddings = OpenAIEmbeddings()711 text_embeddings = embeddings.embed_documents(texts)712 text_embedding_pairs = list(zip(texts, text_embeddings))713 tvs = TimescaleVector.from_embeddings(text_embedding_pairs, embeddings)714 """715 texts = [t[0] for t in text_embeddings]716 embeddings = [t[1] for t in text_embeddings]717 718 return await cls.__afrom(719 texts,720 embeddings,721 embedding,722 metadatas=metadatas,723 ids=ids,724 collection_name=collection_name,725 distance_strategy=distance_strategy,726 pre_delete_collection=pre_delete_collection,727 **kwargs,728 )729 730 @classmethod731 def from_existing_index(732 cls: Type[TimescaleVector],733 embedding: Embeddings,734 collection_name: str = _LANGCHAIN_DEFAULT_COLLECTION_NAME,735 distance_strategy: DistanceStrategy = DEFAULT_DISTANCE_STRATEGY,736 pre_delete_collection: bool = False,737 **kwargs: Any,738 ) -> TimescaleVector:739 """740 Get instance of an existing TimescaleVector store.This method will741 return the instance of the store without inserting any new742 embeddings743 """744 745 service_url = cls.get_service_url(kwargs)746 747 store = cls(748 service_url=service_url,749 collection_name=collection_name,750 embedding=embedding,751 distance_strategy=distance_strategy,752 pre_delete_collection=pre_delete_collection,753 )754 755 return store756 757 @classmethod758 def get_service_url(cls, kwargs: Dict[str, Any]) -> str:759 service_url: str = get_from_dict_or_env(760 data=kwargs,761 key="service_url",762 env_key="TIMESCALE_SERVICE_URL",763 )764 765 if not service_url:766 raise ValueError(767 "Postgres connection string is required"768 "Either pass it as a parameter"769 "or set the TIMESCALE_SERVICE_URL environment variable."770 )771 772 return service_url773 774 @classmethod775 def service_url_from_db_params(776 cls,777 host: str,778 port: int,779 database: str,780 user: str,781 password: str,782 ) -> str:783 """Return connection string from database parameters."""784 return f"postgresql://{user}:{password}@{host}:{port}/{database}"785 786 def _select_relevance_score_fn(self) -> Callable[[float], float]:787 """788 The 'correct' relevance function789 may differ depending on a few things, including:790 - the distance / similarity metric used by the VectorStore791 - the scale of your embeddings (OpenAI's are unit normed. Many others are not!)792 - embedding dimensionality793 - etc.794 """795 if self.override_relevance_score_fn is not None:796 return self.override_relevance_score_fn797 798 # Default strategy is to rely on distance strategy provided799 # in vectorstore constructor800 if self._distance_strategy == DistanceStrategy.COSINE:801 return self._cosine_relevance_score_fn802 elif self._distance_strategy == DistanceStrategy.EUCLIDEAN_DISTANCE:803 return self._euclidean_relevance_score_fn804 elif self._distance_strategy == DistanceStrategy.MAX_INNER_PRODUCT:805 return self._max_inner_product_relevance_score_fn806 else:807 raise ValueError(808 "No supported normalization function"809 f" for distance_strategy of {self._distance_strategy}."810 "Consider providing relevance_score_fn to TimescaleVector constructor."811 )812 813 def delete(self, ids: Optional[List[str]] = None, **kwargs: Any) -> Optional[bool]:814 """Delete by vector ID or other criteria.815 816 Args:817 ids: List of ids to delete.818 **kwargs: Other keyword arguments that subclasses might use.819 820 Returns:821 Optional[bool]: True if deletion is successful,822 False otherwise, None if not implemented.823 """824 if ids is None:825 raise ValueError("No ids provided to delete.")826 827 self.sync_client.delete_by_ids(ids)828 return True829 830 # todo should this be part of delete|()?831 def delete_by_metadata(832 self, filter: Union[Dict[str, str], List[Dict[str, str]]], **kwargs: Any833 ) -> Optional[bool]:834 """Delete by vector ID or other criteria.835 836 Args:837 ids: List of ids to delete.838 **kwargs: Other keyword arguments that subclasses might use.839 840 Returns:841 Optional[bool]: True if deletion is successful,842 False otherwise, None if not implemented.843 """844 845 self.sync_client.delete_by_metadata(filter)846 return True847 848 class IndexType(str, enum.Enum):849 """Enumerator for the supported Index types"""850 851 TIMESCALE_VECTOR = "tsv"852 PGVECTOR_IVFFLAT = "ivfflat"853 PGVECTOR_HNSW = "hnsw"854 855 DEFAULT_INDEX_TYPE = IndexType.TIMESCALE_VECTOR856 857 def create_index(858 self, index_type: Union[IndexType, str] = DEFAULT_INDEX_TYPE, **kwargs: Any859 ) -> None:860 try:861 from timescale_vector import client862 except ImportError:863 raise ImportError(864 "Could not import timescale_vector python package. "865 "Please install it with `pip install timescale-vector`."866 )867 868 index_type = (869 index_type.value if isinstance(index_type, self.IndexType) else index_type870 )871 if index_type == self.IndexType.PGVECTOR_IVFFLAT.value:872 self.sync_client.create_embedding_index(client.IvfflatIndex(**kwargs))873 874 if index_type == self.IndexType.PGVECTOR_HNSW.value:875 self.sync_client.create_embedding_index(client.HNSWIndex(**kwargs))876 877 if index_type == self.IndexType.TIMESCALE_VECTOR.value:878 self.sync_client.create_embedding_index(879 client.TimescaleVectorIndex(**kwargs)880 )881 882 def drop_index(self) -> None:883 self.sync_client.drop_embedding_index()884 