From 3623ab2c57ea4c916558cc55bd4f2eca8ed0e558 Mon Sep 17 00:00:00 2001 From: egor <58992960+egor-romanov@users.noreply.github.com> Date: Mon, 15 May 2023 18:34:02 +0300 Subject: [PATCH 01/19] add supabase and postgres + pgvector datastore providers (#53) * add supabase + pgvector datastore provider * fix conflicts: move supabase docs from readme * add supabase to unused deps doc * add supabase local setup docs and tests * small improvements in setup.md * add pure postgres implementation and more tests * add postgres datastore docs to readme * rebase to latest main * fix typo in readme - postgres envs * add some indexes and pgvector idx strategy in docs * enable RLS by default --- README.md | 36 +- datastore/factory.py | 9 + datastore/providers/pgvector_datastore.py | 180 +++ datastore/providers/postgres_datastore.py | 132 +++ datastore/providers/supabase_datastore.py | 95 ++ .../removing-unused-dependencies.md | 20 +- docs/providers/postgres/setup.md | 81 ++ docs/providers/supabase/setup.md | 87 ++ examples/providers/supabase/.gitignore | 3 + examples/providers/supabase/config.toml | 72 ++ .../20230414142107_init_pg_vector.sql | 70 ++ examples/providers/supabase/seed.sql | 0 poetry.lock | 1052 +++++++++++++---- pyproject.toml | 3 + .../postgres/test_postgres_datastore.py | 291 +++++ .../supabase/test_supabase_datastore.py | 291 +++++ 16 files changed, 2190 insertions(+), 232 deletions(-) create mode 100644 datastore/providers/pgvector_datastore.py create mode 100644 datastore/providers/postgres_datastore.py create mode 100644 datastore/providers/supabase_datastore.py create mode 100644 docs/providers/postgres/setup.md create mode 100644 docs/providers/supabase/setup.md create mode 100644 examples/providers/supabase/.gitignore create mode 100644 examples/providers/supabase/config.toml create mode 100644 examples/providers/supabase/migrations/20230414142107_init_pg_vector.sql create mode 100644 examples/providers/supabase/seed.sql create mode 100644 tests/datastore/providers/postgres/test_postgres_datastore.py create mode 100644 tests/datastore/providers/supabase/test_supabase_datastore.py diff --git a/README.md b/README.md index e03c06016..d4cb5d8f5 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,8 @@ This README provides detailed information on how to set up, develop, and deploy - [Llama Index](#llamaindex) - [Chroma](#chroma) - [Azure Cognitive Search](#azure-cognitive-search) + - [Supabase](#supabase) + - [Postgres](#postgres) - [Running the API Locally](#running-the-api-locally) - [Testing a Localhost Plugin in ChatGPT](#testing-a-localhost-plugin-in-chatgpt) - [Personalization](#personalization) @@ -142,6 +144,17 @@ Follow these steps to quickly set up and run the ChatGPT Retrieval Plugin: export AZURESEARCH_SERVICE= export AZURESEARCH_INDEX= export AZURESEARCH_API_KEY= (optional, uses key-free managed identity if not set) + + # Supabase + export SUPABASE_URL= + export SUPABASE_ANON_KEY= + + # Postgres + export PG_HOST= + export PG_PORT= + export PG_USER= + export PG_PASSWORD= + export PG_DATABASE= ``` 10. Run the API locally: `poetry run start` @@ -253,11 +266,11 @@ poetry install The API requires the following environment variables to work: -| Name | Required | Description | -| ---------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `DATASTORE` | Yes | This specifies the vector database provider you want to use to store and query embeddings. You can choose from `chroma`, `pinecone`, `weaviate`, `zilliz`, `milvus`, `qdrant`, `redis`, `azuresearch`. | -| `BEARER_TOKEN` | Yes | This is a secret token that you need to authenticate your requests to the API. You can generate one using any tool or method you prefer, such as [jwt.io](https://jwt.io/). | -| `OPENAI_API_KEY` | Yes | This is your OpenAI API key that you need to generate embeddings using the `text-embedding-ada-002` model. You can get an API key by creating an account on [OpenAI](https://openai.com/). | +| Name | Required | Description | +| ---------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `DATASTORE` | Yes | This specifies the vector database provider you want to use to store and query embeddings. You can choose from `chroma`, `pinecone`, `weaviate`, `zilliz`, `milvus`, `qdrant`, `redis`, `azuresearch`, `supabase`, `postgres`. | +| `BEARER_TOKEN` | Yes | This is a secret token that you need to authenticate your requests to the API. You can generate one using any tool or method you prefer, such as [jwt.io](https://jwt.io/). | +| `OPENAI_API_KEY` | Yes | This is your OpenAI API key that you need to generate embeddings using the `text-embedding-ada-002` model. You can get an API key by creating an account on [OpenAI](https://openai.com/). | ### Using the plugin with Azure OpenAI @@ -316,6 +329,14 @@ For detailed setup instructions, refer to [`/docs/providers/llama/setup.md`](/do [Azure Cognitive Search](https://azure.microsoft.com/products/search/) is a complete retrieval cloud service that supports vector search, text search, and hybrid (vectors + text combined to yield the best of the two approaches). It also offers an [optional L2 re-ranking step](https://learn.microsoft.com/azure/search/semantic-search-overview) to further improve results quality. For detailed setup instructions, refer to [`/docs/providers/azuresearch/setup.md`](/docs/providers/azuresearch/setup.md) +#### Supabase + +[Supabase](https://supabase.com/blog/openai-embeddings-postgres-vector) offers an easy and efficient way to store vectors via [pgvector](https://github.com/pgvector/pgvector) extension for Postgres Database. [You can use Supabase CLI](https://github.com/supabase/cli) to set up a whole Supabase stack locally or in the cloud or you can also use docker-compose, k8s and other options available. For a hosted/managed solution, try [Supabase.com](https://supabase.com/) and unlock the full power of Postgres with built-in authentication, storage, auto APIs, and Realtime features. For detailed setup instructions, refer to [`/docs/providers/supabase/setup.md`](/docs/providers/supabase/setup.md). + +#### Postgres + +[Postgres](https://www.postgresql.org) offers an easy and efficient way to store vectors via [pgvector](https://github.com/pgvector/pgvector) extension. To use pgvector, you will need to set up a PostgreSQL database with the pgvector extension enabled. For example, you can [use docker](https://www.docker.com/blog/how-to-use-the-postgres-docker-official-image/) to run locally. For a hosted/managed solution, you may try [Supabase](https://supabase.com/) or any other cloud provider with support for pgvector. For detailed setup instructions, refer to [`/docs/providers/postgres/setup.md`](/docs/providers/postgres/setup.md). + ### Running the API locally To run the API locally, you first need to set the requisite environment variables with the `export` command: @@ -506,3 +527,8 @@ We would like to extend our gratitude to the following contributors for their co - [LlamaIndex](https://github.com/jerryjliu/llama_index) - [jerryjliu](https://github.com/jerryjliu) - [Disiok](https://github.com/Disiok) +- [Supabase](https://supabase.com/) + - [egor-romanov](https://github.com/egor-romanov) +- [Postgres](https://www.postgresql.org/) + - [egor-romanov](https://github.com/egor-romanov) + - [mmmaia](https://github.com/mmmaia) diff --git a/datastore/factory.py b/datastore/factory.py index 026798899..f075e3a8f 100644 --- a/datastore/factory.py +++ b/datastore/factory.py @@ -13,6 +13,7 @@ async def get_datastore() -> DataStore: return ChromaDataStore() case "llama": from datastore.providers.llama_datastore import LlamaDataStore + return LlamaDataStore() case "pinecone": @@ -43,6 +44,14 @@ async def get_datastore() -> DataStore: from datastore.providers.azuresearch_datastore import AzureSearchDataStore return AzureSearchDataStore() + case "supabase": + from datastore.providers.supabase_datastore import SupabaseDataStore + + return SupabaseDataStore() + case "postgres": + from datastore.providers.postgres_datastore import PostgresDataStore + + return PostgresDataStore() case _: raise ValueError( f"Unsupported vector database: {datastore}. " diff --git a/datastore/providers/pgvector_datastore.py b/datastore/providers/pgvector_datastore.py new file mode 100644 index 000000000..14c2aea1d --- /dev/null +++ b/datastore/providers/pgvector_datastore.py @@ -0,0 +1,180 @@ +from abc import ABC, abstractmethod +from typing import Any, Dict, List, Optional +from datetime import datetime + +from services.date import to_unix_timestamp +from datastore.datastore import DataStore +from models.models import ( + DocumentChunk, + DocumentChunkMetadata, + DocumentMetadataFilter, + QueryResult, + QueryWithEmbedding, + DocumentChunkWithScore, +) + + +# interface for Postgres client to implement pg based Datastore providers +class PGClient(ABC): + @abstractmethod + async def upsert(self, table: str, json: dict[str, Any]) -> None: + """ + Takes in a list of documents and inserts them into the table. + """ + raise NotImplementedError + + @abstractmethod + async def rpc(self, function_name: str, params: dict[str, Any]) -> Any: + """ + Calls a stored procedure in the database with the given parameters. + """ + raise NotImplementedError + + @abstractmethod + async def delete_like(self, table: str, column: str, pattern: str) -> None: + """ + Deletes rows in the table that match the pattern. + """ + raise NotImplementedError + + @abstractmethod + async def delete_in(self, table: str, column: str, ids: List[str]) -> None: + """ + Deletes rows in the table that match the ids. + """ + raise NotImplementedError + + @abstractmethod + async def delete_by_filters( + self, table: str, filter: DocumentMetadataFilter + ) -> None: + """ + Deletes rows in the table that match the filter. + """ + raise NotImplementedError + + +# abstract class for Postgres based Datastore providers that implements DataStore interface +class PgVectorDataStore(DataStore): + def __init__(self): + self.client = self.create_db_client() + + @abstractmethod + def create_db_client(self) -> PGClient: + """ + Create db client, can be accessing postgres database via different APIs. + Can be supabase client or psycopg2 based client. + Return a client for postgres DB. + """ + + raise NotImplementedError + + async def _upsert(self, chunks: Dict[str, List[DocumentChunk]]) -> List[str]: + """ + Takes in a dict of document_ids to list of document chunks and inserts them into the database. + Return a list of document ids. + """ + for document_id, document_chunks in chunks.items(): + for chunk in document_chunks: + json = { + "id": chunk.id, + "content": chunk.text, + "embedding": chunk.embedding, + "document_id": document_id, + "source": chunk.metadata.source, + "source_id": chunk.metadata.source_id, + "url": chunk.metadata.url, + "author": chunk.metadata.author, + } + if chunk.metadata.created_at: + json["created_at"] = ( + datetime.fromtimestamp( + to_unix_timestamp(chunk.metadata.created_at) + ), + ) + await self.client.upsert("documents", json) + + return list(chunks.keys()) + + async def _query(self, queries: List[QueryWithEmbedding]) -> List[QueryResult]: + """ + Takes in a list of queries with embeddings and filters and returns a list of query results with matching document chunks and scores. + """ + query_results: List[QueryResult] = [] + for query in queries: + # get the top 3 documents with the highest cosine similarity using rpc function in the database called "match_page_sections" + params = { + "in_embedding": query.embedding, + } + if query.top_k: + params["in_match_count"] = query.top_k + if query.filter: + if query.filter.document_id: + params["in_document_id"] = query.filter.document_id + if query.filter.source: + params["in_source"] = query.filter.source.value + if query.filter.source_id: + params["in_source_id"] = query.filter.source_id + if query.filter.author: + params["in_author"] = query.filter.author + if query.filter.start_date: + params["in_start_date"] = datetime.fromtimestamp( + to_unix_timestamp(query.filter.start_date) + ) + if query.filter.end_date: + params["in_end_date"] = datetime.fromtimestamp( + to_unix_timestamp(query.filter.end_date) + ) + try: + data = await self.client.rpc("match_page_sections", params=params) + results: List[DocumentChunkWithScore] = [] + for row in data: + document_chunk = DocumentChunkWithScore( + id=row["id"], + text=row["content"], + # TODO: add embedding to the response ? + # embedding=row["embedding"], + score=float(row["similarity"]), + metadata=DocumentChunkMetadata( + source=row["source"], + source_id=row["source_id"], + document_id=row["document_id"], + url=row["url"], + created_at=row["created_at"], + author=row["author"], + ), + ) + results.append(document_chunk) + query_results.append(QueryResult(query=query.query, results=results)) + except Exception as e: + print("error:", e) + query_results.append(QueryResult(query=query.query, results=[])) + return query_results + + async def delete( + self, + ids: Optional[List[str]] = None, + filter: Optional[DocumentMetadataFilter] = None, + delete_all: Optional[bool] = None, + ) -> bool: + """ + Removes vectors by ids, filter, or everything in the datastore. + Multiple parameters can be used at once. + Returns whether the operation was successful. + """ + if delete_all: + try: + await self.client.delete_like("documents", "document_id", "%") + except: + return False + elif ids: + try: + await self.client.delete_in("documents", "document_id", ids) + except: + return False + elif filter: + try: + await self.client.delete_by_filters("documents", filter) + except: + return False + return True diff --git a/datastore/providers/postgres_datastore.py b/datastore/providers/postgres_datastore.py new file mode 100644 index 000000000..402ad1b28 --- /dev/null +++ b/datastore/providers/postgres_datastore.py @@ -0,0 +1,132 @@ +import os +from typing import Any, List +from datetime import datetime +import numpy as np + +from psycopg2 import connect +from psycopg2.extras import DictCursor +from pgvector.psycopg2 import register_vector + +from services.date import to_unix_timestamp +from datastore.providers.pgvector_datastore import PGClient, PgVectorDataStore +from models.models import ( + DocumentMetadataFilter, +) + +PG_HOST = os.environ.get("PG_HOST", "localhost") +PG_PORT = int(os.environ.get("PG_PORT", 5432)) +PG_DB = os.environ.get("PG_DB", "postgres") +PG_USER = os.environ.get("PG_USER", "postgres") +PG_PASSWORD = os.environ.get("PG_PASSWORD", "postgres") + + +# class that implements the DataStore interface for Postgres Datastore provider +class PostgresDataStore(PgVectorDataStore): + def create_db_client(self): + return PostgresClient() + + +class PostgresClient(PGClient): + def __init__(self) -> None: + super().__init__() + self.client = connect( + dbname=PG_DB, user=PG_USER, password=PG_PASSWORD, host=PG_HOST, port=PG_PORT + ) + register_vector(self.client) + + def __del__(self): + # close the connection when the client is destroyed + self.client.close() + + async def upsert(self, table: str, json: dict[str, Any]): + """ + Takes in a list of documents and inserts them into the table. + """ + with self.client.cursor() as cur: + if not json.get("created_at"): + json["created_at"] = datetime.now() + json["embedding"] = np.array(json["embedding"]) + cur.execute( + f"INSERT INTO {table} (id, content, embedding, document_id, source, source_id, url, author, created_at) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) ON CONFLICT (id) DO UPDATE SET content = %s, embedding = %s, document_id = %s, source = %s, source_id = %s, url = %s, author = %s, created_at = %s", + ( + json["id"], + json["content"], + json["embedding"], + json["document_id"], + json["source"], + json["source_id"], + json["url"], + json["author"], + json["created_at"], + json["content"], + json["embedding"], + json["document_id"], + json["source"], + json["source_id"], + json["url"], + json["author"], + json["created_at"], + ), + ) + self.client.commit() + + async def rpc(self, function_name: str, params: dict[str, Any]): + """ + Calls a stored procedure in the database with the given parameters. + """ + data = [] + params["in_embedding"] = np.array(params["in_embedding"]) + with self.client.cursor(cursor_factory=DictCursor) as cur: + cur.callproc(function_name, params) + rows = cur.fetchall() + self.client.commit() + for row in rows: + row["created_at"] = to_unix_timestamp(row["created_at"]) + data.append(dict(row)) + return data + + async def delete_like(self, table: str, column: str, pattern: str): + """ + Deletes rows in the table that match the pattern. + """ + with self.client.cursor() as cur: + cur.execute( + f"DELETE FROM {table} WHERE {column} LIKE %s", + (f"%{pattern}%",), + ) + self.client.commit() + + async def delete_in(self, table: str, column: str, ids: List[str]): + """ + Deletes rows in the table that match the ids. + """ + with self.client.cursor() as cur: + cur.execute( + f"DELETE FROM {table} WHERE {column} IN %s", + (tuple(ids),), + ) + self.client.commit() + + async def delete_by_filters(self, table: str, filter: DocumentMetadataFilter): + """ + Deletes rows in the table that match the filter. + """ + + filters = "WHERE" + if filter.document_id: + filters += f" document_id = '{filter.document_id}' AND" + if filter.source: + filters += f" source = '{filter.source}' AND" + if filter.source_id: + filters += f" source_id = '{filter.source_id}' AND" + if filter.author: + filters += f" author = '{filter.author}' AND" + if filter.start_date: + filters += f" created_at >= '{filter.start_date}' AND" + if filter.end_date: + filters += f" created_at <= '{filter.end_date}' AND" + filters = filters[:-4] + + with self.client.cursor() as cur: + cur.execute(f"DELETE FROM {table} {filters}") + self.client.commit() diff --git a/datastore/providers/supabase_datastore.py b/datastore/providers/supabase_datastore.py new file mode 100644 index 000000000..ec7395e56 --- /dev/null +++ b/datastore/providers/supabase_datastore.py @@ -0,0 +1,95 @@ +import os +from typing import Any, List +from datetime import datetime + +from supabase import Client + +from datastore.providers.pgvector_datastore import PGClient, PgVectorDataStore +from models.models import ( + DocumentMetadataFilter, +) + +SUPABASE_URL = os.environ.get("SUPABASE_URL") +assert SUPABASE_URL is not None, "SUPABASE_URL is not set" +SUPABASE_ANON_KEY = os.environ.get("SUPABASE_ANON_KEY") +# use service role key if you want this app to be able to bypass your Row Level Security policies +SUPABASE_SERVICE_ROLE_KEY = os.environ.get("SUPABASE_SERVICE_ROLE_KEY") +assert ( + SUPABASE_ANON_KEY is not None or SUPABASE_SERVICE_ROLE_KEY is not None +), "SUPABASE_ANON_KEY or SUPABASE_SERVICE_ROLE_KEY must be set" + + +# class that implements the DataStore interface for Supabase Datastore provider +class SupabaseDataStore(PgVectorDataStore): + def create_db_client(self): + return SupabaseClient() + + +class SupabaseClient(PGClient): + def __init__(self) -> None: + super().__init__() + if not SUPABASE_SERVICE_ROLE_KEY: + self.client = Client(SUPABASE_URL, SUPABASE_ANON_KEY) + else: + self.client = Client(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY) + + async def upsert(self, table: str, json: dict[str, Any]): + """ + Takes in a list of documents and inserts them into the table. + """ + if "created_at" in json: + json["created_at"] = json["created_at"][0].isoformat() + + self.client.table(table).upsert(json).execute() + + async def rpc(self, function_name: str, params: dict[str, Any]): + """ + Calls a stored procedure in the database with the given parameters. + """ + if "in_start_date" in params: + params["in_start_date"] = params["in_start_date"].isoformat() + if "in_end_date" in params: + params["in_end_date"] = params["in_end_date"].isoformat() + + response = self.client.rpc(function_name, params=params).execute() + return response.data + + async def delete_like(self, table: str, column: str, pattern: str): + """ + Deletes rows in the table that match the pattern. + """ + self.client.table(table).delete().like(column, pattern).execute() + + async def delete_in(self, table: str, column: str, ids: List[str]): + """ + Deletes rows in the table that match the ids. + """ + self.client.table(table).delete().in_(column, ids).execute() + + async def delete_by_filters(self, table: str, filter: DocumentMetadataFilter): + """ + Deletes rows in the table that match the filter. + """ + builder = self.client.table(table).delete() + if filter.document_id: + builder = builder.eq( + "document_id", + filter.document_id, + ) + if filter.source: + builder = builder.eq("source", filter.source) + if filter.source_id: + builder = builder.eq("source_id", filter.source_id) + if filter.author: + builder = builder.eq("author", filter.author) + if filter.start_date: + builder = builder.gte( + "created_at", + filter.start_date[0].isoformat(), + ) + if filter.end_date: + builder = builder.lte( + "created_at", + filter.end_date[0].isoformat(), + ) + builder.execute() diff --git a/docs/deployment/removing-unused-dependencies.md b/docs/deployment/removing-unused-dependencies.md index dcdac20c7..3e692dbb0 100644 --- a/docs/deployment/removing-unused-dependencies.md +++ b/docs/deployment/removing-unused-dependencies.md @@ -4,14 +4,16 @@ Before deploying your app, you might want to remove unused dependencies from you Here are the packages you can remove for each vector database provider: -- **Pinecone:** Remove `weaviate-client`, `pymilvus`, `qdrant-client`, `redis`, `chromadb`, `llama-index`, `azure-identity` and `azure-search-documents`. -- **Weaviate:** Remove `pinecone-client`, `pymilvus`, `qdrant-client`, `redis`, `chromadb`, `llama-index`, `azure-identity` and `azure-search-documents`. -- **Zilliz:** Remove `pinecone-client`, `weaviate-client`, `qdrant-client`, `redis`, `chromadb`, `llama-index`, `azure-identity` and `azure-search-documents`. -- **Milvus:** Remove `pinecone-client`, `weaviate-client`, `qdrant-client`, `redis`, `chromadb`, `llama-index`, `azure-identity` and `azure-search-documents`. -- **Qdrant:** Remove `pinecone-client`, `weaviate-client`, `pymilvus`, `redis`, `chromadb`, `llama-index`, `azure-identity` and `azure-search-documents`. -- **Redis:** Remove `pinecone-client`, `weaviate-client`, `pymilvus`, `qdrant-client`, `chromadb`, `llama-index`, `azure-identity` and `azure-search-documents`. -- **LlamaIndex:** Remove `pinecone-client`, `weaviate-client`, `pymilvus`, `qdrant-client`, `chromadb`, `redis`, `azure-identity` and `azure-search-documents`. -- **Chroma:**: Remove `pinecone-client`, `weaviate-client`, `pymilvus`, `qdrant-client`, `llama-index`, `redis`, `azure-identity` and `azure-search-documents`. -- **Azure Cognitive Search**: Remove `pinecone-client`, `weaviate-client`, `pymilvus`, `qdrant-client`, `llama-index`, `redis` and `chromadb`. +- **Pinecone:** Remove `weaviate-client`, `pymilvus`, `qdrant-client`, `redis`, `chromadb`, `llama-index`, `azure-identity` and `azure-search-documents`, `supabase`, and `psycopg2`+`pgvector`. +- **Weaviate:** Remove `pinecone-client`, `pymilvus`, `qdrant-client`, `redis`, `chromadb`, `llama-index`, `azure-identity` and `azure-search-documents`, `supabase`, and `psycopg2`+`pgvector`. +- **Zilliz:** Remove `pinecone-client`, `weaviate-client`, `qdrant-client`, `redis`, `chromadb`, `llama-index`, `azure-identity` and `azure-search-documents`, `supabase`, and `psycopg2`+`pgvector`. +- **Milvus:** Remove `pinecone-client`, `weaviate-client`, `qdrant-client`, `redis`, `chromadb`, `llama-index`, `azure-identity` and `azure-search-documents`, `supabase`, and `psycopg2`+`pgvector`. +- **Qdrant:** Remove `pinecone-client`, `weaviate-client`, `pymilvus`, `redis`, `chromadb`, `llama-index`, `azure-identity` and `azure-search-documents`, `supabase`, and `psycopg2`+`pgvector`. +- **Redis:** Remove `pinecone-client`, `weaviate-client`, `pymilvus`, `qdrant-client`, `chromadb`, `llama-index`, `azure-identity` and `azure-search-documents`, `supabase`, and `psycopg2`+`pgvector`. +- **LlamaIndex:** Remove `pinecone-client`, `weaviate-client`, `pymilvus`, `qdrant-client`, `chromadb`, `redis`, `azure-identity` and `azure-search-documents`, `supabase`, and `psycopg2`+`pgvector`. +- **Chroma:**: Remove `pinecone-client`, `weaviate-client`, `pymilvus`, `qdrant-client`, `llama-index`, `redis`, `azure-identity` and `azure-search-documents`, `supabase`, and `psycopg2`+`pgvector`. +- **Azure Cognitive Search**: Remove `pinecone-client`, `weaviate-client`, `pymilvus`, `qdrant-client`, `llama-index`, `redis` and `chromadb`, `supabase`, and `psycopg2`+`pgvector`. +- **Supabase:** Remove `pinecone-client`, `weaviate-client`, `pymilvus`, `qdrant-client`, `redis`, `llama-index`, `azure-identity` and `azure-search-documents`, and `psycopg2`+`pgvector`. +- **Postgres:** Remove `pinecone-client`, `weaviate-client`, `pymilvus`, `qdrant-client`, `redis`, `llama-index`, `azure-identity` and `azure-search-documents`, and `supabase`. After removing the unnecessary packages from the `pyproject.toml` file, you don't need to run `poetry lock` and `poetry install` manually. The provided Dockerfile takes care of installing the required dependencies using the `requirements.txt` file generated by the `poetry export` command. diff --git a/docs/providers/postgres/setup.md b/docs/providers/postgres/setup.md new file mode 100644 index 000000000..9108e646a --- /dev/null +++ b/docs/providers/postgres/setup.md @@ -0,0 +1,81 @@ +# Postgres + +Postgres Database offers an easy and efficient way to store vectors via [pgvector](https://github.com/pgvector/pgvector) extension. To use pgvector, you will need to set up a PostgreSQL database with the pgvector extension enabled or use a managed solution that provides pgvector. For a hosted/managed solution, you may try [Supabase](https://supabase.com/), more docs you can find in [Supabase Provider](/docs/providers/supabase/setup.md). See more helpful examples of Postgres & pgvector as a vector database [here](https://github.com/supabase-community/nextjs-openai-doc-search). + +- The database needs the `pgvector` extension. +- To apply required migrations you may use any tool you are more familiar with like [pgAdmin](https://www.pgadmin.org/), [DBeaver](https://dbeaver.io/), [DataGrip](https://www.jetbrains.com/datagrip/), or `psql` cli. + +**Retrieval App Environment Variables** + +| Name | Required | Description | +| ---------------- | -------- | -------------------------------------- | +| `DATASTORE` | Yes | Datastore name. Set this to `postgres` | +| `BEARER_TOKEN` | Yes | Your secret token | +| `OPENAI_API_KEY` | Yes | Your OpenAI API key | + +**Supabase Datastore Environment Variables** + +| Name | Required | Description | Default | +| ------------- | -------- | ----------------- | ---------- | +| `PG_HOST` | Optional | Postgres host | localhost | +| `PG_PORT` | Optional | Postgres port | `5432` | +| `PG_PASSWORD` | Optional | Postgres password | `postgres` | +| `PG_USER` | Optional | Postgres username | `postgres` | +| `PG_DB` | Optional | Postgres database | `postgres` | + +## Postgres Datastore local development & testing + +In order to test your changes to the Postgres Datastore, you can run the following: + +1. You can run local or self-hosted instance of PostgreSQL with `pgvector` enabled using Docker. + +```bash +docker pull ankane/pgvector +``` + +```bash +docker run --name pgvector -e POSTGRES_PASSWORD=mysecretpassword -d postgres +``` + +Check PostgreSQL [official docker image](https://github.com/docker-library/docs/blob/master/postgres/README.md) for more options. + +2. Apply migrations using any tool you like most [pgAdmin](https://www.pgadmin.org/), [DBeaver](https://dbeaver.io/), [DataGrip](https://www.jetbrains.com/datagrip/), or `psql` cli. + +```bash +# apply migrations using psql cli +psql -h localhost -p 5432 -U postgres -d postgres -f examples/providers/supabase/migrations/20230414142107_init_pg_vector.sql +``` + +3. Export environment variables required for the Postgres Datastore + +```bash +export PG_HOST=localhost +export PG_PORT=54322 +export PG_PASSWORD=mysecretpassword +``` + +4. Run the Postgres datastore tests from the project's root directory + +```bash +# Run the Postgres datastore tests +# go to project's root directory and run +poetry run pytest -s ./tests/datastore/providers/postgres/test_postgres_datastore.py +``` + +5. When going to prod don't forget to set the password for the `postgres` user to something more secure and apply migrations. + +6. You may want to remove RLS (Row Level Security) from the `documents` table. If you are not using RLS, it is not required in this setup. But it may be useful if you want to separate documents by user or group of users, or if you want to give permissions to insert or query documents to different users. And RLS is especially important if you are willing to use PostgREST. To do so you can just remove the following statement from the `20230414142107_init_pg_vector.sql` migration file: `alter table documents enable row level security;`. + +## Indexes for Postgres + +By default, pgvector performs exact nearest neighbor search. To speed up the vector comparison, you may want to create indexes for the `embedding` column in the `documents` table. You should do this **only** after a few thousand records are inserted. + +As datasotre is using inner product for similarity search, you can add index as follows: + +```sql +create index on documents using ivfflat (embedding vector_ip_ops) with (lists = 100); +``` + +To choose `lists` constant - a good place to start is records / 1000 for up to 1M records and sqrt(records) for over 1M records + +For more information about indexes, see [pgvector docs](https://github.com/pgvector/pgvector#indexing). diff --git a/docs/providers/supabase/setup.md b/docs/providers/supabase/setup.md new file mode 100644 index 000000000..8d2f05a73 --- /dev/null +++ b/docs/providers/supabase/setup.md @@ -0,0 +1,87 @@ +# Supabase + +[Supabase](https://supabase.com/blog/openai-embeddings-postgres-vector) offers an easy and efficient way to store vectors via [pgvector](https://github.com/pgvector/pgvector) extension for Postgres Database. [You can use Supabase CLI](https://github.com/supabase/cli) to set up a whole Supabase stack locally or in the cloud or you can also use docker-compose, k8s and other options available. For a hosted/managed solution, try [Supabase.com](https://supabase.com/) and unlock the full power of Postgres with built-in authentication, storage, auto APIs, and Realtime features. See more helpful examples of Supabase & pgvector as a vector database [here](https://github.com/supabase-community/nextjs-openai-doc-search). + +- The database needs the `pgvector` extension, which is included in [Supabase distribution of Postgres](https://github.com/supabase/postgres). +- It is possible to provide a Postgres connection string and an app will add `documents` table, query Postgres function, and `pgvector` extension automatically. +- But it is recommended to separate the migration process from an app. And execute the migration script in a different pipeline by using SQL statements from `_init_db()` function in [Supabase datastore provider](/datastore/providers/supabase_datastore.py). + +**Retrieval App Environment Variables** + +| Name | Required | Description | +| ---------------- | -------- | -------------------------------------- | +| `DATASTORE` | Yes | Datastore name. Set this to `supabase` | +| `BEARER_TOKEN` | Yes | Your secret token | +| `OPENAI_API_KEY` | Yes | Your OpenAI API key | + +**Supabase Datastore Environment Variables** + +| Name | Required | Description | Default | +| --------------------------- | -------- | ------------------------------------------------------------------------------ | ------- | +| `SUPABASE_URL` | Yes | Supabase Project URL | | +| `SUPABASE_ANON_KEY` | Optional | Supabase Project API anon key | | +| `SUPABASE_SERVICE_ROLE_KEY` | Optional | Supabase Project API service key, will be used if provided instead of anon key | | + +## Supabase Datastore local development & testing + +In order to test your changes to the Supabase Datastore, you can run the following commands: + +1. Install [Supabase CLI](https://github.com/supabase/cli) and [Docker](https://docs.docker.com/get-docker/) + +2. Run the Supabase `start` command from `examples/providers` directory. Config for Supabase local setup is available in `examples/providers/supabase` directory with required migrations. + +```bash +# Run the Supabase stack using cli in docker +# go to examples/providers and run supabase start +cd examples/providers +supabase start +``` + +3. Supabase `start` will download docker images and launch Supabase stack locally. You will see similar output: + +```bash +Applying migration 20230414142107_init_pg_vector.sql... +Seeding data supabase/seed.sql... +Started supabase local development setup. + + API URL: http://localhost:54321 + DB URL: postgresql://postgres:postgres@localhost:54322/postgres + Studio URL: http://localhost:54323 + Inbucket URL: http://localhost:54324 + JWT secret: super-secret-jwt-token-with-at-least-32-characters-long + anon key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0 +service_role key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU +``` + +4. Export environment variables required for the Supabase Datastore + +```bash +export SUPABASE_URL=http://localhost:54321 +export SUPABASE_SERVICE_ROLE_KEY='eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU' +``` + +5. Run the Supabase datastore tests from the project's root directory + +```bash +# Run the Supabase datastore tests +# go to project's root directory and run +poetry run pytest -s ./tests/datastore/providers/supabase/test_supabase_datastore.py +``` + +6. When you go to prod (if cloud hosted) it is recommended to link your supabase project with the local setup from `examples/providers/supabase`. All migrations will be synced with the cloud project after you run `supabase db push`. Or you can manually apply migrations from `examples/providers/supabase/migrations` directory. + +7. You might want to add RLS policies to the `documents` table. Or you can just continue using it on the server side only with the service role key. But you should not use service role key on the client side in any case. + +## Indexes for Postgres + +By default, pgvector performs exact nearest neighbor search. To speed up the vector comparison, you may want to create indexes for the `embedding` column in the `documents` table. You should do this **only** after a few thousand records are inserted. + +As datasotre is using inner product for similarity search, you can add index as follows: + +```sql +create index on documents using ivfflat (embedding vector_ip_ops) with (lists = 100); +``` + +To choose `lists` constant - a good place to start is records / 1000 for up to 1M records and sqrt(records) for over 1M records + +For more information about indexes, see [pgvector docs](https://github.com/pgvector/pgvector#indexing). diff --git a/examples/providers/supabase/.gitignore b/examples/providers/supabase/.gitignore new file mode 100644 index 000000000..773c7c3e0 --- /dev/null +++ b/examples/providers/supabase/.gitignore @@ -0,0 +1,3 @@ +# Supabase +.branches +.temp diff --git a/examples/providers/supabase/config.toml b/examples/providers/supabase/config.toml new file mode 100644 index 000000000..921313039 --- /dev/null +++ b/examples/providers/supabase/config.toml @@ -0,0 +1,72 @@ +# A string used to distinguish different Supabase projects on the same host. Defaults to the working +# directory name when running `supabase init`. +project_id = "providers" + +[api] +# Port to use for the API URL. +port = 54321 +# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API +# endpoints. public and storage are always included. +schemas = ["public", "storage", "graphql_public"] +# Extra schemas to add to the search_path of every request. public is always included. +extra_search_path = ["public", "extensions"] +# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size +# for accidental or malicious requests. +max_rows = 1000 + +[db] +# Port to use for the local database URL. +port = 54322 +# The database major version to use. This has to be the same as your remote database's. Run `SHOW +# server_version;` on the remote database to check. +major_version = 15 + +[studio] +# Port to use for Supabase Studio. +port = 54323 + +# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they +# are monitored, and you can view the emails that would have been sent from the web interface. +[inbucket] +# Port to use for the email testing server web interface. +port = 54324 +smtp_port = 54325 +pop3_port = 54326 + +[storage] +# The maximum file size allowed (e.g. "5MB", "500KB"). +file_size_limit = "50MiB" + +[auth] +# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used +# in emails. +site_url = "http://localhost:3000" +# A list of *exact* URLs that auth providers are permitted to redirect to post authentication. +additional_redirect_urls = ["https://localhost:3000"] +# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 seconds (one +# week). +jwt_expiry = 3600 +# Allow/disallow new user signups to your project. +enable_signup = true + +[auth.email] +# Allow/disallow new user signups via email to your project. +enable_signup = true +# If enabled, a user will be required to confirm any email change on both the old, and new email +# addresses. If disabled, only the new email is required to confirm. +double_confirm_changes = true +# If enabled, users need to confirm their email address before signing in. +enable_confirmations = false + +# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`, +# `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin`, `notion`, `twitch`, +# `twitter`, `slack`, `spotify`, `workos`, `zoom`. +[auth.external.apple] +enabled = false +client_id = "" +secret = "" +# Overrides the default auth redirectUrl. +redirect_uri = "" +# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure, +# or any other third-party OIDC providers. +url = "" diff --git a/examples/providers/supabase/migrations/20230414142107_init_pg_vector.sql b/examples/providers/supabase/migrations/20230414142107_init_pg_vector.sql new file mode 100644 index 000000000..4d54797b2 --- /dev/null +++ b/examples/providers/supabase/migrations/20230414142107_init_pg_vector.sql @@ -0,0 +1,70 @@ +create extension vector; + +create table if not exists documents ( + id text primary key default gen_random_uuid()::text, + source text, + source_id text, + content text, + document_id text, + author text, + url text, + created_at timestamptz default now(), + embedding vector(1536) +); + +create index ix_documents_document_id on documents using btree ( document_id ); +create index ix_documents_source on documents using btree ( source ); +create index ix_documents_source_id on documents using btree ( source_id ); +create index ix_documents_author on documents using btree ( author ); +create index ix_documents_created_at on documents using brin ( created_at ); + +alter table documents enable row level security; + +create or replace function match_page_sections(in_embedding vector(1536) + , in_match_count int default 3 + , in_document_id text default '%%' + , in_source_id text default '%%' + , in_source text default '%%' + , in_author text default '%%' + , in_start_date timestamptz default '-infinity' + , in_end_date timestamptz default 'infinity') +returns table (id text + , source text + , source_id text + , document_id text + , url text + , created_at timestamptz + , author text + , content text + , embedding vector(1536) + , similarity float) +language plpgsql +as $$ +#variable_conflict use_variable +begin +return query +select + documents.id, + documents.source, + documents.source_id, + documents.document_id, + documents.url, + documents.created_at, + documents.author, + documents.content, + documents.embedding, + (documents.embedding <#> in_embedding) * -1 as similarity +from documents + +where in_start_date <= documents.created_at and + documents.created_at <= in_end_date and + (documents.source_id like in_source_id or documents.source_id is null) and + (documents.source like in_source or documents.source is null) and + (documents.author like in_author or documents.author is null) and + (documents.document_id like in_document_id or documents.document_id is null) + +order by documents.embedding <#> in_embedding + +limit in_match_count; +end; +$$; \ No newline at end of file diff --git a/examples/providers/supabase/seed.sql b/examples/providers/supabase/seed.sql new file mode 100644 index 000000000..e69de29bb diff --git a/poetry.lock b/poetry.lock index d119e11aa..2d7bfcd4e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -240,20 +240,20 @@ aio = ["aiohttp (>=3.0)"] [[package]] name = "azure-identity" -version = "1.12.0" +version = "1.13.0" description = "Microsoft Azure Identity Library for Python" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "azure-identity-1.12.0.zip", hash = "sha256:7f9b1ae7d97ea7af3f38dd09305e19ab81a1e16ab66ea186b6579d85c1ca2347"}, - {file = "azure_identity-1.12.0-py3-none-any.whl", hash = "sha256:2a58ce4a209a013e37eaccfd5937570ab99e9118b3e1acf875eed3a85d541b92"}, + {file = "azure-identity-1.13.0.zip", hash = "sha256:c931c27301ffa86b07b4dcf574e29da73e3deba9ab5d1fe4f445bb6a3117e260"}, + {file = "azure_identity-1.13.0-py3-none-any.whl", hash = "sha256:bd700cebb80cd9862098587c29d8677e819beca33c62568ced6d5a8e5e332b82"}, ] [package.dependencies] azure-core = ">=1.11.0,<2.0.0" cryptography = ">=2.5" -msal = ">=1.12.0,<2.0.0" +msal = ">=1.20.0,<2.0.0" msal-extensions = ">=0.3.0,<2.0.0" six = ">=1.12.0" @@ -291,6 +291,25 @@ files = [ {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, ] +[[package]] +name = "bleach" +version = "6.0.0" +description = "An easy safelist-based HTML-sanitizing tool." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "bleach-6.0.0-py3-none-any.whl", hash = "sha256:33c16e3353dbd13028ab4799a0f89a83f113405c766e9c122df8a06f5b85b3f4"}, + {file = "bleach-6.0.0.tar.gz", hash = "sha256:1a1a85c1595e07d8db14c5f09f09e6433502c51c595970edc090551f0db99414"}, +] + +[package.dependencies] +six = ">=1.9.0" +webencodings = "*" + +[package.extras] +css = ["tinycss2 (>=1.1.0,<1.2)"] + [[package]] name = "blobfile" version = "2.0.2" @@ -523,79 +542,94 @@ files = [ [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} +[[package]] +name = "click-log" +version = "0.4.0" +description = "Logging integration for Click" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "click-log-0.4.0.tar.gz", hash = "sha256:3970f8570ac54491237bcdb3d8ab5e3eef6c057df29f8c3d1151a51a9c23b975"}, + {file = "click_log-0.4.0-py2.py3-none-any.whl", hash = "sha256:a43e394b528d52112af599f2fc9e4b7cf3c15f94e53581f74fa6867e68c91756"}, +] + +[package.dependencies] +click = "*" + [[package]] name = "clickhouse-connect" -version = "0.5.23" +version = "0.5.24" description = "ClickHouse core driver, SqlAlchemy, and Superset libraries" category = "main" optional = false python-versions = "~=3.7" files = [ - {file = "clickhouse-connect-0.5.23.tar.gz", hash = "sha256:d4c48f2b05807720a638df4e8f8e71d45a6eb548c6183b44782270631a34b849"}, - {file = "clickhouse_connect-0.5.23-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df35ab8af6d46cce12552d7bd25fe657d3ad8c8b4956a1067441c25c1e63cc61"}, - {file = "clickhouse_connect-0.5.23-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:addb0a933821b68984dff82345846a6c5fd161e471cfdfc22933d3c33dafe545"}, - {file = "clickhouse_connect-0.5.23-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:181670af78bd4186d8b250ad25a2afedf4a50a938e2f10dc945b291d7956f6bc"}, - {file = "clickhouse_connect-0.5.23-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e1ac16086a3f247943aea2fceaec6fddcfaf00f87376dede2199ca26a41aa28"}, - {file = "clickhouse_connect-0.5.23-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f8de0e470329eb1f26c9a3feba38e13732ca91b11f414f76c91d52ff4a5ff4f8"}, - {file = "clickhouse_connect-0.5.23-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e68dad117e7b27ae3ba30ae7b5c8a8ce9a7f34703aea04e18dba5b09e353f38d"}, - {file = "clickhouse_connect-0.5.23-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2d8aadd1508d9acb2f7a604662aa6987efb87ea9e51cd2e3413d61c69f1fdd50"}, - {file = "clickhouse_connect-0.5.23-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1a8f3887c2422ee28512aa944e3bc642c9dd6c2749d8c204c25a90989bf4a430"}, - {file = "clickhouse_connect-0.5.23-cp310-cp310-win32.whl", hash = "sha256:d1909c6359855dd42225709672bb61e3fbc2c8ab5e6cbb48582136df3d57d216"}, - {file = "clickhouse_connect-0.5.23-cp310-cp310-win_amd64.whl", hash = "sha256:95eff3b153a5fd810f54dfc1b3043a86218db0a31f17c926eacca262a269cfac"}, - {file = "clickhouse_connect-0.5.23-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:72d75a02a1caa3fdb5edbab988e4d3abfcf2f380df3ea68275047c40f39e562e"}, - {file = "clickhouse_connect-0.5.23-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bc23c24b2aabdd81366c9a90c6262ecfcb64b22e16b5704d29dc648c732ac8fd"}, - {file = "clickhouse_connect-0.5.23-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99f0f72bccbf55578ab34c7ad9a76f803db1eb778ed7ca0e4f4b867c75a41c16"}, - {file = "clickhouse_connect-0.5.23-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:842150ff73575cfe9cc295ccb4c205ec5a8907c3290d2a99460427993c7a6a03"}, - {file = "clickhouse_connect-0.5.23-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3703d8ff05b2d9ff30bb1fc4eaff5349cc1017a193e6d5746fc35393d2f80899"}, - {file = "clickhouse_connect-0.5.23-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:af3f83a85341bd5d8525533c83a9dfe64676df7e0ede517639b96746ce50a57d"}, - {file = "clickhouse_connect-0.5.23-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:58f95eff87c4d437f39b8192532fcf4e05dee2601b6313e2ad2c4913bef312e2"}, - {file = "clickhouse_connect-0.5.23-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db6b9ad563d5fbc1729249fed60c657f4844eb27105241f5142dae77c007641a"}, - {file = "clickhouse_connect-0.5.23-cp311-cp311-win32.whl", hash = "sha256:ca9ebf6d5a86e336f61c0b9364fb9ae944b6b38195d3b92db2bf712a50f1c213"}, - {file = "clickhouse_connect-0.5.23-cp311-cp311-win_amd64.whl", hash = "sha256:e432a51dd58e0c0921e51df53f33ec0c8740109bb84b65beb0b1702914356a0e"}, - {file = "clickhouse_connect-0.5.23-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:76927edeab12e2539e41e64b8b8923fe636e9a52a6edf58f4c588e5948d0c2bd"}, - {file = "clickhouse_connect-0.5.23-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97902396d33a4b02a9515b37fb56e4dac392f2421e1e53874524a3829a861595"}, - {file = "clickhouse_connect-0.5.23-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef8edec9769656346e74f02948f9565d9f3ba0ca1021d879fb410e30a54f8478"}, - {file = "clickhouse_connect-0.5.23-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:acb72211fad7ea02d16baca6a57a184142b58e8604de32faf4cdb19e18930110"}, - {file = "clickhouse_connect-0.5.23-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d14331736b32c14a8d432ea4785c3a56c12c669ecb0c4049b3a2567a3d10ae18"}, - {file = "clickhouse_connect-0.5.23-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:df465815aba293ed2f96885491c900ab34a2a6b98ff9b446d777938802ac127d"}, - {file = "clickhouse_connect-0.5.23-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1245ad0cb77871b3f3bc87d55bbbc8137125cb71a073d4a522f12707fed1f3a3"}, - {file = "clickhouse_connect-0.5.23-cp37-cp37m-win32.whl", hash = "sha256:e939f6429784d175863bbf321f781510aa165b01e5442a7fb7fa7885071d1846"}, - {file = "clickhouse_connect-0.5.23-cp37-cp37m-win_amd64.whl", hash = "sha256:79f5863afcbad0b9bd37a080be4a710c3562a658d474df4e07be29b33c324cc0"}, - {file = "clickhouse_connect-0.5.23-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7652dd19953cffd51c75cdfef70c763d6dc631af89c4988c29d20e129f391b10"}, - {file = "clickhouse_connect-0.5.23-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:596012ec214b5f4b07e148686669e2be10b83522cd7c72cdb38f8b9bcd181d62"}, - {file = "clickhouse_connect-0.5.23-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91c355f7a203c77aad56cc9c73f1d570446e4ed5bb57b3c90e8c74956bf7c305"}, - {file = "clickhouse_connect-0.5.23-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc67f14f1d6eaa65a48e1170407b1cedd9cdc3b90506c090ea67ba4c4307e1cb"}, - {file = "clickhouse_connect-0.5.23-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ec68ed44ab02ce4d97998bdd215efca5e863dab0819427057bb5ec0e97ca8a6"}, - {file = "clickhouse_connect-0.5.23-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:602fcac024b6314f96f39b3ff0fb1aa54bfe9d2de1d3958ddcedb19616129535"}, - {file = "clickhouse_connect-0.5.23-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3c636c9154d2d9b57188b20ec42524e2258029874442259a605171a420ca0d52"}, - {file = "clickhouse_connect-0.5.23-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:46fd5427353690b68cb41e808919819f376ab503297fbfc59b51506181cd4410"}, - {file = "clickhouse_connect-0.5.23-cp38-cp38-win32.whl", hash = "sha256:6594d3189f4f67e49775aec12ef593052b056aa338d0b7a27905a1176979ae5f"}, - {file = "clickhouse_connect-0.5.23-cp38-cp38-win_amd64.whl", hash = "sha256:69cd6eb7c220cc55f3e9ff6391f7d02fa789740983863acd7e68bdd3956163c6"}, - {file = "clickhouse_connect-0.5.23-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1afd4089296678ed20d38e100b121ea9caa05a24f9cdc66b72ded8ed06d705b8"}, - {file = "clickhouse_connect-0.5.23-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:381279e5382086ae07e988770e38b601342165a577c606589616315324545135"}, - {file = "clickhouse_connect-0.5.23-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3cbf47331b7c220a558be6c6667599f53fc0070c0abdcc27d7bbc215b56da61"}, - {file = "clickhouse_connect-0.5.23-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a924dd889c7c740f7cd8cbb7f4d3fa3e7f794400d50adaa1190f1cd8786f238"}, - {file = "clickhouse_connect-0.5.23-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:37069cf6aa4fd18e7daaa7be52afebe51ae57ebdc001d1c0c1784bfa474cebda"}, - {file = "clickhouse_connect-0.5.23-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c3a29c3473f1c0c60a8639c48487c0ba220636c79a1f37a7fdb1a3ee2392b8a1"}, - {file = "clickhouse_connect-0.5.23-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:52c469867ce021a1b0e831a0c2544062ecc6df4cae93f51895c993c09e9092ee"}, - {file = "clickhouse_connect-0.5.23-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:da233d6584109ee96b1e35137ac5866d38a1ec3b62b36cd0ed05d573729f5234"}, - {file = "clickhouse_connect-0.5.23-cp39-cp39-win32.whl", hash = "sha256:5cb9564f0634f3a1f78ba122c7d05288b46a4c2fc464b378e138b8c7d02f188a"}, - {file = "clickhouse_connect-0.5.23-cp39-cp39-win_amd64.whl", hash = "sha256:22ba7629e2f96b22c41ea018386d1295a379d993c9a8db8721f0fd1c3903e2c0"}, - {file = "clickhouse_connect-0.5.23-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0af8c5162c7c0dbf0be46d0ac5ab32a2e30b9e2581c0c90b51abcbac7a1959d9"}, - {file = "clickhouse_connect-0.5.23-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4624e496b709d55ae76f469040f5ea98a281045adf4bd7f717812a3273fcffa"}, - {file = "clickhouse_connect-0.5.23-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5b812c30283d54e09cb66d33a69352e06a520a0798c51ea2c9b164c9328880b"}, - {file = "clickhouse_connect-0.5.23-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0ff4b37b994fb82e525e4c0ac3e645964efb8ee78499dfbf4bbe7cda4299399"}, - {file = "clickhouse_connect-0.5.23-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:7d595f2b6c7dbbc20f78c0191bb14b186b049069a1249460e912514601818bd8"}, - {file = "clickhouse_connect-0.5.23-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:944ec9e9f7bf5cdb35f077d641dd5dd2ce4fd33ca032c3908f1cccba1a54ae31"}, - {file = "clickhouse_connect-0.5.23-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e59893f62fa538cbcdd61793672ecf6866f2842212f19efecf1d117c0ccd1460"}, - {file = "clickhouse_connect-0.5.23-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f02750cd9d1a9c5a83a94d347caef9cf15109574e374dddf3e541ab4f9272ea"}, - {file = "clickhouse_connect-0.5.23-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:21c5909075514df1cee78d7b854426df15af0a6fc7f6f24626f9d0b928a7a5a0"}, - {file = "clickhouse_connect-0.5.23-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b289d8fc4ed0a71821f23c10acc10f013c7bed96e8fb6ee81ad8f5a6c8fe0ee2"}, - {file = "clickhouse_connect-0.5.23-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f27bcc8709799d35a8e5b0050db746883cf94077b7f5f31f4fb8d86096cf272c"}, - {file = "clickhouse_connect-0.5.23-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60c659f95364617dec828c5fd64b1ec24712bc1b81dab104b118ea2802c8ff70"}, - {file = "clickhouse_connect-0.5.23-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:520cd348dde41c3f43e3ce035c5db7d18e0a0b810d8d1cd08b21e2cc6abb7928"}, - {file = "clickhouse_connect-0.5.23-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4aa370480010ab4b24a28b07533d123e99ca789369c5e8a3cff9bd96cdcea56e"}, - {file = "clickhouse_connect-0.5.23-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:90b3dfcdd92cc42bb14a3f2d98ddfee772c0951bcc2443a178d84c27c9850d30"}, + {file = "clickhouse-connect-0.5.24.tar.gz", hash = "sha256:f1c6a4a20c19612eedaf1cea82e532010942cb08a29326db74cce0ea48bbe56d"}, + {file = "clickhouse_connect-0.5.24-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5b91584305b6133eff83e8a0436b3c48681dd44dcf8b2f5b54d558bafd30afa6"}, + {file = "clickhouse_connect-0.5.24-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:17f3ca231aeff7c9f316dc03cba49ea8cd1e91e0f129519f8857f0e1d9aa7f49"}, + {file = "clickhouse_connect-0.5.24-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b126b324ca9e34662bc07335f55ff51f9a5a5c5e4df97778f0a427b4bde8cfa"}, + {file = "clickhouse_connect-0.5.24-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c756b8f290fc68af83129d378b749e74c40560107b926ef047c098b7c95a2ad"}, + {file = "clickhouse_connect-0.5.24-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:486f538781d765993cc2b6f30ef8c274674b1be2c36dc03767d14feea24df566"}, + {file = "clickhouse_connect-0.5.24-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:67cfb63b155c36413ff301c321de09e2476a936dc784c7954a63d612ec66f1ec"}, + {file = "clickhouse_connect-0.5.24-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:56b004a0e001e49a2b6a022a98832b5558642299de9c808cf7b9333180f28e1b"}, + {file = "clickhouse_connect-0.5.24-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:68bae08ef93aa21e02c961c79f2932cc88d0682a91099ec2f007c032ab4b68e1"}, + {file = "clickhouse_connect-0.5.24-cp310-cp310-win32.whl", hash = "sha256:b7f73598f118c7466230f7149de0b4e1af992b2ac086a9200ac0011ab03ee468"}, + {file = "clickhouse_connect-0.5.24-cp310-cp310-win_amd64.whl", hash = "sha256:5b83b4c6994e43ce3192c11ac4eb84f8ac8b6317d860fc2c4ff8f8f3609b20c1"}, + {file = "clickhouse_connect-0.5.24-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ed329a93171ca867df9b903b95992d9dec2e256a657e16a88d27452dfe8f064e"}, + {file = "clickhouse_connect-0.5.24-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9bc64de89be44c30bf036aab551da196e11ebf14502533b6e2a0e8ca60c27599"}, + {file = "clickhouse_connect-0.5.24-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84adbe15ad0dd745aa1b2a183cf4d1573d39cdb81e9d0a2d37571805dfda4cd7"}, + {file = "clickhouse_connect-0.5.24-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a50f7f3756c64791fa8a4ec73f87954a6c3aa44523394ad22e13e31ba1cd9c25"}, + {file = "clickhouse_connect-0.5.24-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:08499995addd7d0e758086622d32aa8f8fdf6dde61bedb106f453191b16af15f"}, + {file = "clickhouse_connect-0.5.24-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d8607c4b388a46b312fd34cdd26fe958002e414c0320aad0e24ac93854191325"}, + {file = "clickhouse_connect-0.5.24-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f0adcfbda306a1aa9f3cdc2f638b36c748c68104be97d9dc935c130ad632be82"}, + {file = "clickhouse_connect-0.5.24-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2abee0170d60d0621f7feec6b1e9c7434e3bb23a7b025d32a513f2df969b9a2d"}, + {file = "clickhouse_connect-0.5.24-cp311-cp311-win32.whl", hash = "sha256:d6f7ea32b46a5fafa49a85b94b18902af38b0910f34ac588ec95b5b66faf7855"}, + {file = "clickhouse_connect-0.5.24-cp311-cp311-win_amd64.whl", hash = "sha256:f0ae6e14f526c5fe504103d00992bf8e0ab3359266664b327c273e16f957545d"}, + {file = "clickhouse_connect-0.5.24-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dc0b18678b66160ca4ca6ce7fe074188975546c5d196092ef06510eb16067964"}, + {file = "clickhouse_connect-0.5.24-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91a6d666c4c3f4dea7bca84098a4624102cb3efa7f882352e8b914238b0ab3b0"}, + {file = "clickhouse_connect-0.5.24-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1732ea5fddf201425baf53d1434516c1242184139d61202f885575cb8742167c"}, + {file = "clickhouse_connect-0.5.24-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be9c23721caacc52e9f75ba2239a5ca5bbdbafa913d36bcddf9eaf33578ba937"}, + {file = "clickhouse_connect-0.5.24-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b9aee9588b863ab3d33c11e9d2f350cee1f17753db74cedd3eb2bb4fc5ed31d1"}, + {file = "clickhouse_connect-0.5.24-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7158f70e5ba787f64f01098fa729942d1d4dfd1a46c4519aab10ed3a4b32ead"}, + {file = "clickhouse_connect-0.5.24-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6684d253580c2e9cbcab8322189ca66fafc27ccabf67da58f178b31a09ecb60f"}, + {file = "clickhouse_connect-0.5.24-cp37-cp37m-win32.whl", hash = "sha256:ba015b5337ecab0e9064eed3966acd2fe2c10f0391fc5f28d8c0fd73802d0810"}, + {file = "clickhouse_connect-0.5.24-cp37-cp37m-win_amd64.whl", hash = "sha256:34feb3cb81298beff8e2be233719cf1271fd0f1aca2a0ae5dfff9716f9ab94c1"}, + {file = "clickhouse_connect-0.5.24-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5ae2551daec4731373bffc6bc9d3e30a5dfbc0bdceb66cbc93c56dd0797c0740"}, + {file = "clickhouse_connect-0.5.24-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2cf26c82f3bd03e3088251f249776285a01da3268936d88d98b7cbecb2783497"}, + {file = "clickhouse_connect-0.5.24-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0437c44d342edada639fed6f5064226cc9ad9f37406ea1cf550a50cb3f66db5a"}, + {file = "clickhouse_connect-0.5.24-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e7b5f68b7bae44ec5dfc80510bb81f9f2af88662681c103d5a58da170f4eb78"}, + {file = "clickhouse_connect-0.5.24-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc0ccf9ef68377291aba32dc7754b8aab658c2b4cfe06488140114f8abbef819"}, + {file = "clickhouse_connect-0.5.24-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1e9c3f146bdb1929223ebba04610ebf7bbbed313ee452754268c546966eff9db"}, + {file = "clickhouse_connect-0.5.24-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f7e31461ce8e13e2b9f67b21e2ac7bd1121420d85bf6dc888082dfd2f6ca9bc4"}, + {file = "clickhouse_connect-0.5.24-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e7b9b5a24cad361845f1d138ba9fb45f690c84583ca584adac76379a65fd8c00"}, + {file = "clickhouse_connect-0.5.24-cp38-cp38-win32.whl", hash = "sha256:7d223477041ae31b62917b5f9abeaa468fe2a1efa8391070da4258a41fdc7643"}, + {file = "clickhouse_connect-0.5.24-cp38-cp38-win_amd64.whl", hash = "sha256:c82fcf42d9a2318cf53086147376c31246e3842b73a09b4bac16a6f0c299a294"}, + {file = "clickhouse_connect-0.5.24-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:586d7193ece84ddc2608fdc29cd10cc80eff26f283b2ad9d738bbd522f1f84cd"}, + {file = "clickhouse_connect-0.5.24-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:71b452bed17aee315b93944174053cd84dc5efb245d4a556a2e49b78022f7ed6"}, + {file = "clickhouse_connect-0.5.24-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:788722210e636bec7a870b0625999f97c3285bc19fd46763b58472ee445b67e9"}, + {file = "clickhouse_connect-0.5.24-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:268e3375d9a3985ea961cb1be338c1d13154b617f5eb027ace0e8670de9501ce"}, + {file = "clickhouse_connect-0.5.24-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:28ea9abd595d7400e3ef2842f5e9db5307133dfa24d97a8c45f71713048bad97"}, + {file = "clickhouse_connect-0.5.24-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:00b0ac033dc47e0409a19ff974d938006a198445980028d911a47ba05facf6cd"}, + {file = "clickhouse_connect-0.5.24-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:601a26ddb18e266e79b76d1672ac15ef5b6043ea17ba4c9dc3dc80130a0775d9"}, + {file = "clickhouse_connect-0.5.24-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:eb502ccb7c5dcb907cf4c8316f9b787e4bd3a7b65cd8cbc37b24c5e9c890a801"}, + {file = "clickhouse_connect-0.5.24-cp39-cp39-win32.whl", hash = "sha256:e6acedfd795cd1db7d89f21597389805e583f2b4ae9495cb0b89b8eda13ff6ad"}, + {file = "clickhouse_connect-0.5.24-cp39-cp39-win_amd64.whl", hash = "sha256:921d3a8a287844c031c470547c07dd5b7454c883c44f13e1d4f5b9d0896444d2"}, + {file = "clickhouse_connect-0.5.24-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ec051a1f6f3912f2f3b659d3e3c344a67f676d2d42583885b3ed8365c51753b2"}, + {file = "clickhouse_connect-0.5.24-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b116538fd7d75df991b211a3db311c158a2664301b2f5d1ffc18feb5b5da89d"}, + {file = "clickhouse_connect-0.5.24-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b116747e4b187d3aac49a51e865a4fe0c11b39775724f0d7f719b4222810a5a4"}, + {file = "clickhouse_connect-0.5.24-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4fa54e11e651979d9a4e355564d2128c6a8394d4cffda295a8188c9869ab93cc"}, + {file = "clickhouse_connect-0.5.24-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:7c17e691e27d3b2e950cb2f597f0a895eb6b9d6717e886fafae861d34ac5bbb0"}, + {file = "clickhouse_connect-0.5.24-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:34e2ae809ac1244da6fa67c4021431f9a1865d14c6df2d7fe57d22841f361497"}, + {file = "clickhouse_connect-0.5.24-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e7b2ef89e9c1c92a09988a812626f7d529acfda93f420b75e59fe2981960886"}, + {file = "clickhouse_connect-0.5.24-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6200bdf94a52847d3f10ab8675c58db9ff3e90ce6ee98bc0c49f01c74d934798"}, + {file = "clickhouse_connect-0.5.24-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4def3ee218f6fbb320fbb1c5c1bb3b23753b9e56e50759fc396ea70631dff846"}, + {file = "clickhouse_connect-0.5.24-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:378f6a6289080f0c103f17eda9f8edcabc4878eb783e6b4e596d8bf8f543244e"}, + {file = "clickhouse_connect-0.5.24-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e29389baa14a3f1db4e52b32090e1e32533496e35833514c689b190f26dfb039"}, + {file = "clickhouse_connect-0.5.24-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7418e2c6533eebf0de9f3e85f1e3b6095d1a0bf42e4fed479f92f538725ff666"}, + {file = "clickhouse_connect-0.5.24-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3f23f819f20d130daed64ba058e01336e2f5f6d4b9f576038c0b800473af1ac"}, + {file = "clickhouse_connect-0.5.24-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a846fc412475d55d7727c8a82ba1247b1b7ff0c6341a1818f99fd348ee9b1580"}, + {file = "clickhouse_connect-0.5.24-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:34afc74ea27dcb85c1929f6105c4701566f51a1216bd6648b63ccb4871906729"}, ] [package.dependencies] @@ -766,6 +800,21 @@ files = [ {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, ] +[[package]] +name = "deprecation" +version = "2.1.0" +description = "A library to handle automated deprecations" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a"}, + {file = "deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff"}, +] + +[package.dependencies] +packaging = "*" + [[package]] name = "dnspython" version = "2.3.0" @@ -787,6 +836,18 @@ idna = ["idna (>=2.1,<4.0)"] trio = ["trio (>=0.14,<0.23)"] wmi = ["wmi (>=1.5.1,<2.0.0)"] +[[package]] +name = "docutils" +version = "0.20" +description = "Docutils -- Python Documentation Utilities" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "docutils-0.20-py3-none-any.whl", hash = "sha256:a428f10de4de4774389734c986a01b4af2d802d26717108b0f1b9356862937c5"}, + {file = "docutils-0.20.tar.gz", hash = "sha256:f75a5a52fbcacd81b47e42888ad2b380748aaccfb3f13af0fe69deb759f01eb6"}, +] + [[package]] name = "docx2txt" version = "0.8" @@ -798,6 +859,18 @@ files = [ {file = "docx2txt-0.8.tar.gz", hash = "sha256:2c06d98d7cfe2d3947e5760a57d924e3ff07745b379c8737723922e7009236e5"}, ] +[[package]] +name = "dotty-dict" +version = "1.3.1" +description = "Dictionary wrapper for quick access to deeply nested keys." +category = "main" +optional = false +python-versions = ">=3.5,<4.0" +files = [ + {file = "dotty_dict-1.3.1-py3-none-any.whl", hash = "sha256:5022d234d9922f13aa711b4950372a06a6d64cb6d6db9ba43d0ba133ebfce31f"}, + {file = "dotty_dict-1.3.1.tar.gz", hash = "sha256:4b016e03b8ae265539757a53eba24b9bfda506fb94fbce0bee843c6f05541a15"}, +] + [[package]] name = "duckdb" version = "0.7.1" @@ -1050,6 +1123,52 @@ smb = ["smbprotocol"] ssh = ["paramiko"] tqdm = ["tqdm"] +[[package]] +name = "gitdb" +version = "4.0.10" +description = "Git Object Database" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "gitdb-4.0.10-py3-none-any.whl", hash = "sha256:c286cf298426064079ed96a9e4a9d39e7f3e9bf15ba60701e95f5492f28415c7"}, + {file = "gitdb-4.0.10.tar.gz", hash = "sha256:6eb990b69df4e15bad899ea868dc46572c3f75339735663b81de79b06f17eb9a"}, +] + +[package.dependencies] +smmap = ">=3.0.1,<6" + +[[package]] +name = "gitpython" +version = "3.1.31" +description = "GitPython is a Python library used to interact with Git repositories" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "GitPython-3.1.31-py3-none-any.whl", hash = "sha256:f04893614f6aa713a60cbbe1e6a97403ef633103cdd0ef5eb6efe0deb98dbe8d"}, + {file = "GitPython-3.1.31.tar.gz", hash = "sha256:8ce3bcf69adfdf7c7d503e78fd3b1c492af782d58893b650adb2ac8912ddd573"}, +] + +[package.dependencies] +gitdb = ">=4.0.1,<5" + +[[package]] +name = "gotrue" +version = "1.0.1" +description = "Python Client Library for GoTrue" +category = "main" +optional = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "gotrue-1.0.1-py3-none-any.whl", hash = "sha256:005e8bc8d7f2da87606504c9c269f2943245843e2ddefb99e583f45a8612e715"}, + {file = "gotrue-1.0.1.tar.gz", hash = "sha256:9d7e01703beb3c017bcf0461f518f93bc5a400720df3ba8c082264d405cee4d0"}, +] + +[package.dependencies] +httpx = ">=0.23.0,<0.24.0" +pydantic = ">=1.10.0,<2.0.0" + [[package]] name = "greenlet" version = "2.0.2" @@ -1453,6 +1572,26 @@ files = [ {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] +[[package]] +name = "importlib-metadata" +version = "6.6.0" +description = "Read metadata from Python packages" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "importlib_metadata-6.6.0-py3-none-any.whl", hash = "sha256:43dd286a2cd8995d5eaef7fee2066340423b818ed3fd70adf0bad5f1fac53fed"}, + {file = "importlib_metadata-6.6.0.tar.gz", hash = "sha256:92501cdf9cc66ebd3e612f1b4f0c0765dfa42f0fa38ffb319b6bd84dd675d705"}, +] + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] + [[package]] name = "iniconfig" version = "2.0.0" @@ -1465,6 +1604,18 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "invoke" +version = "1.7.3" +description = "Pythonic task execution" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "invoke-1.7.3-py3-none-any.whl", hash = "sha256:d9694a865764dd3fd91f25f7e9a97fb41666e822bbb00e670091e3f43933574d"}, + {file = "invoke-1.7.3.tar.gz", hash = "sha256:41b428342d466a82135d5ab37119685a989713742be46e42a3a399d685579314"}, +] + [[package]] name = "isodate" version = "0.6.1" @@ -1480,6 +1631,41 @@ files = [ [package.dependencies] six = "*" +[[package]] +name = "jaraco-classes" +version = "3.2.3" +description = "Utility functions for Python class constructs" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jaraco.classes-3.2.3-py3-none-any.whl", hash = "sha256:2353de3288bc6b82120752201c6b1c1a14b058267fa424ed5ce5984e3b922158"}, + {file = "jaraco.classes-3.2.3.tar.gz", hash = "sha256:89559fa5c1d3c34eff6f631ad80bb21f378dbcbb35dd161fd2c6b93f5be2f98a"}, +] + +[package.dependencies] +more-itertools = "*" + +[package.extras] +docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[[package]] +name = "jeepney" +version = "0.8.0" +description = "Low-level, pure Python DBus protocol wrapper." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, + {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, +] + +[package.extras] +test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] +trio = ["async_generator", "trio"] + [[package]] name = "jinja2" version = "3.1.2" @@ -1510,16 +1696,40 @@ files = [ {file = "joblib-1.2.0.tar.gz", hash = "sha256:e1cee4a79e4af22881164f218d4311f60074197fb707e082e803b61f6d137018"}, ] +[[package]] +name = "keyring" +version = "23.13.1" +description = "Store and access your passwords safely." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "keyring-23.13.1-py3-none-any.whl", hash = "sha256:771ed2a91909389ed6148631de678f82ddc73737d85a927f382a8a1b157898cd"}, + {file = "keyring-23.13.1.tar.gz", hash = "sha256:ba2e15a9b35e21908d0aaf4e0a47acc52d6ae33444df0da2b49d41a46ef6d678"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.11.4", markers = "python_version < \"3.12\""} +"jaraco.classes" = "*" +jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""} +pywin32-ctypes = {version = ">=0.2.0", markers = "sys_platform == \"win32\""} +SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} + +[package.extras] +completion = ["shtab"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + [[package]] name = "langchain" -version = "0.0.162" +version = "0.0.166" description = "Building applications with LLMs through composability" category = "main" optional = false python-versions = ">=3.8.1,<4.0" files = [ - {file = "langchain-0.0.162-py3-none-any.whl", hash = "sha256:fd499fa047a5e52233bdc7fff442be9175b40b0fa3e6edbea58c9996f6750e38"}, - {file = "langchain-0.0.162.tar.gz", hash = "sha256:90712ca5b953475140fedf019c0c84dc453b15465d460ae0ef31026f5e949024"}, + {file = "langchain-0.0.166-py3-none-any.whl", hash = "sha256:32417cc38ba211d46c3e97f29cb8124175fe46047bda14a4c634351b005acd21"}, + {file = "langchain-0.0.166.tar.gz", hash = "sha256:fb1e90eb0aeef9c574e6683586bfbfed1974e187dd8261b571cb33888c35a92e"}, ] [package.dependencies] @@ -1537,10 +1747,13 @@ tenacity = ">=8.1.0,<9.0.0" tqdm = ">=4.48.0" [package.extras] -all = ["O365 (>=2.0.26,<3.0.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.2.6,<0.3.0)", "arxiv (>=1.4,<2.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "azure-cosmos (>=4.4.0b1,<5.0.0)", "azure-identity (>=1.12.0,<2.0.0)", "beautifulsoup4 (>=4,<5)", "clickhouse-connect (>=0.5.14,<0.6.0)", "cohere (>=3,<4)", "deeplake (>=3.3.0,<4.0.0)", "duckduckgo-search (>=2.8.6,<3.0.0)", "elasticsearch (>=8,<9)", "faiss-cpu (>=1,<2)", "google-api-python-client (==2.70.0)", "google-search-results (>=2,<3)", "gptcache (>=0.1.7)", "html2text (>=2020.1.16,<2021.0.0)", "huggingface_hub (>=0,<1)", "jina (>=3.14,<4.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "lancedb (>=0.1,<0.2)", "lark (>=1.1.5,<2.0.0)", "manifest-ml (>=0.0.1,<0.0.2)", "networkx (>=2.6.3,<3.0.0)", "nlpcloud (>=1,<2)", "nltk (>=3,<4)", "nomic (>=1.0.43,<2.0.0)", "openai (>=0,<1)", "opensearch-py (>=2.0.0,<3.0.0)", "pexpect (>=4.8.0,<5.0.0)", "pgvector (>=0.1.6,<0.2.0)", "pinecone-client (>=2,<3)", "pinecone-text (>=0.4.2,<0.5.0)", "psycopg2-binary (>=2.9.5,<3.0.0)", "pyowm (>=3.3.0,<4.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pytesseract (>=0.3.10,<0.4.0)", "pyvespa (>=0.33.0,<0.34.0)", "qdrant-client (>=1.1.2,<2.0.0)", "redis (>=4,<5)", "sentence-transformers (>=2,<3)", "spacy (>=3,<4)", "tensorflow-text (>=2.11.0,<3.0.0)", "tiktoken (>=0.3.2,<0.4.0)", "torch (>=1,<3)", "transformers (>=4,<5)", "weaviate-client (>=3,<4)", "wikipedia (>=1,<2)", "wolframalpha (==5.0.0)"] +all = ["O365 (>=2.0.26,<3.0.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.2.6,<0.3.0)", "arxiv (>=1.4,<2.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "azure-cosmos (>=4.4.0b1,<5.0.0)", "azure-identity (>=1.12.0,<2.0.0)", "beautifulsoup4 (>=4,<5)", "clickhouse-connect (>=0.5.14,<0.6.0)", "cohere (>=3,<4)", "deeplake (>=3.3.0,<4.0.0)", "docarray (>=0.31.0,<0.32.0)", "duckduckgo-search (>=2.8.6,<3.0.0)", "elasticsearch (>=8,<9)", "faiss-cpu (>=1,<2)", "google-api-python-client (==2.70.0)", "google-search-results (>=2,<3)", "gptcache (>=0.1.7)", "hnswlib (>=0.7.0,<0.8.0)", "html2text (>=2020.1.16,<2021.0.0)", "huggingface_hub (>=0,<1)", "jina (>=3.14,<4.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "lancedb (>=0.1,<0.2)", "lark (>=1.1.5,<2.0.0)", "manifest-ml (>=0.0.1,<0.0.2)", "networkx (>=2.6.3,<3.0.0)", "nlpcloud (>=1,<2)", "nltk (>=3,<4)", "nomic (>=1.0.43,<2.0.0)", "openai (>=0,<1)", "opensearch-py (>=2.0.0,<3.0.0)", "pexpect (>=4.8.0,<5.0.0)", "pgvector (>=0.1.6,<0.2.0)", "pinecone-client (>=2,<3)", "pinecone-text (>=0.4.2,<0.5.0)", "protobuf (==3.19)", "psycopg2-binary (>=2.9.5,<3.0.0)", "pyowm (>=3.3.0,<4.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pytesseract (>=0.3.10,<0.4.0)", "pyvespa (>=0.33.0,<0.34.0)", "qdrant-client (>=1.1.2,<2.0.0)", "redis (>=4,<5)", "sentence-transformers (>=2,<3)", "spacy (>=3,<4)", "tensorflow-text (>=2.11.0,<3.0.0)", "tiktoken (>=0.3.2,<0.4.0)", "torch (>=1,<3)", "transformers (>=4,<5)", "weaviate-client (>=3,<4)", "wikipedia (>=1,<2)", "wolframalpha (==5.0.0)"] azure = ["azure-core (>=1.26.4,<2.0.0)", "azure-cosmos (>=4.4.0b1,<5.0.0)", "azure-identity (>=1.12.0,<2.0.0)", "openai (>=0,<1)"] cohere = ["cohere (>=3,<4)"] embeddings = ["sentence-transformers (>=2,<3)"] +extended-testing = ["pdfminer-six (>=20221105,<20221106)", "pypdf (>=3.4.0,<4.0.0)"] +hnswlib = ["docarray (>=0.31.0,<0.32.0)", "hnswlib (>=0.7.0,<0.8.0)", "protobuf (==3.19)"] +in-memory-store = ["docarray (>=0.31.0,<0.32.0)"] llms = ["anthropic (>=0.2.6,<0.3.0)", "cohere (>=3,<4)", "huggingface_hub (>=0,<1)", "manifest-ml (>=0.0.1,<0.0.2)", "nlpcloud (>=1,<2)", "openai (>=0,<1)", "torch (>=1,<3)", "transformers (>=4,<5)"] openai = ["openai (>=0,<1)"] qdrant = ["qdrant-client (>=1.1.2,<2.0.0)"] @@ -1835,6 +2048,18 @@ files = [ {file = "monotonic-1.6.tar.gz", hash = "sha256:3a55207bcfed53ddd5c5bae174524062935efed17792e9de2ad0205ce9ad63f7"}, ] +[[package]] +name = "more-itertools" +version = "9.1.0" +description = "More routines for operating on iterables, beyond itertools" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "more-itertools-9.1.0.tar.gz", hash = "sha256:cabaa341ad0389ea83c17a94566a53ae4c9d07349861ecb14dc6d0345cf9ac5d"}, + {file = "more_itertools-9.1.0-py3-none-any.whl", hash = "sha256:d2bc7f02446e86a68911e58ded76d6561eea00cddfb2a91e7019bbb586c799f3"}, +] + [[package]] name = "mpmath" version = "1.3.0" @@ -2231,6 +2456,20 @@ sql-other = ["SQLAlchemy (>=1.4.16)"] test = ["hypothesis (>=6.34.2)", "pytest (>=7.0.0)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"] xml = ["lxml (>=4.6.3)"] +[[package]] +name = "pgvector" +version = "0.1.7" +description = "pgvector support for Python" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pgvector-0.1.7-py2.py3-none-any.whl", hash = "sha256:b0da0289959372f916b96c1da7c57437725c7aa33fa0c75b4a53c3677369bdd5"}, +] + +[package.dependencies] +numpy = "*" + [[package]] name = "pillow" version = "9.5.0" @@ -2337,6 +2576,21 @@ urllib3 = ">=1.21.1" [package.extras] grpc = ["googleapis-common-protos (>=1.53.0)", "grpc-gateway-protoc-gen-openapiv2 (==0.1.0)", "grpcio (>=1.44.0)", "lz4 (>=3.1.3)", "protobuf (==3.19.3)"] +[[package]] +name = "pkginfo" +version = "1.9.6" +description = "Query metadata from sdists / bdists / installed packages." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pkginfo-1.9.6-py3-none-any.whl", hash = "sha256:4b7a555a6d5a22169fcc9cf7bfd78d296b0361adad412a346c1226849af5e546"}, + {file = "pkginfo-1.9.6.tar.gz", hash = "sha256:8fd5896e8718a4372f0ea9cc9d96f6417c9b986e23a4d116dda26b62cc29d046"}, +] + +[package.extras] +testing = ["pytest", "pytest-cov"] + [[package]] name = "pluggy" version = "1.0.0" @@ -2373,6 +2627,24 @@ docs = ["sphinx (>=1.7.1)"] redis = ["redis"] tests = ["pytest (>=5.4.1)", "pytest-cov (>=2.8.1)", "pytest-mypy (>=0.8.0)", "pytest-timeout (>=2.1.0)", "redis", "sphinx (>=6.0.0)"] +[[package]] +name = "postgrest" +version = "0.10.6" +description = "PostgREST client for Python. This library provides an ORM interface to PostgREST." +category = "main" +optional = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "postgrest-0.10.6-py3-none-any.whl", hash = "sha256:7302068ce3cd80e761e35d6d665d3e65632442488258e3299c008013119d7fe6"}, + {file = "postgrest-0.10.6.tar.gz", hash = "sha256:ee145d53ea8642a16fa7f42848443baa08ae1e6f41e071865f5f54bcb3b24aa3"}, +] + +[package.dependencies] +deprecation = ">=2.1.0,<3.0.0" +httpx = ">=0.23.0,<0.24.0" +pydantic = ">=1.9.0,<2.0.0" +strenum = ">=0.4.9,<0.5.0" + [[package]] name = "posthog" version = "3.0.1" @@ -2420,6 +2692,29 @@ files = [ {file = "protobuf-4.23.0.tar.gz", hash = "sha256:5f1eba1da2a2f3f7df469fccddef3cc060b8a16cfe3cc65961ad36b4dbcf59c5"}, ] +[[package]] +name = "psycopg2" +version = "2.9.6" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "psycopg2-2.9.6-cp310-cp310-win32.whl", hash = "sha256:f7a7a5ee78ba7dc74265ba69e010ae89dae635eea0e97b055fb641a01a31d2b1"}, + {file = "psycopg2-2.9.6-cp310-cp310-win_amd64.whl", hash = "sha256:f75001a1cbbe523e00b0ef896a5a1ada2da93ccd752b7636db5a99bc57c44494"}, + {file = "psycopg2-2.9.6-cp311-cp311-win32.whl", hash = "sha256:53f4ad0a3988f983e9b49a5d9765d663bbe84f508ed655affdb810af9d0972ad"}, + {file = "psycopg2-2.9.6-cp311-cp311-win_amd64.whl", hash = "sha256:b81fcb9ecfc584f661b71c889edeae70bae30d3ef74fa0ca388ecda50b1222b7"}, + {file = "psycopg2-2.9.6-cp36-cp36m-win32.whl", hash = "sha256:11aca705ec888e4f4cea97289a0bf0f22a067a32614f6ef64fcf7b8bfbc53744"}, + {file = "psycopg2-2.9.6-cp36-cp36m-win_amd64.whl", hash = "sha256:36c941a767341d11549c0fbdbb2bf5be2eda4caf87f65dfcd7d146828bd27f39"}, + {file = "psycopg2-2.9.6-cp37-cp37m-win32.whl", hash = "sha256:869776630c04f335d4124f120b7fb377fe44b0a7645ab3c34b4ba42516951889"}, + {file = "psycopg2-2.9.6-cp37-cp37m-win_amd64.whl", hash = "sha256:a8ad4a47f42aa6aec8d061fdae21eaed8d864d4bb0f0cade5ad32ca16fcd6258"}, + {file = "psycopg2-2.9.6-cp38-cp38-win32.whl", hash = "sha256:2362ee4d07ac85ff0ad93e22c693d0f37ff63e28f0615a16b6635a645f4b9214"}, + {file = "psycopg2-2.9.6-cp38-cp38-win_amd64.whl", hash = "sha256:d24ead3716a7d093b90b27b3d73459fe8cd90fd7065cf43b3c40966221d8c394"}, + {file = "psycopg2-2.9.6-cp39-cp39-win32.whl", hash = "sha256:1861a53a6a0fd248e42ea37c957d36950da00266378746588eab4f4b5649e95f"}, + {file = "psycopg2-2.9.6-cp39-cp39-win_amd64.whl", hash = "sha256:ded2faa2e6dfb430af7713d87ab4abbfc764d8d7fb73eafe96a24155f906ebf5"}, + {file = "psycopg2-2.9.6.tar.gz", hash = "sha256:f15158418fd826831b28585e2ab48ed8df2d0d98f502a2b4fe619e7d5ca29011"}, +] + [[package]] name = "pycparser" version = "2.21" @@ -2528,6 +2823,21 @@ typing-extensions = ">=4.2.0" dotenv = ["python-dotenv (>=0.10.4)"] email = ["email-validator (>=1.0.3)"] +[[package]] +name = "pygments" +version = "2.15.1" +description = "Pygments is a syntax highlighting package written in Python." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"}, + {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"}, +] + +[package.extras] +plugins = ["importlib-metadata"] + [[package]] name = "pyjwt" version = "2.7.0" @@ -2678,6 +2988,26 @@ files = [ [package.extras] cli = ["click (>=5.0)"] +[[package]] +name = "python-gitlab" +version = "3.14.0" +description = "Interact with GitLab API" +category = "main" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "python-gitlab-3.14.0.tar.gz", hash = "sha256:ef3b8960faeee9880f82b0872d807e3fab94ace12b0d2a8418a97875c8812d3c"}, + {file = "python_gitlab-3.14.0-py3-none-any.whl", hash = "sha256:da614c014c6860147783dde8c216218d8fc6bd83a8bd2e3929dcdf11b211aa58"}, +] + +[package.dependencies] +requests = ">=2.25.0" +requests-toolbelt = ">=0.10.1" + +[package.extras] +autocompletion = ["argcomplete (>=1.10.0,<3)"] +yaml = ["PyYaml (>=5.2)"] + [[package]] name = "python-multipart" version = "0.0.6" @@ -2709,6 +3039,38 @@ lxml = ">=3.1.0" Pillow = ">=3.3.2" XlsxWriter = ">=0.5.7" +[[package]] +name = "python-semantic-release" +version = "7.33.2" +description = "Automatic Semantic Versioning for Python projects" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "python-semantic-release-7.33.2.tar.gz", hash = "sha256:c23b4bb746e9ddbe1ba7497c48f7d81403e67a14ceb37928ef667c1fbee5e324"}, + {file = "python_semantic_release-7.33.2-py3-none-any.whl", hash = "sha256:9e4990cc0a4dc37482ac5ec7fe6f70f71681228f68f0fa39370415701fdcf632"}, +] + +[package.dependencies] +click = ">=7,<9" +click-log = ">=0.3,<1" +dotty-dict = ">=1.3.0,<2" +gitpython = ">=3.0.8,<4" +invoke = ">=1.4.1,<2" +packaging = "*" +python-gitlab = ">=2,<4" +requests = ">=2.25,<3" +semver = ">=2.10,<3" +tomlkit = ">=0.10,<1.0" +twine = ">=3,<4" +wheel = "*" + +[package.extras] +dev = ["black", "isort", "tox"] +docs = ["Jinja2 (==3.0.3)", "Sphinx (==1.3.6)"] +mypy = ["mypy", "types-requests"] +test = ["coverage (>=5,<6)", "mock (==1.3.0)", "pytest (>=7,<8)", "pytest-mock (>=2,<3)", "pytest-xdist (>=1,<2)", "responses (==0.13.3)"] + [[package]] name = "pytz" version = "2023.3" @@ -2745,6 +3107,18 @@ files = [ {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, ] +[[package]] +name = "pywin32-ctypes" +version = "0.2.0" +description = "" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"}, + {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, +] + [[package]] name = "pyyaml" version = "6.0" @@ -2797,14 +3171,14 @@ files = [ [[package]] name = "qdrant-client" -version = "1.1.6" +version = "1.1.7" description = "Client library for the Qdrant vector search engine" category = "main" optional = false python-versions = ">=3.7,<3.12" files = [ - {file = "qdrant_client-1.1.6-py3-none-any.whl", hash = "sha256:757e8d65fb6d4305fe6dbb4b087bf62ea3f01c28652f81592800564748a73545"}, - {file = "qdrant_client-1.1.6.tar.gz", hash = "sha256:4b1be451e27e6c8058c565bcf92e5308483b79395f826343477ed376bf601cd3"}, + {file = "qdrant_client-1.1.7-py3-none-any.whl", hash = "sha256:4f5d883660b8193840d8982919ab813a0470ace9a7ff46ee730f909841be5319"}, + {file = "qdrant_client-1.1.7.tar.gz", hash = "sha256:686d86934bec2ebb70676fc0650c9a44a9e552e0149124ca5a22ee8533879deb"}, ] [package.dependencies] @@ -2812,10 +3186,48 @@ grpcio = ">=1.41.0" grpcio-tools = ">=1.41.0" httpx = {version = ">=0.14.0", extras = ["http2"]} numpy = {version = ">=1.21", markers = "python_version >= \"3.8\""} +portalocker = ">=2.7.0,<3.0.0" pydantic = ">=1.8,<2.0" typing-extensions = ">=4.0.0,<5.0.0" urllib3 = ">=1.26.14,<2.0.0" +[[package]] +name = "readme-renderer" +version = "37.3" +description = "readme_renderer is a library for rendering \"readme\" descriptions for Warehouse" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "readme_renderer-37.3-py3-none-any.whl", hash = "sha256:f67a16caedfa71eef48a31b39708637a6f4664c4394801a7b0d6432d13907343"}, + {file = "readme_renderer-37.3.tar.gz", hash = "sha256:cd653186dfc73055656f090f227f5cb22a046d7f71a841dfa305f55c9a513273"}, +] + +[package.dependencies] +bleach = ">=2.1.0" +docutils = ">=0.13.1" +Pygments = ">=2.5.1" + +[package.extras] +md = ["cmarkgfm (>=0.8.0)"] + +[[package]] +name = "realtime" +version = "1.0.0" +description = "" +category = "main" +optional = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "realtime-1.0.0-py3-none-any.whl", hash = "sha256:ceab9e292211ab08b5792ac52b3fa25398440031d5b369bd5799b8125056e2d8"}, + {file = "realtime-1.0.0.tar.gz", hash = "sha256:14e540c4a0cc2736ae83e0cbd7efbbfb8b736df1681df2b9141556cb4848502d"}, +] + +[package.dependencies] +python-dateutil = ">=2.8.1,<3.0.0" +typing-extensions = ">=4.2.0,<5.0.0" +websockets = ">=10.3,<11.0" + [[package]] name = "redis" version = "4.5.1" @@ -2955,6 +3367,21 @@ urllib3 = ">=1.21.1,<1.27" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +description = "A utility belt for advanced users of python-requests" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, + {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, +] + +[package.dependencies] +requests = ">=2.0.1,<3.0.0" + [[package]] name = "rfc3986" version = "1.5.0" @@ -3055,6 +3482,34 @@ dev = ["flake8", "mypy", "pycodestyle", "typing_extensions"] doc = ["matplotlib (>2)", "numpydoc", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-panels (>=0.5.2)", "sphinx-tabs"] test = ["asv", "gmpy2", "mpmath", "pytest", "pytest-cov", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] +[[package]] +name = "secretstorage" +version = "3.3.3" +description = "Python bindings to FreeDesktop.org Secret Service API" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"}, + {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"}, +] + +[package.dependencies] +cryptography = ">=2.0" +jeepney = ">=0.6" + +[[package]] +name = "semver" +version = "2.13.0" +description = "Python helper for Semantic Versioning (http://semver.org/)" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "semver-2.13.0-py2.py3-none-any.whl", hash = "sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4"}, + {file = "semver-2.13.0.tar.gz", hash = "sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f"}, +] + [[package]] name = "sentence-transformers" version = "2.2.2" @@ -3162,6 +3617,18 @@ files = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] +[[package]] +name = "smmap" +version = "5.0.0" +description = "A pure Python implementation of a sliding window memory map manager" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, + {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, +] + [[package]] name = "sniffio" version = "1.3.0" @@ -3176,53 +3643,53 @@ files = [ [[package]] name = "sqlalchemy" -version = "2.0.12" +version = "2.0.13" description = "Database Abstraction Library" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "SQLAlchemy-2.0.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:10f1ff0ebe21d2cea89ead231ba3ecf75678463ab85f19ce2ce91207620737f3"}, - {file = "SQLAlchemy-2.0.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:978bee4ecbcdadf087220618409fb9be9509458df479528b70308f0599c7c519"}, - {file = "SQLAlchemy-2.0.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53b2c8adbcbb59732fb21a024aaa261983655845d86e3fc26a5676cec0ebaa09"}, - {file = "SQLAlchemy-2.0.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91f4b1bdc987ef85fe3a0ce5d26ac72ff8f60207b08272aa2a65494836391d69"}, - {file = "SQLAlchemy-2.0.12-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dfd6385b662aea83e63dd4db5fe116eb11914022deb1745f0b57fa8470c18ffe"}, - {file = "SQLAlchemy-2.0.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5e9d390727c11b9a7e583bf6770de36895c0936bddb98ae93ae99282e6428d5f"}, - {file = "SQLAlchemy-2.0.12-cp310-cp310-win32.whl", hash = "sha256:a4709457f1c317e347051498b91fa2b86c4bcdebf93c84e6d121a4fc8a397307"}, - {file = "SQLAlchemy-2.0.12-cp310-cp310-win_amd64.whl", hash = "sha256:f0843132168b44ca33c5e5a2046c954775dde8c580ce27f5cf2e134d0d9919e4"}, - {file = "SQLAlchemy-2.0.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:32762dba51b663609757f861584a722093487f53737e76474cc6e190904dc31b"}, - {file = "SQLAlchemy-2.0.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5d709f43caee115b03b707b8cbbcb8b303045dd7cdc825b6d29857d71f3425ae"}, - {file = "SQLAlchemy-2.0.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fe98e9d26778d7711ceee2c671741b4f54c74677668481d733d6f70747d7690"}, - {file = "SQLAlchemy-2.0.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a3101252f3de9a18561c1fb0a68b1ee465485990aba458d4510f214bd5a582c"}, - {file = "SQLAlchemy-2.0.12-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6b1fa0ffc378a7061c452cb4a1f804fad1b3b8aa8d0552725531d27941b2e3ed"}, - {file = "SQLAlchemy-2.0.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c5268ec05c21e2ecf5bca09314bcaadfec01f02163088cd602db4379862958dd"}, - {file = "SQLAlchemy-2.0.12-cp311-cp311-win32.whl", hash = "sha256:77a06b0983faf9aa48ee6219d41ade39dee16ce90857cc181dbcf6918acd234d"}, - {file = "SQLAlchemy-2.0.12-cp311-cp311-win_amd64.whl", hash = "sha256:a022c588c0f413f8cddf9fcc597dbf317efeac4186d8bff9aa7f3219258348b0"}, - {file = "SQLAlchemy-2.0.12-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b6ceca432ce88ad12aab5b5896c343a1993c90b325d9193dcd055e73e18a0439"}, - {file = "SQLAlchemy-2.0.12-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e5501c78b5ab917f0f0f75ce7f0018f683a0a76e95f30e6561bf61c9ff69d43"}, - {file = "SQLAlchemy-2.0.12-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc67efd00ce7f428a446ce012673c03c63c5abb5dec3f33750087b8bdc173bf0"}, - {file = "SQLAlchemy-2.0.12-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1fac17c866111283cbcdb7024d646abb71fdd95f3ce975cf3710258bc55742fd"}, - {file = "SQLAlchemy-2.0.12-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:f30c5608c64fc9c1fa9a16277eb4784f782362566fe40ff8d283358c8f2c5fe0"}, - {file = "SQLAlchemy-2.0.12-cp37-cp37m-win32.whl", hash = "sha256:85b0efe1c71459ba435a6593f54a0e39334b16ba383e8010fdb9d0127ca51ba8"}, - {file = "SQLAlchemy-2.0.12-cp37-cp37m-win_amd64.whl", hash = "sha256:b76c2fde827522e21922418325c1b95c2d795cdecfb4bc261e4d37965199ee7f"}, - {file = "SQLAlchemy-2.0.12-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:aec5fb36b53125554ecc2285526eb5cc31b21f6cb059993c1c5ca831959de052"}, - {file = "SQLAlchemy-2.0.12-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4ad525b9dd17b478a2ed8580d7f2bc46b0f5889153c6b1c099729583e395b4b9"}, - {file = "SQLAlchemy-2.0.12-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9796d5c13b2b7f05084d0ce52528cf919f9bde9e0f10672a6393a4490415695"}, - {file = "SQLAlchemy-2.0.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e1d50592cb24d1947c374c666add65ded7c181ec98a89ed17abbe9b8b2e2ff4"}, - {file = "SQLAlchemy-2.0.12-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bf83700faa9642388fbd3167db3f6cbb2e88cc8367b8c22204f3f408ee782d25"}, - {file = "SQLAlchemy-2.0.12-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:297b752d4f30350b64175bbbd57dc94c061a35f5d1dba088d0a367dbbebabc94"}, - {file = "SQLAlchemy-2.0.12-cp38-cp38-win32.whl", hash = "sha256:369f6564e68a9c60f0b9dde121def491e651a4ba8dcdd652a93f1cd5977cd85c"}, - {file = "SQLAlchemy-2.0.12-cp38-cp38-win_amd64.whl", hash = "sha256:7eb25b981cbc9e7df9f56ad7ec4c6d77323090ca4b7147fcdc09d66535377759"}, - {file = "SQLAlchemy-2.0.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f6ebadefc4331dda83c22519e1ea1e61104df6eb38abbb80ab91b0a8527a5c19"}, - {file = "SQLAlchemy-2.0.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3745dee26a7ee012598577ad3b8f6e6cd50a49b2afa0cde9db668da6bf2c2319"}, - {file = "SQLAlchemy-2.0.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09205893a84b6bedae0453d3f384f5d2a6499b6e45ad977549894cdcd85d8f1c"}, - {file = "SQLAlchemy-2.0.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8aad66215a3817a7a1d535769773333250de2653c89b53f7e2d42b677d398027"}, - {file = "SQLAlchemy-2.0.12-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e495ad05a13171fbb5d72fe5993469c8bceac42bcf6b8f9f117a518ee7fbc353"}, - {file = "SQLAlchemy-2.0.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:03206576ca53f55b9de6e890273e498f4b2e6e687a9db9859bdcd21df5a63e53"}, - {file = "SQLAlchemy-2.0.12-cp39-cp39-win32.whl", hash = "sha256:87b2c2d13c3d1384859b60eabb3139e169ce68ada1d2963dbd0c7af797f16efe"}, - {file = "SQLAlchemy-2.0.12-cp39-cp39-win_amd64.whl", hash = "sha256:3c053c3f4c4e45d4c8b27977647566c140d6de3f61a4e2acb92ea24cf9911c7f"}, - {file = "SQLAlchemy-2.0.12-py3-none-any.whl", hash = "sha256:e752c34f7a2057ebe82c856698b9f277c633d4aad006bddf7af74598567c8931"}, - {file = "SQLAlchemy-2.0.12.tar.gz", hash = "sha256:bddfc5bd1dee5db0fddc9dab26f800c283f3243e7281bbf107200fed30125f9c"}, + {file = "SQLAlchemy-2.0.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7ad24c85f2a1caf0cd1ae8c2fdb668777a51a02246d9039420f94bd7dbfd37ed"}, + {file = "SQLAlchemy-2.0.13-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:db24d2738add6db19d66ca820479d2f8f96d3f5a13c223f27fa28dd2f268a4bd"}, + {file = "SQLAlchemy-2.0.13-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72746ec17a7d9c5acf2c57a6e6190ceba3dad7127cd85bb17f24e90acc0e8e3f"}, + {file = "SQLAlchemy-2.0.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:755f653d693f9b8f4286d987aec0d4279821bf8d179a9de8e8a5c685e77e57d6"}, + {file = "SQLAlchemy-2.0.13-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e0d20f27edfd6f35b388da2bdcd7769e4ffa374fef8994980ced26eb287e033a"}, + {file = "SQLAlchemy-2.0.13-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:37de4010f53f452e94e5ed6684480432cfe6a7a8914307ef819cd028b05b98d5"}, + {file = "SQLAlchemy-2.0.13-cp310-cp310-win32.whl", hash = "sha256:31f72bb300eed7bfdb373c7c046121d84fa0ae6f383089db9505ff553ac27cef"}, + {file = "SQLAlchemy-2.0.13-cp310-cp310-win_amd64.whl", hash = "sha256:ec2f525273528425ed2f51861b7b88955160cb95dddb17af0914077040aff4a5"}, + {file = "SQLAlchemy-2.0.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2424a84f131901fbb20a99844d47b38b517174c6e964c8efb15ea6bb9ced8c2b"}, + {file = "SQLAlchemy-2.0.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f9832815257969b3ca9bf0501351e4c02c8d60cbd3ec9f9070d5b0f8852900e"}, + {file = "SQLAlchemy-2.0.13-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a30e4db983faa5145e00ef6eaf894a2d503b3221dbf40a595f3011930d3d0bac"}, + {file = "SQLAlchemy-2.0.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f717944aee40e9f48776cf85b523bb376aa2d9255a268d6d643c57ab387e7264"}, + {file = "SQLAlchemy-2.0.13-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9119795d2405eb23bf7e6707e228fe38124df029494c1b3576459aa3202ea432"}, + {file = "SQLAlchemy-2.0.13-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2ad9688debf1f0ae9c6e0706a4e2d33b1a01281317cee9bd1d7eef8020c5baac"}, + {file = "SQLAlchemy-2.0.13-cp311-cp311-win32.whl", hash = "sha256:c61b89803a87a3b2a394089a7dadb79a6c64c89f2e8930cc187fec43b319f8d2"}, + {file = "SQLAlchemy-2.0.13-cp311-cp311-win_amd64.whl", hash = "sha256:0aa2cbde85a6eab9263ab480f19e8882d022d30ebcdc14d69e6a8d7c07b0a871"}, + {file = "SQLAlchemy-2.0.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9ad883ac4f5225999747f0849643c4d0ec809d9ffe0ddc81a81dd3e68d0af463"}, + {file = "SQLAlchemy-2.0.13-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e481e54db8cec1457ee7c05f6d2329e3298a304a70d3b5e2e82e77170850b385"}, + {file = "SQLAlchemy-2.0.13-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4e08e3831671008888bad5d160d757ef35ce34dbb73b78c3998d16aa1334c97"}, + {file = "SQLAlchemy-2.0.13-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f234ba3bb339ad17803009c8251f5ee65dcf283a380817fe486823b08b26383d"}, + {file = "SQLAlchemy-2.0.13-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:375b7ba88f261dbd79d044f20cbcd919d88befb63f26af9d084614f10cdf97a6"}, + {file = "SQLAlchemy-2.0.13-cp37-cp37m-win32.whl", hash = "sha256:9136d596111c742d061c0f99bab95c5370016c4101a32e72c2b634ad5e0757e6"}, + {file = "SQLAlchemy-2.0.13-cp37-cp37m-win_amd64.whl", hash = "sha256:7612a7366a0855a04430363fb4ab392dc6818aaece0b2e325ff30ee77af9b21f"}, + {file = "SQLAlchemy-2.0.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:49c138856035cb97f0053e5e57ba90ec936b28a0b8b0020d44965c7b0c0bf03a"}, + {file = "SQLAlchemy-2.0.13-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a5e9e78332a5d841422b88b8c490dfd7f761e64b3430249b66c05d02f72ceab0"}, + {file = "SQLAlchemy-2.0.13-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd0febae872a4042da44e972c070f0fd49a85a0a7727ab6b85425f74348be14e"}, + {file = "SQLAlchemy-2.0.13-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:566a0ac347cf4632f551e7b28bbd0d215af82e6ffaa2556f565a3b6b51dc3f81"}, + {file = "SQLAlchemy-2.0.13-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e5e5dc300a0ca8755ada1569f5caccfcdca28607dfb98b86a54996b288a8ebd3"}, + {file = "SQLAlchemy-2.0.13-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a25b4c4fdd633501233924f873e6f6cd8970732859ecfe4ecfb60635881f70be"}, + {file = "SQLAlchemy-2.0.13-cp38-cp38-win32.whl", hash = "sha256:6777673d346071451bf7cccf8d0499024f1bd6a835fc90b4fe7af50373d92ce6"}, + {file = "SQLAlchemy-2.0.13-cp38-cp38-win_amd64.whl", hash = "sha256:2f0a355264af0952570f18457102984e1f79510f856e5e0ae652e63316d1ca23"}, + {file = "SQLAlchemy-2.0.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d93ebbff3dcf05274843ad8cf650b48ee634626e752c5d73614e5ec9df45f0ce"}, + {file = "SQLAlchemy-2.0.13-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fec56c7d1b6a22c8f01557de3975d962ee40270b81b60d1cfdadf2a105d10e84"}, + {file = "SQLAlchemy-2.0.13-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0eb14a386a5b610305bec6639b35540b47f408b0a59f75999199aed5b3d40079"}, + {file = "SQLAlchemy-2.0.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f3b5236079bc3e318a92bab2cc3f669cc32127075ab03ff61cacbae1c392b8"}, + {file = "SQLAlchemy-2.0.13-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bf1aae95e80acea02a0a622e1c12d3fefc52ffd0fe7bda70a30d070373fbb6c3"}, + {file = "SQLAlchemy-2.0.13-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cdf80359b641185ae7e580afb9f88cf560298f309a38182972091165bfe1225d"}, + {file = "SQLAlchemy-2.0.13-cp39-cp39-win32.whl", hash = "sha256:f463598f9e51ccc04f0fe08500f9a0c3251a7086765350be418598b753b5561d"}, + {file = "SQLAlchemy-2.0.13-cp39-cp39-win_amd64.whl", hash = "sha256:881cc388dded44ae6e17a1666364b98bd76bcdc71b869014ae725f06ba298e0e"}, + {file = "SQLAlchemy-2.0.13-py3-none-any.whl", hash = "sha256:0d6979c9707f8b82366ba34b38b5a6fe32f75766b2e901f9820e271e95384070"}, + {file = "SQLAlchemy-2.0.13.tar.gz", hash = "sha256:8d97b37b4e60073c38bcf94e289e3be09ef9be870de88d163f16e08f2b9ded1a"}, ] [package.dependencies] @@ -3270,16 +3737,86 @@ anyio = ">=3.4.0,<5" [package.extras] full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] +[[package]] +name = "storage3" +version = "0.5.2" +description = "Supabase Storage client for Python." +category = "main" +optional = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "storage3-0.5.2-py3-none-any.whl", hash = "sha256:3aaba8cebf89eef6b5fc48739b8c8c8539461f2eed9ea1dc4c763dea10c6d009"}, + {file = "storage3-0.5.2.tar.gz", hash = "sha256:e9932fca869a8f9cdab9a20e5249439928cfe2d07c4524141b15fef1882a7f61"}, +] + +[package.dependencies] +httpx = ">=0.23,<0.24" +python-dateutil = ">=2.8.2,<3.0.0" +typing-extensions = ">=4.2.0,<5.0.0" + +[[package]] +name = "strenum" +version = "0.4.10" +description = "An Enum that inherits from str." +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "StrEnum-0.4.10-py3-none-any.whl", hash = "sha256:aebf04bba8e5af435937c452d69a86798b6f8d5ca5f20ba18561dbfad571ccdd"}, + {file = "StrEnum-0.4.10.tar.gz", hash = "sha256:898cc0ebb5054ee07400341ac1d75fdfee489d76d6df3fbc1c2eaf95971e3916"}, +] + +[package.extras] +docs = ["myst-parser[linkify]", "sphinx", "sphinx-rtd-theme"] +release = ["twine"] +test = ["pylint", "pytest", "pytest-black", "pytest-cov", "pytest-pylint"] + +[[package]] +name = "supabase" +version = "1.0.3" +description = "Supabase client for Python." +category = "main" +optional = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "supabase-1.0.3-py3-none-any.whl", hash = "sha256:2418113b7f503522d33fafd442e587356636bad6cb803f7e406e614acf2611d7"}, + {file = "supabase-1.0.3.tar.gz", hash = "sha256:c6eac0144b4236a61ccc72024a8e88d8f08979e47ea635307afae7fb4fc24bc6"}, +] + +[package.dependencies] +gotrue = ">=1.0.1,<2.0.0" +httpx = ">=0.23.0,<0.24.0" +postgrest = ">=0.10.6,<0.11.0" +python-semantic-release = "7.33.2" +realtime = ">=1.0.0,<2.0.0" +storage3 = ">=0.5.2,<0.6.0" +supafunc = ">=0.2.2,<0.3.0" + +[[package]] +name = "supafunc" +version = "0.2.2" +description = "Library for Supabase Functions" +category = "main" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "supafunc-0.2.2-py3-none-any.whl", hash = "sha256:a292812532cca05afc08d2cc040eea5bd79a8909e46051630620b67508070795"}, + {file = "supafunc-0.2.2.tar.gz", hash = "sha256:84f1f8d47297b0c8b712f1d8e20843406c025a203bba00cb7216e2163f295c24"}, +] + +[package.dependencies] +httpx = ">=0.23.0,<0.24.0" + [[package]] name = "sympy" -version = "1.11.1" +version = "1.12" description = "Computer algebra system (CAS) in Python" category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "sympy-1.11.1-py3-none-any.whl", hash = "sha256:938f984ee2b1e8eae8a07b884c8b7a1146010040fccddc6539c54f401c8f6fcf"}, - {file = "sympy-1.11.1.tar.gz", hash = "sha256:e32380dce63cb7c0108ed525570092fd45168bdae2faa17e528221ef72e88658"}, + {file = "sympy-1.12-py3-none-any.whl", hash = "sha256:c3588cd4295d0c0f603d0f2ae780587e64e2efeedb3521e46b9bb1d08d184fa5"}, + {file = "sympy-1.12.tar.gz", hash = "sha256:ebf595c8dac3e0fdc4152c51878b498396ec7f30e7a914d6071e674d49420fb8"}, ] [package.dependencies] @@ -3415,6 +3952,18 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] +[[package]] +name = "tomlkit" +version = "0.11.8" +description = "Style preserving TOML library" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomlkit-0.11.8-py3-none-any.whl", hash = "sha256:8c726c4c202bdb148667835f68d68780b9a003a9ec34167b6c673b38eff2a171"}, + {file = "tomlkit-0.11.8.tar.gz", hash = "sha256:9330fc7faa1db67b541b28e62018c17d20be733177d290a13b24c62d1614e0c3"}, +] + [[package]] name = "torch" version = "2.0.1" @@ -3517,19 +4066,19 @@ telegram = ["requests"] [[package]] name = "transformers" -version = "4.28.1" +version = "4.29.1" description = "State-of-the-art Machine Learning for JAX, PyTorch and TensorFlow" category = "main" optional = false python-versions = ">=3.7.0" files = [ - {file = "transformers-4.28.1-py3-none-any.whl", hash = "sha256:f30a006220d0475789ac0e7c874f51bf5143956797616d89975b637883ce0be6"}, - {file = "transformers-4.28.1.tar.gz", hash = "sha256:7334f8730cff7ac31d9ba5c12f2113fcb7a7a5b61eeb5dbbdb162117c3aaa2d1"}, + {file = "transformers-4.29.1-py3-none-any.whl", hash = "sha256:75f851f2420c26410edbdf4a2a1a5b434ab2b96aea36eb5931d06cc3b2e7b509"}, + {file = "transformers-4.29.1.tar.gz", hash = "sha256:3dc9cd198918e140468edbf37d7edf3b7a75633655ce0771ce323bbf8c118c4d"}, ] [package.dependencies] filelock = "*" -huggingface-hub = ">=0.11.0,<1.0" +huggingface-hub = ">=0.14.1,<1.0" numpy = ">=1.17" packaging = ">=20.0" pyyaml = ">=5.1" @@ -3539,20 +4088,21 @@ tokenizers = ">=0.11.1,<0.11.3 || >0.11.3,<0.14" tqdm = ">=4.27" [package.extras] -accelerate = ["accelerate (>=0.10.0)"] -all = ["Pillow", "accelerate (>=0.10.0)", "av (==9.2.0)", "codecarbon (==1.2.0)", "decord (==0.6.0)", "flax (>=0.4.1)", "jax (>=0.2.8,!=0.3.2,<=0.3.6)", "jaxlib (>=0.1.65,<=0.3.6)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "onnxconverter-common", "optax (>=0.0.8)", "optuna", "phonemizer", "protobuf (<=3.20.2)", "pyctcdecode (>=0.4.0)", "ray[tune]", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "tensorflow (>=2.4,<2.13)", "tensorflow-text (<2.13)", "tf2onnx", "timm", "tokenizers (>=0.11.1,!=0.11.3,<0.14)", "torch (>=1.9,!=1.12.0)", "torchaudio", "torchvision"] -audio = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] +accelerate = ["accelerate (>=0.19.0)"] +agents = ["Pillow", "accelerate (>=0.19.0)", "datasets (!=2.5.0)", "diffusers", "opencv-python", "sentencepiece (>=0.1.91,!=0.1.92)", "torch (>=1.9,!=1.12.0)"] +all = ["Pillow", "accelerate (>=0.19.0)", "av (==9.2.0)", "codecarbon (==1.2.0)", "decord (==0.6.0)", "flax (>=0.4.1,<=0.6.9)", "jax (>=0.2.8,!=0.3.2,<=0.3.6)", "jaxlib (>=0.1.65,<=0.3.6)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "numba (<0.57.0)", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "phonemizer", "protobuf (<=3.20.2)", "pyctcdecode (>=0.4.0)", "ray[tune]", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "tensorflow (>=2.4,<2.13)", "tensorflow-text (<2.13)", "tf2onnx", "timm", "tokenizers (>=0.11.1,!=0.11.3,<0.14)", "torch (>=1.9,!=1.12.0)", "torchaudio", "torchvision"] +audio = ["kenlm", "librosa", "numba (<0.57.0)", "phonemizer", "pyctcdecode (>=0.4.0)"] codecarbon = ["codecarbon (==1.2.0)"] -deepspeed = ["accelerate (>=0.10.0)", "deepspeed (>=0.8.3)"] -deepspeed-testing = ["GitPython (<3.1.19)", "accelerate (>=0.10.0)", "beautifulsoup4", "black (>=23.1,<24.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "deepspeed (>=0.8.3)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "hf-doc-builder (>=0.3.0)", "nltk", "optuna", "parameterized", "protobuf (<=3.20.2)", "psutil", "pytest", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "safetensors (>=0.2.1)", "sentencepiece (>=0.1.91,!=0.1.92)", "timeout-decorator"] -dev = ["GitPython (<3.1.19)", "Pillow", "accelerate (>=0.10.0)", "av (==9.2.0)", "beautifulsoup4", "black (>=23.1,<24.0)", "codecarbon (==1.2.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "decord (==0.6.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "flax (>=0.4.1)", "fugashi (>=1.0)", "hf-doc-builder", "hf-doc-builder (>=0.3.0)", "ipadic (>=1.0.0,<2.0)", "isort (>=5.5.4)", "jax (>=0.2.8,!=0.3.2,<=0.3.6)", "jaxlib (>=0.1.65,<=0.3.6)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "nltk", "onnxconverter-common", "optax (>=0.0.8)", "optuna", "parameterized", "phonemizer", "protobuf (<=3.20.2)", "psutil", "pyctcdecode (>=0.4.0)", "pytest", "pytest-timeout", "pytest-xdist", "ray[tune]", "rhoknp (>=1.1.0)", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (>=0.0.241,<=0.0.259)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "safetensors (>=0.2.1)", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "tensorflow (>=2.4,<2.13)", "tensorflow-text (<2.13)", "tf2onnx", "timeout-decorator", "timm", "tokenizers (>=0.11.1,!=0.11.3,<0.14)", "torch (>=1.9,!=1.12.0)", "torchaudio", "torchvision", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)"] -dev-tensorflow = ["GitPython (<3.1.19)", "Pillow", "beautifulsoup4", "black (>=23.1,<24.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "hf-doc-builder", "hf-doc-builder (>=0.3.0)", "isort (>=5.5.4)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "nltk", "onnxconverter-common", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "parameterized", "phonemizer", "protobuf (<=3.20.2)", "psutil", "pyctcdecode (>=0.4.0)", "pytest", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (>=0.0.241,<=0.0.259)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "safetensors (>=0.2.1)", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorflow (>=2.4,<2.13)", "tensorflow-text (<2.13)", "tf2onnx", "timeout-decorator", "tokenizers (>=0.11.1,!=0.11.3,<0.14)"] -dev-torch = ["GitPython (<3.1.19)", "Pillow", "beautifulsoup4", "black (>=23.1,<24.0)", "codecarbon (==1.2.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "fugashi (>=1.0)", "hf-doc-builder", "hf-doc-builder (>=0.3.0)", "ipadic (>=1.0.0,<2.0)", "isort (>=5.5.4)", "kenlm", "librosa", "nltk", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "optuna", "parameterized", "phonemizer", "protobuf (<=3.20.2)", "psutil", "pyctcdecode (>=0.4.0)", "pytest", "pytest-timeout", "pytest-xdist", "ray[tune]", "rhoknp (>=1.1.0)", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (>=0.0.241,<=0.0.259)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "safetensors (>=0.2.1)", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "timeout-decorator", "timm", "tokenizers (>=0.11.1,!=0.11.3,<0.14)", "torch (>=1.9,!=1.12.0)", "torchaudio", "torchvision", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)"] -docs = ["Pillow", "accelerate (>=0.10.0)", "av (==9.2.0)", "codecarbon (==1.2.0)", "decord (==0.6.0)", "flax (>=0.4.1)", "hf-doc-builder", "jax (>=0.2.8,!=0.3.2,<=0.3.6)", "jaxlib (>=0.1.65,<=0.3.6)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "onnxconverter-common", "optax (>=0.0.8)", "optuna", "phonemizer", "protobuf (<=3.20.2)", "pyctcdecode (>=0.4.0)", "ray[tune]", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "tensorflow (>=2.4,<2.13)", "tensorflow-text (<2.13)", "tf2onnx", "timm", "tokenizers (>=0.11.1,!=0.11.3,<0.14)", "torch (>=1.9,!=1.12.0)", "torchaudio", "torchvision"] +deepspeed = ["accelerate (>=0.19.0)", "deepspeed (>=0.8.3)"] +deepspeed-testing = ["GitPython (<3.1.19)", "accelerate (>=0.19.0)", "beautifulsoup4", "black (>=23.1,<24.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "deepspeed (>=0.8.3)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "hf-doc-builder (>=0.3.0)", "nltk", "optuna", "parameterized", "protobuf (<=3.20.2)", "psutil", "pytest", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "safetensors (>=0.2.1)", "sentencepiece (>=0.1.91,!=0.1.92)", "timeout-decorator"] +dev = ["GitPython (<3.1.19)", "Pillow", "accelerate (>=0.19.0)", "av (==9.2.0)", "beautifulsoup4", "black (>=23.1,<24.0)", "codecarbon (==1.2.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "decord (==0.6.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "flax (>=0.4.1,<=0.6.9)", "fugashi (>=1.0)", "hf-doc-builder", "hf-doc-builder (>=0.3.0)", "ipadic (>=1.0.0,<2.0)", "isort (>=5.5.4)", "jax (>=0.2.8,!=0.3.2,<=0.3.6)", "jaxlib (>=0.1.65,<=0.3.6)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "nltk", "numba (<0.57.0)", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "parameterized", "phonemizer", "protobuf (<=3.20.2)", "psutil", "pyctcdecode (>=0.4.0)", "pytest", "pytest-timeout", "pytest-xdist", "ray[tune]", "rhoknp (>=1.1.0)", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (>=0.0.241,<=0.0.259)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "safetensors (>=0.2.1)", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "tensorflow (>=2.4,<2.13)", "tensorflow-text (<2.13)", "tf2onnx", "timeout-decorator", "timm", "tokenizers (>=0.11.1,!=0.11.3,<0.14)", "torch (>=1.9,!=1.12.0)", "torchaudio", "torchvision", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)", "urllib3 (<2.0.0)"] +dev-tensorflow = ["GitPython (<3.1.19)", "Pillow", "beautifulsoup4", "black (>=23.1,<24.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "hf-doc-builder", "hf-doc-builder (>=0.3.0)", "isort (>=5.5.4)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "nltk", "numba (<0.57.0)", "onnxconverter-common", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "parameterized", "phonemizer", "protobuf (<=3.20.2)", "psutil", "pyctcdecode (>=0.4.0)", "pytest", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (>=0.0.241,<=0.0.259)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "safetensors (>=0.2.1)", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorflow (>=2.4,<2.13)", "tensorflow-text (<2.13)", "tf2onnx", "timeout-decorator", "tokenizers (>=0.11.1,!=0.11.3,<0.14)", "urllib3 (<2.0.0)"] +dev-torch = ["GitPython (<3.1.19)", "Pillow", "accelerate (>=0.19.0)", "beautifulsoup4", "black (>=23.1,<24.0)", "codecarbon (==1.2.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "fugashi (>=1.0)", "hf-doc-builder", "hf-doc-builder (>=0.3.0)", "ipadic (>=1.0.0,<2.0)", "isort (>=5.5.4)", "kenlm", "librosa", "nltk", "numba (<0.57.0)", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "optuna", "parameterized", "phonemizer", "protobuf (<=3.20.2)", "psutil", "pyctcdecode (>=0.4.0)", "pytest", "pytest-timeout", "pytest-xdist", "ray[tune]", "rhoknp (>=1.1.0)", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (>=0.0.241,<=0.0.259)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "safetensors (>=0.2.1)", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "timeout-decorator", "timm", "tokenizers (>=0.11.1,!=0.11.3,<0.14)", "torch (>=1.9,!=1.12.0)", "torchaudio", "torchvision", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)", "urllib3 (<2.0.0)"] +docs = ["Pillow", "accelerate (>=0.19.0)", "av (==9.2.0)", "codecarbon (==1.2.0)", "decord (==0.6.0)", "flax (>=0.4.1,<=0.6.9)", "hf-doc-builder", "jax (>=0.2.8,!=0.3.2,<=0.3.6)", "jaxlib (>=0.1.65,<=0.3.6)", "kenlm", "keras-nlp (>=0.3.1)", "librosa", "numba (<0.57.0)", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "phonemizer", "protobuf (<=3.20.2)", "pyctcdecode (>=0.4.0)", "ray[tune]", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "tensorflow (>=2.4,<2.13)", "tensorflow-text (<2.13)", "tf2onnx", "timm", "tokenizers (>=0.11.1,!=0.11.3,<0.14)", "torch (>=1.9,!=1.12.0)", "torchaudio", "torchvision"] docs-specific = ["hf-doc-builder"] fairscale = ["fairscale (>0.3)"] -flax = ["flax (>=0.4.1)", "jax (>=0.2.8,!=0.3.2,<=0.3.6)", "jaxlib (>=0.1.65,<=0.3.6)", "optax (>=0.0.8)"] -flax-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] +flax = ["flax (>=0.4.1,<=0.6.9)", "jax (>=0.2.8,!=0.3.2,<=0.3.6)", "jaxlib (>=0.1.65,<=0.3.6)", "optax (>=0.0.8,<=0.1.4)"] +flax-speech = ["kenlm", "librosa", "numba (<0.57.0)", "phonemizer", "pyctcdecode (>=0.4.0)"] ftfy = ["ftfy"] integrations = ["optuna", "ray[tune]", "sigopt"] ja = ["fugashi (>=1.0)", "ipadic (>=1.0.0,<2.0)", "rhoknp (>=1.1.0)", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)"] @@ -3561,7 +4111,7 @@ natten = ["natten (>=0.14.6)"] onnx = ["onnxconverter-common", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "tf2onnx"] onnxruntime = ["onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)"] optuna = ["optuna"] -quality = ["GitPython (<3.1.19)", "black (>=23.1,<24.0)", "datasets (!=2.5.0)", "hf-doc-builder (>=0.3.0)", "isort (>=5.5.4)", "ruff (>=0.0.241,<=0.0.259)"] +quality = ["GitPython (<3.1.19)", "black (>=23.1,<24.0)", "datasets (!=2.5.0)", "hf-doc-builder (>=0.3.0)", "isort (>=5.5.4)", "ruff (>=0.0.241,<=0.0.259)", "urllib3 (<2.0.0)"] ray = ["ray[tune]"] retrieval = ["datasets (!=2.5.0)", "faiss-cpu"] sagemaker = ["sagemaker (>=2.31.0)"] @@ -3569,20 +4119,44 @@ sentencepiece = ["protobuf (<=3.20.2)", "sentencepiece (>=0.1.91,!=0.1.92)"] serving = ["fastapi", "pydantic", "starlette", "uvicorn"] sigopt = ["sigopt"] sklearn = ["scikit-learn"] -speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)", "torchaudio"] +speech = ["kenlm", "librosa", "numba (<0.57.0)", "phonemizer", "pyctcdecode (>=0.4.0)", "torchaudio"] testing = ["GitPython (<3.1.19)", "beautifulsoup4", "black (>=23.1,<24.0)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "hf-doc-builder (>=0.3.0)", "nltk", "parameterized", "protobuf (<=3.20.2)", "psutil", "pytest", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "safetensors (>=0.2.1)", "timeout-decorator"] tf = ["keras-nlp (>=0.3.1)", "onnxconverter-common", "tensorflow (>=2.4,<2.13)", "tensorflow-text (<2.13)", "tf2onnx"] tf-cpu = ["keras-nlp (>=0.3.1)", "onnxconverter-common", "tensorflow-cpu (>=2.4,<2.13)", "tensorflow-text (<2.13)", "tf2onnx"] -tf-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] +tf-speech = ["kenlm", "librosa", "numba (<0.57.0)", "phonemizer", "pyctcdecode (>=0.4.0)"] timm = ["timm"] tokenizers = ["tokenizers (>=0.11.1,!=0.11.3,<0.14)"] -torch = ["torch (>=1.9,!=1.12.0)"] -torch-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)", "torchaudio"] +torch = ["accelerate (>=0.19.0)", "torch (>=1.9,!=1.12.0)"] +torch-speech = ["kenlm", "librosa", "numba (<0.57.0)", "phonemizer", "pyctcdecode (>=0.4.0)", "torchaudio"] torch-vision = ["Pillow", "torchvision"] -torchhub = ["filelock", "huggingface-hub (>=0.11.0,<1.0)", "importlib-metadata", "numpy (>=1.17)", "packaging (>=20.0)", "protobuf (<=3.20.2)", "regex (!=2019.12.17)", "requests", "sentencepiece (>=0.1.91,!=0.1.92)", "tokenizers (>=0.11.1,!=0.11.3,<0.14)", "torch (>=1.9,!=1.12.0)", "tqdm (>=4.27)"] +torchhub = ["filelock", "huggingface-hub (>=0.14.1,<1.0)", "importlib-metadata", "numpy (>=1.17)", "packaging (>=20.0)", "protobuf (<=3.20.2)", "regex (!=2019.12.17)", "requests", "sentencepiece (>=0.1.91,!=0.1.92)", "tokenizers (>=0.11.1,!=0.11.3,<0.14)", "torch (>=1.9,!=1.12.0)", "tqdm (>=4.27)"] video = ["av (==9.2.0)", "decord (==0.6.0)"] vision = ["Pillow"] +[[package]] +name = "twine" +version = "3.8.0" +description = "Collection of utilities for publishing packages on PyPI" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "twine-3.8.0-py3-none-any.whl", hash = "sha256:d0550fca9dc19f3d5e8eadfce0c227294df0a2a951251a4385797c8a6198b7c8"}, + {file = "twine-3.8.0.tar.gz", hash = "sha256:8efa52658e0ae770686a13b675569328f1fba9837e5de1867bfe5f46a9aefe19"}, +] + +[package.dependencies] +colorama = ">=0.4.3" +importlib-metadata = ">=3.6" +keyring = ">=15.1" +pkginfo = ">=1.8.1" +readme-renderer = ">=21.0" +requests = ">=2.20" +requests-toolbelt = ">=0.8.0,<0.9.0 || >0.9.0" +rfc3986 = ">=1.4.0" +tqdm = ">=4.14" +urllib3 = ">=1.26.0" + [[package]] name = "typing-extensions" version = "4.5.0" @@ -3840,14 +4414,14 @@ anyio = ">=3.0.0" [[package]] name = "weaviate-client" -version = "3.17.1" +version = "3.18.0" description = "A python native weaviate client" category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "weaviate-client-3.17.1.tar.gz", hash = "sha256:04277030396a0e63e73b994a185c705f07f948254d27c0a3774c60b4795c37ab"}, - {file = "weaviate_client-3.17.1-py3-none-any.whl", hash = "sha256:0c86f4d5fcb155efd0888515c8caa20364241c0df01dead361ce0c023dbc5da9"}, + {file = "weaviate-client-3.18.0.tar.gz", hash = "sha256:423a526518a32505c5293328e5f252e6cbbf20e4b3124733f70d10fc0d6823c9"}, + {file = "weaviate_client-3.18.0-py3-none-any.whl", hash = "sha256:42b324286a4b4436317e5d2c6ba48c07da6cf01518efdd47ee097e7a8cc7584c"}, ] [package.dependencies] @@ -3859,86 +4433,112 @@ validators = ">=0.18.2,<=0.21.0" [package.extras] grpc = ["grpcio", "grpcio-tools"] +[[package]] +name = "webencodings" +version = "0.5.1" +description = "Character encoding aliases for legacy web content" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, + {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, +] + [[package]] name = "websockets" -version = "11.0.3" +version = "10.4" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "websockets-11.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3ccc8a0c387629aec40f2fc9fdcb4b9d5431954f934da3eaf16cdc94f67dbfac"}, - {file = "websockets-11.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d67ac60a307f760c6e65dad586f556dde58e683fab03323221a4e530ead6f74d"}, - {file = "websockets-11.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84d27a4832cc1a0ee07cdcf2b0629a8a72db73f4cf6de6f0904f6661227f256f"}, - {file = "websockets-11.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffd7dcaf744f25f82190856bc26ed81721508fc5cbf2a330751e135ff1283564"}, - {file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7622a89d696fc87af8e8d280d9b421db5133ef5b29d3f7a1ce9f1a7bf7fcfa11"}, - {file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bceab846bac555aff6427d060f2fcfff71042dba6f5fca7dc4f75cac815e57ca"}, - {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:54c6e5b3d3a8936a4ab6870d46bdd6ec500ad62bde9e44462c32d18f1e9a8e54"}, - {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:41f696ba95cd92dc047e46b41b26dd24518384749ed0d99bea0a941ca87404c4"}, - {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86d2a77fd490ae3ff6fae1c6ceaecad063d3cc2320b44377efdde79880e11526"}, - {file = "websockets-11.0.3-cp310-cp310-win32.whl", hash = "sha256:2d903ad4419f5b472de90cd2d40384573b25da71e33519a67797de17ef849b69"}, - {file = "websockets-11.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:1d2256283fa4b7f4c7d7d3e84dc2ece74d341bce57d5b9bf385df109c2a1a82f"}, - {file = "websockets-11.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e848f46a58b9fcf3d06061d17be388caf70ea5b8cc3466251963c8345e13f7eb"}, - {file = "websockets-11.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa5003845cdd21ac0dc6c9bf661c5beddd01116f6eb9eb3c8e272353d45b3288"}, - {file = "websockets-11.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b58cbf0697721120866820b89f93659abc31c1e876bf20d0b3d03cef14faf84d"}, - {file = "websockets-11.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:660e2d9068d2bedc0912af508f30bbeb505bbbf9774d98def45f68278cea20d3"}, - {file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1f0524f203e3bd35149f12157438f406eff2e4fb30f71221c8a5eceb3617b6b"}, - {file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:def07915168ac8f7853812cc593c71185a16216e9e4fa886358a17ed0fd9fcf6"}, - {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b30c6590146e53149f04e85a6e4fcae068df4289e31e4aee1fdf56a0dead8f97"}, - {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:619d9f06372b3a42bc29d0cd0354c9bb9fb39c2cbc1a9c5025b4538738dbffaf"}, - {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:01f5567d9cf6f502d655151645d4e8b72b453413d3819d2b6f1185abc23e82dd"}, - {file = "websockets-11.0.3-cp311-cp311-win32.whl", hash = "sha256:e1459677e5d12be8bbc7584c35b992eea142911a6236a3278b9b5ce3326f282c"}, - {file = "websockets-11.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:e7837cb169eca3b3ae94cc5787c4fed99eef74c0ab9506756eea335e0d6f3ed8"}, - {file = "websockets-11.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f59a3c656fef341a99e3d63189852be7084c0e54b75734cde571182c087b152"}, - {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2529338a6ff0eb0b50c7be33dc3d0e456381157a31eefc561771ee431134a97f"}, - {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34fd59a4ac42dff6d4681d8843217137f6bc85ed29722f2f7222bd619d15e95b"}, - {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:332d126167ddddec94597c2365537baf9ff62dfcc9db4266f263d455f2f031cb"}, - {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6505c1b31274723ccaf5f515c1824a4ad2f0d191cec942666b3d0f3aa4cb4007"}, - {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f467ba0050b7de85016b43f5a22b46383ef004c4f672148a8abf32bc999a87f0"}, - {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9d9acd80072abcc98bd2c86c3c9cd4ac2347b5a5a0cae7ed5c0ee5675f86d9af"}, - {file = "websockets-11.0.3-cp37-cp37m-win32.whl", hash = "sha256:e590228200fcfc7e9109509e4d9125eace2042fd52b595dd22bbc34bb282307f"}, - {file = "websockets-11.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:b16fff62b45eccb9c7abb18e60e7e446998093cdcb50fed33134b9b6878836de"}, - {file = "websockets-11.0.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fb06eea71a00a7af0ae6aefbb932fb8a7df3cb390cc217d51a9ad7343de1b8d0"}, - {file = "websockets-11.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8a34e13a62a59c871064dfd8ffb150867e54291e46d4a7cf11d02c94a5275bae"}, - {file = "websockets-11.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4841ed00f1026dfbced6fca7d963c4e7043aa832648671b5138008dc5a8f6d99"}, - {file = "websockets-11.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a073fc9ab1c8aff37c99f11f1641e16da517770e31a37265d2755282a5d28aa"}, - {file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68b977f21ce443d6d378dbd5ca38621755f2063d6fdb3335bda981d552cfff86"}, - {file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1a99a7a71631f0efe727c10edfba09ea6bee4166a6f9c19aafb6c0b5917d09c"}, - {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bee9fcb41db2a23bed96c6b6ead6489702c12334ea20a297aa095ce6d31370d0"}, - {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4b253869ea05a5a073ebfdcb5cb3b0266a57c3764cf6fe114e4cd90f4bfa5f5e"}, - {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1553cb82942b2a74dd9b15a018dce645d4e68674de2ca31ff13ebc2d9f283788"}, - {file = "websockets-11.0.3-cp38-cp38-win32.whl", hash = "sha256:f61bdb1df43dc9c131791fbc2355535f9024b9a04398d3bd0684fc16ab07df74"}, - {file = "websockets-11.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:03aae4edc0b1c68498f41a6772d80ac7c1e33c06c6ffa2ac1c27a07653e79d6f"}, - {file = "websockets-11.0.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:777354ee16f02f643a4c7f2b3eff8027a33c9861edc691a2003531f5da4f6bc8"}, - {file = "websockets-11.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8c82f11964f010053e13daafdc7154ce7385ecc538989a354ccc7067fd7028fd"}, - {file = "websockets-11.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3580dd9c1ad0701169e4d6fc41e878ffe05e6bdcaf3c412f9d559389d0c9e016"}, - {file = "websockets-11.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f1a3f10f836fab6ca6efa97bb952300b20ae56b409414ca85bff2ad241d2a61"}, - {file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df41b9bc27c2c25b486bae7cf42fccdc52ff181c8c387bfd026624a491c2671b"}, - {file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:279e5de4671e79a9ac877427f4ac4ce93751b8823f276b681d04b2156713b9dd"}, - {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1fdf26fa8a6a592f8f9235285b8affa72748dc12e964a5518c6c5e8f916716f7"}, - {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:69269f3a0b472e91125b503d3c0b3566bda26da0a3261c49f0027eb6075086d1"}, - {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:97b52894d948d2f6ea480171a27122d77af14ced35f62e5c892ca2fae9344311"}, - {file = "websockets-11.0.3-cp39-cp39-win32.whl", hash = "sha256:c7f3cb904cce8e1be667c7e6fef4516b98d1a6a0635a58a57528d577ac18a128"}, - {file = "websockets-11.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c792ea4eabc0159535608fc5658a74d1a81020eb35195dd63214dcf07556f67e"}, - {file = "websockets-11.0.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e58f2c36cc52d41f2659e4c0cbf7353e28c8c9e63e30d8c6d3494dc9fdedcf"}, - {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de36fe9c02995c7e6ae6efe2e205816f5f00c22fd1fbf343d4d18c3d5ceac2f5"}, - {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ac56b661e60edd453585f4bd68eb6a29ae25b5184fd5ba51e97652580458998"}, - {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e052b8467dd07d4943936009f46ae5ce7b908ddcac3fda581656b1b19c083d9b"}, - {file = "websockets-11.0.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:42cc5452a54a8e46a032521d7365da775823e21bfba2895fb7b77633cce031bb"}, - {file = "websockets-11.0.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e6316827e3e79b7b8e7d8e3b08f4e331af91a48e794d5d8b099928b6f0b85f20"}, - {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8531fdcad636d82c517b26a448dcfe62f720e1922b33c81ce695d0edb91eb931"}, - {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c114e8da9b475739dde229fd3bc6b05a6537a88a578358bc8eb29b4030fac9c9"}, - {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e063b1865974611313a3849d43f2c3f5368093691349cf3c7c8f8f75ad7cb280"}, - {file = "websockets-11.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:92b2065d642bf8c0a82d59e59053dd2fdde64d4ed44efe4870fa816c1232647b"}, - {file = "websockets-11.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0ee68fe502f9031f19d495dae2c268830df2760c0524cbac5d759921ba8c8e82"}, - {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcacf2c7a6c3a84e720d1bb2b543c675bf6c40e460300b628bab1b1efc7c034c"}, - {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b67c6f5e5a401fc56394f191f00f9b3811fe843ee93f4a70df3c389d1adf857d"}, - {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d5023a4b6a5b183dc838808087033ec5df77580485fc533e7dab2567851b0a4"}, - {file = "websockets-11.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ed058398f55163a79bb9f06a90ef9ccc063b204bb346c4de78efc5d15abfe602"}, - {file = "websockets-11.0.3-py3-none-any.whl", hash = "sha256:6681ba9e7f8f3b19440921e99efbb40fc89f26cd71bf539e45d8c8a25c976dc6"}, - {file = "websockets-11.0.3.tar.gz", hash = "sha256:88fc51d9a26b10fc331be344f1781224a375b78488fc343620184e95a4b27016"}, + {file = "websockets-10.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d58804e996d7d2307173d56c297cf7bc132c52df27a3efaac5e8d43e36c21c48"}, + {file = "websockets-10.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc0b82d728fe21a0d03e65f81980abbbcb13b5387f733a1a870672c5be26edab"}, + {file = "websockets-10.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ba089c499e1f4155d2a3c2a05d2878a3428cf321c848f2b5a45ce55f0d7d310c"}, + {file = "websockets-10.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33d69ca7612f0ddff3316b0c7b33ca180d464ecac2d115805c044bf0a3b0d032"}, + {file = "websockets-10.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62e627f6b6d4aed919a2052efc408da7a545c606268d5ab5bfab4432734b82b4"}, + {file = "websockets-10.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ea7b82bfcae927eeffc55d2ffa31665dc7fec7b8dc654506b8e5a518eb4d50"}, + {file = "websockets-10.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e0cb5cc6ece6ffa75baccfd5c02cffe776f3f5c8bf486811f9d3ea3453676ce8"}, + {file = "websockets-10.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ae5e95cfb53ab1da62185e23b3130e11d64431179debac6dc3c6acf08760e9b1"}, + {file = "websockets-10.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7c584f366f46ba667cfa66020344886cf47088e79c9b9d39c84ce9ea98aaa331"}, + {file = "websockets-10.4-cp310-cp310-win32.whl", hash = "sha256:b029fb2032ae4724d8ae8d4f6b363f2cc39e4c7b12454df8df7f0f563ed3e61a"}, + {file = "websockets-10.4-cp310-cp310-win_amd64.whl", hash = "sha256:8dc96f64ae43dde92530775e9cb169979f414dcf5cff670455d81a6823b42089"}, + {file = "websockets-10.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47a2964021f2110116cc1125b3e6d87ab5ad16dea161949e7244ec583b905bb4"}, + {file = "websockets-10.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e789376b52c295c4946403bd0efecf27ab98f05319df4583d3c48e43c7342c2f"}, + {file = "websockets-10.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7d3f0b61c45c3fa9a349cf484962c559a8a1d80dae6977276df8fd1fa5e3cb8c"}, + {file = "websockets-10.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f55b5905705725af31ccef50e55391621532cd64fbf0bc6f4bac935f0fccec46"}, + {file = "websockets-10.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00c870522cdb69cd625b93f002961ffb0c095394f06ba8c48f17eef7c1541f96"}, + {file = "websockets-10.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f38706e0b15d3c20ef6259fd4bc1700cd133b06c3c1bb108ffe3f8947be15fa"}, + {file = "websockets-10.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f2c38d588887a609191d30e902df2a32711f708abfd85d318ca9b367258cfd0c"}, + {file = "websockets-10.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fe10ddc59b304cb19a1bdf5bd0a7719cbbc9fbdd57ac80ed436b709fcf889106"}, + {file = "websockets-10.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:90fcf8929836d4a0e964d799a58823547df5a5e9afa83081761630553be731f9"}, + {file = "websockets-10.4-cp311-cp311-win32.whl", hash = "sha256:b9968694c5f467bf67ef97ae7ad4d56d14be2751000c1207d31bf3bb8860bae8"}, + {file = "websockets-10.4-cp311-cp311-win_amd64.whl", hash = "sha256:a7a240d7a74bf8d5cb3bfe6be7f21697a28ec4b1a437607bae08ac7acf5b4882"}, + {file = "websockets-10.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:74de2b894b47f1d21cbd0b37a5e2b2392ad95d17ae983e64727e18eb281fe7cb"}, + {file = "websockets-10.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3a686ecb4aa0d64ae60c9c9f1a7d5d46cab9bfb5d91a2d303d00e2cd4c4c5cc"}, + {file = "websockets-10.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d15c968ea7a65211e084f523151dbf8ae44634de03c801b8bd070b74e85033"}, + {file = "websockets-10.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00213676a2e46b6ebf6045bc11d0f529d9120baa6f58d122b4021ad92adabd41"}, + {file = "websockets-10.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:e23173580d740bf8822fd0379e4bf30aa1d5a92a4f252d34e893070c081050df"}, + {file = "websockets-10.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:dd500e0a5e11969cdd3320935ca2ff1e936f2358f9c2e61f100a1660933320ea"}, + {file = "websockets-10.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4239b6027e3d66a89446908ff3027d2737afc1a375f8fd3eea630a4842ec9a0c"}, + {file = "websockets-10.4-cp37-cp37m-win32.whl", hash = "sha256:8a5cc00546e0a701da4639aa0bbcb0ae2bb678c87f46da01ac2d789e1f2d2038"}, + {file = "websockets-10.4-cp37-cp37m-win_amd64.whl", hash = "sha256:a9f9a735deaf9a0cadc2d8c50d1a5bcdbae8b6e539c6e08237bc4082d7c13f28"}, + {file = "websockets-10.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5c1289596042fad2cdceb05e1ebf7aadf9995c928e0da2b7a4e99494953b1b94"}, + {file = "websockets-10.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0cff816f51fb33c26d6e2b16b5c7d48eaa31dae5488ace6aae468b361f422b63"}, + {file = "websockets-10.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dd9becd5fe29773d140d68d607d66a38f60e31b86df75332703757ee645b6faf"}, + {file = "websockets-10.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45ec8e75b7dbc9539cbfafa570742fe4f676eb8b0d3694b67dabe2f2ceed8aa6"}, + {file = "websockets-10.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f72e5cd0f18f262f5da20efa9e241699e0cf3a766317a17392550c9ad7b37d8"}, + {file = "websockets-10.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:185929b4808b36a79c65b7865783b87b6841e852ef5407a2fb0c03381092fa3b"}, + {file = "websockets-10.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7d27a7e34c313b3a7f91adcd05134315002aaf8540d7b4f90336beafaea6217c"}, + {file = "websockets-10.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:884be66c76a444c59f801ac13f40c76f176f1bfa815ef5b8ed44321e74f1600b"}, + {file = "websockets-10.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:931c039af54fc195fe6ad536fde4b0de04da9d5916e78e55405436348cfb0e56"}, + {file = "websockets-10.4-cp38-cp38-win32.whl", hash = "sha256:db3c336f9eda2532ec0fd8ea49fef7a8df8f6c804cdf4f39e5c5c0d4a4ad9a7a"}, + {file = "websockets-10.4-cp38-cp38-win_amd64.whl", hash = "sha256:48c08473563323f9c9debac781ecf66f94ad5a3680a38fe84dee5388cf5acaf6"}, + {file = "websockets-10.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:40e826de3085721dabc7cf9bfd41682dadc02286d8cf149b3ad05bff89311e4f"}, + {file = "websockets-10.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:56029457f219ade1f2fc12a6504ea61e14ee227a815531f9738e41203a429112"}, + {file = "websockets-10.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f5fc088b7a32f244c519a048c170f14cf2251b849ef0e20cbbb0fdf0fdaf556f"}, + {file = "websockets-10.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fc8709c00704194213d45e455adc106ff9e87658297f72d544220e32029cd3d"}, + {file = "websockets-10.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0154f7691e4fe6c2b2bc275b5701e8b158dae92a1ab229e2b940efe11905dff4"}, + {file = "websockets-10.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c6d2264f485f0b53adf22697ac11e261ce84805c232ed5dbe6b1bcb84b00ff0"}, + {file = "websockets-10.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9bc42e8402dc5e9905fb8b9649f57efcb2056693b7e88faa8fb029256ba9c68c"}, + {file = "websockets-10.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:edc344de4dac1d89300a053ac973299e82d3db56330f3494905643bb68801269"}, + {file = "websockets-10.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:84bc2a7d075f32f6ed98652db3a680a17a4edb21ca7f80fe42e38753a58ee02b"}, + {file = "websockets-10.4-cp39-cp39-win32.whl", hash = "sha256:c94ae4faf2d09f7c81847c63843f84fe47bf6253c9d60b20f25edfd30fb12588"}, + {file = "websockets-10.4-cp39-cp39-win_amd64.whl", hash = "sha256:bbccd847aa0c3a69b5f691a84d2341a4f8a629c6922558f2a70611305f902d74"}, + {file = "websockets-10.4-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:82ff5e1cae4e855147fd57a2863376ed7454134c2bf49ec604dfe71e446e2193"}, + {file = "websockets-10.4-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d210abe51b5da0ffdbf7b43eed0cfdff8a55a1ab17abbec4301c9ff077dd0342"}, + {file = "websockets-10.4-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:942de28af58f352a6f588bc72490ae0f4ccd6dfc2bd3de5945b882a078e4e179"}, + {file = "websockets-10.4-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9b27d6c1c6cd53dc93614967e9ce00ae7f864a2d9f99fe5ed86706e1ecbf485"}, + {file = "websockets-10.4-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:3d3cac3e32b2c8414f4f87c1b2ab686fa6284a980ba283617404377cd448f631"}, + {file = "websockets-10.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:da39dd03d130162deb63da51f6e66ed73032ae62e74aaccc4236e30edccddbb0"}, + {file = "websockets-10.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389f8dbb5c489e305fb113ca1b6bdcdaa130923f77485db5b189de343a179393"}, + {file = "websockets-10.4-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09a1814bb15eff7069e51fed0826df0bc0702652b5cb8f87697d469d79c23576"}, + {file = "websockets-10.4-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff64a1d38d156d429404aaa84b27305e957fd10c30e5880d1765c9480bea490f"}, + {file = "websockets-10.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b343f521b047493dc4022dd338fc6db9d9282658862756b4f6fd0e996c1380e1"}, + {file = "websockets-10.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:932af322458da7e4e35df32f050389e13d3d96b09d274b22a7aa1808f292fee4"}, + {file = "websockets-10.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a4162139374a49eb18ef5b2f4da1dd95c994588f5033d64e0bbfda4b6b6fcf"}, + {file = "websockets-10.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c57e4c1349fbe0e446c9fa7b19ed2f8a4417233b6984277cce392819123142d3"}, + {file = "websockets-10.4-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b627c266f295de9dea86bd1112ed3d5fafb69a348af30a2422e16590a8ecba13"}, + {file = "websockets-10.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:05a7233089f8bd355e8cbe127c2e8ca0b4ea55467861906b80d2ebc7db4d6b72"}, + {file = "websockets-10.4.tar.gz", hash = "sha256:eef610b23933c54d5d921c92578ae5f89813438fded840c2e9809d378dc765d3"}, +] + +[[package]] +name = "wheel" +version = "0.40.0" +description = "A built-package format for Python" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "wheel-0.40.0-py3-none-any.whl", hash = "sha256:d236b20e7cb522daf2390fa84c55eea81c5c30190f90f29ae2ca1ad8355bf247"}, + {file = "wheel-0.40.0.tar.gz", hash = "sha256:cd1196f3faee2b31968d626e1731c94f99cbdb67cf5a46e4f5656cbee7738873"}, ] +[package.extras] +test = ["pytest (>=6.0.0)"] + [[package]] name = "win32-setctime" version = "1.1.0" @@ -4054,6 +4654,22 @@ files = [ idna = ">=2.0" multidict = ">=4.0" +[[package]] +name = "zipp" +version = "3.15.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, + {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + [[package]] name = "zstandard" version = "0.21.0" @@ -4116,4 +4732,4 @@ cffi = ["cffi (>=1.11)"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "565f4035798b2616c26c04feee5e98ebf7630dec67b4df73adae876ffd52ec86" +content-hash = "c2fa92a98fca644d3e989cfdd9cac00591bb415f67ec0c01e8fd0e3c20f7e5b7" diff --git a/pyproject.toml b/pyproject.toml index bb7fa1705..d4d4e1ddb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,9 +32,12 @@ weaviate-client = "^3.12.0" pymilvus = "^2.2.2" qdrant-client = {version = "^1.0.4", python = "<3.12"} redis = "4.5.1" +supabase = "^1.0.2" +psycopg2 = "^2.9.5" llama-index = "0.5.4" azure-identity = "^1.12.0" azure-search-documents = {version = "11.4.0a20230509004", source = "azure-sdk-dev"} +pgvector = "^0.1.7" [tool.poetry.scripts] start = "server.main:start" diff --git a/tests/datastore/providers/postgres/test_postgres_datastore.py b/tests/datastore/providers/postgres/test_postgres_datastore.py new file mode 100644 index 000000000..1e4f0a847 --- /dev/null +++ b/tests/datastore/providers/postgres/test_postgres_datastore.py @@ -0,0 +1,291 @@ +from typing import Dict, List +import pytest +from datastore.providers.postgres_datastore import PostgresDataStore +from models.models import ( + DocumentChunk, + DocumentChunkMetadata, + DocumentMetadataFilter, + QueryWithEmbedding, +) + + +def create_embedding(non_zero_pos: int) -> List[float]: + # create a vector with a single non-zero value of dimension 1535 + vector = [0.0] * 1536 + vector[non_zero_pos - 1] = 1.0 + return vector + + +@pytest.fixture +def initial_document_chunks() -> Dict[str, List[DocumentChunk]]: + first_doc_chunks = [ + DocumentChunk( + id=f"first-doc-{i}", + text=f"Lorem ipsum {i}", + metadata=DocumentChunkMetadata(), + embedding=create_embedding(i), + ) + for i in range(4, 7) + ] + return { + "first-doc": first_doc_chunks, + } + + +@pytest.fixture +def queries() -> List[QueryWithEmbedding]: + queries = [ + QueryWithEmbedding( + query="Query 1", + top_k=1, + embedding=create_embedding(4), + ), + QueryWithEmbedding( + query="Query 2", + top_k=2, + embedding=create_embedding(5), + ), + ] + return queries + + +@pytest.fixture +def postgres_datastore() -> PostgresDataStore: + return PostgresDataStore() + + +@pytest.mark.asyncio +async def test_upsert( + postgres_datastore: PostgresDataStore, + initial_document_chunks: Dict[str, List[DocumentChunk]], +) -> None: + """Test basic upsert.""" + doc_ids = await postgres_datastore._upsert(initial_document_chunks) + assert doc_ids == [doc_id for doc_id in initial_document_chunks] + + +@pytest.mark.asyncio +async def test_query( + postgres_datastore: PostgresDataStore, + initial_document_chunks: Dict[str, List[DocumentChunk]], + queries: List[QueryWithEmbedding], +) -> None: + """Test basic query.""" + # insert to prepare for test + await postgres_datastore._upsert(initial_document_chunks) + + query_results = await postgres_datastore._query(queries) + assert len(query_results) == len(queries) + + query_0_results = query_results[0].results + query_1_results = query_results[1].results + + assert len(query_0_results) == 1 + assert len(query_1_results) == 2 + + # NOTE: this is the correct behavior + assert query_0_results[0].id == "first-doc-4" + assert query_1_results[0].id == "first-doc-5" + assert query_1_results[1].id == "first-doc-4" + + +@pytest.mark.asyncio +async def test_delete( + postgres_datastore: PostgresDataStore, + initial_document_chunks: Dict[str, List[DocumentChunk]], +) -> None: + # insert to prepare for test + await postgres_datastore._upsert(initial_document_chunks) + + is_success = await postgres_datastore.delete(["first-doc"]) + assert is_success + + +@pytest.mark.asyncio +async def test_upsert_new_chunk(postgres_datastore): + await postgres_datastore.delete(delete_all=True) + chunk = DocumentChunk( + id="chunk1", + text="Sample text", + embedding=[1] * 1536, + metadata=DocumentChunkMetadata(), + ) + ids = await postgres_datastore._upsert({"doc1": [chunk]}) + assert len(ids) == 1 + + +@pytest.mark.asyncio +async def test_upsert_existing_chunk(postgres_datastore): + await postgres_datastore.delete(delete_all=True) + chunk = DocumentChunk( + id="chunk1", + text="Sample text", + embedding=[1] * 1536, + metadata=DocumentChunkMetadata(), + ) + ids = await postgres_datastore._upsert({"doc1": [chunk]}) + + chunk = DocumentChunk( + id="chunk1", + text="New text", + embedding=[1] * 1536, + metadata=DocumentChunkMetadata(), + ) + ids = await postgres_datastore._upsert({"doc1": [chunk]}) + + query_embedding = [1] * 1536 + query = QueryWithEmbedding( + query="Query", + embedding=query_embedding, + top_k=1, + ) + results = await postgres_datastore._query([query]) + + assert len(ids) == 1 + assert len(results[0].results) == 1 + assert results[0].results[0].id == "chunk1" + assert results[0].results[0].text == "New text" + + +@pytest.mark.asyncio +async def test_query_score(postgres_datastore): + await postgres_datastore.delete(delete_all=True) + chunk1 = DocumentChunk( + id="chunk1", + text="Sample text", + embedding=[1] * 1536, + metadata=DocumentChunkMetadata(), + ) + chunk2 = DocumentChunk( + id="chunk2", + text="Another text", + embedding=[-1 if i % 2 == 0 else 1 for i in range(1536)], + metadata=DocumentChunkMetadata(), + ) + await postgres_datastore._upsert({"doc1": [chunk1], "doc2": [chunk2]}) + + query_embedding = [1] * 1536 + query = QueryWithEmbedding( + query="Query", + embedding=query_embedding, + ) + results = await postgres_datastore._query([query]) + + assert results[0].results[0].id == "chunk1" + assert int(results[0].results[0].score) == 1536 + + +@pytest.mark.asyncio +async def test_query_filter(postgres_datastore): + await postgres_datastore.delete(delete_all=True) + chunk1 = DocumentChunk( + id="chunk1", + text="Sample text", + embedding=[1] * 1536, + metadata=DocumentChunkMetadata( + source="email", created_at="2021-01-01", author="John" + ), + ) + chunk2 = DocumentChunk( + id="chunk2", + text="Another text", + embedding=[1] * 1536, + metadata=DocumentChunkMetadata( + source="chat", created_at="2022-02-02", author="Mike" + ), + ) + await postgres_datastore._upsert({"doc1": [chunk1], "doc2": [chunk2]}) + + # Test author filter -- string + query_embedding = [1] * 1536 + query = QueryWithEmbedding( + query="Query", + embedding=query_embedding, + filter=DocumentMetadataFilter(author="John"), + ) + results = await postgres_datastore._query([query]) + assert results[0].results[0].id == "chunk1" + + # Test source filter -- enum + query_embedding = [1] * 1536 + query = QueryWithEmbedding( + query="Query", + embedding=query_embedding, + filter=DocumentMetadataFilter(source="chat"), + ) + results = await postgres_datastore._query([query]) + assert results[0].results[0].id == "chunk2" + + # Test created_at filter -- date + query_embedding = [1] * 1536 + query = QueryWithEmbedding( + query="Query", + embedding=query_embedding, + filter=DocumentMetadataFilter(start_date="2022-01-01"), + ) + results = await postgres_datastore._query([query]) + assert results[0].results[0].id == "chunk2" + + +@pytest.mark.asyncio +async def test_delete(postgres_datastore): + await postgres_datastore.delete(delete_all=True) + chunk1 = DocumentChunk( + id="chunk1", + text="Sample text", + embedding=[1] * 1536, + metadata=DocumentChunkMetadata(), + ) + chunk2 = DocumentChunk( + id="chunk2", + text="Another text", + embedding=[1] * 1536, + metadata=DocumentChunkMetadata(), + ) + await postgres_datastore._upsert({"doc1": [chunk1], "doc2": [chunk2]}) + + query_embedding = [1] * 1536 + query = QueryWithEmbedding( + query="Another query", + embedding=query_embedding, + ) + results = await postgres_datastore._query([query]) + + assert len(results[0].results) == 2 + assert results[0].results[0].id == "chunk1" + assert results[0].results[1].id == "chunk2" + + await postgres_datastore.delete(ids=["doc1"]) + results_after_delete = await postgres_datastore._query([query]) + + assert len(results_after_delete[0].results) == 1 + assert results_after_delete[0].results[0].id == "chunk2" + + +@pytest.mark.asyncio +async def test_delete_all(postgres_datastore): + await postgres_datastore.delete(delete_all=True) + chunk = DocumentChunk( + id="chunk", + text="Another text", + embedding=[1] * 1536, + metadata=DocumentChunkMetadata(), + ) + await postgres_datastore._upsert({"doc": [chunk]}) + + query_embedding = [1] * 1536 + query = QueryWithEmbedding( + query="Another query", + embedding=query_embedding, + top_k=1, + ) + results = await postgres_datastore._query([query]) + + assert len(results) == 1 + assert len(results[0].results) == 1 + assert results[0].results[0].id == "chunk" + + await postgres_datastore.delete(delete_all=True) + results_after_delete = await postgres_datastore._query([query]) + + assert len(results_after_delete[0].results) == 0 diff --git a/tests/datastore/providers/supabase/test_supabase_datastore.py b/tests/datastore/providers/supabase/test_supabase_datastore.py new file mode 100644 index 000000000..0fff42559 --- /dev/null +++ b/tests/datastore/providers/supabase/test_supabase_datastore.py @@ -0,0 +1,291 @@ +from typing import Dict, List +import pytest +from datastore.providers.supabase_datastore import SupabaseDataStore +from models.models import ( + DocumentChunk, + DocumentChunkMetadata, + DocumentMetadataFilter, + QueryWithEmbedding, +) + + +def create_embedding(non_zero_pos: int) -> List[float]: + # create a vector with a single non-zero value of dimension 1535 + vector = [0.0] * 1536 + vector[non_zero_pos - 1] = 1.0 + return vector + + +@pytest.fixture +def initial_document_chunks() -> Dict[str, List[DocumentChunk]]: + first_doc_chunks = [ + DocumentChunk( + id=f"first-doc-{i}", + text=f"Lorem ipsum {i}", + metadata=DocumentChunkMetadata(), + embedding=create_embedding(i), + ) + for i in range(4, 7) + ] + return { + "first-doc": first_doc_chunks, + } + + +@pytest.fixture +def queries() -> List[QueryWithEmbedding]: + queries = [ + QueryWithEmbedding( + query="Query 1", + top_k=1, + embedding=create_embedding(4), + ), + QueryWithEmbedding( + query="Query 2", + top_k=2, + embedding=create_embedding(5), + ), + ] + return queries + + +@pytest.fixture +def supabase_datastore() -> SupabaseDataStore: + return SupabaseDataStore() + + +@pytest.mark.asyncio +async def test_upsert( + supabase_datastore: SupabaseDataStore, + initial_document_chunks: Dict[str, List[DocumentChunk]], +) -> None: + """Test basic upsert.""" + doc_ids = await supabase_datastore._upsert(initial_document_chunks) + assert doc_ids == [doc_id for doc_id in initial_document_chunks] + + +@pytest.mark.asyncio +async def test_query( + supabase_datastore: SupabaseDataStore, + initial_document_chunks: Dict[str, List[DocumentChunk]], + queries: List[QueryWithEmbedding], +) -> None: + """Test basic query.""" + # insert to prepare for test + await supabase_datastore._upsert(initial_document_chunks) + + query_results = await supabase_datastore._query(queries) + assert len(query_results) == len(queries) + + query_0_results = query_results[0].results + query_1_results = query_results[1].results + + assert len(query_0_results) == 1 + assert len(query_1_results) == 2 + + # NOTE: this is the correct behavior + assert query_0_results[0].id == "first-doc-4" + assert query_1_results[0].id == "first-doc-5" + assert query_1_results[1].id == "first-doc-4" + + +@pytest.mark.asyncio +async def test_delete( + supabase_datastore: SupabaseDataStore, + initial_document_chunks: Dict[str, List[DocumentChunk]], +) -> None: + # insert to prepare for test + await supabase_datastore._upsert(initial_document_chunks) + + is_success = await supabase_datastore.delete(["first-doc"]) + assert is_success + + +@pytest.mark.asyncio +async def test_upsert_new_chunk(supabase_datastore): + await supabase_datastore.delete(delete_all=True) + chunk = DocumentChunk( + id="chunk1", + text="Sample text", + embedding=[1] * 1536, + metadata=DocumentChunkMetadata(), + ) + ids = await supabase_datastore._upsert({"doc1": [chunk]}) + assert len(ids) == 1 + + +@pytest.mark.asyncio +async def test_upsert_existing_chunk(supabase_datastore): + await supabase_datastore.delete(delete_all=True) + chunk = DocumentChunk( + id="chunk1", + text="Sample text", + embedding=[1] * 1536, + metadata=DocumentChunkMetadata(), + ) + ids = await supabase_datastore._upsert({"doc1": [chunk]}) + + chunk = DocumentChunk( + id="chunk1", + text="New text", + embedding=[1] * 1536, + metadata=DocumentChunkMetadata(), + ) + ids = await supabase_datastore._upsert({"doc1": [chunk]}) + + query_embedding = [1] * 1536 + query = QueryWithEmbedding( + query="Query", + embedding=query_embedding, + top_k=1, + ) + results = await supabase_datastore._query([query]) + + assert len(ids) == 1 + assert len(results[0].results) == 1 + assert results[0].results[0].id == "chunk1" + assert results[0].results[0].text == "New text" + + +@pytest.mark.asyncio +async def test_query_score(supabase_datastore): + await supabase_datastore.delete(delete_all=True) + chunk1 = DocumentChunk( + id="chunk1", + text="Sample text", + embedding=[1] * 1536, + metadata=DocumentChunkMetadata(), + ) + chunk2 = DocumentChunk( + id="chunk2", + text="Another text", + embedding=[-1 if i % 2 == 0 else 1 for i in range(1536)], + metadata=DocumentChunkMetadata(), + ) + await supabase_datastore._upsert({"doc1": [chunk1], "doc2": [chunk2]}) + + query_embedding = [1] * 1536 + query = QueryWithEmbedding( + query="Query", + embedding=query_embedding, + ) + results = await supabase_datastore._query([query]) + + assert results[0].results[0].id == "chunk1" + assert int(results[0].results[0].score) == 1536 + + +@pytest.mark.asyncio +async def test_query_filter(supabase_datastore): + await supabase_datastore.delete(delete_all=True) + chunk1 = DocumentChunk( + id="chunk1", + text="Sample text", + embedding=[1] * 1536, + metadata=DocumentChunkMetadata( + source="email", created_at="2021-01-01", author="John" + ), + ) + chunk2 = DocumentChunk( + id="chunk2", + text="Another text", + embedding=[1] * 1536, + metadata=DocumentChunkMetadata( + source="chat", created_at="2022-02-02", author="Mike" + ), + ) + await supabase_datastore._upsert({"doc1": [chunk1], "doc2": [chunk2]}) + + # Test author filter -- string + query_embedding = [1] * 1536 + query = QueryWithEmbedding( + query="Query", + embedding=query_embedding, + filter=DocumentMetadataFilter(author="John"), + ) + results = await supabase_datastore._query([query]) + assert results[0].results[0].id == "chunk1" + + # Test source filter -- enum + query_embedding = [1] * 1536 + query = QueryWithEmbedding( + query="Query", + embedding=query_embedding, + filter=DocumentMetadataFilter(source="chat"), + ) + results = await supabase_datastore._query([query]) + assert results[0].results[0].id == "chunk2" + + # Test created_at filter -- date + query_embedding = [1] * 1536 + query = QueryWithEmbedding( + query="Query", + embedding=query_embedding, + filter=DocumentMetadataFilter(start_date="2022-01-01"), + ) + results = await supabase_datastore._query([query]) + assert results[0].results[0].id == "chunk2" + + +@pytest.mark.asyncio +async def test_delete(supabase_datastore): + await supabase_datastore.delete(delete_all=True) + chunk1 = DocumentChunk( + id="chunk1", + text="Sample text", + embedding=[1] * 1536, + metadata=DocumentChunkMetadata(), + ) + chunk2 = DocumentChunk( + id="chunk2", + text="Another text", + embedding=[1] * 1536, + metadata=DocumentChunkMetadata(), + ) + await supabase_datastore._upsert({"doc1": [chunk1], "doc2": [chunk2]}) + + query_embedding = [1] * 1536 + query = QueryWithEmbedding( + query="Another query", + embedding=query_embedding, + ) + results = await supabase_datastore._query([query]) + + assert len(results[0].results) == 2 + assert results[0].results[0].id == "chunk1" + assert results[0].results[1].id == "chunk2" + + await supabase_datastore.delete(ids=["doc1"]) + results_after_delete = await supabase_datastore._query([query]) + + assert len(results_after_delete[0].results) == 1 + assert results_after_delete[0].results[0].id == "chunk2" + + +@pytest.mark.asyncio +async def test_delete_all(supabase_datastore): + await supabase_datastore.delete(delete_all=True) + chunk = DocumentChunk( + id="chunk", + text="Another text", + embedding=[1] * 1536, + metadata=DocumentChunkMetadata(), + ) + await supabase_datastore._upsert({"doc": [chunk]}) + + query_embedding = [1] * 1536 + query = QueryWithEmbedding( + query="Another query", + embedding=query_embedding, + top_k=1, + ) + results = await supabase_datastore._query([query]) + + assert len(results) == 1 + assert len(results[0].results) == 1 + assert results[0].results[0].id == "chunk" + + await supabase_datastore.delete(delete_all=True) + results_after_delete = await supabase_datastore._query([query]) + + assert len(results_after_delete[0].results) == 0 From 120ff44302e745323a1edb603daef2da882baed1 Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Tue, 16 May 2023 00:40:41 +0900 Subject: [PATCH 02/19] Fix typo in test_chroma_datastore.py (#247) Initalize -> Initialize --- tests/datastore/providers/chroma/test_chroma_datastore.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/datastore/providers/chroma/test_chroma_datastore.py b/tests/datastore/providers/chroma/test_chroma_datastore.py index 2820af1f6..eacc22ee4 100644 --- a/tests/datastore/providers/chroma/test_chroma_datastore.py +++ b/tests/datastore/providers/chroma/test_chroma_datastore.py @@ -17,7 +17,7 @@ def ephemeral_chroma_datastore() -> ChromaDataStore: - # Initalize an ephemeral in-memory ChromaDB instance + # Initialize an ephemeral in-memory ChromaDB instance return ChromaDataStore( collection_name=COLLECTION_NAME, in_memory=True, persistence_dir=None ) From 80e0155e1a526f0f269c7bbee847ce7998f4d944 Mon Sep 17 00:00:00 2001 From: chris-lindstrom Date: Mon, 15 May 2023 11:51:19 -0700 Subject: [PATCH 03/19] Sanitize local-server name => local_server (#245) --- .dockerignore | 2 +- README.md | 2 +- {local-server => local_server}/ai-plugin.json | 0 {local-server => local_server}/logo.png | Bin {local-server => local_server}/main.py | 8 ++++---- {local-server => local_server}/openapi.yaml | 0 pyproject.toml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) rename {local-server => local_server}/ai-plugin.json (100%) rename {local-server => local_server}/logo.png (100%) rename {local-server => local_server}/main.py (94%) rename {local-server => local_server}/openapi.yaml (100%) diff --git a/.dockerignore b/.dockerignore index eff3dca4f..a25c7ee96 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,7 +4,7 @@ scripts/ tests/ examples/ -local-server/ +local_server/ *.md *.pyc .dockerignore diff --git a/README.md b/README.md index d4cb5d8f5..85569e9e2 100644 --- a/README.md +++ b/README.md @@ -360,7 +360,7 @@ Append `docs` to the URL shown in the terminal and open it in a browser to acces ### Testing a Localhost Plugin in ChatGPT -To test a localhost plugin in ChatGPT, use the provided [`local-server/main.py`](/local-server/main.py) file, which is specifically configured for localhost testing with CORS settings, no authentication and routes for the manifest, OpenAPI schema and logo. +To test a localhost plugin in ChatGPT, use the provided [`local_server/main.py`](/local_server/main.py) file, which is specifically configured for localhost testing with CORS settings, no authentication and routes for the manifest, OpenAPI schema and logo. Follow these steps to test your localhost plugin: diff --git a/local-server/ai-plugin.json b/local_server/ai-plugin.json similarity index 100% rename from local-server/ai-plugin.json rename to local_server/ai-plugin.json diff --git a/local-server/logo.png b/local_server/logo.png similarity index 100% rename from local-server/logo.png rename to local_server/logo.png diff --git a/local-server/main.py b/local_server/main.py similarity index 94% rename from local-server/main.py rename to local_server/main.py index ce274b5a2..93182aa9f 100644 --- a/local-server/main.py +++ b/local_server/main.py @@ -41,19 +41,19 @@ @app.route("/.well-known/ai-plugin.json") async def get_manifest(request): - file_path = "./local-server/ai-plugin.json" + file_path = "./local_server/ai-plugin.json" return FileResponse(file_path, media_type="text/json") @app.route("/.well-known/logo.png") async def get_logo(request): - file_path = "./local-server/logo.png" + file_path = "./local_server/logo.png" return FileResponse(file_path, media_type="text/json") @app.route("/.well-known/openapi.yaml") async def get_openapi(request): - file_path = "./local-server/openapi.yaml" + file_path = "./local_server/openapi.yaml" return FileResponse(file_path, media_type="text/json") @@ -142,4 +142,4 @@ async def startup(): def start(): - uvicorn.run("local-server.main:app", host="localhost", port=PORT, reload=True) + uvicorn.run("local_server.main:app", host="localhost", port=PORT, reload=True) diff --git a/local-server/openapi.yaml b/local_server/openapi.yaml similarity index 100% rename from local-server/openapi.yaml rename to local_server/openapi.yaml diff --git a/pyproject.toml b/pyproject.toml index d4d4e1ddb..67f539898 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ pgvector = "^0.1.7" [tool.poetry.scripts] start = "server.main:start" -dev = "local-server.main:start" +dev = "local_server.main:start" [tool.poetry.group.dev.dependencies] httpx = "^0.23.3" From 059e759a936397304a865255d6a7e627a7cca48a Mon Sep 17 00:00:00 2001 From: isafulf <51974293+isafulf@users.noreply.github.com> Date: Mon, 15 May 2023 12:57:16 -0700 Subject: [PATCH 04/19] Update README.md (#254) * Update README.md * Update README.md --- README.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 85569e9e2..ff8a49c10 100644 --- a/README.md +++ b/README.md @@ -8,17 +8,18 @@ Find an example video of a Retrieval Plugin that has access to the UN Annual Rep The ChatGPT Retrieval Plugin repository provides a flexible solution for semantic search and retrieval of personal or organizational documents using natural language queries. The repository is organized into several directories: -| Directory | Description | -| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------- | -| [`datastore`](/datastore) | Contains the core logic for storing and querying document embeddings using various vector database providers. | -| [`docs`](/docs) | Includes documentation for setting up and using each vector database provider, webhooks, and removing unused dependencies. | -| [`examples`](/examples) | Provides example configurations, authentication methods, and provider-specific examples. | -| [`models`](/models) | Contains the data models used by the plugin, such as document and metadata models. | -| [`scripts`](/scripts) | Offers scripts for processing and uploading documents from different data sources. | -| [`server`](/server) | Houses the main FastAPI server implementation. | -| [`services`](/services) | Contains utility services for tasks like chunking, metadata extraction, and PII detection. | -| [`tests`](/tests) | Includes integration tests for various vector database providers. | -| [`.well-known`](/.well-known) | Stores the plugin manifest file and OpenAPI schema, which define the plugin configuration and API specification. | +| Directory | Description | +| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | +| [`datastore`](/datastore) | Contains the core logic for storing and querying document embeddings using various vector database providers. | +| [`docs`](/docs) | Includes documentation for setting up and using each vector database provider, webhooks, and removing unused dependencies. | +| [`examples`](/examples) | Provides example configurations, authentication methods, and provider-specific examples. | +| [`local_server`](/local_server) | Contains an implementation of the retrieval plugin configured for localhost testing. | +| [`models`](/models) | Contains the data models used by the plugin, such as document and metadata models. | +| [`scripts`](/scripts) | Offers scripts for processing and uploading documents from different data sources. | +| [`server`](/server) | Houses the main FastAPI server implementation. | +| [`services`](/services) | Contains utility services for tasks like chunking, metadata extraction, and PII detection. | +| [`tests`](/tests) | Includes integration tests for various vector database providers. | +| [`.well-known`](/.well-known) | Stores the plugin manifest file and OpenAPI schema, which define the plugin configuration and API specification. | This README provides detailed information on how to set up, develop, and deploy the ChatGPT Retrieval Plugin. From ddba10f0b42d660c03f14133be15ec55e1f98e6e Mon Sep 17 00:00:00 2001 From: isafulf <51974293+isafulf@users.noreply.github.com> Date: Mon, 15 May 2023 12:59:48 -0700 Subject: [PATCH 05/19] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ff8a49c10..ab7ac5933 100644 --- a/README.md +++ b/README.md @@ -277,10 +277,10 @@ The API requires the following environment variables to work: The Azure Open AI uses URLs that are specific to your resource and references models not by model name but by the deployment id. As a result, you need to set additional environment variables for this case. -In addition to the OPENAI_API_BASE (your specific URL) and OPENAI_API_TYPE (azure), you should also set OPENAI_EMBEDDINGMODEL_DEPLOYMENTID which specifies the model to use for getting embeddings on upsert and query. For this, we recommend deploying text-embedding-ada-002 model and using the deployment name here. +In addition to the `OPENAI_API_BASE` (your specific URL) and `OPENAI_API_TYPE` (azure), you should also set `OPENAI_EMBEDDINGMODEL_DEPLOYMENTID` which specifies the model to use for getting embeddings on upsert and query. For this, we recommend deploying `text-embedding-ada-002` model and using the deployment name here. -If you wish to use the data preparation scripts, you will also need to set OPENAI_METADATA_EXTRACTIONMODEL_DEPLOYMENTID, used for metadata extraction and -OPENAI_COMPLETIONMODEL_DEPLOYMENTID, used for PII handling. +If you wish to use the data preparation scripts, you will also need to set `OPENAI_METADATA_EXTRACTIONMODEL_DEPLOYMENTID`, used for metadata extraction and +`OPENAI_COMPLETIONMODEL_DEPLOYMENTID`, used for PII handling. ### Choosing a Vector Database From af10132cb6a299fb8c1e927d6fbada7b7415def7 Mon Sep 17 00:00:00 2001 From: isafulf <51974293+isafulf@users.noreply.github.com> Date: Mon, 15 May 2023 13:46:47 -0700 Subject: [PATCH 06/19] Update weaviate_datastore.py --- datastore/providers/weaviate_datastore.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/datastore/providers/weaviate_datastore.py b/datastore/providers/weaviate_datastore.py index 9202835e5..4baae392a 100644 --- a/datastore/providers/weaviate_datastore.py +++ b/datastore/providers/weaviate_datastore.py @@ -245,11 +245,11 @@ async def _single_query(query: QueryWithEmbedding) -> QueryResult: result = DocumentChunkWithScore( id=resp["chunk_id"], text=resp["text"], - embedding=resp["_additional"]["vector"], + # embedding=resp["_additional"]["vector"], score=resp["_additional"]["score"], metadata=DocumentChunkMetadata( document_id=resp["document_id"] if resp["document_id"] else "", - source=Source(resp["source"]), + source=Source(resp["source"]) if resp["source"] else None, source_id=resp["source_id"], url=resp["url"], created_at=resp["created_at"], From eb25370ecd5a5c04aae466ffc833a2bcfa0c6ed4 Mon Sep 17 00:00:00 2001 From: Copple <10214025+kiwicopple@users.noreply.github.com> Date: Mon, 15 May 2023 14:55:54 -0700 Subject: [PATCH 07/19] Postgres provider udpates (#256) The text in the postgres section specifically called out supabase, but pgvector is available on many vendors this points directly to the pgvector repo so that users can choose --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ab7ac5933..b50777f44 100644 --- a/README.md +++ b/README.md @@ -336,7 +336,7 @@ For detailed setup instructions, refer to [`/docs/providers/llama/setup.md`](/do #### Postgres -[Postgres](https://www.postgresql.org) offers an easy and efficient way to store vectors via [pgvector](https://github.com/pgvector/pgvector) extension. To use pgvector, you will need to set up a PostgreSQL database with the pgvector extension enabled. For example, you can [use docker](https://www.docker.com/blog/how-to-use-the-postgres-docker-official-image/) to run locally. For a hosted/managed solution, you may try [Supabase](https://supabase.com/) or any other cloud provider with support for pgvector. For detailed setup instructions, refer to [`/docs/providers/postgres/setup.md`](/docs/providers/postgres/setup.md). +[Postgres](https://www.postgresql.org) offers an easy and efficient way to store vectors via [pgvector](https://github.com/pgvector/pgvector) extension. To use pgvector, you will need to set up a PostgreSQL database with the pgvector extension enabled. For example, you can [use docker](https://www.docker.com/blog/how-to-use-the-postgres-docker-official-image/) to run locally. For a hosted/managed solution, you can use any of the cloud vendors which support [pgvector](https://github.com/pgvector/pgvector#hosted-postgres). For detailed setup instructions, refer to [`/docs/providers/postgres/setup.md`](/docs/providers/postgres/setup.md). ### Running the API locally From 13348a6727c8f2a353ea84b03fac6f1f4803d58a Mon Sep 17 00:00:00 2001 From: chris-lindstrom Date: Mon, 15 May 2023 15:00:37 -0700 Subject: [PATCH 08/19] Upgraded redis to fix security vulnerabilities (#252) * UPgraded fastapi and redis to fix security vulnerabilities * Could not justify fastapi upgrade --- poetry.lock | 10 +++++----- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2d7bfcd4e..966a401e4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3230,18 +3230,18 @@ websockets = ">=10.3,<11.0" [[package]] name = "redis" -version = "4.5.1" +version = "4.5.4" description = "Python client for Redis database and key-value store" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "redis-4.5.1-py3-none-any.whl", hash = "sha256:5deb072d26e67d2be1712603bfb7947ec3431fb0eec9c578994052e33035af6d"}, - {file = "redis-4.5.1.tar.gz", hash = "sha256:1eec3741cda408d3a5f84b78d089c8b8d895f21b3b050988351e925faf202864"}, + {file = "redis-4.5.4-py3-none-any.whl", hash = "sha256:2c19e6767c474f2e85167909061d525ed65bea9301c0770bb151e041b7ac89a2"}, + {file = "redis-4.5.4.tar.gz", hash = "sha256:73ec35da4da267d6847e47f68730fdd5f62e2ca69e3ef5885c6a78a9374c3893"}, ] [package.dependencies] -async-timeout = ">=4.0.2" +async-timeout = {version = ">=4.0.2", markers = "python_version <= \"3.11.2\""} [package.extras] hiredis = ["hiredis (>=1.0.0)"] @@ -4732,4 +4732,4 @@ cffi = ["cffi (>=1.11)"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "c2fa92a98fca644d3e989cfdd9cac00591bb415f67ec0c01e8fd0e3c20f7e5b7" +content-hash = "89915adf15b0d7bfb147c5e8dc64abaf2c6bb7db16e9fc2ae13eac2dc4f58267" diff --git a/pyproject.toml b/pyproject.toml index 67f539898..68cacdf8b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ pinecone-client = "^2.1.0" weaviate-client = "^3.12.0" pymilvus = "^2.2.2" qdrant-client = {version = "^1.0.4", python = "<3.12"} -redis = "4.5.1" +redis = "4.5.4" supabase = "^1.0.2" psycopg2 = "^2.9.5" llama-index = "0.5.4" From f10c67738024525606471512dd9ee3f6bc5e4881 Mon Sep 17 00:00:00 2001 From: Copple <10214025+kiwicopple@users.noreply.github.com> Date: Mon, 15 May 2023 15:44:22 -0700 Subject: [PATCH 09/19] pointing to full list of postgres providers (#258) --- docs/providers/postgres/setup.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/providers/postgres/setup.md b/docs/providers/postgres/setup.md index 9108e646a..25c64e4cc 100644 --- a/docs/providers/postgres/setup.md +++ b/docs/providers/postgres/setup.md @@ -1,6 +1,6 @@ # Postgres -Postgres Database offers an easy and efficient way to store vectors via [pgvector](https://github.com/pgvector/pgvector) extension. To use pgvector, you will need to set up a PostgreSQL database with the pgvector extension enabled or use a managed solution that provides pgvector. For a hosted/managed solution, you may try [Supabase](https://supabase.com/), more docs you can find in [Supabase Provider](/docs/providers/supabase/setup.md). See more helpful examples of Postgres & pgvector as a vector database [here](https://github.com/supabase-community/nextjs-openai-doc-search). +Postgres Database offers an easy and efficient way to store vectors via [pgvector](https://github.com/pgvector/pgvector) extension. To use pgvector, you will need to set up a PostgreSQL database with the pgvector extension enabled or use a managed solution that provides pgvector. For a hosted/managed solution, you can use any of the cloud vendors which support [pgvector](https://github.com/pgvector/pgvector#hosted-postgres). - The database needs the `pgvector` extension. - To apply required migrations you may use any tool you are more familiar with like [pgAdmin](https://www.pgadmin.org/), [DBeaver](https://dbeaver.io/), [DataGrip](https://www.jetbrains.com/datagrip/), or `psql` cli. @@ -13,7 +13,7 @@ Postgres Database offers an easy and efficient way to store vectors via [pgvecto | `BEARER_TOKEN` | Yes | Your secret token | | `OPENAI_API_KEY` | Yes | Your OpenAI API key | -**Supabase Datastore Environment Variables** +**Postgres Datastore Environment Variables** | Name | Required | Description | Default | | ------------- | -------- | ----------------- | ---------- | From 0d3c789dcbeccaac61924f2c05502e96567fd578 Mon Sep 17 00:00:00 2001 From: Pablo Castro Date: Mon, 15 May 2023 16:31:34 -0700 Subject: [PATCH 10/19] Fix min number of candidates for L2 reranking and env name for overriding the embedding field name (#260) --- datastore/providers/azuresearch_datastore.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/datastore/providers/azuresearch_datastore.py b/datastore/providers/azuresearch_datastore.py index 4ae0182cc..3852258e3 100644 --- a/datastore/providers/azuresearch_datastore.py +++ b/datastore/providers/azuresearch_datastore.py @@ -28,7 +28,7 @@ # Allow overriding field names for Azure Search FIELDS_ID = os.environ.get("AZURESEARCH_FIELDS_ID", "id") FIELDS_TEXT = os.environ.get("AZURESEARCH_FIELDS_TEXT", "text") -FIELDS_EMBEDDING = os.environ.get("AZURESEARCH_FIELDS_TEXT", "embedding") +FIELDS_EMBEDDING = os.environ.get("AZURESEARCH_FIELDS_EMBEDDING", "embedding") FIELDS_DOCUMENT_ID = os.environ.get("AZURESEARCH_FIELDS_DOCUMENT_ID", "document_id") FIELDS_SOURCE = os.environ.get("AZURESEARCH_FIELDS_SOURCE", "source") FIELDS_SOURCE_ID = os.environ.get("AZURESEARCH_FIELDS_SOURCE_ID", "source_id") @@ -132,14 +132,16 @@ async def _single_query(self, query: QueryWithEmbedding) -> QueryResult: """ filter = self._translate_filter(query.filter) if query.filter is not None else None try: - k = query.top_k if filter is None else query.top_k * 2 + vector_top_k = query.top_k if filter is None else query.top_k * 2 q = query.query if not AZURESEARCH_DISABLE_HYBRID else None if AZURESEARCH_SEMANTIC_CONFIG != None and not AZURESEARCH_DISABLE_HYBRID: + # Ensure we're feeding a good number of candidates to the L2 reranker + vector_top_k = max(50, vector_top_k) r = await self.client.search( q, filter=filter, top=query.top_k, - vector=Vector(value=query.embedding, k=k, fields=FIELDS_EMBEDDING), + vector=Vector(value=query.embedding, k=vector_top_k, fields=FIELDS_EMBEDDING), query_type=QueryType.SEMANTIC, query_language=AZURESEARCH_LANGUAGE, semantic_configuration_name=AZURESEARCH_SEMANTIC_CONFIG) @@ -148,7 +150,7 @@ async def _single_query(self, query: QueryWithEmbedding) -> QueryResult: q, filter=filter, top=query.top_k, - vector=Vector(value=query.embedding, k=k, fields=FIELDS_EMBEDDING)) + vector=Vector(value=query.embedding, k=vector_top_k, fields=FIELDS_EMBEDDING)) results: List[DocumentChunkWithScore] = [] async for hit in r: f = lambda field: hit.get(field) if field != "-" else None From d37b831681fff3e759a10a19d231bfc2b2679acf Mon Sep 17 00:00:00 2001 From: egor <58992960+egor-romanov@users.noreply.github.com> Date: Tue, 16 May 2023 19:33:24 +0300 Subject: [PATCH 11/19] add allow private network header in local setup (#248) --- local_server/main.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/local_server/main.py b/local_server/main.py index 93182aa9f..7df685127 100644 --- a/local_server/main.py +++ b/local_server/main.py @@ -42,7 +42,9 @@ @app.route("/.well-known/ai-plugin.json") async def get_manifest(request): file_path = "./local_server/ai-plugin.json" - return FileResponse(file_path, media_type="text/json") + simple_headers = {} + simple_headers["Access-Control-Allow-Private-Network"] = "true" + return FileResponse(file_path, media_type="text/json", headers=simple_headers) @app.route("/.well-known/logo.png") From f1a8c62b515819c5acee417b279045fa2e854b7c Mon Sep 17 00:00:00 2001 From: isafulf <51974293+isafulf@users.noreply.github.com> Date: Tue, 16 May 2023 09:48:02 -0700 Subject: [PATCH 12/19] Update README.md --- README.md | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index b50777f44..8cdb60072 100644 --- a/README.md +++ b/README.md @@ -409,21 +409,13 @@ You can deploy your app to different cloud providers, depending on your preferen Before deploying your app, you might want to remove unused dependencies from your [pyproject.toml](/pyproject.toml) file to reduce the size of your app and improve its performance. Depending on the vector database provider you choose, you can remove the packages that are not needed for your specific provider. Refer to the respective documentation in the [`/docs/deployment/removing-unused-dependencies.md`](/docs/deployment/removing-unused-dependencies.md) file for information on removing unused dependencies for each provider. -Once you have deployed your app, consider uploading an initial batch of documents using one of [these scripts](/scripts) or by calling the `/upsert` endpoint. - -- **Chroma:** Remove `pinecone-client`, `weaviate-client`, `pymilvus`, `qdrant-client`, and `redis`. -- **Pinecone:** Remove `chromadb`, `weaviate-client`, `pymilvus`, `qdrant-client`, and `redis`. -- **Weaviate:** Remove `chromadb`, `pinecone-client`, `pymilvus`, `qdrant-client`, and `redis`. -- **Zilliz:** Remove `chromadb`, `pinecone-client`, `weaviate-client`, `qdrant-client`, and `redis`. -- **Milvus:** Remove `chromadb`, `pinecone-client`, `weaviate-client`, `qdrant-client`, and `redis`. -- **Qdrant:** Remove `chromadb`, `pinecone-client`, `weaviate-client`, `pymilvus`, and `redis`. -- **Redis:** Remove `chromadb`, `pinecone-client`, `weaviate-client`, `pymilvus`, and `qdrant-client`. +Instructions: - [Deploying to Fly.io](/docs/deployment/flyio.md) - [Deploying to Heroku](/docs/deployment/heroku.md) - [Other Deployment Options](/docs/deployment/other-options.md) (Azure Container Apps, Google Cloud Run, AWS Elastic Container Service, etc.) -After you create your app, make sure to change the plugin url in your plugin manifest file [here](/.well-known/ai-plugin.json), and in your OpenAPI schema [here](/.well-known/openapi.yaml), and redeploy. +Once you have deployed your app, consider uploading an initial batch of documents using one of [these scripts](/scripts) or by calling the `/upsert` endpoint. ## Installing a Developer Plugin From 2ff38587c02d595181cf5572132ebab21aafbb3b Mon Sep 17 00:00:00 2001 From: davidmauskop <66637250+davidmauskop@users.noreply.github.com> Date: Wed, 17 May 2023 20:55:25 -0700 Subject: [PATCH 13/19] Add Render as deployment option (#224) --- README.md | 3 ++- docs/deployment/render-thumbnail.png | Bin 0 -> 260172 bytes docs/deployment/render.md | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 docs/deployment/render-thumbnail.png create mode 100644 docs/deployment/render.md diff --git a/README.md b/README.md index 8cdb60072..6b2dbb83d 100644 --- a/README.md +++ b/README.md @@ -192,7 +192,7 @@ This is a plugin for ChatGPT that enables semantic search and retrieval of perso The plugin uses OpenAI's `text-embedding-ada-002` embeddings model to generate embeddings of document chunks, and then stores and queries them using a vector database on the backend. As an open-source and self-hosted solution, developers can deploy their own Retrieval Plugin and register it with ChatGPT. The Retrieval Plugin supports several vector database providers, allowing developers to choose their preferred one from a list. -A FastAPI server exposes the plugin's endpoints for upserting, querying, and deleting documents. Users can refine their search results by using metadata filters by source, date, author, or other criteria. The plugin can be hosted on any cloud platform that supports Docker containers, such as Fly.io, Heroku or Azure Container Apps. To keep the vector database updated with the latest documents, the plugin can process and store documents from various data sources continuously, using incoming webhooks to the upsert and delete endpoints. Tools like [Zapier](https://zapier.com) or [Make](https://www.make.com) can help configure the webhooks based on events or schedules. +A FastAPI server exposes the plugin's endpoints for upserting, querying, and deleting documents. Users can refine their search results by using metadata filters by source, date, author, or other criteria. The plugin can be hosted on any cloud platform that supports Docker containers, such as Fly.io, Heroku, Render, or Azure Container Apps. To keep the vector database updated with the latest documents, the plugin can process and store documents from various data sources continuously, using incoming webhooks to the upsert and delete endpoints. Tools like [Zapier](https://zapier.com) or [Make](https://www.make.com) can help configure the webhooks based on events or schedules. ### Memory Feature @@ -413,6 +413,7 @@ Instructions: - [Deploying to Fly.io](/docs/deployment/flyio.md) - [Deploying to Heroku](/docs/deployment/heroku.md) +- [Deploying to Render](/docs/deployment/render.md) - [Other Deployment Options](/docs/deployment/other-options.md) (Azure Container Apps, Google Cloud Run, AWS Elastic Container Service, etc.) Once you have deployed your app, consider uploading an initial batch of documents using one of [these scripts](/scripts) or by calling the `/upsert` endpoint. diff --git a/docs/deployment/render-thumbnail.png b/docs/deployment/render-thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..4bd725aaeffa399af7d2302f0529e7420889380e GIT binary patch literal 260172 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%0D?ZJZ{0&sVuHLo}e}(gVdg0h6b=f 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

KY`tV^3x4}jA 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 kw@BkC`QkR*H$bf&!O6s@rZxc~qF literal 0 HcmV?d00001 diff --git a/docs/deployment/render.md b/docs/deployment/render.md new file mode 100644 index 000000000..741f55538 --- /dev/null +++ b/docs/deployment/render.md @@ -0,0 +1,19 @@ +# Deploying to Render + +## Removing Unused Dependencies + +Before deploying your app, you might want to remove unused dependencies from your [pyproject.toml](/pyproject.toml) file to reduce the size of your app and improve its performance. Depending on the vector database provider you choose, you can remove the packages that are not needed for your specific provider. + +Find the packages you can remove for each vector database provider [here](removing-unused-dependencies.md). + +After removing the unnecessary packages from the `pyproject.toml` file, you don't need to run `poetry lock` and `poetry install` manually. The provided Dockerfile takes care of installing the required dependencies using the `requirements.txt` file generated by the `poetry export` command. + +## Deployment + +Render maintains a [fork](https://github.com/render-examples/chatgpt-retrieval-plugin/) of this repository with a few small changes that facilitate easy deployment. The source code is unchanged. To deploy both the Docker container from this repository and a self-hosted Weaviate database to back it, just click the button below. Enter your OpenAI API key when prompted. + +[Deploy to Render](https://render.com/deploy?repo=https://github.com/render-examples/chatgpt-retrieval-plugin/tree/main) + +The bearer token will be randomly generated for you. You can view it in in the "Environment" tab on the [Render dashboard](https://dashboard.render.com) page for your server. For more guidance, consult the [README in Render's fork](https://github.com/render-examples/chatgpt-retrieval-plugin/blob/main/README.md), [Render's documentation](https://render.com/docs), or the screen recording linked below. + +[![Deploy to Render screen recording](render-thumbnail.png)](https://vimeo.com/823610578) From 9969191297c34db131075fbdaa05ccd814f85119 Mon Sep 17 00:00:00 2001 From: davidmauskop <66637250+davidmauskop@users.noreply.github.com> Date: Thu, 18 May 2023 21:54:30 -0700 Subject: [PATCH 14/19] Add Deploy to Render button (#267) --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 6b2dbb83d..de5dc2222 100644 --- a/README.md +++ b/README.md @@ -407,6 +407,10 @@ Consider the benefits and drawbacks of each authentication method before choosin You can deploy your app to different cloud providers, depending on your preferences and requirements. However, regardless of the provider you choose, you will need to update two files in your app: [openapi.yaml](/.well-known/openapi.yaml) and [ai-plugin.json](/.well-known/ai-plugin.json). As outlined above, these files define the API specification and the AI plugin configuration for your app, respectively. You need to change the url field in both files to match the address of your deployed app. +Render has a 1-click deploy option that automatically updates the url field in both files: + +[Deploy to Render](https://render.com/deploy?repo=https://github.com/render-examples/chatgpt-retrieval-plugin/tree/main) + Before deploying your app, you might want to remove unused dependencies from your [pyproject.toml](/pyproject.toml) file to reduce the size of your app and improve its performance. Depending on the vector database provider you choose, you can remove the packages that are not needed for your specific provider. Refer to the respective documentation in the [`/docs/deployment/removing-unused-dependencies.md`](/docs/deployment/removing-unused-dependencies.md) file for information on removing unused dependencies for each provider. Instructions: From 9cdbf31fde72c8ca366150f97b437a40beddab2b Mon Sep 17 00:00:00 2001 From: Richy Wang Date: Wed, 31 May 2023 00:08:09 +0800 Subject: [PATCH 15/19] Add a PostgreSQL syntax database AnalyticDB as a datastore. (#147) * Add a full PostgresSQL syntax distributed database AnalyticDB as a datastore. * Replace psycopg2 with psycopg2cffi to compatibility with Python >=3.10 and Refactor AnalyticDB related ENV * Change the `psycopg2cfii` package to optional and update setup.md with PostgreSQL dependency installation instructions * Use a connection pool to replace connect with the PostgreSQl vector store directly. To prevent connection lost problem. * Modify READEME. Add `analyticdb` into `DATASTORE` --- README.md | 26 +- datastore/factory.py | 4 + datastore/providers/analyticdb_datastore.py | 311 +++++++++++++++++ docs/providers/analyticdb/setup.md | 86 +++++ poetry.lock | 18 + pyproject.toml | 4 + .../analyticdb/test_analyticdb_datastore.py | 323 ++++++++++++++++++ 7 files changed, 767 insertions(+), 5 deletions(-) create mode 100644 datastore/providers/analyticdb_datastore.py create mode 100644 docs/providers/analyticdb/setup.md create mode 100644 tests/datastore/providers/analyticdb/test_analyticdb_datastore.py diff --git a/README.md b/README.md index de5dc2222..d70d6c4c1 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,15 @@ Follow these steps to quickly set up and run the ChatGPT Retrieval Plugin: export QDRANT_GRPC_PORT= export QDRANT_API_KEY= export QDRANT_COLLECTION= + + # AnalyticDB + export PG_HOST= + export PG_PORT= + export PG_USER= + export PG_PASSWORD= + export PG_DATABASE= + export PG_COLLECTION= + # Redis export REDIS_HOST= @@ -267,11 +276,11 @@ poetry install The API requires the following environment variables to work: -| Name | Required | Description | -| ---------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `DATASTORE` | Yes | This specifies the vector database provider you want to use to store and query embeddings. You can choose from `chroma`, `pinecone`, `weaviate`, `zilliz`, `milvus`, `qdrant`, `redis`, `azuresearch`, `supabase`, `postgres`. | -| `BEARER_TOKEN` | Yes | This is a secret token that you need to authenticate your requests to the API. You can generate one using any tool or method you prefer, such as [jwt.io](https://jwt.io/). | -| `OPENAI_API_KEY` | Yes | This is your OpenAI API key that you need to generate embeddings using the `text-embedding-ada-002` model. You can get an API key by creating an account on [OpenAI](https://openai.com/). | +| Name | Required | Description | +| ---------------- | -------- |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `DATASTORE` | Yes | This specifies the vector database provider you want to use to store and query embeddings. You can choose from `chroma`, `pinecone`, `weaviate`, `zilliz`, `milvus`, `qdrant`, `redis`, `azuresearch`, `supabase`, `postgres`, `analyticdb`. | +| `BEARER_TOKEN` | Yes | This is a secret token that you need to authenticate your requests to the API. You can generate one using any tool or method you prefer, such as [jwt.io](https://jwt.io/). | +| `OPENAI_API_KEY` | Yes | This is your OpenAI API key that you need to generate embeddings using the `text-embedding-ada-002` model. You can get an API key by creating an account on [OpenAI](https://openai.com/). | ### Using the plugin with Azure OpenAI @@ -338,6 +347,13 @@ For detailed setup instructions, refer to [`/docs/providers/llama/setup.md`](/do [Postgres](https://www.postgresql.org) offers an easy and efficient way to store vectors via [pgvector](https://github.com/pgvector/pgvector) extension. To use pgvector, you will need to set up a PostgreSQL database with the pgvector extension enabled. For example, you can [use docker](https://www.docker.com/blog/how-to-use-the-postgres-docker-official-image/) to run locally. For a hosted/managed solution, you can use any of the cloud vendors which support [pgvector](https://github.com/pgvector/pgvector#hosted-postgres). For detailed setup instructions, refer to [`/docs/providers/postgres/setup.md`](/docs/providers/postgres/setup.md). +#### AnalyticDB +[AnalyticDB](https://www.alibabacloud.com/help/en/analyticdb-for-postgresql/latest/product-introduction-overview) is a distributed cloud-native vector database designed for storing documents and vector embeddings. +As a high-performance vector database, it is fully compatible with PostgreSQL syntax, making it easy to use. Managed by Alibaba Cloud, AnalyticDB is a cloud-native database with a powerful vector compute engine. +Its out-of-the-box experience enables processing of billions of data vectors and offers a wide range of features, including indexing algorithms, structured and unstructured data capabilities, real-time updates, distance metrics, scalar filtering, and time travel searches. +Additionally, it provides full OLAP database functionality and an SLA commitment for production use. +For detailed setup instructions, refer to [`/docs/providers/analyticdb/setup.md`](/docs/providers/analyticdb/setup.md). + ### Running the API locally To run the API locally, you first need to set the requisite environment variables with the `export` command: diff --git a/datastore/factory.py b/datastore/factory.py index f075e3a8f..adde49d76 100644 --- a/datastore/factory.py +++ b/datastore/factory.py @@ -52,6 +52,10 @@ async def get_datastore() -> DataStore: from datastore.providers.postgres_datastore import PostgresDataStore return PostgresDataStore() + case "analyticdb": + from datastore.providers.analyticdb_datastore import AnalyticDBDataStore + + return AnalyticDBDataStore() case _: raise ValueError( f"Unsupported vector database: {datastore}. " diff --git a/datastore/providers/analyticdb_datastore.py b/datastore/providers/analyticdb_datastore.py new file mode 100644 index 000000000..4cd4b1ae5 --- /dev/null +++ b/datastore/providers/analyticdb_datastore.py @@ -0,0 +1,311 @@ +import os +import asyncio +from typing import Dict, List, Optional, Tuple, Any +from datetime import datetime + +from psycopg2cffi import compat + +compat.register() +import psycopg2 +from psycopg2.extras import DictCursor +from psycopg2.pool import SimpleConnectionPool + +from services.date import to_unix_timestamp +from datastore.datastore import DataStore +from models.models import ( + DocumentChunk, + DocumentChunkMetadata, + DocumentMetadataFilter, + QueryResult, + QueryWithEmbedding, + DocumentChunkWithScore, +) + +PG_CONFIG = { + "collection": os.environ.get("PG_COLLECTION", "document_chunks"), + "database": os.environ.get("PG_DATABASE", "postgres"), + "user": os.environ.get("PG_USER", "user"), + "password": os.environ.get("PG_PASSWORD", "password"), + "host": os.environ.get("PG_HOST", "localhost"), + "port": int(os.environ.get("PG_PORT", "5432")), +} +OUTPUT_DIM = 1536 + + +class AnalyticDBDataStore(DataStore): + def __init__(self, config: Dict[str, str] = PG_CONFIG): + self.collection_name = config["collection"] + self.user = config["user"] + self.password = config["password"] + self.database = config["database"] + self.host = config["host"] + self.port = config["port"] + + self.connection_pool = SimpleConnectionPool( + minconn=1, + maxconn=100, + dbname=self.database, + user=self.user, + password=self.password, + host=self.host, + port=self.port, + ) + + self._initialize_db() + + def _initialize_db(self): + conn = self.connection_pool.getconn() + try: + with conn.cursor() as cur: + self._create_table(cur) + self._create_embedding_index(cur) + conn.commit() + finally: + self.connection_pool.putconn(conn) + + def _create_table(self, cur: psycopg2.extensions.cursor): + cur.execute( + f""" + CREATE TABLE IF NOT EXISTS {self.collection_name} ( + id TEXT PRIMARY KEY DEFAULT uuid_generate_v4()::TEXT, + source TEXT, + source_id TEXT, + content TEXT, + document_id TEXT, + author TEXT, + url TEXT, + created_at TIMESTAMPTZ DEFAULT NOW(), + embedding real[] + ); + """ + ) + + def _create_embedding_index(self, cur: psycopg2.extensions.cursor): + cur.execute( + f""" + SELECT * FROM pg_indexes WHERE tablename='{self.collection_name}'; + """ + ) + index_exists = any( + index[2] == f"{self.collection_name}_embedding_idx" + for index in cur.fetchall() + ) + if not index_exists: + cur.execute( + f""" + CREATE INDEX {self.collection_name}_embedding_idx + ON {self.collection_name} + USING ann(embedding) + WITH ( + distancemeasure=L2, + dim=OUTPUT_DIM, + pq_segments=64, + hnsw_m=100, + pq_centers=2048 + ); + """ + ) + + async def _upsert(self, chunks: Dict[str, List[DocumentChunk]]) -> List[str]: + """ + Takes in a dict of document_ids to list of document chunks and inserts them into the database. + Return a list of document ids. + """ + loop = asyncio.get_event_loop() + tasks = [ + loop.run_in_executor(None, self._upsert_chunk, chunk) + for document_chunks in chunks.values() + for chunk in document_chunks + ] + await asyncio.gather(*tasks) + + return list(chunks.keys()) + + def _upsert_chunk(self, chunk: DocumentChunk): + created_at = ( + datetime.fromtimestamp(to_unix_timestamp(chunk.metadata.created_at)) + if chunk.metadata.created_at + else None + ) + data = ( + chunk.id, + chunk.text, + chunk.embedding, + chunk.metadata.document_id, + chunk.metadata.source, + chunk.metadata.source_id, + chunk.metadata.url, + chunk.metadata.author, + created_at, + ) + + conn = self.connection_pool.getconn() + try: + with conn.cursor() as cur: + # Construct the SQL query and data + query = f""" + INSERT INTO {self.collection_name} (id, content, embedding, document_id, source, source_id, url, author, created_at) + VALUES (%s::text, %s::text, %s::real[], %s::text, %s::text, %s::text, %s::text, %s::text, %s::timestamp with time zone) + ON CONFLICT (id) DO UPDATE SET + content = EXCLUDED.content, + embedding = EXCLUDED.embedding, + document_id = EXCLUDED.document_id, + source = EXCLUDED.source, + source_id = EXCLUDED.source_id, + url = EXCLUDED.url, + author = EXCLUDED.author, + created_at = EXCLUDED.created_at; + """ + + # Execute the query + cur.execute(query, data) + + # Commit the transaction + conn.commit() + finally: + self.connection_pool.putconn(conn) + + async def _query(self, queries: List[QueryWithEmbedding]) -> List[QueryResult]: + """ + Takes in a list of queries with embeddings and filters and returns a list of query results with matching document chunks and scores. + """ + query_results: List[QueryResult] = [] + + def generate_query(query: QueryWithEmbedding) -> Tuple[str, List[Any]]: + embedding = "[" + ", ".join(str(x) for x in query.embedding) + "]" + q = f""" + SELECT + id, + content, + source, + source_id, + document_id, + url, + created_at, + author, + embedding, + l2_distance(embedding,array{embedding}::real[]) AS similarity + FROM + {self.collection_name} + """ + where_clause, params = generate_where_clause(query.filter) + q += where_clause + q += f"ORDER BY embedding <-> array{embedding}::real[] LIMIT {query.top_k};" + return q, params + + def generate_where_clause( + query_filter: Optional[DocumentMetadataFilter], + ) -> Tuple[str, List[Any]]: + if query_filter is None: + return "", [] + + conditions = [ + ("document_id=%s", query_filter.document_id), + ("source_id=%s", query_filter.source_id), + ("source LIKE %s", query_filter.source), + ("author LIKE %s", query_filter.author), + ("created_at >= %s", query_filter.start_date), + ("created_at <= %s", query_filter.end_date), + ] + + where_clause = "WHERE " + " AND ".join( + [cond[0] for cond in conditions if cond[1] is not None] + ) + + values = [cond[1] for cond in conditions if cond[1] is not None] + + return where_clause, values + + def fetch_data(cur, q: str, params: List[Any]): + cur.execute(q, params) + return cur.fetchall() + + def create_results(data): + results = [] + for row in data: + document_chunk = DocumentChunkWithScore( + id=row["id"], + text=row["content"], + score=float(row["similarity"]), + metadata=DocumentChunkMetadata( + source=row["source"], + source_id=row["source_id"], + document_id=row["document_id"], + url=row["url"], + created_at=str(row["created_at"]), + author=row["author"], + ), + ) + results.append(document_chunk) + return results + + conn = self.connection_pool.getconn() + try: + for query in queries: + try: + cur = conn.cursor(cursor_factory=DictCursor) + for query in queries: + q, params = generate_query(query) + data = fetch_data(cur, q, params) + results = create_results(data) + query_results.append( + QueryResult(query=query.query, results=results) + ) + except Exception as e: + print("error:", e) + query_results.append(QueryResult(query=query.query, results=[])) + return query_results + finally: + self.connection_pool.putconn(conn) + + async def delete( + self, + ids: Optional[List[str]] = None, + filter: Optional[DocumentMetadataFilter] = None, + delete_all: Optional[bool] = None, + ) -> bool: + async def execute_delete(query: str, params: Optional[List] = None) -> bool: + conn = self.connection_pool.getconn() + try: + with conn.cursor() as cur: + if params: + cur.execute(query, params) + else: + cur.execute(query) + self.conn.commit() + return True + except Exception as e: + print(f"Error: {e}") + return False + finally: + self.connection_pool.putconn(conn) + + if delete_all: + query = f"DELETE FROM {self.collection_name} WHERE document_id LIKE %s;" + return await execute_delete(query, ["%"]) + elif ids: + query = f"DELETE FROM {self.collection_name} WHERE document_id IN ({','.join(['%s'] * len(ids))});" + return await execute_delete(query, ids) + elif filter is not None: + query, params = self._generate_delete_query(filter) + return await execute_delete(query, params) + else: + return True + + def _generate_delete_query( + self, filter: DocumentMetadataFilter + ) -> Tuple[str, List]: + conditions = [ + (filter.document_id, "document_id = %s"), + (filter.source, "source = %s"), + (filter.source_id, "source_id = %s"), + (filter.author, "author = %s"), + (filter.start_date, "created_at >= %s"), + (filter.end_date, "created_at <= %s"), + ] + + where_conditions = [f for value, f in conditions if value] + where_values = [value for value, _ in conditions if value] + + query = f"DELETE FROM {self.collection_name} WHERE {' AND '.join(where_conditions)};" + return query, where_values diff --git a/docs/providers/analyticdb/setup.md b/docs/providers/analyticdb/setup.md new file mode 100644 index 000000000..6b499f4e2 --- /dev/null +++ b/docs/providers/analyticdb/setup.md @@ -0,0 +1,86 @@ +# AnalyticDB + +[AnalyticDB]( https://www.alibabacloud.com/help/en/analyticdb-for-postgresql/latest/product-introduction-overview) is a distributed cloud-native vector database designed for storing documents and vector embeddings. +As a high-performance vector database, it is fully compatible with PostgreSQL syntax, making it easy to use. +Managed by Alibaba Cloud, AnalyticDB is a cloud-native database with a powerful vector compute engine. +Its out-of-the-box experience enables processing of billions of data vectors and offers a wide range of features, including indexing algorithms, structured and unstructured data capabilities, real-time updates, distance metrics, scalar filtering, and time travel searches. +Additionally, it provides full OLAP database functionality and an SLA commitment for production use. + +## Install requirements +Running the following command to install requirement packages. It will install `psycopg2cffi` package. +``` + poetry install --extras "postgresql" +``` +If your meet with this issue `Error: pg_config executable not found.` +It appears that the `pg_config` executable is not found in your system. The `pg_config` utility is part of the PostgreSQL development package, which is required for building `psycopg2cffi`. + +To resolve this issue, you need to install the PostgreSQL development package on your system. Here's how to do it on different Linux distributions: + +1. On Debian-based systems (e.g., Ubuntu): + +```bash +sudo apt-get update +sudo apt-get install libpq-dev +``` + +2. On RHEL-based systems (e.g., CentOS, Fedora): + +```bash +sudo yum install postgresql-devel +``` + +3. On Arch-based systems (e.g., Manjaro, Arch Linux): + +```bash +sudo pacman -S postgresql-libs +``` + +4. On macOS +```bash +brew install postgresql +``` + +After installing the required package, try to install `psycopg2cffi` again. If the `pg_config` executable is still not found, you might need to add its location to your system's `PATH` variable. You can typically find the `pg_config` executable in the `bin` directory of your PostgreSQL installation, for example `/usr/pgsql-13/bin/pg_config`. To add it to your `PATH` variable, use the following command (replace the path with the correct one for your system): + +```bash +export PATH=$PATH:/usr/pgsql-13/bin +``` + +Now, try installing `psycopg2cffi` again using Poetry. + +**Environment Variables:** + +| Name | Required | Description | Default | +|------------------|----------|-------------------------------------|-------------------| +| `DATASTORE` | Yes | Datastore name, set to `analyticdb` | | +| `BEARER_TOKEN` | Yes | Secret token | | +| `OPENAI_API_KEY` | Yes | OpenAI API key | | +| `PG_HOST` | Yes | AnalyticDB instance URL | `localhost` | +| `PG_USER` | Yes | Database user | `user` | +| `PG_PASSWORD` | Yes | Database password | `password` | +| `PG_PORT` | Optional | Port for AnalyticDB communication | `5432` | +| `PG_DATABASE` | Optional | Database name | `postgres` | +| `PG_COLLECTION` | Optional | AnalyticDB relation name | `document_chunks` | + +## AnalyticDB Cloud + +For a hosted [AnalyticDB Cloud](https://cloud.qdrant.io/) version, provide the AnalyticDB instance +URL + +**Example:** + +```bash +PG_HOST="https://YOUR-CLUSTER-URL.gpdb.rds.aliyuncs.com" +PG_USER="YOUR-USER-NAME" +PG_PASSWORD="YOUR-PASSWORD" +``` + +The other parameters are optional and can be changed if needed. + +## Running AnalyticDB Integration Tests + +A suite of integration tests verifies the AnalyticDB integration. Launch the test suite with this command: + +```bash +pytest ./tests/datastore/providers/analyticdb/test_analyticdb_datastore.py +``` \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 966a401e4..702aac934 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2715,6 +2715,21 @@ files = [ {file = "psycopg2-2.9.6.tar.gz", hash = "sha256:f15158418fd826831b28585e2ab48ed8df2d0d98f502a2b4fe619e7d5ca29011"}, ] +[[package]] +name = "psycopg2cffi" +version = "2.9.0" +description = ".. image:: https://travis-ci.org/chtd/psycopg2cffi.svg?branch=master" +category = "main" +optional = true +python-versions = "*" +files = [ + {file = "psycopg2cffi-2.9.0.tar.gz", hash = "sha256:7e272edcd837de3a1d12b62185eb85c45a19feda9e62fa1b120c54f9e8d35c52"}, +] + +[package.dependencies] +cffi = ">=1.0" +six = "*" + [[package]] name = "pycparser" version = "2.21" @@ -4729,6 +4744,9 @@ cffi = {version = ">=1.11", markers = "platform_python_implementation == \"PyPy\ [package.extras] cffi = ["cffi (>=1.11)"] +[extras] +postgresql = ["psycopg2cffi"] + [metadata] lock-version = "2.0" python-versions = "^3.10" diff --git a/pyproject.toml b/pyproject.toml index 68cacdf8b..f39a81ec6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,11 +38,15 @@ llama-index = "0.5.4" azure-identity = "^1.12.0" azure-search-documents = {version = "11.4.0a20230509004", source = "azure-sdk-dev"} pgvector = "^0.1.7" +psycopg2cffi = {version = "^2.9.0", optional = true} [tool.poetry.scripts] start = "server.main:start" dev = "local_server.main:start" +[tool.poetry.extras] +postgresql = ["psycopg2cffi"] + [tool.poetry.group.dev.dependencies] httpx = "^0.23.3" pytest = "^7.2.1" diff --git a/tests/datastore/providers/analyticdb/test_analyticdb_datastore.py b/tests/datastore/providers/analyticdb/test_analyticdb_datastore.py new file mode 100644 index 000000000..9a79b9f43 --- /dev/null +++ b/tests/datastore/providers/analyticdb/test_analyticdb_datastore.py @@ -0,0 +1,323 @@ +import pytest +from models.models import ( + DocumentChunkMetadata, + DocumentMetadataFilter, + DocumentChunk, + QueryWithEmbedding, + Source, +) +from datastore.providers.analyticdb_datastore import ( + OUTPUT_DIM, + AnalyticDBDataStore, +) + + +@pytest.fixture +def analyticdb_datastore(): + return AnalyticDBDataStore() + + +@pytest.fixture +def document_chunk_one(): + doc_id = "zerp" + doc_chunks = [] + + ids = ["abc_123", "def_456", "ghi_789"] + texts = [ + "lorem ipsum dolor sit amet", + "consectetur adipiscing elit", + "sed do eiusmod tempor incididunt", + ] + sources = [Source.email, Source.file, Source.chat] + source_ids = ["foo", "bar", "baz"] + urls = ["foo.com", "bar.net", "baz.org"] + created_ats = [ + "1929-10-28T09:30:00-05:00", + "2009-01-03T16:39:57-08:00", + "2021-01-21T10:00:00-02:00", + ] + authors = ["Max Mustermann", "John Doe", "Jane Doe"] + embeddings = [[x] * OUTPUT_DIM for x in range(3)] + + for i in range(3): + chunk = DocumentChunk( + id=ids[i], + text=texts[i], + metadata=DocumentChunkMetadata( + document_id=doc_id, + source=sources[i], + source_id=source_ids[i], + url=urls[i], + created_at=created_ats[i], + author=authors[i], + ), + embedding=embeddings[i], # type: ignore + ) + + doc_chunks.append(chunk) + + return {doc_id: doc_chunks} + + +@pytest.fixture +def document_chunk_two(): + doc_id_1 = "zerp" + doc_chunks_1 = [] + + ids = ["abc_123", "def_456", "ghi_789"] + texts = [ + "1lorem ipsum dolor sit amet", + "2consectetur adipiscing elit", + "3sed do eiusmod tempor incididunt", + ] + sources = [Source.email, Source.file, Source.chat] + source_ids = ["foo", "bar", "baz"] + urls = ["foo.com", "bar.net", "baz.org"] + created_ats = [ + "1929-10-28T09:30:00-05:00", + "2009-01-03T16:39:57-08:00", + "3021-01-21T10:00:00-02:00", + ] + authors = ["Max Mustermann", "John Doe", "Jane Doe"] + embeddings = [[x] * OUTPUT_DIM for x in range(3)] + + for i in range(3): + chunk = DocumentChunk( + id=ids[i], + text=texts[i], + metadata=DocumentChunkMetadata( + document_id=doc_id_1, + source=sources[i], + source_id=source_ids[i], + url=urls[i], + created_at=created_ats[i], + author=authors[i], + ), + embedding=embeddings[i], # type: ignore + ) + + doc_chunks_1.append(chunk) + + doc_id_2 = "merp" + doc_chunks_2 = [] + + ids = ["jkl_123", "lmn_456", "opq_789"] + texts = [ + "3sdsc efac feas sit qweas", + "4wert sdfas fdsc", + "52dsc fdsf eiusmod asdasd incididunt", + ] + sources = [Source.email, Source.file, Source.chat] + source_ids = ["foo", "bar", "baz"] + urls = ["foo.com", "bar.net", "baz.org"] + created_ats = [ + "4929-10-28T09:30:00-05:00", + "5009-01-03T16:39:57-08:00", + "6021-01-21T10:00:00-02:00", + ] + authors = ["Max Mustermann", "John Doe", "Jane Doe"] + embeddings = [[x] * OUTPUT_DIM for x in range(3, 6)] + + for i in range(3): + chunk = DocumentChunk( + id=ids[i], + text=texts[i], + metadata=DocumentChunkMetadata( + document_id=doc_id_2, + source=sources[i], + source_id=source_ids[i], + url=urls[i], + created_at=created_ats[i], + author=authors[i], + ), + embedding=embeddings[i], # type: ignore + ) + + doc_chunks_2.append(chunk) + + return {doc_id_1: doc_chunks_1, doc_id_2: doc_chunks_2} + + +@pytest.mark.asyncio +async def test_upsert(analyticdb_datastore, document_chunk_one): + await analyticdb_datastore.delete(delete_all=True) + res = await analyticdb_datastore._upsert(document_chunk_one) + assert res == list(document_chunk_one.keys()) + query = QueryWithEmbedding( + query="lorem", + top_k=10, + embedding=[0.5] * OUTPUT_DIM, + ) + query_results = await analyticdb_datastore._query(queries=[query]) + assert 3 == len(query_results[0].results) + + +@pytest.mark.asyncio +async def test_reload(analyticdb_datastore, document_chunk_one, document_chunk_two): + await analyticdb_datastore.delete(delete_all=True) + + res = await analyticdb_datastore._upsert(document_chunk_one) + assert res == list(document_chunk_one.keys()) + + query = QueryWithEmbedding( + query="lorem", + top_k=10, + embedding=[0.5] * OUTPUT_DIM, + ) + + query_results = await analyticdb_datastore._query(queries=[query]) + assert 3 == len(query_results[0].results) + new_store = AnalyticDBDataStore() + another_in = {i: document_chunk_two[i] for i in document_chunk_two if i != res[0]} + res = await new_store._upsert(another_in) + + query_results = await analyticdb_datastore._query(queries=[query]) + assert 1 == len(query_results) + assert 6 == len(query_results[0].results) + + +@pytest.mark.asyncio +async def test_upsert_query_all(analyticdb_datastore, document_chunk_two): + await analyticdb_datastore.delete(delete_all=True) + res = await analyticdb_datastore._upsert(document_chunk_two) + assert res == list(document_chunk_two.keys()) + # Num entities currently doesn't track deletes + query = QueryWithEmbedding( + query="lorem", + top_k=10, + embedding=[0.5] * OUTPUT_DIM, + ) + query_results = await analyticdb_datastore._query(queries=[query]) + + assert 1 == len(query_results) + assert 6 == len(query_results[0].results) + + +@pytest.mark.asyncio +async def test_query_accuracy(analyticdb_datastore, document_chunk_one): + await analyticdb_datastore.delete(delete_all=True) + res = await analyticdb_datastore._upsert(document_chunk_one) + assert res == list(document_chunk_one.keys()) + query = QueryWithEmbedding( + query="lorem", + top_k=1, + embedding=[0] * OUTPUT_DIM, + ) + query_results = await analyticdb_datastore._query(queries=[query]) + + assert 1 == len(query_results) + assert 1 == len(query_results[0].results) + assert 0 == query_results[0].results[0].score + assert "abc_123" == query_results[0].results[0].id + + +@pytest.mark.asyncio +async def test_query_filter(analyticdb_datastore, document_chunk_one): + await analyticdb_datastore.delete(delete_all=True) + res = await analyticdb_datastore._upsert(document_chunk_one) + assert res == list(document_chunk_one.keys()) + query = QueryWithEmbedding( + query="lorem", + top_k=1, + embedding=[0] * OUTPUT_DIM, + filter=DocumentMetadataFilter( + start_date="2000-01-03T16:39:57-08:00", end_date="2010-01-03T16:39:57-08:00" + ), + ) + query_results = await analyticdb_datastore._query(queries=[query]) + + assert 1 == len(query_results) + assert 1 == len(query_results[0].results) + assert 0 != query_results[0].results[0].score + assert "def_456" == query_results[0].results[0].id + + +@pytest.mark.asyncio +async def test_delete_with_date_filter(analyticdb_datastore, document_chunk_one): + await analyticdb_datastore.delete(delete_all=True) + res = await analyticdb_datastore._upsert(document_chunk_one) + assert res == list(document_chunk_one.keys()) + await analyticdb_datastore.delete( + filter=DocumentMetadataFilter( + end_date="2009-01-03T16:39:57-08:00", + ) + ) + + query = QueryWithEmbedding( + query="lorem", + top_k=9, + embedding=[0] * OUTPUT_DIM, + ) + query_results = await analyticdb_datastore._query(queries=[query]) + + assert 1 == len(query_results) + assert 1 == len(query_results[0].results) + assert "ghi_789" == query_results[0].results[0].id + + +@pytest.mark.asyncio +async def test_delete_with_source_filter(analyticdb_datastore, document_chunk_one): + await analyticdb_datastore.delete(delete_all=True) + res = await analyticdb_datastore._upsert(document_chunk_one) + assert res == list(document_chunk_one.keys()) + await analyticdb_datastore.delete( + filter=DocumentMetadataFilter( + source=Source.email, + ) + ) + + query = QueryWithEmbedding( + query="lorem", + top_k=9, + embedding=[0] * OUTPUT_DIM, + ) + query_results = await analyticdb_datastore._query(queries=[query]) + + assert 1 == len(query_results) + assert 2 == len(query_results[0].results) + assert "def_456" == query_results[0].results[0].id + + +@pytest.mark.asyncio +async def test_delete_with_document_id_filter(analyticdb_datastore, document_chunk_one): + await analyticdb_datastore.delete(delete_all=True) + res = await analyticdb_datastore._upsert(document_chunk_one) + assert res == list(document_chunk_one.keys()) + await analyticdb_datastore.delete( + filter=DocumentMetadataFilter( + document_id=res[0], + ) + ) + query = QueryWithEmbedding( + query="lorem", + top_k=9, + embedding=[0] * OUTPUT_DIM, + ) + query_results = await analyticdb_datastore._query(queries=[query]) + + assert 1 == len(query_results) + assert 0 == len(query_results[0].results) + + +@pytest.mark.asyncio +async def test_delete_with_document_id(analyticdb_datastore, document_chunk_one): + await analyticdb_datastore.delete(delete_all=True) + res = await analyticdb_datastore._upsert(document_chunk_one) + assert res == list(document_chunk_one.keys()) + await analyticdb_datastore.delete([res[0]]) + + query = QueryWithEmbedding( + query="lorem", + top_k=9, + embedding=[0] * OUTPUT_DIM, + ) + query_results = await analyticdb_datastore._query(queries=[query]) + + assert 1 == len(query_results) + assert 0 == len(query_results[0].results) + + +# if __name__ == '__main__': +# import sys +# import pytest +# pytest.main(sys.argv) From 0f737649289fe392ec9a68ccda4cfbb8aeee4bff Mon Sep 17 00:00:00 2001 From: isafulf <51974293+isafulf@users.noreply.github.com> Date: Tue, 30 May 2023 09:19:34 -0700 Subject: [PATCH 16/19] Docs update --- README.md | 13 +++---- .../removing-unused-dependencies.md | 23 +++++------ docs/providers/analyticdb/setup.md | 38 +++++++++---------- 3 files changed, 34 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index d70d6c4c1..9ebde7fb4 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ Follow these steps to quickly set up and run the ChatGPT Retrieval Plugin: export QDRANT_GRPC_PORT= export QDRANT_API_KEY= export QDRANT_COLLECTION= - + # AnalyticDB export PG_HOST= export PG_PORT= @@ -126,7 +126,7 @@ Follow these steps to quickly set up and run the ChatGPT Retrieval Plugin: export PG_PASSWORD= export PG_DATABASE= export PG_COLLECTION= - + # Redis export REDIS_HOST= @@ -277,7 +277,7 @@ poetry install The API requires the following environment variables to work: | Name | Required | Description | -| ---------------- | -------- |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| ---------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `DATASTORE` | Yes | This specifies the vector database provider you want to use to store and query embeddings. You can choose from `chroma`, `pinecone`, `weaviate`, `zilliz`, `milvus`, `qdrant`, `redis`, `azuresearch`, `supabase`, `postgres`, `analyticdb`. | | `BEARER_TOKEN` | Yes | This is a secret token that you need to authenticate your requests to the API. You can generate one using any tool or method you prefer, such as [jwt.io](https://jwt.io/). | | `OPENAI_API_KEY` | Yes | This is your OpenAI API key that you need to generate embeddings using the `text-embedding-ada-002` model. You can get an API key by creating an account on [OpenAI](https://openai.com/). | @@ -348,11 +348,8 @@ For detailed setup instructions, refer to [`/docs/providers/llama/setup.md`](/do [Postgres](https://www.postgresql.org) offers an easy and efficient way to store vectors via [pgvector](https://github.com/pgvector/pgvector) extension. To use pgvector, you will need to set up a PostgreSQL database with the pgvector extension enabled. For example, you can [use docker](https://www.docker.com/blog/how-to-use-the-postgres-docker-official-image/) to run locally. For a hosted/managed solution, you can use any of the cloud vendors which support [pgvector](https://github.com/pgvector/pgvector#hosted-postgres). For detailed setup instructions, refer to [`/docs/providers/postgres/setup.md`](/docs/providers/postgres/setup.md). #### AnalyticDB -[AnalyticDB](https://www.alibabacloud.com/help/en/analyticdb-for-postgresql/latest/product-introduction-overview) is a distributed cloud-native vector database designed for storing documents and vector embeddings. -As a high-performance vector database, it is fully compatible with PostgreSQL syntax, making it easy to use. Managed by Alibaba Cloud, AnalyticDB is a cloud-native database with a powerful vector compute engine. -Its out-of-the-box experience enables processing of billions of data vectors and offers a wide range of features, including indexing algorithms, structured and unstructured data capabilities, real-time updates, distance metrics, scalar filtering, and time travel searches. -Additionally, it provides full OLAP database functionality and an SLA commitment for production use. -For detailed setup instructions, refer to [`/docs/providers/analyticdb/setup.md`](/docs/providers/analyticdb/setup.md). + +[AnalyticDB](https://www.alibabacloud.com/help/en/analyticdb-for-postgresql/latest/product-introduction-overview) is a distributed cloud-native vector database designed for storing documents and vector embeddings. It is fully compatible with PostgreSQL syntax and managed by Alibaba Cloud. AnalyticDB offers a powerful vector compute engine, processing billions of data vectors and providing features such as indexing algorithms, structured and unstructured data capabilities, real-time updates, distance metrics, scalar filtering, and time travel searches. For detailed setup instructions, refer to [`/docs/providers/analyticdb/setup.md`](/docs/providers/analyticdb/setup.md). ### Running the API locally diff --git a/docs/deployment/removing-unused-dependencies.md b/docs/deployment/removing-unused-dependencies.md index 3e692dbb0..44a56c630 100644 --- a/docs/deployment/removing-unused-dependencies.md +++ b/docs/deployment/removing-unused-dependencies.md @@ -4,16 +4,17 @@ Before deploying your app, you might want to remove unused dependencies from you Here are the packages you can remove for each vector database provider: -- **Pinecone:** Remove `weaviate-client`, `pymilvus`, `qdrant-client`, `redis`, `chromadb`, `llama-index`, `azure-identity` and `azure-search-documents`, `supabase`, and `psycopg2`+`pgvector`. -- **Weaviate:** Remove `pinecone-client`, `pymilvus`, `qdrant-client`, `redis`, `chromadb`, `llama-index`, `azure-identity` and `azure-search-documents`, `supabase`, and `psycopg2`+`pgvector`. -- **Zilliz:** Remove `pinecone-client`, `weaviate-client`, `qdrant-client`, `redis`, `chromadb`, `llama-index`, `azure-identity` and `azure-search-documents`, `supabase`, and `psycopg2`+`pgvector`. -- **Milvus:** Remove `pinecone-client`, `weaviate-client`, `qdrant-client`, `redis`, `chromadb`, `llama-index`, `azure-identity` and `azure-search-documents`, `supabase`, and `psycopg2`+`pgvector`. -- **Qdrant:** Remove `pinecone-client`, `weaviate-client`, `pymilvus`, `redis`, `chromadb`, `llama-index`, `azure-identity` and `azure-search-documents`, `supabase`, and `psycopg2`+`pgvector`. -- **Redis:** Remove `pinecone-client`, `weaviate-client`, `pymilvus`, `qdrant-client`, `chromadb`, `llama-index`, `azure-identity` and `azure-search-documents`, `supabase`, and `psycopg2`+`pgvector`. -- **LlamaIndex:** Remove `pinecone-client`, `weaviate-client`, `pymilvus`, `qdrant-client`, `chromadb`, `redis`, `azure-identity` and `azure-search-documents`, `supabase`, and `psycopg2`+`pgvector`. -- **Chroma:**: Remove `pinecone-client`, `weaviate-client`, `pymilvus`, `qdrant-client`, `llama-index`, `redis`, `azure-identity` and `azure-search-documents`, `supabase`, and `psycopg2`+`pgvector`. -- **Azure Cognitive Search**: Remove `pinecone-client`, `weaviate-client`, `pymilvus`, `qdrant-client`, `llama-index`, `redis` and `chromadb`, `supabase`, and `psycopg2`+`pgvector`. -- **Supabase:** Remove `pinecone-client`, `weaviate-client`, `pymilvus`, `qdrant-client`, `redis`, `llama-index`, `azure-identity` and `azure-search-documents`, and `psycopg2`+`pgvector`. -- **Postgres:** Remove `pinecone-client`, `weaviate-client`, `pymilvus`, `qdrant-client`, `redis`, `llama-index`, `azure-identity` and `azure-search-documents`, and `supabase`. +- **Pinecone:** Remove `weaviate-client`, `pymilvus`, `qdrant-client`, `redis`, `chromadb`, `llama-index`, `azure-identity`, `azure-search-documents`, `supabase`, `psycopg2`+`pgvector`, and `psycopg2cffi`. +- **Weaviate:** Remove `pinecone-client`, `pymilvus`, `qdrant-client`, `redis`, `chromadb`, `llama-index`, `azure-identity` and `azure-search-documents`, `supabase`, `psycopg2`+`pgvector`, `psycopg2cffi`. +- **Zilliz:** Remove `pinecone-client`, `weaviate-client`, `qdrant-client`, `redis`, `chromadb`, `llama-index`, `azure-identity` and `azure-search-documents`, `supabase`, `psycopg2`+`pgvector`, and `psycopg2cffi`. +- **Milvus:** Remove `pinecone-client`, `weaviate-client`, `qdrant-client`, `redis`, `chromadb`, `llama-index`, `azure-identity` and `azure-search-documents`, `supabase`, `psycopg2`+`pgvector`, and `psycopg2cffi`. +- **Qdrant:** Remove `pinecone-client`, `weaviate-client`, `pymilvus`, `redis`, `chromadb`, `llama-index`, `azure-identity` and `azure-search-documents`, `supabase`, `psycopg2`+`pgvector`, and `psycopg2cffi`. +- **Redis:** Remove `pinecone-client`, `weaviate-client`, `pymilvus`, `qdrant-client`, `chromadb`, `llama-index`, `azure-identity` and `azure-search-documents`, `supabase`, `psycopg2`+`pgvector`, and `psycopg2cffi`. +- **LlamaIndex:** Remove `pinecone-client`, `weaviate-client`, `pymilvus`, `qdrant-client`, `chromadb`, `redis`, `azure-identity` and `azure-search-documents`, `supabase`, `psycopg2`+`pgvector`, and `psycopg2cffi`. +- **Chroma:**: Remove `pinecone-client`, `weaviate-client`, `pymilvus`, `qdrant-client`, `llama-index`, `redis`, `azure-identity` and `azure-search-documents`, `supabase`, `psycopg2`+`pgvector`, and `psycopg2cffi`. +- **Azure Cognitive Search**: Remove `pinecone-client`, `weaviate-client`, `pymilvus`, `qdrant-client`, `llama-index`, `redis` and `chromadb`, `supabase`, `psycopg2`+`pgvector`, and `psycopg2cffi`. +- **Supabase:** Remove `pinecone-client`, `weaviate-client`, `pymilvus`, `qdrant-client`, `redis`, `llama-index`, `azure-identity` and `azure-search-documents`, `psycopg2`+`pgvector`, and `psycopg2cffi`. +- **Postgres:** Remove `pinecone-client`, `weaviate-client`, `pymilvus`, `qdrant-client`, `redis`, `llama-index`, `azure-identity` and `azure-search-documents`, `supabase`, and `psycopg2cffi`. +- **AnalyticDB:** Remove `pinecone-client`, `weaviate-client`, `pymilvus`, `qdrant-client`, `redis`, `llama-index`, `azure-identity` and `azure-search-documents`, `supabase`, and `psycopg2`+`pgvector`. After removing the unnecessary packages from the `pyproject.toml` file, you don't need to run `poetry lock` and `poetry install` manually. The provided Dockerfile takes care of installing the required dependencies using the `requirements.txt` file generated by the `poetry export` command. diff --git a/docs/providers/analyticdb/setup.md b/docs/providers/analyticdb/setup.md index 6b499f4e2..8aed01f44 100644 --- a/docs/providers/analyticdb/setup.md +++ b/docs/providers/analyticdb/setup.md @@ -1,46 +1,43 @@ # AnalyticDB -[AnalyticDB]( https://www.alibabacloud.com/help/en/analyticdb-for-postgresql/latest/product-introduction-overview) is a distributed cloud-native vector database designed for storing documents and vector embeddings. -As a high-performance vector database, it is fully compatible with PostgreSQL syntax, making it easy to use. -Managed by Alibaba Cloud, AnalyticDB is a cloud-native database with a powerful vector compute engine. -Its out-of-the-box experience enables processing of billions of data vectors and offers a wide range of features, including indexing algorithms, structured and unstructured data capabilities, real-time updates, distance metrics, scalar filtering, and time travel searches. -Additionally, it provides full OLAP database functionality and an SLA commitment for production use. - -## Install requirements -Running the following command to install requirement packages. It will install `psycopg2cffi` package. +[AnalyticDB](https://www.alibabacloud.com/help/en/analyticdb-for-postgresql/latest/product-introduction-overview) is a distributed cloud-native vector database designed for storing documents and vector embeddings. It is a high-performance vector database that is fully compatible with PostgreSQL syntax, making it easy to use. Managed by Alibaba Cloud, AnalyticDB offers a powerful vector compute engine, processing billions of data vectors and providing a wide range of features, including indexing algorithms, structured and unstructured data capabilities, real-time updates, distance metrics, scalar filtering, and time travel searches. Additionally, it offers full OLAP database functionality and an SLA commitment for production use. + +## Install Requirements + +Run the following command to install the required packages, including the `psycopg2cffi` package: + ``` - poetry install --extras "postgresql" +poetry install --extras "postgresql" ``` -If your meet with this issue `Error: pg_config executable not found.` -It appears that the `pg_config` executable is not found in your system. The `pg_config` utility is part of the PostgreSQL development package, which is required for building `psycopg2cffi`. -To resolve this issue, you need to install the PostgreSQL development package on your system. Here's how to do it on different Linux distributions: +If you encounter the `Error: pg_config executable not found.` issue, you need to install the PostgreSQL development package on your system. Follow the instructions for your specific Linux distribution: -1. On Debian-based systems (e.g., Ubuntu): +1. Debian-based systems (e.g., Ubuntu): ```bash sudo apt-get update sudo apt-get install libpq-dev ``` -2. On RHEL-based systems (e.g., CentOS, Fedora): +2. RHEL-based systems (e.g., CentOS, Fedora): ```bash sudo yum install postgresql-devel ``` -3. On Arch-based systems (e.g., Manjaro, Arch Linux): +3. Arch-based systems (e.g., Manjaro, Arch Linux): ```bash sudo pacman -S postgresql-libs ``` -4. On macOS +4. macOS: + ```bash brew install postgresql ``` -After installing the required package, try to install `psycopg2cffi` again. If the `pg_config` executable is still not found, you might need to add its location to your system's `PATH` variable. You can typically find the `pg_config` executable in the `bin` directory of your PostgreSQL installation, for example `/usr/pgsql-13/bin/pg_config`. To add it to your `PATH` variable, use the following command (replace the path with the correct one for your system): +After installing the required package, try to install `psycopg2cffi` again. If the `pg_config` executable is still not found, add its location to your system's `PATH` variable. You can typically find the `pg_config` executable in the `bin` directory of your PostgreSQL installation, for example `/usr/pgsql-13/bin/pg_config`. To add it to your `PATH` variable, use the following command (replace the path with the correct one for your system): ```bash export PATH=$PATH:/usr/pgsql-13/bin @@ -51,7 +48,7 @@ Now, try installing `psycopg2cffi` again using Poetry. **Environment Variables:** | Name | Required | Description | Default | -|------------------|----------|-------------------------------------|-------------------| +| ---------------- | -------- | ----------------------------------- | ----------------- | | `DATASTORE` | Yes | Datastore name, set to `analyticdb` | | | `BEARER_TOKEN` | Yes | Secret token | | | `OPENAI_API_KEY` | Yes | OpenAI API key | | @@ -64,8 +61,7 @@ Now, try installing `psycopg2cffi` again using Poetry. ## AnalyticDB Cloud -For a hosted [AnalyticDB Cloud](https://cloud.qdrant.io/) version, provide the AnalyticDB instance -URL +For a hosted [AnalyticDB Cloud](https://cloud.qdrant.io/) version, provide the AnalyticDB instance URL: **Example:** @@ -83,4 +79,4 @@ A suite of integration tests verifies the AnalyticDB integration. Launch the tes ```bash pytest ./tests/datastore/providers/analyticdb/test_analyticdb_datastore.py -``` \ No newline at end of file +``` From 66d91c93bcd62b045effdb003ba4ad521adeb377 Mon Sep 17 00:00:00 2001 From: isafulf <51974293+isafulf@users.noreply.github.com> Date: Tue, 30 May 2023 09:20:19 -0700 Subject: [PATCH 17/19] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9ebde7fb4..277f30ca2 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ This README provides detailed information on how to set up, develop, and deploy - [Azure Cognitive Search](#azure-cognitive-search) - [Supabase](#supabase) - [Postgres](#postgres) + - [AnalyticDB](#analyticdb) - [Running the API Locally](#running-the-api-locally) - [Testing a Localhost Plugin in ChatGPT](#testing-a-localhost-plugin-in-chatgpt) - [Personalization](#personalization) From 36354a2031387aa030c7610e293f434ae5676381 Mon Sep 17 00:00:00 2001 From: Raghu Date: Wed, 31 May 2023 14:13:12 -0700 Subject: [PATCH 18/19] Add loguru for logging (#282) * Add loguru for logging * Add logger * Replace exception with error --------- Co-authored-by: Raghu Ganapathi --- datastore/providers/analyticdb_datastore.py | 5 +- datastore/providers/milvus_datastore.py | 59 ++++++++----------- datastore/providers/pgvector_datastore.py | 3 +- datastore/providers/pinecone_datastore.py | 43 +++++++------- datastore/providers/redis_datastore.py | 34 +++++------ datastore/providers/weaviate_datastore.py | 2 +- datastore/providers/zilliz_datastore.py | 5 +- .../authentication-methods/no-auth/main.py | 11 ++-- examples/memory/main.py | 13 ++-- local_server/main.py | 9 +-- poetry.lock | 2 +- pyproject.toml | 1 + scripts/process_json/process_json.py | 19 +++--- scripts/process_jsonl/process_jsonl.py | 15 ++--- scripts/process_zip/process_zip.py | 17 +++--- server/main.py | 11 ++-- services/date.py | 3 +- services/extract_metadata.py | 3 +- services/file.py | 11 ++-- services/openai.py | 7 ++- 20 files changed, 141 insertions(+), 132 deletions(-) diff --git a/datastore/providers/analyticdb_datastore.py b/datastore/providers/analyticdb_datastore.py index 4cd4b1ae5..ba206f2e1 100644 --- a/datastore/providers/analyticdb_datastore.py +++ b/datastore/providers/analyticdb_datastore.py @@ -2,6 +2,7 @@ import asyncio from typing import Dict, List, Optional, Tuple, Any from datetime import datetime +from loguru import logger from psycopg2cffi import compat @@ -252,7 +253,7 @@ def create_results(data): QueryResult(query=query.query, results=results) ) except Exception as e: - print("error:", e) + logger.error(e) query_results.append(QueryResult(query=query.query, results=[])) return query_results finally: @@ -275,7 +276,7 @@ async def execute_delete(query: str, params: Optional[List] = None) -> bool: self.conn.commit() return True except Exception as e: - print(f"Error: {e}") + logger.error(e) return False finally: self.connection_pool.putconn(conn) diff --git a/datastore/providers/milvus_datastore.py b/datastore/providers/milvus_datastore.py index 202e86d55..d105cc4e9 100644 --- a/datastore/providers/milvus_datastore.py +++ b/datastore/providers/milvus_datastore.py @@ -2,6 +2,7 @@ import os import asyncio +from loguru import logger from typing import Dict, List, Optional from pymilvus import ( Collection, @@ -124,14 +125,6 @@ def __init__( self._create_collection(MILVUS_COLLECTION, create_new) # type: ignore self._create_index() - def _print_info(self, msg): - # TODO: logger - print(msg) - - def _print_err(self, msg): - # TODO: logger - print(msg) - def _get_schema(self): return SCHEMA_V1 if self._schema_ver == "V1" else SCHEMA_V2 @@ -143,7 +136,7 @@ def _create_connection(self): addr = connections.get_connection_addr(x[0]) if x[1] and ('address' in addr) and (addr['address'] == "{}:{}".format(MILVUS_HOST, MILVUS_PORT)): self.alias = x[0] - self._print_info("Reuse connection to Milvus server '{}:{}' with alias '{:s}'" + logger.info("Reuse connection to Milvus server '{}:{}' with alias '{:s}'" .format(MILVUS_HOST, MILVUS_PORT, self.alias)) break @@ -158,10 +151,10 @@ def _create_connection(self): password=MILVUS_PASSWORD, # type: ignore secure=MILVUS_USE_SECURITY, ) - self._print_info("Create connection to Milvus server '{}:{}' with alias '{:s}'" + logger.info("Create connection to Milvus server '{}:{}' with alias '{:s}'" .format(MILVUS_HOST, MILVUS_PORT, self.alias)) except Exception as e: - self._print_err("Failed to create connection to Milvus server '{}:{}', error: {}" + logger.error("Failed to create connection to Milvus server '{}:{}', error: {}" .format(MILVUS_HOST, MILVUS_PORT, e)) def _create_collection(self, collection_name, create_new: bool) -> None: @@ -189,7 +182,7 @@ def _create_collection(self, collection_name, create_new: bool) -> None: consistency_level=self._consistency_level, ) self._schema_ver = "V2" - self._print_info("Create Milvus collection '{}' with schema {} and consistency level {}" + logger.info("Create Milvus collection '{}' with schema {} and consistency level {}" .format(collection_name, self._schema_ver, self._consistency_level)) else: # If the collection exists, point to it @@ -201,10 +194,10 @@ def _create_collection(self, collection_name, create_new: bool) -> None: if field.name == "id" and field.is_primary: self._schema_ver = "V2" break - self._print_info("Milvus collection '{}' already exists with schema {}" + logger.info("Milvus collection '{}' already exists with schema {}" .format(collection_name, self._schema_ver)) except Exception as e: - self._print_err("Failed to create collection '{}', error: {}".format(collection_name, e)) + logger.error("Failed to create collection '{}', error: {}".format(collection_name, e)) def _create_index(self): # TODO: verify index/search params passed by os.environ @@ -216,7 +209,7 @@ def _create_index(self): if self.index_params is not None: # Convert the string format to JSON format parameters passed by MILVUS_INDEX_PARAMS self.index_params = json.loads(self.index_params) - self._print_info("Create Milvus index: {}".format(self.index_params)) + logger.info("Create Milvus index: {}".format(self.index_params)) # Create an index on the 'embedding' field with the index params found in init self.col.create_index(EMBEDDING_FIELD, index_params=self.index_params) else: @@ -227,24 +220,24 @@ def _create_index(self): "index_type": "HNSW", "params": {"M": 8, "efConstruction": 64}, } - self._print_info("Attempting creation of Milvus '{}' index".format(i_p["index_type"])) + logger.info("Attempting creation of Milvus '{}' index".format(i_p["index_type"])) self.col.create_index(EMBEDDING_FIELD, index_params=i_p) self.index_params = i_p - self._print_info("Creation of Milvus '{}' index successful".format(i_p["index_type"])) + logger.info("Creation of Milvus '{}' index successful".format(i_p["index_type"])) # If create fails, most likely due to being Zilliz Cloud instance, try to create an AutoIndex except MilvusException: - self._print_info("Attempting creation of Milvus default index") + logger.info("Attempting creation of Milvus default index") i_p = {"metric_type": "IP", "index_type": "AUTOINDEX", "params": {}} self.col.create_index(EMBEDDING_FIELD, index_params=i_p) self.index_params = i_p - self._print_info("Creation of Milvus default index successful") + logger.info("Creation of Milvus default index successful") # If an index already exists, grab its params else: # How about if the first index is not vector index? for index in self.col.indexes: idx = index.to_dict() if idx["field"] == EMBEDDING_FIELD: - self._print_info("Index already exists: {}".format(idx)) + logger.info("Index already exists: {}".format(idx)) self.index_params = idx['index_param'] break @@ -272,9 +265,9 @@ def _create_index(self): } # Set the search params self.search_params = default_search_params[self.index_params["index_type"]] - self._print_info("Milvus search parameters: {}".format(self.search_params)) + logger.info("Milvus search parameters: {}".format(self.search_params)) except Exception as e: - self._print_err("Failed to create index, error: {}".format(e)) + logger.error("Failed to create index, error: {}".format(e)) async def _upsert(self, chunks: Dict[str, List[DocumentChunk]]) -> List[str]: """Upsert chunks into the datastore. @@ -319,18 +312,18 @@ async def _upsert(self, chunks: Dict[str, List[DocumentChunk]]) -> List[str]: for batch in batches: if len(batch[0]) != 0: try: - self._print_info(f"Upserting batch of size {len(batch[0])}") + logger.info(f"Upserting batch of size {len(batch[0])}") self.col.insert(batch) - self._print_info(f"Upserted batch successfully") + logger.info(f"Upserted batch successfully") except Exception as e: - self._print_err(f"Failed to insert batch records, error: {e}") + logger.error(f"Failed to insert batch records, error: {e}") raise e # This setting perfoms flushes after insert. Small insert == bad to use # self.col.flush() return doc_ids except Exception as e: - self._print_err("Failed to insert records, error: {}".format(e)) + logger.error("Failed to insert records, error: {}".format(e)) return [] @@ -365,7 +358,7 @@ def _get_values(self, chunk: DocumentChunk) -> List[any] | None: # type: ignore x = values.get(key) or default # If one of our required fields is missing, ignore the entire entry if x is Required: - self._print_info("Chunk " + values["id"] + " missing " + key + " skipping") + logger.info("Chunk " + values["id"] + " missing " + key + " skipping") return None # Add the corresponding value if it passes the tests ret.append(x) @@ -436,7 +429,7 @@ async def _single_query(query: QueryWithEmbedding) -> QueryResult: return QueryResult(query=query.query, results=results) except Exception as e: - self._print_err("Failed to query, error: {}".format(e)) + logger.error("Failed to query, error: {}".format(e)) return QueryResult(query=query.query, results=[]) results: List[QueryResult] = await asyncio.gather( @@ -460,7 +453,7 @@ async def delete( # If deleting all, drop and create the new collection if delete_all: coll_name = self.col.name - self._print_info("Delete the entire collection {} and create new one".format(coll_name)) + logger.info("Delete the entire collection {} and create new one".format(coll_name)) # Release the collection from memory self.col.release() # Drop the collection @@ -490,7 +483,7 @@ async def delete( pks = ['"' + pk + '"' for pk in pks] # Delete by ids batch by batch(avoid too long expression) - self._print_info("Apply {:d} deletions to schema {:s}".format(len(pks), self._schema_ver)) + logger.info("Apply {:d} deletions to schema {:s}".format(len(pks), self._schema_ver)) while len(pks) > 0: batch_pks = pks[:batch_size] pks = pks[batch_size:] @@ -499,7 +492,7 @@ async def delete( # Increment our deleted count delete_count += int(res.delete_count) # type: ignore except Exception as e: - self._print_err("Failed to delete by ids, error: {}".format(e)) + logger.error("Failed to delete by ids, error: {}".format(e)) try: # Check if empty filter @@ -524,9 +517,9 @@ async def delete( # Increment our delete count delete_count += int(res.delete_count) # type: ignore except Exception as e: - self._print_err("Failed to delete by filter, error: {}".format(e)) + logger.error("Failed to delete by filter, error: {}".format(e)) - self._print_info("{:d} records deleted".format(delete_count)) + logger.info("{:d} records deleted".format(delete_count)) # This setting performs flushes after delete. Small delete == bad to use # self.col.flush() diff --git a/datastore/providers/pgvector_datastore.py b/datastore/providers/pgvector_datastore.py index 14c2aea1d..cd7026b23 100644 --- a/datastore/providers/pgvector_datastore.py +++ b/datastore/providers/pgvector_datastore.py @@ -1,6 +1,7 @@ from abc import ABC, abstractmethod from typing import Any, Dict, List, Optional from datetime import datetime +from loguru import logger from services.date import to_unix_timestamp from datastore.datastore import DataStore @@ -147,7 +148,7 @@ async def _query(self, queries: List[QueryWithEmbedding]) -> List[QueryResult]: results.append(document_chunk) query_results.append(QueryResult(query=query.query, results=results)) except Exception as e: - print("error:", e) + logger.error(e) query_results.append(QueryResult(query=query.query, results=[])) return query_results diff --git a/datastore/providers/pinecone_datastore.py b/datastore/providers/pinecone_datastore.py index 2896cf66b..c10ee2bea 100644 --- a/datastore/providers/pinecone_datastore.py +++ b/datastore/providers/pinecone_datastore.py @@ -3,6 +3,7 @@ import pinecone from tenacity import retry, wait_random_exponential, stop_after_attempt import asyncio +from loguru import logger from datastore.datastore import DataStore from models.models import ( @@ -41,7 +42,7 @@ def __init__(self): # Create a new index with the specified name, dimension, and metadata configuration try: - print( + logger.info( f"Creating index {PINECONE_INDEX} with metadata config {fields_to_index}" ) pinecone.create_index( @@ -50,18 +51,18 @@ def __init__(self): metadata_config={"indexed": fields_to_index}, ) self.index = pinecone.Index(PINECONE_INDEX) - print(f"Index {PINECONE_INDEX} created successfully") + logger.info(f"Index {PINECONE_INDEX} created successfully") except Exception as e: - print(f"Error creating index {PINECONE_INDEX}: {e}") + logger.error(f"Error creating index {PINECONE_INDEX}: {e}") raise e elif PINECONE_INDEX and PINECONE_INDEX in pinecone.list_indexes(): # Connect to an existing index with the specified name try: - print(f"Connecting to existing index {PINECONE_INDEX}") + logger.info(f"Connecting to existing index {PINECONE_INDEX}") self.index = pinecone.Index(PINECONE_INDEX) - print(f"Connected to index {PINECONE_INDEX} successfully") + logger.info(f"Connected to index {PINECONE_INDEX} successfully") except Exception as e: - print(f"Error connecting to index {PINECONE_INDEX}: {e}") + logger.error(f"Error connecting to index {PINECONE_INDEX}: {e}") raise e @retry(wait=wait_random_exponential(min=1, max=20), stop=stop_after_attempt(3)) @@ -78,7 +79,7 @@ async def _upsert(self, chunks: Dict[str, List[DocumentChunk]]) -> List[str]: for doc_id, chunk_list in chunks.items(): # Append the id to the ids list doc_ids.append(doc_id) - print(f"Upserting document_id: {doc_id}") + logger.info(f"Upserting document_id: {doc_id}") for chunk in chunk_list: # Create a vector tuple of (id, embedding, metadata) # Convert the metadata object to a dict with unix timestamps for dates @@ -97,11 +98,11 @@ async def _upsert(self, chunks: Dict[str, List[DocumentChunk]]) -> List[str]: # Upsert each batch to Pinecone for batch in batches: try: - print(f"Upserting batch of size {len(batch)}") + logger.info(f"Upserting batch of size {len(batch)}") self.index.upsert(vectors=batch) - print(f"Upserted batch successfully") + logger.info(f"Upserted batch successfully") except Exception as e: - print(f"Error upserting batch: {e}") + logger.error(f"Error upserting batch: {e}") raise e return doc_ids @@ -117,7 +118,7 @@ async def _query( # Define a helper coroutine that performs a single query and returns a QueryResult async def _single_query(query: QueryWithEmbedding) -> QueryResult: - print(f"Query: {query.query}") + logger.debug(f"Query: {query.query}") # Convert the metadata filter object to a dict with pinecone filter expressions pinecone_filter = self._get_pinecone_filter(query.filter) @@ -132,7 +133,7 @@ async def _single_query(query: QueryWithEmbedding) -> QueryResult: include_metadata=True, ) except Exception as e: - print(f"Error querying index: {e}") + logger.error(f"Error querying index: {e}") raise e query_results: List[DocumentChunkWithScore] = [] @@ -184,12 +185,12 @@ async def delete( # Delete all vectors from the index if delete_all is True if delete_all: try: - print(f"Deleting all vectors from index") + logger.info(f"Deleting all vectors from index") self.index.delete(delete_all=True) - print(f"Deleted all vectors successfully") + logger.info(f"Deleted all vectors successfully") return True except Exception as e: - print(f"Error deleting all vectors: {e}") + logger.error(f"Error deleting all vectors: {e}") raise e # Convert the metadata filter object to a dict with pinecone filter expressions @@ -197,22 +198,22 @@ async def delete( # Delete vectors that match the filter from the index if the filter is not empty if pinecone_filter != {}: try: - print(f"Deleting vectors with filter {pinecone_filter}") + logger.info(f"Deleting vectors with filter {pinecone_filter}") self.index.delete(filter=pinecone_filter) - print(f"Deleted vectors with filter successfully") + logger.info(f"Deleted vectors with filter successfully") except Exception as e: - print(f"Error deleting vectors with filter: {e}") + logger.error(f"Error deleting vectors with filter: {e}") raise e # Delete vectors that match the document ids from the index if the ids list is not empty if ids is not None and len(ids) > 0: try: - print(f"Deleting vectors with ids {ids}") + logger.info(f"Deleting vectors with ids {ids}") pinecone_filter = {"document_id": {"$in": ids}} self.index.delete(filter=pinecone_filter) # type: ignore - print(f"Deleted vectors with ids successfully") + logger.info(f"Deleted vectors with ids successfully") except Exception as e: - print(f"Error deleting vectors with ids: {e}") + logger.error(f"Error deleting vectors with ids: {e}") raise e return True diff --git a/datastore/providers/redis_datastore.py b/datastore/providers/redis_datastore.py index 669f3fb83..da13348f7 100644 --- a/datastore/providers/redis_datastore.py +++ b/datastore/providers/redis_datastore.py @@ -1,5 +1,4 @@ import asyncio -import logging import os import re import json @@ -14,6 +13,7 @@ NumericField, VectorField, ) +from loguru import logger from typing import Dict, List, Optional from datastore.datastore import DataStore from models.models import ( @@ -62,7 +62,7 @@ async def _check_redis_module_exist(client: redis.Redis, modules: List[dict]): if module["name"] not in installed_modules or int(installed_modules[module["name"]]["ver"]) < int(module["ver"]): error_message = "You must add the RediSearch (>= 2.6) and ReJSON (>= 2.4) modules from Redis Stack. " \ "Please refer to Redis Stack docs: https://redis.io/docs/stack/" - logging.error(error_message) + logger.error(error_message) raise AttributeError(error_message) @@ -84,12 +84,12 @@ async def init(cls, **kwargs): """ try: # Connect to the Redis Client - logging.info("Connecting to Redis") + logger.info("Connecting to Redis") client = redis.Redis( host=REDIS_HOST, port=REDIS_PORT, password=REDIS_PASSWORD ) except Exception as e: - logging.error(f"Error setting up Redis: {e}") + logger.error(f"Error setting up Redis: {e}") raise e await _check_redis_module_exist(client, modules=REDIS_REQUIRED_MODULES) @@ -117,15 +117,15 @@ async def init(cls, **kwargs): try: # Check for existence of RediSearch Index await client.ft(REDIS_INDEX_NAME).info() - logging.info(f"RediSearch index {REDIS_INDEX_NAME} already exists") + logger.info(f"RediSearch index {REDIS_INDEX_NAME} already exists") except: # Create the RediSearch Index - logging.info(f"Creating new RediSearch index {REDIS_INDEX_NAME}") + logger.info(f"Creating new RediSearch index {REDIS_INDEX_NAME}") definition = IndexDefinition( prefix=[REDIS_DOC_PREFIX], index_type=IndexType.JSON ) fields = list(unpack_schema(redisearch_schema)) - logging.info(f"Creating index with fields: {fields}") + logger.info(f"Creating index with fields: {fields}") await client.ft(REDIS_INDEX_NAME).create_index( fields=fields, definition=definition ) @@ -299,10 +299,10 @@ async def _query( results: List[QueryResult] = [] # Gather query results in a pipeline - logging.info(f"Gathering {len(queries)} query results") + logger.info(f"Gathering {len(queries)} query results") for query in queries: - logging.info(f"Query: {query.query}") + logger.debug(f"Query: {query.query}") query_results: List[DocumentChunkWithScore] = [] # Extract Redis query @@ -348,12 +348,12 @@ async def delete( # Delete all vectors from the index if delete_all is True if delete_all: try: - logging.info(f"Deleting all documents from index") + logger.info(f"Deleting all documents from index") await self.client.ft(REDIS_INDEX_NAME).dropindex(True) - logging.info(f"Deleted all documents successfully") + logger.info(f"Deleted all documents successfully") return True except Exception as e: - logging.info(f"Error deleting all documents: {e}") + logger.error(f"Error deleting all documents: {e}") raise e # Delete by filter @@ -365,15 +365,15 @@ async def delete( f"{REDIS_DOC_PREFIX}:{filter.document_id}:*" ) await self._redis_delete(keys) - logging.info(f"Deleted document {filter.document_id} successfully") + logger.info(f"Deleted document {filter.document_id} successfully") except Exception as e: - logging.info(f"Error deleting document {filter.document_id}: {e}") + logger.error(f"Error deleting document {filter.document_id}: {e}") raise e # Delete by explicit ids (Redis keys) if ids: try: - logging.info(f"Deleting document ids {ids}") + logger.info(f"Deleting document ids {ids}") keys = [] # find all keys associated with the document ids for document_id in ids: @@ -382,10 +382,10 @@ async def delete( ) keys.extend(doc_keys) # delete all keys - logging.info(f"Deleting {len(keys)} keys from Redis") + logger.info(f"Deleting {len(keys)} keys from Redis") await self._redis_delete(keys) except Exception as e: - logging.info(f"Error deleting ids: {e}") + logger.error(f"Error deleting ids: {e}") raise e return True diff --git a/datastore/providers/weaviate_datastore.py b/datastore/providers/weaviate_datastore.py index 4baae392a..fe3ae3b56 100644 --- a/datastore/providers/weaviate_datastore.py +++ b/datastore/providers/weaviate_datastore.py @@ -97,7 +97,7 @@ def handle_errors(self, results: Optional[List[dict]]) -> List[str]: continue for message in result["result"]["errors"]["error"]: error_messages.append(message["message"]) - logger.exception(message["message"]) + logger.error(message["message"]) return error_messages diff --git a/datastore/providers/zilliz_datastore.py b/datastore/providers/zilliz_datastore.py index 1db641f63..81f151c43 100644 --- a/datastore/providers/zilliz_datastore.py +++ b/datastore/providers/zilliz_datastore.py @@ -1,5 +1,6 @@ import os +from loguru import logger from typing import Optional from pymilvus import ( connections, @@ -47,7 +48,7 @@ def _create_connection(self): # Connect to the Zilliz instance using the passed in Environment variables self.alias = uuid4().hex connections.connect(alias=self.alias, uri=ZILLIZ_URI, user=ZILLIZ_USER, password=ZILLIZ_PASSWORD, secure=ZILLIZ_USE_SECURITY) # type: ignore - self._print_info("Connect to zilliz cloud server") + logger.info("Connect to zilliz cloud server") def _create_index(self): try: @@ -59,6 +60,6 @@ def _create_index(self): self.col.load() self.search_params = {"metric_type": "IP", "params": {}} except Exception as e: - self._print_err("Failed to create index, error: {}".format(e)) + logger.error("Failed to create index, error: {}".format(e)) diff --git a/examples/authentication-methods/no-auth/main.py b/examples/authentication-methods/no-auth/main.py index 1fd5458b4..961c725c1 100644 --- a/examples/authentication-methods/no-auth/main.py +++ b/examples/authentication-methods/no-auth/main.py @@ -4,6 +4,7 @@ import uvicorn from fastapi import FastAPI, File, Form, HTTPException, Body, UploadFile from fastapi.staticfiles import StaticFiles +from loguru import logger from models.api import ( DeleteRequest, @@ -55,7 +56,7 @@ async def upsert_file( ids = await datastore.upsert([document]) return UpsertResponse(ids=ids) except Exception as e: - print("Error:", e) + logger.error(e) raise HTTPException(status_code=500, detail=f"str({e})") @@ -70,7 +71,7 @@ async def upsert( ids = await datastore.upsert(request.documents) return UpsertResponse(ids=ids) except Exception as e: - print("Error:", e) + logger.error(e) raise HTTPException(status_code=500, detail="Internal Service Error") @@ -87,7 +88,7 @@ async def query_main( ) return QueryResponse(results=results) except Exception as e: - print("Error:", e) + logger.error(e) raise HTTPException(status_code=500, detail="Internal Service Error") @@ -105,7 +106,7 @@ async def query( ) return QueryResponse(results=results) except Exception as e: - print("Error:", e) + logger.error(e) raise HTTPException(status_code=500, detail="Internal Service Error") @@ -129,7 +130,7 @@ async def delete( ) return DeleteResponse(success=success) except Exception as e: - print("Error:", e) + logger.error(e) raise HTTPException(status_code=500, detail="Internal Service Error") diff --git a/examples/memory/main.py b/examples/memory/main.py index 5c96e4289..c94d3f94d 100644 --- a/examples/memory/main.py +++ b/examples/memory/main.py @@ -8,6 +8,7 @@ from fastapi import FastAPI, File, Form, HTTPException, Depends, Body, UploadFile from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from fastapi.staticfiles import StaticFiles +from loguru import logger from models.api import ( DeleteRequest, @@ -71,7 +72,7 @@ async def upsert_file( ids = await datastore.upsert([document]) return UpsertResponse(ids=ids) except Exception as e: - print("Error:", e) + logger.error(e) raise HTTPException(status_code=500, detail=f"str({e})") @@ -87,7 +88,7 @@ async def upsert_main( ids = await datastore.upsert(request.documents) return UpsertResponse(ids=ids) except Exception as e: - print("Error:", e) + logger.error(e) raise HTTPException(status_code=500, detail="Internal Service Error") @@ -105,7 +106,7 @@ async def upsert( ids = await datastore.upsert(request.documents) return UpsertResponse(ids=ids) except Exception as e: - print("Error:", e) + logger.error(e) raise HTTPException(status_code=500, detail="Internal Service Error") @@ -123,7 +124,7 @@ async def query_main( ) return QueryResponse(results=results) except Exception as e: - print("Error:", e) + logger.error(e) raise HTTPException(status_code=500, detail="Internal Service Error") @@ -143,7 +144,7 @@ async def query( ) return QueryResponse(results=results) except Exception as e: - print("Error:", e) + logger.error(e) raise HTTPException(status_code=500, detail="Internal Service Error") @@ -168,7 +169,7 @@ async def delete( ) return DeleteResponse(success=success) except Exception as e: - print("Error:", e) + logger.error(e) raise HTTPException(status_code=500, detail="Internal Service Error") diff --git a/local_server/main.py b/local_server/main.py index 7df685127..81506fd2b 100644 --- a/local_server/main.py +++ b/local_server/main.py @@ -3,6 +3,7 @@ from typing import Optional import uvicorn from fastapi import FastAPI, File, Form, HTTPException, Body, UploadFile +from loguru import logger from models.api import ( DeleteRequest, @@ -82,7 +83,7 @@ async def upsert_file( ids = await datastore.upsert([document]) return UpsertResponse(ids=ids) except Exception as e: - print("Error:", e) + logger.error(e) raise HTTPException(status_code=500, detail=f"str({e})") @@ -97,7 +98,7 @@ async def upsert( ids = await datastore.upsert(request.documents) return UpsertResponse(ids=ids) except Exception as e: - print("Error:", e) + logger.error(e) raise HTTPException(status_code=500, detail="Internal Service Error") @@ -109,7 +110,7 @@ async def query_main(request: QueryRequest = Body(...)): ) return QueryResponse(results=results) except Exception as e: - print("Error:", e) + logger.error(e) raise HTTPException(status_code=500, detail="Internal Service Error") @@ -133,7 +134,7 @@ async def delete( ) return DeleteResponse(success=success) except Exception as e: - print("Error:", e) + logger.error(e) raise HTTPException(status_code=500, detail="Internal Service Error") diff --git a/poetry.lock b/poetry.lock index 702aac934..55cf9e55c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -4750,4 +4750,4 @@ postgresql = ["psycopg2cffi"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "89915adf15b0d7bfb147c5e8dc64abaf2c6bb7db16e9fc2ae13eac2dc4f58267" +content-hash = "33df6345a0d11114e42451f44618e95a9573dc15311e1e64a85a878cd3df773f" diff --git a/pyproject.toml b/pyproject.toml index f39a81ec6..628ab44df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ azure-identity = "^1.12.0" azure-search-documents = {version = "11.4.0a20230509004", source = "azure-sdk-dev"} pgvector = "^0.1.7" psycopg2cffi = {version = "^2.9.0", optional = true} +loguru = "^0.7.0" [tool.poetry.scripts] start = "server.main:start" diff --git a/scripts/process_json/process_json.py b/scripts/process_json/process_json.py index 0dc2f8439..8b9624cbb 100644 --- a/scripts/process_json/process_json.py +++ b/scripts/process_json/process_json.py @@ -3,6 +3,7 @@ import argparse import asyncio +from loguru import logger from models.models import Document, DocumentMetadata from datastore.datastore import DataStore from datastore.factory import get_datastore @@ -28,7 +29,7 @@ async def process_json_dump( # iterate over the data and create document objects for item in data: if len(documents) % 20 == 0: - print(f"Processed {len(documents)} documents") + logger.info(f"Processed {len(documents)} documents") try: # get the id, text, source, source_id, url, created_at and author from the item @@ -42,7 +43,7 @@ async def process_json_dump( author = item.get("author", None) if not text: - print("No document text, skipping...") + logger.info("No document text, skipping...") continue # create a metadata object with the source, source_id, url, created_at and author @@ -53,7 +54,7 @@ async def process_json_dump( created_at=created_at, author=author, ) - print("metadata: ", str(metadata)) + logger.info("metadata: ", str(metadata)) # update metadata with custom values for key, value in custom_metadata.items(): @@ -65,7 +66,7 @@ async def process_json_dump( pii_detected = screen_text_for_pii(text) # if pii detected, print a warning and skip the document if pii_detected: - print("PII detected in document, skipping") + logger.info("PII detected in document, skipping") skipped_items.append(item) # add the skipped item to the list continue @@ -87,7 +88,7 @@ async def process_json_dump( documents.append(document) except Exception as e: # log the error and continue with the next item - print(f"Error processing {item}: {e}") + logger.error(f"Error processing {item}: {e}") skipped_items.append(item) # add the skipped item to the list # do this in batches, the upsert method already batches documents but this allows @@ -95,14 +96,14 @@ async def process_json_dump( for i in range(0, len(documents), DOCUMENT_UPSERT_BATCH_SIZE): # Get the text of the chunks in the current batch batch_documents = documents[i : i + DOCUMENT_UPSERT_BATCH_SIZE] - print(f"Upserting batch of {len(batch_documents)} documents, batch {i}") - print("documents: ", documents) + logger.info(f"Upserting batch of {len(batch_documents)} documents, batch {i}") + logger.info("documents: ", documents) await datastore.upsert(batch_documents) # print the skipped items - print(f"Skipped {len(skipped_items)} items due to errors or PII detection") + logger.info(f"Skipped {len(skipped_items)} items due to errors or PII detection") for item in skipped_items: - print(item) + logger.info(item) async def main(): diff --git a/scripts/process_jsonl/process_jsonl.py b/scripts/process_jsonl/process_jsonl.py index 8795553cd..463871b96 100644 --- a/scripts/process_jsonl/process_jsonl.py +++ b/scripts/process_jsonl/process_jsonl.py @@ -3,6 +3,7 @@ import argparse import asyncio +from loguru import logger from models.models import Document, DocumentMetadata from datastore.datastore import DataStore from datastore.factory import get_datastore @@ -28,7 +29,7 @@ async def process_jsonl_dump( # iterate over the data and create document objects for item in data: if len(documents) % 20 == 0: - print(f"Processed {len(documents)} documents") + logger.info(f"Processed {len(documents)} documents") try: # get the id, text, source, source_id, url, created_at and author from the item @@ -42,7 +43,7 @@ async def process_jsonl_dump( author = item.get("author", None) if not text: - print("No document text, skipping...") + logger.info("No document text, skipping...") continue # create a metadata object with the source, source_id, url, created_at and author @@ -64,7 +65,7 @@ async def process_jsonl_dump( pii_detected = screen_text_for_pii(text) # if pii detected, print a warning and skip the document if pii_detected: - print("PII detected in document, skipping") + logger.info("PII detected in document, skipping") skipped_items.append(item) # add the skipped item to the list continue @@ -86,7 +87,7 @@ async def process_jsonl_dump( documents.append(document) except Exception as e: # log the error and continue with the next item - print(f"Error processing {item}: {e}") + logger.error(f"Error processing {item}: {e}") skipped_items.append(item) # add the skipped item to the list # do this in batches, the upsert method already batches documents but this allows @@ -94,13 +95,13 @@ async def process_jsonl_dump( for i in range(0, len(documents), DOCUMENT_UPSERT_BATCH_SIZE): # Get the text of the chunks in the current batch batch_documents = documents[i : i + DOCUMENT_UPSERT_BATCH_SIZE] - print(f"Upserting batch of {len(batch_documents)} documents, batch {i}") + logger.info(f"Upserting batch of {len(batch_documents)} documents, batch {i}") await datastore.upsert(batch_documents) # print the skipped items - print(f"Skipped {len(skipped_items)} items due to errors or PII detection") + logger.info(f"Skipped {len(skipped_items)} items due to errors or PII detection") for item in skipped_items: - print(item) + logger.info(item) async def main(): diff --git a/scripts/process_zip/process_zip.py b/scripts/process_zip/process_zip.py index cffca2df7..7865c85b5 100644 --- a/scripts/process_zip/process_zip.py +++ b/scripts/process_zip/process_zip.py @@ -5,6 +5,7 @@ import argparse import asyncio +from loguru import logger from models.models import Document, DocumentMetadata, Source from datastore.datastore import DataStore from datastore.factory import get_datastore @@ -32,13 +33,13 @@ async def process_file_dump( for root, dirs, files in os.walk("dump"): for filename in files: if len(documents) % 20 == 0: - print(f"Processed {len(documents)} documents") + logger.info(f"Processed {len(documents)} documents") filepath = os.path.join(root, filename) try: extracted_text = extract_text_from_filepath(filepath) - print(f"extracted_text from {filepath}") + logger.info(f"extracted_text from {filepath}") # create a metadata object with the source and source_id fields metadata = DocumentMetadata( @@ -56,7 +57,7 @@ async def process_file_dump( pii_detected = screen_text_for_pii(extracted_text) # if pii detected, print a warning and skip the document if pii_detected: - print("PII detected in document, skipping") + logger.info("PII detected in document, skipping") skipped_files.append( filepath ) # add the skipped file to the list @@ -80,7 +81,7 @@ async def process_file_dump( documents.append(document) except Exception as e: # log the error and continue with the next file - print(f"Error processing {filepath}: {e}") + logger.error(f"Error processing {filepath}: {e}") skipped_files.append(filepath) # add the skipped file to the list # do this in batches, the upsert method already batches documents but this allows @@ -88,8 +89,8 @@ async def process_file_dump( for i in range(0, len(documents), DOCUMENT_UPSERT_BATCH_SIZE): # Get the text of the chunks in the current batch batch_documents = [doc for doc in documents[i : i + DOCUMENT_UPSERT_BATCH_SIZE]] - print(f"Upserting batch of {len(batch_documents)} documents, batch {i}") - print("documents: ", documents) + logger.info(f"Upserting batch of {len(batch_documents)} documents, batch {i}") + logger.info("documents: ", documents) await datastore.upsert(batch_documents) # delete all files in the dump directory @@ -105,9 +106,9 @@ async def process_file_dump( os.rmdir("dump") # print the skipped files - print(f"Skipped {len(skipped_files)} files due to errors or PII detection") + logger.info(f"Skipped {len(skipped_files)} files due to errors or PII detection") for file in skipped_files: - print(file) + logger.info(file) async def main(): diff --git a/server/main.py b/server/main.py index 3d44ced4f..dc3377a1b 100644 --- a/server/main.py +++ b/server/main.py @@ -4,6 +4,7 @@ from fastapi import FastAPI, File, Form, HTTPException, Depends, Body, UploadFile from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from fastapi.staticfiles import StaticFiles +from loguru import logger from models.api import ( DeleteRequest, @@ -66,7 +67,7 @@ async def upsert_file( ids = await datastore.upsert([document]) return UpsertResponse(ids=ids) except Exception as e: - print("Error:", e) + logger.error(e) raise HTTPException(status_code=500, detail=f"str({e})") @@ -81,7 +82,7 @@ async def upsert( ids = await datastore.upsert(request.documents) return UpsertResponse(ids=ids) except Exception as e: - print("Error:", e) + logger.error(e) raise HTTPException(status_code=500, detail="Internal Service Error") @@ -98,7 +99,7 @@ async def query_main( ) return QueryResponse(results=results) except Exception as e: - print("Error:", e) + logger.error(e) raise HTTPException(status_code=500, detail="Internal Service Error") @@ -117,7 +118,7 @@ async def query( ) return QueryResponse(results=results) except Exception as e: - print("Error:", e) + logger.error(e) raise HTTPException(status_code=500, detail="Internal Service Error") @@ -141,7 +142,7 @@ async def delete( ) return DeleteResponse(success=success) except Exception as e: - print("Error:", e) + logger.error(e) raise HTTPException(status_code=500, detail="Internal Service Error") diff --git a/services/date.py b/services/date.py index 57b2ee5eb..476c7aedb 100644 --- a/services/date.py +++ b/services/date.py @@ -1,4 +1,5 @@ import arrow +from loguru import logger def to_unix_timestamp(date_str: str) -> int: @@ -19,5 +20,5 @@ def to_unix_timestamp(date_str: str) -> int: return int(date_obj.timestamp()) except arrow.parser.ParserError: # If the parsing fails, return the current unix timestamp and print a warning - print(f"Invalid date format: {date_str}") + logger.info(f"Invalid date format: {date_str}") return int(arrow.now().timestamp()) diff --git a/services/extract_metadata.py b/services/extract_metadata.py index deecb677b..8c8d4ae7e 100644 --- a/services/extract_metadata.py +++ b/services/extract_metadata.py @@ -3,6 +3,7 @@ import json from typing import Dict import os +from loguru import logger def extract_metadata_from_document(text: str) -> Dict[str, str]: sources = Source.__members__.keys() @@ -32,7 +33,7 @@ def extract_metadata_from_document(text: str) -> Dict[str, str]: os.environ.get("OPENAI_METADATA_EXTRACTIONMODEL_DEPLOYMENTID") ) # TODO: change to your preferred model name - print(f"completion: {completion}") + logger.info(f"completion: {completion}") try: metadata = json.loads(completion) diff --git a/services/file.py b/services/file.py index 90e0e5ea0..136fc17c5 100644 --- a/services/file.py +++ b/services/file.py @@ -7,6 +7,7 @@ import docx2txt import csv import pptx +from loguru import logger from models.models import Document, DocumentMetadata @@ -38,7 +39,7 @@ def extract_text_from_filepath(filepath: str, mimetype: Optional[str] = None) -> with open(filepath, "rb") as file: extracted_text = extract_text_from_file(file, mimetype) except Exception as e: - print(f"Error: {e}") + logger.error(e) raise e return extracted_text @@ -91,9 +92,9 @@ async def extract_text_from_form_file(file: UploadFile): """Return the text content of a file.""" # get the file body from the upload file object mimetype = file.content_type - print(f"mimetype: {mimetype}") - print(f"file.file: {file.file}") - print("file: ", file) + logger.info(f"mimetype: {mimetype}") + logger.info(f"file.file: {file.file}") + logger.info("file: ", file) file_stream = await file.read() @@ -106,7 +107,7 @@ async def extract_text_from_form_file(file: UploadFile): try: extracted_text = extract_text_from_filepath(temp_file_path, mimetype) except Exception as e: - print(f"Error: {e}") + logger.error(e) os.remove(temp_file_path) raise e diff --git a/services/openai.py b/services/openai.py index 426ad3511..ddc2855ee 100644 --- a/services/openai.py +++ b/services/openai.py @@ -1,6 +1,7 @@ from typing import List import openai import os +from loguru import logger from tenacity import retry, wait_random_exponential, stop_after_attempt @@ -28,7 +29,7 @@ def get_embeddings(texts: List[str]) -> List[List[float]]: response = openai.Embedding.create(input=texts, model="text-embedding-ada-002") else: response = openai.Embedding.create(input=texts, deployment_id=deployment) - + # Extract the embedding data from the response data = response["data"] # type: ignore @@ -68,9 +69,9 @@ def get_chat_completion( deployment_id = deployment_id, messages=messages, ) - + choices = response["choices"] # type: ignore completion = choices[0].message.content.strip() - print(f"Completion: {completion}") + logger.info(f"Completion: {completion}") return completion From 742fdf7cfcd1ca6082de1ee2ee5dc5e14dc00e0f Mon Sep 17 00:00:00 2001 From: Lucas Negritto <33329230+luquitared@users.noreply.github.com> Date: Thu, 1 Jun 2023 13:59:34 -0700 Subject: [PATCH 19/19] Add PR checklist for contributors (#301) * add PR checklist for contributions * add PR checklist template --- .github/pull_request_template.md | 40 +++++++++++++++++++++++++++++++ README.md | 41 ++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..c072578a3 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,40 @@ +## Pull Request (PR) Checklist +If you'd like to contribute, please follow the checklist below when submitting a PR. This will help us review and merge your changes faster! Thank you for contributing! + +1. **Type of PR**: Indicate the type of PR by adding a label in square brackets at the beginning of the title, such as `[Bugfix]`, `[Feature]`, `[Enhancement]`, `[Refactor]`, or `[Documentation]`. + +2. **Short Description**: Provide a brief, informative description of the PR that explains the changes made. + +3. **Issue(s) Linked**: Mention any related issue(s) by using the keyword `Fixes` or `Closes` followed by the respective issue number(s) (e.g., Fixes #123, Closes #456). + +4. **Branch**: Ensure that you have created a new branch for the changes, and it is based on the latest version of the `main` branch. + +5. **Code Changes**: Make sure the code changes are minimal, focused, and relevant to the issue or feature being addressed. + +6. **Commit Messages**: Write clear and concise commit messages that explain the purpose of each commit. + +7. **Tests**: Include unit tests and/or integration tests for any new code or changes to existing code. Make sure all tests pass before submitting the PR. + +8. **Documentation**: Update relevant documentation (e.g., README, inline comments, or external documentation) to reflect any changes made. + +9. **Review Requested**: Request a review from at least one other contributor or maintainer of the repository. + +10. **Video Submission** (For Complex/Large PRs): If your PR introduces significant changes, complexities, or a large number of lines of code, submit a brief video walkthrough along with the PR. The video should explain the purpose of the changes, the logic behind them, and how they address the issue or add the proposed feature. This will help reviewers to better understand your contribution and expedite the review process. + +## Pull Request Naming Convention + +Use the following naming convention for your PR branches: + +``` +/- +``` + +- ``: The type of PR, such as `bugfix`, `feature`, `enhancement`, `refactor`, or `docs`. Multiple types are ok and should appear as , +- ``: A brief description of the changes made, using hyphens to separate words. +- ``: The issue number associated with the changes made (if applicable). + +Example: + +``` +feature/advanced-chunking-strategy-123 +``` \ No newline at end of file diff --git a/README.md b/README.md index 277f30ca2..edd6f516a 100644 --- a/README.md +++ b/README.md @@ -488,6 +488,47 @@ The scripts are: - [`process_jsonl`](scripts/process_jsonl/): This script processes a file dump of documents in a JSONL format and stores them in the vector database with some metadata. The format of the JSONL file should be a newline-delimited JSON file, where each line is a valid JSON object representing a document. The JSON object should have a `text` field and optionally other fields to populate the metadata. You can provide custom metadata as a JSON string and flags to screen for PII and extract metadata. - [`process_zip`](scripts/process_zip/): This script processes a file dump of documents in a zip file and stores them in the vector database with some metadata. The format of the zip file should be a flat zip file folder of docx, pdf, txt, md, pptx or csv files. You can provide custom metadata as a JSON string and flags to screen for PII and extract metadata. +## Pull Request (PR) Checklist +If you'd like to contribute, please follow the checklist below when submitting a PR. This will help us review and merge your changes faster! Thank you for contributing! + +1. **Type of PR**: Indicate the type of PR by adding a label in square brackets at the beginning of the title, such as `[Bugfix]`, `[Feature]`, `[Enhancement]`, `[Refactor]`, or `[Documentation]`. + +2. **Short Description**: Provide a brief, informative description of the PR that explains the changes made. + +3. **Issue(s) Linked**: Mention any related issue(s) by using the keyword `Fixes` or `Closes` followed by the respective issue number(s) (e.g., Fixes #123, Closes #456). + +4. **Branch**: Ensure that you have created a new branch for the changes, and it is based on the latest version of the `main` branch. + +5. **Code Changes**: Make sure the code changes are minimal, focused, and relevant to the issue or feature being addressed. + +6. **Commit Messages**: Write clear and concise commit messages that explain the purpose of each commit. + +7. **Tests**: Include unit tests and/or integration tests for any new code or changes to existing code. Make sure all tests pass before submitting the PR. + +8. **Documentation**: Update relevant documentation (e.g., README, inline comments, or external documentation) to reflect any changes made. + +9. **Review Requested**: Request a review from at least one other contributor or maintainer of the repository. + +10. **Video Submission** (For Complex/Large PRs): If your PR introduces significant changes, complexities, or a large number of lines of code, submit a brief video walkthrough along with the PR. The video should explain the purpose of the changes, the logic behind them, and how they address the issue or add the proposed feature. This will help reviewers to better understand your contribution and expedite the review process. + +## Pull Request Naming Convention + +Use the following naming convention for your PR branches: + +``` +/- +``` + +- ``: The type of PR, such as `bugfix`, `feature`, `enhancement`, `refactor`, or `docs`. Multiple types are ok and should appear as , +- ``: A brief description of the changes made, using hyphens to separate words. +- ``: The issue number associated with the changes made (if applicable). + +Example: + +``` +feature/advanced-chunking-strategy-123 +``` + ## Limitations While the ChatGPT Retrieval Plugin is designed to provide a flexible solution for semantic search and retrieval, it does have some limitations: