diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index e9105edd056771eb02c4eeb052bbcc9836c02065..a69425a5e2da521dad6048bc25bec45be6516fe9 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -14,9 +14,9 @@ jobs:
     strategy:
       matrix:
         python-version:
-          - "3.11"
+          - "3.13"
     env:
-      POETRY_VERSION: "1.8.3"
+      POETRY_VERSION: "1.8.4"
     steps:
       - uses: actions/checkout@v4
       - name: Cache Poetry
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 424c9036240a76e4c73edc07e5480fabc72059bb..ca5a94e9058cedcab1e00554fea84c296a53d9f8 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -6,7 +6,7 @@ on:
   pull_request:
 
 env:
-  POETRY_VERSION: "1.5.1"
+  POETRY_VERSION: "1.8.4"
 
 jobs:
   build:
@@ -14,7 +14,7 @@ jobs:
     strategy:
       matrix:
         python-version:
-          - "3.11"
+          - "3.13"
     steps:
       - uses: actions/checkout@v3
       - name: Cache Poetry
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 57b3eb0bd6a7e86231d6bcc81d402a7e02f5a021..25fcef711ee7b1234783c9ce9fb4e2b50b441ffe 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -13,7 +13,7 @@ jobs:
     - name: Set up Python
       uses: actions/setup-python@v2
       with:
-        python-version: '3.11'
+        python-version: '3.13'
     - name: Install Poetry
       run: |
         curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1
@@ -30,10 +30,10 @@ jobs:
     - name: Set up Python
       uses: actions/setup-python@v2
       with:
-        python-version: '3.11'
+        python-version: '3.13'
     - name: Install Poetry
       run: |
-        curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1
+        curl -sSL https://install.python-poetry.org | python - -y --version 1.8.4
     - name: Publish to PyPI
       run: |
         poetry config repositories.remote https://upload.pypi.org/legacy/
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 99d8ed78afda14f15afa9e7e6598dfa6a55e6089..944cd476f17e6d6221ec623d3a0d21b8c3326ae7 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -4,7 +4,7 @@ on:
   pull_request:
 
 env:
-  POETRY_VERSION: "1.5.1"
+  POETRY_VERSION: "1.8.4"
 
 jobs:
   build:
@@ -15,6 +15,7 @@ jobs:
           - "3.10"
           - "3.11"
           - "3.12"
+          - "3.13"
     steps:
       - uses: actions/checkout@v4
       - name: Cache Poetry
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index b24594f35ab4a09c525946915f027ed267ac78c5..1dfe6d342f478deceb8a02ab83e1cad0569b040d 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,5 +1,5 @@
 default_language_version:
-  python: python3.9
+  python: python3.13
 repos:
   - repo: meta
     hooks:
diff --git a/.python-version b/.python-version
index 2c0733315e415bfb5e5b353f9996ecd964d395b2..24ee5b1be9961e38a503c8e764b7385dbb6ba124 100644
--- a/.python-version
+++ b/.python-version
@@ -1 +1 @@
-3.11
+3.13
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
index 6ad1571dae762366510051c85a744313e8e63184..34e88daf03d6e13aa88604372127a32e213f5b04 100644
--- a/.readthedocs.yaml
+++ b/.readthedocs.yaml
@@ -9,7 +9,7 @@ version: 2
 build:
   os: ubuntu-22.04
   tools:
-    python: "3.12"
+    python: "3.13"
     # You can also specify other tool versions:
     # nodejs: "19"
     # rust: "1.64"
diff --git a/LICENSE b/LICENSE
index 3fa741bbaa7903b52deccb2358ad3b0ac6b24759..06edeb37df222cc1accb948a0353addcc8cfdec5 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2023 Aurelio AI
+Copyright (c) 2024 Aurelio AI
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/Makefile b/Makefile
index 979a97e522253154794c78688035ab5128dd6dba..30e1f6ad0d44a349017eb3b39aa569226bb03f3c 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
 format:
-	poetry run black --target-version py39 -l 88 .
+	poetry run black --target-version py313 -l 88 .
 	poetry run ruff --select I --fix .
 
 PYTHON_FILES=.
@@ -7,7 +7,7 @@ lint: PYTHON_FILES=.
 lint_diff: PYTHON_FILES=$(shell git diff --name-only --diff-filter=d main | grep -E '\.py$$')
 
 lint lint_diff:
-	poetry run black --target-version py311 -l 88 $(PYTHON_FILES) --check
+	poetry run black --target-version py313 -l 88 $(PYTHON_FILES) --check
 	poetry run ruff check .
 	poetry run mypy $(PYTHON_FILES)
 
diff --git a/poetry.lock b/poetry.lock
index 19288adbc6b52ed524059c8bcceaf8cbb9156d9a..1202d622c07a86c3e21d2d6b30bed1500ce7517c 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -13,13 +13,13 @@ files = [
 
 [[package]]
 name = "aiohappyeyeballs"
-version = "2.4.3"
+version = "2.4.4"
 description = "Happy Eyeballs for asyncio"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "aiohappyeyeballs-2.4.3-py3-none-any.whl", hash = "sha256:8a7a83727b2756f394ab2895ea0765a0a8c475e3c71e98d43d76f22b4b435572"},
-    {file = "aiohappyeyeballs-2.4.3.tar.gz", hash = "sha256:75cf88a15106a5002a8eb1dab212525c00d1f4c0fa96e551c9fbe6f09a621586"},
+    {file = "aiohappyeyeballs-2.4.4-py3-none-any.whl", hash = "sha256:a980909d50efcd44795c4afeca523296716d50cd756ddca6af8c65b996e27de8"},
+    {file = "aiohappyeyeballs-2.4.4.tar.gz", hash = "sha256:5fdd7d87889c63183afc18ce9271f9b0a7d32c2303e394468dd45d514a757745"},
 ]
 
 [[package]]
