├── images ├── screen.png └── CLIP-search.png ├── .env.example ├── pyproject.toml ├── src ├── clip.py ├── clusterer.py ├── main.py └── db.py ├── README.md ├── .gitignore └── requirements.txt /images/screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AkiRusProd/CLIP-search/HEAD/images/screen.png -------------------------------------------------------------------------------- /images/CLIP-search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AkiRusProd/CLIP-search/HEAD/images/CLIP-search.png -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | DEFAULT_IMAGES_PATH = "Directory with images" 2 | HUGGINGFACE_HUB_CACHE = "Path to Huggingface cache" 3 | INDEX_PATH = "./embed_data" -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "clip-search" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["AkiRusProd "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.10" 10 | faiss-cpu = "1.7.4" 11 | gradio = "4.24.0" 12 | numpy = "1.24.0" 13 | pandas = "1.3.5" 14 | Pillow = "9.5.0" 15 | torch = "2.0.0" 16 | tqdm = "4.65.0" 17 | transformers = "4.30.2" 18 | python-dotenv = "^1.0.1" 19 | 20 | 21 | 22 | [build-system] 23 | requires = ["poetry-core"] 24 | build-backend = "poetry.core.masonry.api" 25 | -------------------------------------------------------------------------------- /src/clip.py: -------------------------------------------------------------------------------- 1 | 2 | import torch 3 | from transformers import CLIPProcessor, CLIPModel, CLIPTokenizer 4 | 5 | 6 | class CLIPSearcher: 7 | def __init__(self, model_id = "openai/clip-vit-base-patch32", device = None): 8 | if device is None: 9 | self.device = "cuda" if torch.cuda.is_available() else "cpu" 10 | else: 11 | self.device = device 12 | 13 | self.model: CLIPModel = CLIPModel.from_pretrained(model_id).to(device) 14 | self.tokenizer: CLIPTokenizer = CLIPTokenizer.from_pretrained(model_id) 15 | self.processor: CLIPProcessor = CLIPProcessor.from_pretrained(model_id) 16 | 17 | def get_text_features(self, text): 18 | inputs = self.tokenizer(text, return_tensors = "pt").to(self.device) 19 | return self.model.get_text_features(**inputs).cpu().detach().numpy() 20 | 21 | def get_image_features(self, image): 22 | inputs = self.processor(images=image, return_tensors="pt").to(self.device) 23 | return self.model.get_image_features(**inputs).cpu().detach().numpy() 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/clusterer.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import faiss 3 | import numpy as np 4 | 5 | class ImageIndexer: 6 | def __init__(self, index_path): 7 | self.index = None 8 | self.index_path = Path(index_path) 9 | 10 | def fit(self, image_embeds, num_clusters=None, m=32): 11 | if isinstance(image_embeds, list): 12 | image_embeds = np.array(image_embeds) 13 | if num_clusters is None: 14 | num_clusters = min(round(np.sqrt(image_embeds.shape[0])), image_embeds.shape[0] // 39) # Emperically determined 15 | 16 | n_dims = image_embeds.shape[1] 17 | 18 | quantizer = faiss.IndexHNSWFlat(n_dims, m) 19 | self.index = faiss.IndexIVFFlat(quantizer, n_dims, num_clusters, faiss.METRIC_INNER_PRODUCT) # METRIC_INNER_PRODUCT: the higher this value, the better; METRIC_L2: other way round 20 | self.index.train(image_embeds) 21 | self.index.add(image_embeds) 22 | 23 | faiss.write_index(self.index, str(self.index_path / 'faiss_clusters.index')) 24 | 25 | return self.index 26 | 27 | def predict(self, image_embeds, embed, k): 28 | if isinstance(image_embeds, list): 29 | image_embeds = np.array(image_embeds) 30 | if self.index is None: 31 | self.index = faiss.read_index(str(self.index_path / 'faiss_clusters.index')) 32 | 33 | D, I = self.index.search(embed, k) 34 | return D[0], I[0], image_embeds[I[0]] 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CLIP-search 2 | This is an impelementation of image search engine using CLIP (Contrastive Language-Image Pre-Training) 3 | 4 | 5 | CLIP is a model that links text and images in a common space and makes it possible to understand the semantic relationship between them. CLIP is trained on a large dataset and is trained to understand how text and images relate to each other. CLIP can be used to classify images, search for similar images, generate text descriptions for images, and other tasks related to text and images. 6 | 7 | 8 |

9 | 10 |

11 | 12 | The current implementation allows you to find images in a local directory based on either text or another image. The CLIP model first computes embeddings for the provided text or image. Then it compares these embeddings with the embeddings of images in the local directory to find similar images. Finally, it returns the top k similar images from the directory. 13 | 14 | This implementation leverages the power of CLIP's embeddings to enable searching for visually or semantically similar images in a local directory. By computing embeddings for both the query and the images in the directory, it can efficiently compare and identify the most similar images based on their embeddings. 15 | 16 | 17 | The implementation provides two methods for finding similar images: 18 | 19 | 1. Slow method: Computes the cosine similarity between the query and all images in the directory individually. Сan be computationally expensive and time-consuming for large directories. 20 | 21 | 2. Fast method: Utilizes clustering to find similar images by comparing the query with cluster centroids. Can be less accurate but faster. Speed matters for large directories. 22 | 23 | You can choose between these methods based on your requirements for speed and accuracy. 24 | 25 | 26 | 27 | 28 | This implementation utilizes the [Hugging Face CLIP model](https://huggingface.co/openai/clip-vit-base-patch32). The [Faiss](https://github.com/facebookresearch/faiss) library is used for clustering, and the web UI is built using [Gradio](https://gradio.app/). 29 | 30 |

31 | 32 |

33 | 34 | 35 | Read more about CLIP at https://github.com/openai/CLIP. 36 | -------------------------------------------------------------------------------- /src/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import gradio as gr 3 | from pathlib import Path 4 | from PIL import Image 5 | from db import SearchMechanism 6 | from clip import CLIPSearcher 7 | from clusterer import ImageIndexer 8 | from dotenv import dotenv_values 9 | 10 | env = dotenv_values('.env') 11 | 12 | path = env['DEFAULT_IMAGES_PATH'] 13 | index_path = env['INDEX_PATH'] 14 | 15 | # your models cache will be stored here 16 | os.environ['HUGGINGFACE_HUB_CACHE'] = env['HUGGINGFACE_HUB_CACHE'] 17 | 18 | 19 | def search_by_text(text, top_k, use_cluster_search): 20 | top_k_df = search_mechanism.query_by_text(text, top_k, use_cluster_search) 21 | images = [Image.open(row['image_path']) for _, row in top_k_df.iterrows()] 22 | 23 | return images 24 | 25 | def search_by_image(image, top_k, use_cluster_search): 26 | top_k_df = search_mechanism.query_by_image(image, top_k, use_cluster_search) 27 | images = [Image.open(row['image_path']) for _, row in top_k_df.iterrows()] 28 | 29 | return images 30 | 31 | def scan_dir(path): 32 | if path is None or not os.path.exists(path): 33 | return gr.Info("Path does not exist") 34 | 35 | search_mechanism.scan_directory(Path(path)) 36 | 37 | return path 38 | 39 | clip_searcher = CLIPSearcher() 40 | image_indexer = ImageIndexer(index_path) 41 | search_mechanism = SearchMechanism(clip_searcher, image_indexer) 42 | 43 | with gr.Blocks() as webui: 44 | gr.Markdown("CLIP Searcher") 45 | 46 | with gr.Row(): 47 | with gr.Column(): 48 | text = gr.Textbox(label = "Text", info = "Text to search") 49 | 50 | with gr.Row(): 51 | image = gr.Image(label = "Image") 52 | 53 | with gr.Column(): 54 | top_k_slider = gr.Slider(label="Top K", minimum=1, maximum=50, step=1, value=5, info = "Top K closest results to the query") 55 | use_cluster_search = gr.Checkbox(label="Use cluster search", value=False, info="Faster embedding search using clusters, may be less accurate") 56 | search_by_text_btn = gr.Button("Search by text") 57 | search_by_image_btn = gr.Button("Search by image") 58 | 59 | path = gr.Textbox(label = "Path", info = "Path with images to scan", value=path) 60 | scan_dir_btn = gr.Button("Scan directory", variant="primary") 61 | 62 | 63 | gallery = gr.Gallery(label = "Gallery", show_label=False, columns=3, rows = 2, height="auto", preview = False) 64 | 65 | search_by_text_btn.click( 66 | search_by_text, 67 | inputs = [text, top_k_slider, use_cluster_search], 68 | outputs = gallery 69 | ) 70 | 71 | search_by_image_btn.click( 72 | search_by_image, 73 | inputs = [image, top_k_slider, use_cluster_search], 74 | outputs = gallery 75 | ) 76 | 77 | scan_dir_btn.click( 78 | scan_dir, 79 | inputs = [path], 80 | outputs = path 81 | ) 82 | 83 | webui.queue() 84 | webui.launch() -------------------------------------------------------------------------------- /src/db.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import pandas as pd 3 | import numpy as np 4 | import torch 5 | from tqdm import tqdm 6 | from clip import CLIPSearcher 7 | from clusterer import ImageIndexer 8 | from PIL import Image 9 | 10 | tqdm.pandas() 11 | 12 | 13 | class SearchMechanism: 14 | def __init__(self, clip_searcher: CLIPSearcher, image_indexer: ImageIndexer) -> None: 15 | self.clip_searcher = clip_searcher 16 | self.image_indexer = image_indexer 17 | self.index_path = Path(self.image_indexer.index_path) 18 | 19 | self.df: pd.DataFrame = None 20 | self.df_image_embeds: list = None 21 | 22 | self.load_db() 23 | 24 | def load_db(self): 25 | try: 26 | if not self.index_path.exists(): 27 | self.index_path.mkdir(parents=True, exist_ok=True) 28 | 29 | df_path = self.index_path / 'df.csv' 30 | embeds_path = self.index_path / 'df_image_embeds.npy' 31 | 32 | if df_path.exists() and embeds_path.exists(): 33 | self.df = pd.read_csv(df_path, sep='\t') 34 | self.df_image_embeds = [x.flatten() for x in np.load(embeds_path)] 35 | except Exception as e: 36 | print(f"Error loading database: {e}") 37 | 38 | def scan_directory(self, path: Path): 39 | if path is None or not path.exists(): 40 | raise Exception("Path does not exist") 41 | 42 | df = pd.DataFrame(columns=['image_path']) 43 | df_image_embeds = [] 44 | 45 | image_files = list(path.iterdir()) 46 | for i, img_path in tqdm(enumerate(image_files), total=len(image_files)): 47 | df.loc[i, 'image_path'] = str(img_path) 48 | df_image_embeds.append(self.clip_searcher.get_image_features(Image.open(img_path)).flatten()) 49 | 50 | df.to_csv(self.index_path / 'df.csv', sep='\t', index=False) 51 | np.save(self.index_path / 'df_image_embeds.npy', df_image_embeds) 52 | 53 | self.image_indexer.fit(df_image_embeds) 54 | 55 | self.load_db() 56 | 57 | def query_by_embeds(self, embeds: np.ndarray, top_k: int = 5, use_cluster_search: bool = False): 58 | if self.df is None or self.df_image_embeds is None: 59 | return 60 | 61 | df = self.df 62 | 63 | if not use_cluster_search: 64 | df['cos_sim'] = pd.Series(self.df_image_embeds).progress_apply( 65 | lambda x: torch.nn.functional.cosine_similarity(torch.tensor(x), torch.tensor(embeds)) 66 | ) 67 | df = df.sort_values(by='cos_sim', ascending=False).head(top_k) 68 | 69 | return df.reset_index(drop=True) 70 | else: 71 | score, ids, _ = self.image_indexer.predict(self.df_image_embeds, embeds, top_k) 72 | 73 | ids = [id for id in ids if id != -1] 74 | score = score[:len(ids)] 75 | 76 | df = df.iloc[ids].copy() 77 | df['score'] = score 78 | 79 | df = df.sort_values(by='score', ascending=False) 80 | 81 | return df.reset_index(drop=True) 82 | 83 | def query_by_text(self, text: str, top_k: int = 5, use_cluster_search: bool = False): 84 | text_embeds = self.clip_searcher.get_text_features(text) 85 | 86 | return self.query_by_embeds(text_embeds, top_k, use_cluster_search) 87 | 88 | def query_by_image(self, image, top_k: int = 5, use_cluster_search: bool = False): 89 | if isinstance(image, str): 90 | image = Image.open(image) 91 | 92 | image_embeds = self.clip_searcher.get_image_features(image) 93 | 94 | return self.query_by_embeds(image_embeds, top_k, use_cluster_search) 95 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | 162 | test.py 163 | embed_data/ -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiofiles==23.2.1 ; python_version >= "3.10" and python_version < "4.0" 2 | altair==5.3.0 ; python_version >= "3.10" and python_version < "4.0" 3 | annotated-types==0.6.0 ; python_version >= "3.10" and python_version < "4.0" 4 | anyio==4.3.0 ; python_version >= "3.10" and python_version < "4.0" 5 | attrs==23.2.0 ; python_version >= "3.10" and python_version < "4.0" 6 | certifi==2024.2.2 ; python_version >= "3.10" and python_version < "4.0" 7 | charset-normalizer==3.3.2 ; python_version >= "3.10" and python_version < "4.0" 8 | click==8.1.7 ; python_version >= "3.10" and python_version < "4.0" and sys_platform != "emscripten" 9 | cmake==3.29.0.1 ; platform_system == "Linux" and platform_machine == "x86_64" and python_version >= "3.10" and python_version < "4.0" 10 | colorama==0.4.6 ; python_version >= "3.10" and python_version < "4.0" and platform_system == "Windows" 11 | contourpy==1.2.0 ; python_version >= "3.10" and python_version < "4.0" 12 | cycler==0.12.1 ; python_version >= "3.10" and python_version < "4.0" 13 | exceptiongroup==1.2.0 ; python_version >= "3.10" and python_version < "3.11" 14 | faiss-cpu==1.7.4 ; python_version >= "3.10" and python_version < "4.0" 15 | fastapi==0.110.0 ; python_version >= "3.10" and python_version < "4.0" 16 | ffmpy==0.3.2 ; python_version >= "3.10" and python_version < "4.0" 17 | filelock==3.13.3 ; python_version >= "3.10" and python_version < "4.0" 18 | fonttools==4.50.0 ; python_version >= "3.10" and python_version < "4.0" 19 | fsspec==2024.3.1 ; python_version >= "3.10" and python_version < "4.0" 20 | gradio-client==0.14.0 ; python_version >= "3.10" and python_version < "4.0" 21 | gradio==4.24.0 ; python_version >= "3.10" and python_version < "4.0" 22 | h11==0.14.0 ; python_version >= "3.10" and python_version < "4.0" 23 | httpcore==1.0.5 ; python_version >= "3.10" and python_version < "4.0" 24 | httpx==0.27.0 ; python_version >= "3.10" and python_version < "4.0" 25 | huggingface-hub==0.22.2 ; python_version >= "3.10" and python_version < "4.0" 26 | idna==3.6 ; python_version >= "3.10" and python_version < "4.0" 27 | importlib-resources==6.4.0 ; python_version >= "3.10" and python_version < "4.0" 28 | jinja2==3.1.3 ; python_version >= "3.10" and python_version < "4.0" 29 | jsonschema-specifications==2023.12.1 ; python_version >= "3.10" and python_version < "4.0" 30 | jsonschema==4.21.1 ; python_version >= "3.10" and python_version < "4.0" 31 | kiwisolver==1.4.5 ; python_version >= "3.10" and python_version < "4.0" 32 | lit==18.1.2 ; platform_system == "Linux" and platform_machine == "x86_64" and python_version >= "3.10" and python_version < "4.0" 33 | markdown-it-py==3.0.0 ; python_version >= "3.10" and python_version < "4.0" and sys_platform != "emscripten" 34 | markupsafe==2.1.5 ; python_version >= "3.10" and python_version < "4.0" 35 | matplotlib==3.8.3 ; python_version >= "3.10" and python_version < "4.0" 36 | mdurl==0.1.2 ; python_version >= "3.10" and python_version < "4.0" and sys_platform != "emscripten" 37 | mpmath==1.3.0 ; python_version >= "3.10" and python_version < "4.0" 38 | networkx==3.2.1 ; python_version >= "3.10" and python_version < "4.0" 39 | numpy==1.24.0 ; python_version >= "3.10" and python_version < "4.0" 40 | nvidia-cublas-cu11==11.10.3.66 ; platform_system == "Linux" and platform_machine == "x86_64" and python_version >= "3.10" and python_version < "4.0" 41 | nvidia-cuda-cupti-cu11==11.7.101 ; platform_system == "Linux" and platform_machine == "x86_64" and python_version >= "3.10" and python_version < "4.0" 42 | nvidia-cuda-nvrtc-cu11==11.7.99 ; platform_system == "Linux" and platform_machine == "x86_64" and python_version >= "3.10" and python_version < "4.0" 43 | nvidia-cuda-runtime-cu11==11.7.99 ; platform_system == "Linux" and platform_machine == "x86_64" and python_version >= "3.10" and python_version < "4.0" 44 | nvidia-cudnn-cu11==8.5.0.96 ; platform_system == "Linux" and platform_machine == "x86_64" and python_version >= "3.10" and python_version < "4.0" 45 | nvidia-cufft-cu11==10.9.0.58 ; platform_system == "Linux" and platform_machine == "x86_64" and python_version >= "3.10" and python_version < "4.0" 46 | nvidia-curand-cu11==10.2.10.91 ; platform_system == "Linux" and platform_machine == "x86_64" and python_version >= "3.10" and python_version < "4.0" 47 | nvidia-cusolver-cu11==11.4.0.1 ; platform_system == "Linux" and platform_machine == "x86_64" and python_version >= "3.10" and python_version < "4.0" 48 | nvidia-cusparse-cu11==11.7.4.91 ; platform_system == "Linux" and platform_machine == "x86_64" and python_version >= "3.10" and python_version < "4.0" 49 | nvidia-nccl-cu11==2.14.3 ; platform_system == "Linux" and platform_machine == "x86_64" and python_version >= "3.10" and python_version < "4.0" 50 | nvidia-nvtx-cu11==11.7.91 ; platform_system == "Linux" and platform_machine == "x86_64" and python_version >= "3.10" and python_version < "4.0" 51 | orjson==3.10.0 ; python_version >= "3.10" and python_version < "4.0" 52 | packaging==24.0 ; python_version >= "3.10" and python_version < "4.0" 53 | pandas==1.3.5 ; python_version >= "3.10" and python_version < "4.0" 54 | pillow==9.5.0 ; python_version >= "3.10" and python_version < "4.0" 55 | pydantic-core==2.16.3 ; python_version >= "3.10" and python_version < "4.0" 56 | pydantic==2.6.4 ; python_version >= "3.10" and python_version < "4.0" 57 | pydub==0.25.1 ; python_version >= "3.10" and python_version < "4.0" 58 | pygments==2.17.2 ; python_version >= "3.10" and python_version < "4.0" and sys_platform != "emscripten" 59 | pyparsing==3.1.2 ; python_version >= "3.10" and python_version < "4.0" 60 | python-dateutil==2.9.0.post0 ; python_version >= "3.10" and python_version < "4.0" 61 | python-dotenv==1.0.1 ; python_version >= "3.10" and python_version < "4.0" 62 | python-multipart==0.0.9 ; python_version >= "3.10" and python_version < "4.0" 63 | pytz==2024.1 ; python_version >= "3.10" and python_version < "4.0" 64 | pyyaml==6.0.1 ; python_version >= "3.10" and python_version < "4.0" 65 | referencing==0.34.0 ; python_version >= "3.10" and python_version < "4.0" 66 | regex==2023.12.25 ; python_version >= "3.10" and python_version < "4.0" 67 | requests==2.31.0 ; python_version >= "3.10" and python_version < "4.0" 68 | rich==13.7.1 ; python_version >= "3.10" and python_version < "4.0" and sys_platform != "emscripten" 69 | rpds-py==0.18.0 ; python_version >= "3.10" and python_version < "4.0" 70 | ruff==0.3.4 ; python_version >= "3.10" and python_version < "4.0" and sys_platform != "emscripten" 71 | safetensors==0.4.2 ; python_version >= "3.10" and python_version < "4.0" 72 | semantic-version==2.10.0 ; python_version >= "3.10" and python_version < "4.0" 73 | setuptools==69.2.0 ; platform_system == "Linux" and platform_machine == "x86_64" and python_version >= "3.10" and python_version < "4.0" 74 | shellingham==1.5.4 ; python_version >= "3.10" and python_version < "4.0" and sys_platform != "emscripten" 75 | six==1.16.0 ; python_version >= "3.10" and python_version < "4.0" 76 | sniffio==1.3.1 ; python_version >= "3.10" and python_version < "4.0" 77 | starlette==0.36.3 ; python_version >= "3.10" and python_version < "4.0" 78 | sympy==1.12 ; python_version >= "3.10" and python_version < "4.0" 79 | tokenizers==0.13.3 ; python_version >= "3.10" and python_version < "4.0" 80 | tomlkit==0.12.0 ; python_version >= "3.10" and python_version < "4.0" 81 | toolz==0.12.1 ; python_version >= "3.10" and python_version < "4.0" 82 | torch==2.0.0 ; python_version >= "3.10" and python_version < "4.0" 83 | tqdm==4.65.0 ; python_version >= "3.10" and python_version < "4.0" 84 | transformers==4.30.2 ; python_version >= "3.10" and python_version < "4.0" 85 | triton==2.0.0 ; platform_system == "Linux" and platform_machine == "x86_64" and python_version >= "3.10" and python_version < "4.0" 86 | typer-cli==0.12.0 ; python_version >= "3.10" and python_version < "4.0" and sys_platform != "emscripten" 87 | typer-slim[standard]==0.12.0 ; python_version >= "3.10" and python_version < "4.0" and sys_platform != "emscripten" 88 | typer[all]==0.12.0 ; python_version >= "3.10" and python_version < "4.0" and sys_platform != "emscripten" 89 | typing-extensions==4.10.0 ; python_version >= "3.10" and python_version < "4.0" 90 | urllib3==2.2.1 ; python_version >= "3.10" and python_version < "4.0" 91 | uvicorn==0.29.0 ; python_version >= "3.10" and python_version < "4.0" and sys_platform != "emscripten" 92 | websockets==11.0.3 ; python_version >= "3.10" and python_version < "4.0" 93 | wheel==0.43.0 ; platform_system == "Linux" and platform_machine == "x86_64" and python_version >= "3.10" and python_version < "4.0" 94 | --------------------------------------------------------------------------------