@@ -191,21 +191,18 @@ files = [
 
 [[package]]
 name = "asttokens"
-version = "2.4.1"
+version = "3.0.0"
 description = "Annotate AST trees with source code positions"
 optional = false
-python-versions = "*"
+python-versions = ">=3.8"
 files = [
-    {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"},
-    {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"},
+    {file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"},
+    {file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"},
 ]
 
-[package.dependencies]
-six = ">=1.12.0"
-
 [package.extras]
-astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"]
-test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"]
+astroid = ["astroid (>=2,<4)"]
+test = ["astroid (>=2,<4)", "pytest", "pytest-cov", "pytest-xdist"]
 
 [[package]]
 name = "async-timeout"
@@ -612,13 +609,13 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""}
 
 [[package]]
 name = "cohere"
-version = "5.11.4"
+version = "5.12.0"
 description = ""
 optional = true
 python-versions = "<4.0,>=3.8"
 files = [
-    {file = "cohere-5.11.4-py3-none-any.whl", hash = "sha256:59fb427e5426e0ee1c25b9deec83f0418a1c082240c57007f41384b34cd41552"},
-    {file = "cohere-5.11.4.tar.gz", hash = "sha256:5586335a20de3bf6816f34151f9d9f2928880cdf776c57aae793b5cca58d1826"},
+    {file = "cohere-5.12.0-py3-none-any.whl", hash = "sha256:47f61c6db274f61fb06781da3808d717b4ac4d46b1ee487c2f727450038c14cb"},
+    {file = "cohere-5.12.0.tar.gz", hash = "sha256:52a30edd4f7253b551045eb624df6c14e840c350306c8a69ae322e1f59743969"},
 ]
 
 [package.dependencies]
@@ -1170,7 +1167,10 @@ grpcio-status = [
     {version = ">=1.33.2,<2.0.dev0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""},
     {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""},
 ]
-proto-plus = ">=1.22.3,<2.0.0dev"
+proto-plus = [
+    {version = ">=1.22.3,<2.0.0dev", markers = "python_version < \"3.13\""},
+    {version = ">=1.25.0,<2.0.0dev", markers = "python_version >= \"3.13\""},
+]
 protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0"
 requests = ">=2.18.0,<3.0.0.dev0"
 
@@ -1314,7 +1314,10 @@ files = [
 google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.11.dev0,<3.0.0dev", extras = ["grpc"]}
 google-auth = ">=2.14.1,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0dev"
 grpc-google-iam-v1 = ">=0.12.4,<1.0.0dev"
-proto-plus = ">=1.22.3,<2.0.0dev"
+proto-plus = [
+    {version = ">=1.22.3,<2.0.0dev", markers = "python_version < \"3.13\""},
+    {version = ">=1.25.0,<2.0.0dev", markers = "python_version >= \"3.13\""},
+]
 protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0dev"
 
 [[package]]
@@ -2790,13 +2793,13 @@ sympy = "*"
 
 [[package]]
 name = "openai"
-version = "1.55.2"
+version = "1.55.3"
 description = "The official Python library for the openai API"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "openai-1.55.2-py3-none-any.whl", hash = "sha256:3027c7fa4a33ed759f4a3d076093fcfa1c55658660c889bec33f651e2dc77922"},
-    {file = "openai-1.55.2.tar.gz", hash = "sha256:5cc0b1162b65dcdf670b4b41448f18dd470d2724ca04821ab1e86b6b4e88650b"},
+    {file = "openai-1.55.3-py3-none-any.whl", hash = "sha256:2a235d0e1e312cd982f561b18c27692e253852f4e5fb6ccf08cb13540a9bdaa1"},
+    {file = "openai-1.55.3.tar.gz", hash = "sha256:547e85b94535469f137a779d8770c8c5adebd507c2cc6340ca401a7c4d5d16f0"},
 ]
 
 [package.dependencies]
@@ -5558,5 +5561,5 @@ vision = ["pillow", "torch", "torchvision", "transformers"]
 
 [metadata]
 lock-version = "2.0"
-python-versions = ">=3.9,<3.13"
-content-hash = "d649e614d9c7122c642ed351abf8886cc008960f0fd7664483cfb5dde7865808"
+python-versions = ">=3.9,<3.14"
+content-hash = "8637391565e137e9f5d9d52bd927845242b7dea9217901c1b69f7be6b0b31917"
diff --git a/pyproject.toml b/pyproject.toml
index e1eff0c7657e89c66fc3dfe35fab7b9a88406f68..a914965b233696d97f98eb7bc836b3234455a466 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -8,8 +8,8 @@ packages = [{include = "semantic_router"}]
 license = "MIT"
 
 [tool.poetry.dependencies]
-python = ">=3.9,<3.13"
-pydantic = "^2.5.3"
+python = ">=3.9,<3.14"
+pydantic = "^2.10.2"
 openai = ">=1.10.0,<2.0.0"
 cohere = {version = ">=5.9.4,<6.00", optional = true}
 mistralai= {version = ">=0.0.12,<0.1.0", optional = true}
@@ -18,15 +18,15 @@ colorlog = "^6.8.0"
 pyyaml = "^6.0.1"
 aurelio-sdk = {version = "^0.0.16"}
 pinecone-text = {version = ">=0.7.1,<0.10.0", optional = true}
-torch = {version = ">=2.1.0,<2.6.0", optional = true}
-transformers = {version = ">=4.36.2", optional = true}
-tokenizers = {version = ">=0.19", optional = true}
-llama-cpp-python = {version = ">=0.2.28,<0.2.86", optional = true}
+torch = {version = ">=2.1.0,<2.6.0", optional = true, python = "<3.13" }
+transformers = {version = ">=4.36.2", optional = true, python = "<3.13" }
+tokenizers = {version = ">=0.19", optional = true, python = "<3.13" }
+llama-cpp-python = {version = ">=0.2.28,<0.2.86", optional = true, python = "<3.13" }
 colorama = "^0.4.6"
 pinecone = {version=">=5.0.0", optional = true}
 regex = ">=2023.12.25"
-torchvision = { version = ">=0.17.0,<0.18.0", optional = true}
-pillow = { version = ">=10.2.0,<11.0.0", optional = true}
+torchvision = { version = ">=0.17.0,<0.18.0", optional = true, python = "<3.13" }
+pillow = { version = ">=10.2.0,<11.0.0", optional = true, python = "<3.13" }
 tiktoken = ">=0.6.0,<1.0.0"
 qdrant-client = {version = "^1.11.1", optional = true}
 google-cloud-aiplatform = {version = "^1.45.0", optional = true}
@@ -34,7 +34,7 @@ requests-mock = "^1.12.1"
 boto3 = { version = "^1.34.98", optional = true }
 botocore = {version = "^1.34.110", optional = true}
 aiohttp = "^3.10.11"
-fastembed = {version = "^0.3.0", optional = true}
+fastembed = {version = "^0.3.0", optional = true, python = "<3.13" }
 psycopg2-binary = {version = "^2.9.9", optional = true}
 sphinx = {version = "^7.0.0", optional = true}
 sphinxawesome-theme = {version = "^5.2.0", optional = true}
diff --git a/semantic_router/encoders/aurelio.py b/semantic_router/encoders/aurelio.py
index 8b2501ba4faa0a22a5a18c5500a300e77fa63f95..c50c6c4e51d648221b2421cdf4dcfb3481ef6c35 100644
--- a/semantic_router/encoders/aurelio.py
+++ b/semantic_router/encoders/aurelio.py
@@ -1,6 +1,6 @@
 import os
 from typing import Any, List, Optional
-from pydantic.v1 import Field
+from pydantic import Field
 
 from aurelio_sdk import AurelioClient, AsyncAurelioClient, EmbeddingResponse
 
diff --git a/semantic_router/encoders/base.py b/semantic_router/encoders/base.py
index 0a25f21050e2e5da1ea610fb5194324cdeef9a5f..993093c1a2132c58e769b3035ed7588aefe7a689 100644
--- a/semantic_router/encoders/base.py
+++ b/semantic_router/encoders/base.py
@@ -1,6 +1,6 @@
 from typing import Any, Coroutine, List, Optional
 
-from pydantic.v1 import BaseModel, Field, validator
+from pydantic import BaseModel, Field, field_validator
 import numpy as np
 
 from semantic_router.schema import SparseEmbedding
@@ -14,7 +14,7 @@ class DenseEncoder(BaseModel):
     class Config:
         arbitrary_types_allowed = True
 
-    @validator("score_threshold", pre=True, always=True)
+    @field_validator("score_threshold")
     def set_score_threshold(cls, v):
         return float(v) if v is not None else None
 
diff --git a/semantic_router/encoders/clip.py b/semantic_router/encoders/clip.py
index 65fbdb8f0312d5339d68ae5900542aea2c896693..065ff115bede8d4597b57a62857627ea8099525f 100644
--- a/semantic_router/encoders/clip.py
+++ b/semantic_router/encoders/clip.py
@@ -1,7 +1,7 @@
 from typing import Any, List, Optional
 
 import numpy as np
-from pydantic.v1 import PrivateAttr
+from pydantic import PrivateAttr
 from typing import Dict
 from semantic_router.encoders import DenseEncoder
 
@@ -9,7 +9,6 @@ from semantic_router.encoders import DenseEncoder
 class CLIPEncoder(DenseEncoder):
     name: str = "openai/clip-vit-base-patch16"
     type: str = "huggingface"
-    score_threshold: float = 0.2
     tokenizer_kwargs: Dict = {}
     processor_kwargs: Dict = {}
     model_kwargs: Dict = {}
@@ -21,6 +20,8 @@ class CLIPEncoder(DenseEncoder):
     _Image: Any = PrivateAttr()
 
     def __init__(self, **data):
+        if data.get("score_threshold") is None:
+            data["score_threshold"] = 0.2
         super().__init__(**data)
         self._tokenizer, self._processor, self._model = self._initialize_hf_model()
 
diff --git a/semantic_router/encoders/cohere.py b/semantic_router/encoders/cohere.py
index 04b878141a4b33d742d5bb28a5bfc8bcd1347d19..e919bae110769f87f3c76ff58104ef14a30b34a7 100644
--- a/semantic_router/encoders/cohere.py
+++ b/semantic_router/encoders/cohere.py
@@ -1,7 +1,7 @@
 import os
 from typing import Any, List, Optional
 
-from pydantic.v1 import PrivateAttr
+from pydantic import PrivateAttr
 
 from semantic_router.encoders import DenseEncoder
 from semantic_router.utils.defaults import EncoderDefault
diff --git a/semantic_router/encoders/fastembed.py b/semantic_router/encoders/fastembed.py
index 5cda5e643746c660cd6e820cb767a6a333981b2d..2c9977955994f100366d67ef75a419f83fd55bce 100644
--- a/semantic_router/encoders/fastembed.py
+++ b/semantic_router/encoders/fastembed.py
@@ -1,7 +1,7 @@
 from typing import Any, List, Optional
 
 import numpy as np
-from pydantic.v1 import PrivateAttr
+from pydantic import PrivateAttr
 
 from semantic_router.encoders import DenseEncoder
 
diff --git a/semantic_router/encoders/huggingface.py b/semantic_router/encoders/huggingface.py
index 7c7e56f77f11cf5143437c822ffda338c8f92cf0..f553a082f663bb6dc697c9ecd80eb581fb21be73 100644
--- a/semantic_router/encoders/huggingface.py
+++ b/semantic_router/encoders/huggingface.py
@@ -25,7 +25,7 @@ import time
 import os
 from typing import Any, List, Optional, Dict
 
-from pydantic.v1 import PrivateAttr
+from pydantic import PrivateAttr
 
 from semantic_router.encoders import DenseEncoder
 from semantic_router.utils.logger import logger
@@ -34,7 +34,6 @@ from semantic_router.utils.logger import logger
 class HuggingFaceEncoder(DenseEncoder):
     name: str = "sentence-transformers/all-MiniLM-L6-v2"
     type: str = "huggingface"
-    score_threshold: float = 0.5
     tokenizer_kwargs: Dict = {}
     model_kwargs: Dict = {}
     device: Optional[str] = None
@@ -43,6 +42,8 @@ class HuggingFaceEncoder(DenseEncoder):
     _torch: Any = PrivateAttr()
 
     def __init__(self, **data):
+        if data.get("score_threshold") is None:
+            data["score_threshold"] = 0.5
         super().__init__(**data)
         self._tokenizer, self._model = self._initialize_hf_model()
 
@@ -153,7 +154,6 @@ class HFEndpointEncoder(DenseEncoder):
     name: str = "hugging_face_custom_endpoint"
     huggingface_url: Optional[str] = None
     huggingface_api_key: Optional[str] = None
-    score_threshold: float = 0.8
 
     def __init__(
         self,
@@ -180,6 +180,8 @@ class HFEndpointEncoder(DenseEncoder):
         """
         huggingface_url = huggingface_url or os.getenv("HF_API_URL")
         huggingface_api_key = huggingface_api_key or os.getenv("HF_API_KEY")
+        if score_threshold is None:
+            score_threshold = 0.8
 
         super().__init__(name=name, score_threshold=score_threshold)  # type: ignore
 
diff --git a/semantic_router/encoders/mistral.py b/semantic_router/encoders/mistral.py
index 46bc89aec0ed9ed41fffc5734fa5a7658f6f5cd7..6c3a2f5ed64f2a2c581b1aa8241fb81930c07c42 100644
--- a/semantic_router/encoders/mistral.py
+++ b/semantic_router/encoders/mistral.py
@@ -4,7 +4,7 @@ import os
 from time import sleep
 from typing import Any, List, Optional
 
-from pydantic.v1 import PrivateAttr
+from pydantic import PrivateAttr
 
 from semantic_router.encoders import DenseEncoder
 from semantic_router.utils.defaults import EncoderDefault
diff --git a/semantic_router/encoders/openai.py b/semantic_router/encoders/openai.py
index fb8a83f097e252a7fcd03a356f648077eb267cda..1ac5e56e4e519ffb8f71ba18f7fdcb86d3168f23 100644
--- a/semantic_router/encoders/openai.py
+++ b/semantic_router/encoders/openai.py
@@ -2,7 +2,7 @@ from asyncio import sleep as asleep
 import os
 from time import sleep
 from typing import Any, List, Optional, Union
-from pydantic.v1 import PrivateAttr
+from pydantic import PrivateAttr
 
 import openai
 from openai import OpenAIError
@@ -36,8 +36,8 @@ model_configs = {
 
 
 class OpenAIEncoder(DenseEncoder):
-    client: Optional[openai.Client]
-    async_client: Optional[openai.AsyncClient]
+    _client: Optional[openai.Client] = PrivateAttr(default=None)
+    _async_client: Optional[openai.AsyncClient] = PrivateAttr(default=None)
     dimensions: Union[int, NotGiven] = NotGiven()
     token_limit: int = 8192  # default value, should be replaced by config
     _token_encoder: Any = PrivateAttr()
@@ -77,10 +77,10 @@ class OpenAIEncoder(DenseEncoder):
         if max_retries is not None:
             self.max_retries = max_retries
         try:
-            self.client = openai.Client(
+            self._client = openai.Client(
                 base_url=base_url, api_key=api_key, organization=openai_org_id
             )
-            self.async_client = openai.AsyncClient(
+            self._async_client = openai.AsyncClient(
                 base_url=base_url, api_key=api_key, organization=openai_org_id
             )
         except Exception as e:
@@ -103,7 +103,7 @@ class OpenAIEncoder(DenseEncoder):
             False and a document exceeds the token limit, an error will be
             raised.
         :return: List of embeddings for each document."""
-        if self.client is None:
+        if self._client is None:
             raise ValueError("OpenAI client is not initialized.")
         embeds = None
 
@@ -114,7 +114,7 @@ class OpenAIEncoder(DenseEncoder):
         # Exponential backoff
         for j in range(self.max_retries + 1):
             try:
-                embeds = self.client.embeddings.create(
+                embeds = self._client.embeddings.create(
                     input=docs,
                     model=self.name,
                     dimensions=self.dimensions,
@@ -160,7 +160,7 @@ class OpenAIEncoder(DenseEncoder):
         return text
 
     async def acall(self, docs: List[str], truncate: bool = True) -> List[List[float]]:
-        if self.async_client is None:
+        if self._async_client is None:
             raise ValueError("OpenAI async client is not initialized.")
         embeds = None
 
@@ -171,7 +171,7 @@ class OpenAIEncoder(DenseEncoder):
         # Exponential backoff
         for j in range(self.max_retries + 1):
             try:
-                embeds = await self.async_client.embeddings.create(
+                embeds = await self._async_client.embeddings.create(
                     input=docs,
                     model=self.name,
                     dimensions=self.dimensions,
diff --git a/semantic_router/encoders/vit.py b/semantic_router/encoders/vit.py
index 73cb058281d3c47d452bfa3d5a8e4c92246ae173..dec768e43d3eff3a8eb6a428ea4e5773a488c9ad 100644
--- a/semantic_router/encoders/vit.py
+++ b/semantic_router/encoders/vit.py
@@ -1,6 +1,6 @@
-from typing import Any, List, Optional, Dict
+from typing import Any, Dict, List, Optional
 
-from pydantic.v1 import PrivateAttr
+from pydantic import PrivateAttr
 
 from semantic_router.encoders import DenseEncoder
 
@@ -8,7 +8,6 @@ from semantic_router.encoders import DenseEncoder
 class VitEncoder(DenseEncoder):
     name: str = "google/vit-base-patch16-224"
     type: str = "huggingface"
-    score_threshold: float = 0.5
     processor_kwargs: Dict = {}
     model_kwargs: Dict = {}
     device: Optional[str] = None
@@ -19,6 +18,8 @@ class VitEncoder(DenseEncoder):
     _Image: Any = PrivateAttr()
 
     def __init__(self, **data):
+        if data.get("score_threshold") is None:
+            data["score_threshold"] = 0.5
         super().__init__(**data)
         self._processor, self._model = self._initialize_hf_model()
 
diff --git a/semantic_router/index/base.py b/semantic_router/index/base.py
index 97fe3bd4eadc4066f172ed159d0adef7f88f4e5b..884106c0fbe5b6fa79f201b761d2b18368282be5 100644
--- a/semantic_router/index/base.py
+++ b/semantic_router/index/base.py
@@ -2,7 +2,7 @@ from typing import Any, List, Optional, Tuple, Union, Dict
 import json
 
 import numpy as np
-from pydantic.v1 import BaseModel
+from pydantic import BaseModel
 
 from semantic_router.schema import ConfigParameter, SparseEmbedding, Utterance
 from semantic_router.route import Route
diff --git a/semantic_router/index/pinecone.py b/semantic_router/index/pinecone.py
index 303b47157533fbe71a74231ce550a2485d1f8e30..b4ba144e89fefe739f5a1d0dbc1ac84be58460f6 100644
--- a/semantic_router/index/pinecone.py
+++ b/semantic_router/index/pinecone.py
@@ -8,7 +8,7 @@ import json
 from typing import Any, Dict, List, Optional, Union, Tuple
 
 import numpy as np
-from pydantic.v1 import BaseModel, Field
+from pydantic import BaseModel, Field
 
 from semantic_router.index.base import BaseIndex
 from semantic_router.schema import ConfigParameter, SparseEmbedding
diff --git a/semantic_router/index/postgres.py b/semantic_router/index/postgres.py
index 6d445f2bedabeba63a24712db955d6d4757647a2..71ea32e85fb84531f85178fd74663ad8bce61bcb 100644
--- a/semantic_router/index/postgres.py
+++ b/semantic_router/index/postgres.py
@@ -103,7 +103,6 @@ class PostgresIndex(BaseIndex):
     connection_string: Optional[str] = None
     index_prefix: str = "semantic_router_"
     index_name: str = "index"
-    dimensions: int = 1536
     metric: Metric = Metric.COSINE
     namespace: Optional[str] = ""
     conn: Optional["psycopg2.extensions.connection"] = None
@@ -115,9 +114,9 @@ class PostgresIndex(BaseIndex):
         connection_string: Optional[str] = None,
         index_prefix: str = "semantic_router_",
         index_name: str = "index",
-        dimensions: int = 1536,
         metric: Metric = Metric.COSINE,
         namespace: Optional[str] = "",
+        dimensions: int | None = None,
     ):
         """
         Initializes the Postgres index with the specified parameters.
@@ -135,6 +134,8 @@ class PostgresIndex(BaseIndex):
         :param namespace: An optional namespace for the index.
         :type namespace: Optional[str]
         """
+        if dimensions is None:
+            dimensions = 1536
         super().__init__()
         # try and import psycopg2
         try:
diff --git a/semantic_router/index/qdrant.py b/semantic_router/index/qdrant.py
index 0da5c25eb9dd8891a5d1aa9ef1338b8a07c0caf3..1b23753ba886db48947a14108d1fe7a0ced56c42 100644
--- a/semantic_router/index/qdrant.py
+++ b/semantic_router/index/qdrant.py
@@ -1,7 +1,7 @@
 from typing import Any, Dict, List, Optional, Tuple, Union
 
 import numpy as np
-from pydantic.v1 import Field
+from pydantic import Field
 
 from semantic_router.index.base import BaseIndex
 from semantic_router.schema import ConfigParameter, Metric, SparseEmbedding, Utterance
@@ -40,7 +40,7 @@ class QdrantIndex(BaseIndex):
         default=6334,
         description="Port of the gRPC interface.",
     )
-    prefer_grpc: bool = Field(
+    prefer_grpc: Optional[bool] = Field(
         default=None,
         description="Whether to use gPRC interface whenever possible in methods",
     )
diff --git a/semantic_router/llms/base.py b/semantic_router/llms/base.py
index bbd39b4ef4fdfe2fa6527cba6419d91137fd7946..02df2764502d2d2bdd70135406c5e27c5a900fda 100644
--- a/semantic_router/llms/base.py
+++ b/semantic_router/llms/base.py
@@ -1,7 +1,7 @@
 import json
 from typing import Any, List, Optional, Dict
 
-from pydantic.v1 import BaseModel
+from pydantic import BaseModel
 
 from semantic_router.schema import Message
 from semantic_router.utils.logger import logger
@@ -9,6 +9,8 @@ from semantic_router.utils.logger import logger
 
 class BaseLLM(BaseModel):
     name: str
+    temperature: Optional[float] = 0.0
+    max_tokens: Optional[int] = None
 
     class Config:
         arbitrary_types_allowed = True
diff --git a/semantic_router/llms/cohere.py b/semantic_router/llms/cohere.py
index 05a9b1bdb0f84f175103e741742777525469a3f1..d37f979d623613c91bbc9a433b6b03d7a9f9cb3f 100644
--- a/semantic_router/llms/cohere.py
+++ b/semantic_router/llms/cohere.py
@@ -1,7 +1,7 @@
 import os
 from typing import Any, List, Optional
 
-from pydantic.v1 import PrivateAttr
+from pydantic import PrivateAttr
 
 from semantic_router.llms import BaseLLM
 from semantic_router.schema import Message
diff --git a/semantic_router/llms/llamacpp.py b/semantic_router/llms/llamacpp.py
index 102f7fff7d253533a9b6e195cf5aaa8f7a9d3984..8257593c6f250a8bc1ded5f3af0dbc30a8b44141 100644
--- a/semantic_router/llms/llamacpp.py
+++ b/semantic_router/llms/llamacpp.py
@@ -2,7 +2,7 @@ from contextlib import contextmanager
 from pathlib import Path
 from typing import Any, Optional, List, Dict
 
-from pydantic.v1 import PrivateAttr
+from pydantic import PrivateAttr
 
 from semantic_router.llms.base import BaseLLM
 from semantic_router.schema import Message
@@ -11,8 +11,6 @@ from semantic_router.utils.logger import logger
 
 class LlamaCppLLM(BaseLLM):
     llm: Any
-    temperature: float
-    max_tokens: Optional[int] = 200
     grammar: Optional[Any] = None
     _llama_cpp: Any = PrivateAttr()
 
diff --git a/semantic_router/llms/mistral.py b/semantic_router/llms/mistral.py
index 8ddd1482975f4cb091816bcd197c416ac3e39da7..370fba65756224ded1af6841382f02f63578851d 100644
--- a/semantic_router/llms/mistral.py
+++ b/semantic_router/llms/mistral.py
@@ -1,7 +1,7 @@
 import os
 from typing import Any, List, Optional
 
-from pydantic.v1 import PrivateAttr
+from pydantic import PrivateAttr
 
 from semantic_router.llms import BaseLLM
 from semantic_router.schema import Message
@@ -11,8 +11,6 @@ from semantic_router.utils.logger import logger
 
 class MistralAILLM(BaseLLM):
     _client: Any = PrivateAttr()
-    temperature: Optional[float]
-    max_tokens: Optional[int]
     _mistralai: Any = PrivateAttr()
 
     def __init__(
diff --git a/semantic_router/llms/ollama.py b/semantic_router/llms/ollama.py
index df35ac06a97748ece6a2da86fad58bb7e4a0a52f..f6e9779ea65197e08f47135af8fec0c2dba8f295 100644
--- a/semantic_router/llms/ollama.py
+++ b/semantic_router/llms/ollama.py
@@ -8,22 +8,17 @@ from semantic_router.utils.logger import logger
 
 
 class OllamaLLM(BaseLLM):
-    temperature: Optional[float]
-    llm_name: Optional[str]
-    max_tokens: Optional[int]
-    stream: Optional[bool]
+    stream: bool = False
 
     def __init__(
         self,
-        name: str = "ollama",
+        name: str = "openhermes",
         temperature: float = 0.2,
-        llm_name: str = "openhermes",
         max_tokens: Optional[int] = 200,
         stream: bool = False,
     ):
         super().__init__(name=name)
         self.temperature = temperature
-        self.llm_name = llm_name
         self.max_tokens = max_tokens
         self.stream = stream
 
@@ -31,19 +26,19 @@ class OllamaLLM(BaseLLM):
         self,
         messages: List[Message],
         temperature: Optional[float] = None,
-        llm_name: Optional[str] = None,
+        name: Optional[str] = None,
         max_tokens: Optional[int] = None,
         stream: Optional[bool] = None,
     ) -> str:
         # Use instance defaults if not overridden
         temperature = temperature if temperature is not None else self.temperature
-        llm_name = llm_name if llm_name is not None else self.llm_name
+        name = name if name is not None else self.name
         max_tokens = max_tokens if max_tokens is not None else self.max_tokens
         stream = stream if stream is not None else self.stream
 
         try:
             payload = {
-                "model": llm_name,
+                "model": name,
                 "messages": [m.to_openai() for m in messages],
                 "options": {"temperature": temperature, "num_predict": max_tokens},
                 "format": "json",
diff --git a/semantic_router/llms/openai.py b/semantic_router/llms/openai.py
index dfff80968a588cc1dbf387fc049219b64718f9ec..7dc565e8877a46d74e5fd86d4e3d05e04c52175c 100644
--- a/semantic_router/llms/openai.py
+++ b/semantic_router/llms/openai.py
@@ -1,5 +1,6 @@
 import os
 from typing import List, Optional, Any, Callable, Dict, Union
+from pydantic import PrivateAttr
 
 import openai
 from openai._types import NotGiven, NOT_GIVEN
@@ -21,10 +22,8 @@ from openai.types.chat.chat_completion_message_tool_call import (
 
 
 class OpenAILLM(BaseLLM):
-    client: Optional[openai.OpenAI]
-    async_client: Optional[openai.AsyncOpenAI]
-    temperature: Optional[float]
-    max_tokens: Optional[int]
+    _client: Optional[openai.OpenAI] = PrivateAttr(default=None)
+    _async_client: Optional[openai.AsyncOpenAI] = PrivateAttr(default=None)
 
     def __init__(
         self,
@@ -40,8 +39,8 @@ class OpenAILLM(BaseLLM):
         if api_key is None:
             raise ValueError("OpenAI API key cannot be 'None'.")
         try:
-            self.async_client = openai.AsyncOpenAI(api_key=api_key)
-            self.client = openai.OpenAI(api_key=api_key)
+            self._async_client = openai.AsyncOpenAI(api_key=api_key)
+            self._client = openai.OpenAI(api_key=api_key)
         except Exception as e:
             raise ValueError(
                 f"OpenAI API client failed to initialize. Error: {e}"
@@ -88,14 +87,14 @@ class OpenAILLM(BaseLLM):
         messages: List[Message],
         function_schemas: Optional[List[Dict[str, Any]]] = None,
     ) -> str:
-        if self.client is None:
+        if self._client is None:
             raise ValueError("OpenAI client is not initialized.")
         try:
             tools: Union[List[Dict[str, Any]], NotGiven] = (
                 function_schemas if function_schemas else NOT_GIVEN
             )
 
-            completion = self.client.chat.completions.create(
+            completion = self._client.chat.completions.create(
                 model=self.name,
                 messages=[m.to_openai() for m in messages],
                 temperature=self.temperature,
@@ -132,14 +131,14 @@ class OpenAILLM(BaseLLM):
         messages: List[Message],
         function_schemas: Optional[List[Dict[str, Any]]] = None,
     ) -> str:
-        if self.async_client is None:
+        if self._async_client is None:
             raise ValueError("OpenAI async_client is not initialized.")
         try:
             tools: Union[List[Dict[str, Any]], NotGiven] = (
                 function_schemas if function_schemas is not None else NOT_GIVEN
             )
 
-            completion = await self.async_client.chat.completions.create(
+            completion = await self._async_client.chat.completions.create(
                 model=self.name,
                 messages=[m.to_openai() for m in messages],
                 temperature=self.temperature,
diff --git a/semantic_router/llms/openrouter.py b/semantic_router/llms/openrouter.py
index b00d68a4730c6bf681c0ba4d90a5a79c7febe603..0376236486c06f32775730bb332f13c5f87b0ab8 100644
--- a/semantic_router/llms/openrouter.py
+++ b/semantic_router/llms/openrouter.py
@@ -1,6 +1,8 @@
 import os
 from typing import List, Optional
 
+from pydantic import PrivateAttr
+
 import openai
 
 from semantic_router.llms import BaseLLM
@@ -9,10 +11,8 @@ from semantic_router.utils.logger import logger
 
 
 class OpenRouterLLM(BaseLLM):
-    client: Optional[openai.OpenAI]
-    base_url: Optional[str]
-    temperature: Optional[float]
-    max_tokens: Optional[int]
+    _client: Optional[openai.OpenAI] = PrivateAttr(default=None)
+    _base_url: str = PrivateAttr(default="https://openrouter.ai/api/v1")
 
     def __init__(
         self,
@@ -27,12 +27,12 @@ class OpenRouterLLM(BaseLLM):
                 "OPENROUTER_CHAT_MODEL_NAME", "mistralai/mistral-7b-instruct"
             )
         super().__init__(name=name)
-        self.base_url = base_url
+        self._base_url = base_url
         api_key = openrouter_api_key or os.getenv("OPENROUTER_API_KEY")
         if api_key is None:
             raise ValueError("OpenRouter API key cannot be 'None'.")
         try:
-            self.client = openai.OpenAI(api_key=api_key, base_url=self.base_url)
+            self._client = openai.OpenAI(api_key=api_key, base_url=self._base_url)
         except Exception as e:
             raise ValueError(
                 f"OpenRouter API client failed to initialize. Error: {e}"
@@ -41,10 +41,10 @@ class OpenRouterLLM(BaseLLM):
         self.max_tokens = max_tokens
 
     def __call__(self, messages: List[Message]) -> str:
-        if self.client is None:
+        if self._client is None:
             raise ValueError("OpenRouter client is not initialized.")
         try:
-            completion = self.client.chat.completions.create(
+            completion = self._client.chat.completions.create(
                 model=self.name,
                 messages=[m.to_openai() for m in messages],
                 temperature=self.temperature,
diff --git a/semantic_router/llms/zure.py b/semantic_router/llms/zure.py
index 26b7901f7b595ed1705a81484598f4389a242d26..ba833d044785b775b0da123db554a79f55dd5be8 100644
--- a/semantic_router/llms/zure.py
+++ b/semantic_router/llms/zure.py
@@ -1,5 +1,6 @@
 import os
 from typing import List, Optional
+from pydantic import PrivateAttr
 
 import openai
 
@@ -10,9 +11,7 @@ from semantic_router.utils.logger import logger
 
 
 class AzureOpenAILLM(BaseLLM):
-    client: Optional[openai.AzureOpenAI]
-    temperature: Optional[float]
-    max_tokens: Optional[int]
+    _client: Optional[openai.AzureOpenAI] = PrivateAttr(default=None)
 
     def __init__(
         self,
@@ -33,7 +32,7 @@ class AzureOpenAILLM(BaseLLM):
         if azure_endpoint is None:
             raise ValueError("Azure endpoint API key cannot be 'None'.")
         try:
-            self.client = openai.AzureOpenAI(
+            self._client = openai.AzureOpenAI(
                 api_key=api_key, azure_endpoint=azure_endpoint, api_version=api_version
             )
         except Exception as e:
@@ -42,10 +41,10 @@ class AzureOpenAILLM(BaseLLM):
         self.max_tokens = max_tokens
 
     def __call__(self, messages: List[Message]) -> str:
-        if self.client is None:
+        if self._client is None:
             raise ValueError("AzureOpenAI client is not initialized.")
         try:
-            completion = self.client.chat.completions.create(
+            completion = self._client.chat.completions.create(
                 model=self.name,
                 messages=[m.to_openai() for m in messages],
                 temperature=self.temperature,
diff --git a/semantic_router/route.py b/semantic_router/route.py
index 50d516fa5bbe31d7eff25b8ece8372801649abbd..b1fc5e8b699bcaa7a117fabc7f8654a2ae4f4f9c 100644
--- a/semantic_router/route.py
+++ b/semantic_router/route.py
@@ -2,18 +2,13 @@ import json
 import re
 from typing import Any, Callable, Dict, List, Optional, Union
 
-from pydantic.v1 import BaseModel
+from pydantic import BaseModel
 
 from semantic_router.llms import BaseLLM
 from semantic_router.schema import Message, RouteChoice
 from semantic_router.utils import function_call
 from semantic_router.utils.logger import logger
 
-try:
-    from PIL.Image import Image
-except ImportError:
-    pass
-
 
 def is_valid(route_config: str) -> bool:
     try:
@@ -45,7 +40,7 @@ def is_valid(route_config: str) -> bool:
 
 class Route(BaseModel):
     name: str
-    utterances: Union[List[str], List[Union[Any, "Image"]]]
+    utterances: Union[List[str], List[Any]]
     description: Optional[str] = None
     function_schemas: Optional[List[Dict[str, Any]]] = None
     llm: Optional[BaseLLM] = None
diff --git a/semantic_router/routers/base.py b/semantic_router/routers/base.py
index e4376b421b1d9a87b964443795c2ccd4d2e1cab5..3628fda8553a8a6e6979d6684e2fdfaa1611ead5 100644
--- a/semantic_router/routers/base.py
+++ b/semantic_router/routers/base.py
@@ -4,7 +4,7 @@ import os
 import random
 import hashlib
 from typing import Any, Callable, Dict, List, Optional, Tuple, Union
-from pydantic.v1 import BaseModel, Field
+from pydantic import BaseModel, Field
 
 import numpy as np
 import yaml  # type: ignore
diff --git a/semantic_router/routers/hybrid.py b/semantic_router/routers/hybrid.py
index 36ccd8f94c5e4aa1360462a34ad10382f0c92171..994fcb2dfc8c5e4d85589ca5cf777c2ab7a26d26 100644
--- a/semantic_router/routers/hybrid.py
+++ b/semantic_router/routers/hybrid.py
@@ -1,6 +1,6 @@
 from typing import Dict, List, Optional
 import asyncio
-from pydantic.v1 import Field
+from pydantic import Field
 
 import numpy as np
 
diff --git a/semantic_router/schema.py b/semantic_router/schema.py
index c9e63943823aef590f993c0a2910197d2b78a0f9..adb881fa1cedf56a2e103b7208e2b518e1bf0799 100644
--- a/semantic_router/schema.py
+++ b/semantic_router/schema.py
@@ -3,7 +3,7 @@ from difflib import Differ
 from enum import Enum
 import numpy as np
 from typing import List, Optional, Union, Any, Dict, Tuple
-from pydantic.v1 import BaseModel, Field
+from pydantic import BaseModel, Field
 from semantic_router.utils.logger import logger
 from aurelio_sdk.schema import BM25Embedding
 
diff --git a/semantic_router/utils/function_call.py b/semantic_router/utils/function_call.py
index d03c0798836294400c44ddca280a75e09e500988..f5fd5a87fd100482cd65f3502c3f8f67349e36c2 100644
--- a/semantic_router/utils/function_call.py
+++ b/semantic_router/utils/function_call.py
@@ -1,7 +1,7 @@
 import inspect
 from typing import Any, Callable, Dict, List, Optional, Union
 
-from pydantic.v1 import BaseModel
+from pydantic import BaseModel
 
 from semantic_router.llms import BaseLLM
 from semantic_router.schema import Message, RouteChoice
diff --git a/tests/integration/encoders/test_openai_integration.py b/tests/integration/encoders/test_openai_integration.py
index c7bd8d9add219c14a9937ba2325c34ebf5b679e7..8c557d809d54413ae28a0676d5abe56026d29022 100644
--- a/tests/integration/encoders/test_openai_integration.py
+++ b/tests/integration/encoders/test_openai_integration.py
@@ -21,7 +21,7 @@ class TestOpenAIEncoder:
         os.environ.get("OPENAI_API_KEY") is None, reason="OpenAI API key required"
     )
     def test_openai_encoder_init_success(self, openai_encoder):
-        assert openai_encoder.client is not None
+        assert openai_encoder._client is not None
 
     @pytest.mark.skipif(
         os.environ.get("OPENAI_API_KEY") is None, reason="OpenAI API key required"
@@ -50,7 +50,7 @@ class TestOpenAIEncoder:
     )
     def test_openai_encoder_call_uninitialized_client(self, openai_encoder):
         # Set the client to None to simulate an uninitialized client
-        openai_encoder.client = None
+        openai_encoder._client = None
         with pytest.raises(ValueError) as e:
             openai_encoder(["test document"])
         assert "OpenAI client is not initialized." in str(e.value)
diff --git a/tests/unit/encoders/test_clip.py b/tests/unit/encoders/test_clip.py
index 1cfc7d4313a1a17302dff195ac71e137e3ae4581..297761519f29870723970da7f6ece10e98ed9ff8 100644
--- a/tests/unit/encoders/test_clip.py
+++ b/tests/unit/encoders/test_clip.py
@@ -1,11 +1,14 @@
 import os
 import numpy as np
 import pytest
-import torch
-from PIL import Image
-from unittest.mock import patch
 
-from semantic_router.encoders import CLIPEncoder
+_ = pytest.importorskip("torch")
+
+import torch  # noqa: E402
+from PIL import Image  # noqa: E402
+from unittest.mock import patch  # noqa: E402
+
+from semantic_router.encoders import CLIPEncoder  # noqa: E402
 
 test_model_name = "aurelio-ai/sr-test-clip"
 embed_dim = 64
diff --git a/tests/unit/encoders/test_fastembed.py b/tests/unit/encoders/test_fastembed.py
index 9b0f32296117db7f9a16ea09284082ce5a24498f..f46303c1bb069653c792fcf00c05ee9ced112923 100644
--- a/tests/unit/encoders/test_fastembed.py
+++ b/tests/unit/encoders/test_fastembed.py
@@ -1,5 +1,9 @@
 from semantic_router.encoders import FastEmbedEncoder
 
+import pytest
+
+_ = pytest.importorskip("fastembed")
+
 
 class TestFastEmbedEncoder:
     def test_fastembed_encoder(self):
diff --git a/tests/unit/encoders/test_hfendpointencoder.py b/tests/unit/encoders/test_hfendpointencoder.py
index cb8dd16a661c4194a76935d51390ed4f5b3d3fcc..840df2a9fd0f27ce2cd0dab4ca46b15c994cd5ce 100644
--- a/tests/unit/encoders/test_hfendpointencoder.py
+++ b/tests/unit/encoders/test_hfendpointencoder.py
@@ -1,4 +1,5 @@
 import pytest
+
 from semantic_router.encoders.huggingface import HFEndpointEncoder
 
 
diff --git a/tests/unit/encoders/test_huggingface.py b/tests/unit/encoders/test_huggingface.py
index b615a87d13d64d0a45f6542f137d31ef9fc9a05f..7f496e39f8117dbe4242070e59517acf751ca9a0 100644
--- a/tests/unit/encoders/test_huggingface.py
+++ b/tests/unit/encoders/test_huggingface.py
@@ -4,7 +4,9 @@ import os
 import numpy as np
 import pytest
 
-from semantic_router.encoders.huggingface import HuggingFaceEncoder
+_ = pytest.importorskip("transformers")
+
+from semantic_router.encoders.huggingface import HuggingFaceEncoder  # noqa: E402
 
 test_model_name = "aurelio-ai/sr-test-huggingface"
 
diff --git a/tests/unit/encoders/test_mistral.py b/tests/unit/encoders/test_mistral.py
index 25dba6b759bc2a033c64ae0055e0b1b45d626f48..a236ad167139b5d5994ed8ad2d3610e4b49fc349 100644
--- a/tests/unit/encoders/test_mistral.py
+++ b/tests/unit/encoders/test_mistral.py
@@ -1,6 +1,7 @@
 from unittest.mock import patch
 
 import pytest
+
 from mistralai.exceptions import MistralException
 from mistralai.models.embeddings import EmbeddingObject, EmbeddingResponse, UsageInfo
 
diff --git a/tests/unit/encoders/test_openai.py b/tests/unit/encoders/test_openai.py
index 538e969250051c3da305702a42944903d1cb71cf..96171cce5b462f76dfbd88d7795bfc684ca100c8 100644
--- a/tests/unit/encoders/test_openai.py
+++ b/tests/unit/encoders/test_openai.py
@@ -30,7 +30,7 @@ class TestOpenAIEncoder:
         side_effect = ["fake-model-name", "fake-api-key", "fake-org-id"]
         mocker.patch("os.getenv", side_effect=side_effect)
         encoder = OpenAIEncoder()
-        assert encoder.client is not None
+        assert encoder._client is not None
 
     def test_openai_encoder_init_no_api_key(self, mocker):
         mocker.patch("os.getenv", return_value=None)
@@ -39,7 +39,7 @@ class TestOpenAIEncoder:
 
     def test_openai_encoder_call_uninitialized_client(self, openai_encoder):
         # Set the client to None to simulate an uninitialized client
-        openai_encoder.client = None
+        openai_encoder._client = None
         with pytest.raises(ValueError) as e:
             openai_encoder(["test document"])
         assert "OpenAI client is not initialized." in str(e.value)
@@ -74,7 +74,7 @@ class TestOpenAIEncoder:
 
         responses = [OpenAIError("OpenAI error"), mock_response]
         mocker.patch.object(
-            openai_encoder.client.embeddings, "create", side_effect=responses
+            openai_encoder._client.embeddings, "create", side_effect=responses
         )
         with patch("semantic_router.encoders.openai.sleep", return_value=None):
             embeddings = openai_encoder(["test document"])
@@ -84,7 +84,7 @@ class TestOpenAIEncoder:
         mocker.patch("os.getenv", return_value="fake-api-key")
         mocker.patch("time.sleep", return_value=None)  # To speed up the test
         mocker.patch.object(
-            openai_encoder.client.embeddings,
+            openai_encoder._client.embeddings,
             "create",
             side_effect=Exception("Non-OpenAIError"),
         )
@@ -114,7 +114,7 @@ class TestOpenAIEncoder:
 
         responses = [OpenAIError("OpenAI error"), mock_response]
         mocker.patch.object(
-            openai_encoder.client.embeddings, "create", side_effect=responses
+            openai_encoder._client.embeddings, "create", side_effect=responses
         )
         with patch("semantic_router.encoders.openai.sleep", return_value=None):
             embeddings = openai_encoder(["test document"])
diff --git a/tests/unit/encoders/test_vit.py b/tests/unit/encoders/test_vit.py
index b598a818a3af0753a0627fead4801a56c5b03f54..622b22689f327054392a98cb336ec3f40adee6cd 100644
--- a/tests/unit/encoders/test_vit.py
+++ b/tests/unit/encoders/test_vit.py
@@ -3,10 +3,13 @@ from unittest.mock import patch
 import os
 import numpy as np
 import pytest
-import torch
-from PIL import Image
 
-from semantic_router.encoders import VitEncoder
+_ = pytest.importorskip("torch")
+
+import torch  # noqa: E402
+from PIL import Image  # noqa: E402
+
+from semantic_router.encoders import VitEncoder  # noqa: E402
 
 test_model_name = "aurelio-ai/sr-test-vit"
 embed_dim = 32
diff --git a/tests/unit/llms/test_llm_azure_openai.py b/tests/unit/llms/test_llm_azure_openai.py
index 793091957df7fdd89792aebec43a6fe87763e02b..def792b372790a912b68956bd83e5aa5023a5b7e 100644
--- a/tests/unit/llms/test_llm_azure_openai.py
+++ b/tests/unit/llms/test_llm_azure_openai.py
@@ -12,13 +12,13 @@ def azure_openai_llm(mocker):
 
 class TestOpenAILLM:
     def test_azure_openai_llm_init_with_api_key(self, azure_openai_llm):
-        assert azure_openai_llm.client is not None, "Client should be initialized"
+        assert azure_openai_llm._client is not None, "Client should be initialized"
         assert azure_openai_llm.name == "gpt-4o", "Default name not set correctly"
 
     def test_azure_openai_llm_init_success(self, mocker):
         mocker.patch("os.getenv", return_value="fake-api-key")
         llm = AzureOpenAILLM()
-        assert llm.client is not None
+        assert llm._client is not None
 
     def test_azure_openai_llm_init_without_api_key(self, mocker):
         mocker.patch("os.getenv", return_value=None)
@@ -44,7 +44,7 @@ class TestOpenAILLM:
 
     def test_azure_openai_llm_call_uninitialized_client(self, azure_openai_llm):
         # Set the client to None to simulate an uninitialized client
-        azure_openai_llm.client = None
+        azure_openai_llm._client = None
         with pytest.raises(ValueError) as e:
             llm_input = [Message(role="user", content="test")]
             azure_openai_llm(llm_input)
@@ -83,7 +83,7 @@ class TestOpenAILLM:
 
         mocker.patch("os.getenv", return_value="fake-api-key")
         mocker.patch.object(
-            azure_openai_llm.client.chat.completions,
+            azure_openai_llm._client.chat.completions,
             "create",
             return_value=mock_completion,
         )
diff --git a/tests/unit/llms/test_llm_llamacpp.py b/tests/unit/llms/test_llm_llamacpp.py
index 04a3ad3bc5528d8797ec326b444ba6af2db1c236..fc9813faad19f8c69cf7a21cff9d614d8fc62888 100644
--- a/tests/unit/llms/test_llm_llamacpp.py
+++ b/tests/unit/llms/test_llm_llamacpp.py
@@ -1,10 +1,13 @@
 from unittest.mock import patch
 
 import pytest
-from llama_cpp import Llama
 
-from semantic_router.llms.llamacpp import LlamaCppLLM
-from semantic_router.schema import Message
+_ = pytest.importorskip("llama_cpp")
+
+from llama_cpp import Llama  # noqa: E402
+
+from semantic_router.llms.llamacpp import LlamaCppLLM  # noqa: E402
+from semantic_router.schema import Message  # noqa: E402
 
 
 @pytest.fixture
diff --git a/tests/unit/llms/test_llm_ollama.py b/tests/unit/llms/test_llm_ollama.py
index 369e5f4444789c3bb70988455bb49c7df4792445..91af8a3c7b3a565ade17c5ecb529b080ecebf978 100644
--- a/tests/unit/llms/test_llm_ollama.py
+++ b/tests/unit/llms/test_llm_ollama.py
@@ -11,9 +11,8 @@ def ollama_llm():
 
 class TestOllamaLLM:
     def test_ollama_llm_init_success(self, ollama_llm):
-        assert ollama_llm.name == "ollama"
         assert ollama_llm.temperature == 0.2
-        assert ollama_llm.llm_name == "openhermes"
+        assert ollama_llm.name == "openhermes"
         assert ollama_llm.max_tokens == 200
         assert ollama_llm.stream is False
 
diff --git a/tests/unit/llms/test_llm_openai.py b/tests/unit/llms/test_llm_openai.py
index 4287fc882214c8524c5ce611c11af136eea935c5..ee7e82a9d741d31cc7303756a87e5dd007d32f72 100644
--- a/tests/unit/llms/test_llm_openai.py
+++ b/tests/unit/llms/test_llm_openai.py
@@ -42,13 +42,13 @@ example_function_schema = {
 
 class TestOpenAILLM:
     def test_openai_llm_init_with_api_key(self, openai_llm):
-        assert openai_llm.client is not None, "Client should be initialized"
+        assert openai_llm._client is not None, "Client should be initialized"
         assert openai_llm.name == "gpt-4o", "Default name not set correctly"
 
     def test_openai_llm_init_success(self, mocker):
         mocker.patch("os.getenv", return_value="fake-api-key")
         llm = OpenAILLM()
-        assert llm.client is not None
+        assert llm._client is not None
 
     def test_openai_llm_init_without_api_key(self, mocker):
         mocker.patch("os.getenv", return_value=None)
@@ -57,7 +57,7 @@ class TestOpenAILLM:
 
     def test_openai_llm_call_uninitialized_client(self, openai_llm):
         # Set the client to None to simulate an uninitialized client
-        openai_llm.client = None
+        openai_llm._client = None
         with pytest.raises(ValueError) as e:
             llm_input = [Message(role="user", content="test")]
             openai_llm(llm_input)
@@ -79,7 +79,7 @@ class TestOpenAILLM:
 
         mocker.patch("os.getenv", return_value="fake-api-key")
         mocker.patch.object(
-            openai_llm.client.chat.completions, "create", return_value=mock_completion
+            openai_llm._client.chat.completions, "create", return_value=mock_completion
         )
         llm_input = [Message(role="user", content="test")]
         output = openai_llm(llm_input)
@@ -127,7 +127,7 @@ class TestOpenAILLM:
     #         mocker.MagicMock(function=mocker.MagicMock(arguments="result"))
     #     ]
     #     mocker.patch.object(
-    #         openai_llm.client.chat.completions, "create", return_value=mock_completion
+    #         openai_llm._client.chat.completions, "create", return_value=mock_completion
     #     )
     #     llm_input = [Message(role="user", content="test")]
     #     function_schemas = [{"type": "function", "name": "sample_function"}]
@@ -145,7 +145,7 @@ class TestOpenAILLM:
         mock_completion.choices[0].message.tool_calls = [mock_tool_call]
 
         mocker.patch.object(
-            openai_llm.client.chat.completions, "create", return_value=mock_completion
+            openai_llm._client.chat.completions, "create", return_value=mock_completion
         )
 
         llm_input = [Message(role="user", content="test")]
@@ -160,7 +160,7 @@ class TestOpenAILLM:
         mock_completion = mocker.MagicMock()
         mock_completion.choices[0].message.tool_calls = None
         mocker.patch.object(
-            openai_llm.client.chat.completions, "create", return_value=mock_completion
+            openai_llm._client.chat.completions, "create", return_value=mock_completion
         )
         llm_input = [Message(role="user", content="test")]
         function_schemas = [{"type": "function", "name": "sample_function"}]
@@ -180,7 +180,7 @@ class TestOpenAILLM:
             mocker.MagicMock(function=mocker.MagicMock(arguments=None))
         ]
         mocker.patch.object(
-            openai_llm.client.chat.completions, "create", return_value=mock_completion
+            openai_llm._client.chat.completions, "create", return_value=mock_completion
         )
         llm_input = [Message(role="user", content="test")]
         function_schemas = [{"type": "function", "name": "sample_function"}]
@@ -230,7 +230,7 @@ class TestOpenAILLM:
 
         # Patching the completions.create method to return the mocked completion
         mocker.patch.object(
-            openai_llm.client.chat.completions, "create", return_value=mock_completion
+            openai_llm._client.chat.completions, "create", return_value=mock_completion
         )
 
         # Input message list
diff --git a/tests/unit/llms/test_llm_openrouter.py b/tests/unit/llms/test_llm_openrouter.py
index 9b1ee150f2b301984c24eeb144453cd6a5ea0973..71e874f10124748c5e32e869807dcec2fcb0960d 100644
--- a/tests/unit/llms/test_llm_openrouter.py
+++ b/tests/unit/llms/test_llm_openrouter.py
@@ -12,7 +12,7 @@ def openrouter_llm(mocker):
 
 class TestOpenRouterLLM:
     def test_openrouter_llm_init_with_api_key(self, openrouter_llm):
-        assert openrouter_llm.client is not None, "Client should be initialized"
+        assert openrouter_llm._client is not None, "Client should be initialized"
         assert (
             openrouter_llm.name == "mistralai/mistral-7b-instruct"
         ), "Default name not set correctly"
@@ -20,7 +20,7 @@ class TestOpenRouterLLM:
     def test_openrouter_llm_init_success(self, mocker):
         mocker.patch("os.getenv", return_value="fake-api-key")
         llm = OpenRouterLLM()
-        assert llm.client is not None
+        assert llm._client is not None
 
     def test_openrouter_llm_init_without_api_key(self, mocker):
         mocker.patch("os.getenv", return_value=None)
@@ -29,7 +29,7 @@ class TestOpenRouterLLM:
 
     def test_openrouter_llm_call_uninitialized_client(self, openrouter_llm):
         # Set the client to None to simulate an uninitialized client
-        openrouter_llm.client = None
+        openrouter_llm._client = None
         with pytest.raises(ValueError) as e:
             llm_input = [Message(role="user", content="test")]
             openrouter_llm(llm_input)
@@ -51,7 +51,7 @@ class TestOpenRouterLLM:
 
         mocker.patch("os.getenv", return_value="fake-api-key")
         mocker.patch.object(
-            openrouter_llm.client.chat.completions,
+            openrouter_llm._client.chat.completions,
             "create",
             return_value=mock_completion,
         )
diff --git a/tests/unit/test_schema.py b/tests/unit/test_schema.py
index d0fce781be2bbf948b43b7a48d3047ab7318b92a..bd5d9729a53d40acbbf76ba48b39bd22b92b214e 100644
--- a/tests/unit/test_schema.py
+++ b/tests/unit/test_schema.py
@@ -1,5 +1,5 @@
 import pytest
-from pydantic.v1 import ValidationError
+from pydantic import ValidationError
 
 from semantic_router.schema import (
     Message,