diff --git a/docs/indexes/pinecone-sync-routes.ipynb b/docs/indexes/pinecone-sync-routes.ipynb
index 6cb9b4eb7df99602b6adb60b73d1284dea0e36f4..d2ba412f552199f0a2b4178e962d5861f3a4a8f3 100644
--- a/docs/indexes/pinecone-sync-routes.ipynb
+++ b/docs/indexes/pinecone-sync-routes.ipynb
@@ -135,15 +135,6 @@
     "The `RouteLayer` class supports both sync and async operations by default, so we initialize as usual:"
    ]
   },
-  {
-   "cell_type": "code",
-   "execution_count": 5,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "encoder.score_threshold = None"
-   ]
-  },
   {
    "cell_type": "code",
    "execution_count": 5,
@@ -153,18 +144,18 @@
      "name": "stderr",
      "output_type": "stream",
      "text": [
-      "\u001b[33m2024-11-23 23:10:13 WARNING semantic_router.utils.logger TEMP | add:\n",
+      "\u001b[33m2024-11-23 23:46:42 WARNING semantic_router.utils.logger TEMP | add:\n",
+      "chitchat: how are things going?\n",
+      "chitchat: how's the weather today?\n",
+      "chitchat: let's go to the chippy\n",
+      "chitchat: lovely weather today\n",
+      "chitchat: the weather is horrendous\u001b[0m\n",
+      "\u001b[33m2024-11-23 23:46:50 WARNING semantic_router.utils.logger TEMP | add:\n",
       "chitchat: how are things going?\n",
       "chitchat: how's the weather today?\n",
       "chitchat: let's go to the chippy\n",
       "chitchat: lovely weather today\n",
-      "chitchat: the weather is horrendous\n",
-      "politics: don't you just hate the president\n",
-      "politics: don't you just love the president\n",
-      "politics: isn't politics the best thing ever\n",
-      "politics: they will save the country!\n",
-      "politics: they're going to destroy this country!\n",
-      "politics: why don't you tell me about your political opinions\u001b[0m\n"
+      "chitchat: the weather is horrendous\u001b[0m\n"
      ]
     }
    ],
@@ -172,10 +163,7 @@
     "from semantic_router.routers import RouteLayer\n",
     "import time\n",
     "\n",
-    "rl = RouteLayer(\n",
-    "    encoder=encoder, routes=routes, index=pc_index,\n",
-    "    auto_sync=\"local\"\n",
-    ")\n",
+    "rl = RouteLayer(encoder=encoder, routes=routes, index=pc_index, auto_sync=\"local\")\n",
     "# due to pinecone indexing latency we wait 3 seconds\n",
     "time.sleep(3)"
    ]
@@ -216,7 +204,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 8,
+   "execution_count": 7,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -235,7 +223,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 9,
+   "execution_count": 8,
    "metadata": {},
    "outputs": [
     {
@@ -244,7 +232,7 @@
        "False"
       ]
      },
-     "execution_count": 9,
+     "execution_count": 8,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -262,7 +250,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 10,
+   "execution_count": 9,
    "metadata": {},
    "outputs": [
     {
@@ -281,7 +269,7 @@
        " \"  politics: why don't you tell me about your political opinions\"]"
       ]
      },
-     "execution_count": 10,
+     "execution_count": 9,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -306,7 +294,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 11,
+   "execution_count": 10,
    "metadata": {},
    "outputs": [
     {
@@ -325,7 +313,7 @@
        " Utterance(route='politics', utterance=\"why don't you tell me about your political opinions\", function_schemas=None, metadata={}, diff_tag=' ')]"
       ]
      },
-     "execution_count": 11,
+     "execution_count": 10,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -337,7 +325,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 12,
+   "execution_count": 11,
    "metadata": {},
    "outputs": [
     {
@@ -351,7 +339,7 @@
        " Utterance(route='politics', utterance='they will save the country!', function_schemas=None, metadata={}, diff_tag=' ')]"
       ]
      },
-     "execution_count": 12,
+     "execution_count": 11,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -370,7 +358,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 13,
+   "execution_count": 12,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -390,7 +378,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 14,
+   "execution_count": 13,
    "metadata": {},
    "outputs": [
     {
@@ -409,7 +397,7 @@
        " Utterance(route='politics', utterance=\"why don't you tell me about your political opinions\", function_schemas=None, metadata={}, diff_tag=' ')]"
       ]
      },
-     "execution_count": 14,
+     "execution_count": 13,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -435,7 +423,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 15,
+   "execution_count": 14,
    "metadata": {},
    "outputs": [
     {
@@ -448,7 +436,7 @@
        " Utterance(route='chitchat', utterance='the weather is horrendous', function_schemas=None, metadata={}, diff_tag='+')]"
       ]
      },
-     "execution_count": 15,
+     "execution_count": 14,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -466,7 +454,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 16,
+   "execution_count": 15,
    "metadata": {},
    "outputs": [
     {
@@ -475,7 +463,7 @@
        "[]"
       ]
      },
-     "execution_count": 16,
+     "execution_count": 15,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -493,7 +481,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 17,
+   "execution_count": 16,
    "metadata": {},
    "outputs": [
     {
@@ -507,7 +495,7 @@
        " Utterance(route='politics', utterance=\"why don't you tell me about your political opinions\", function_schemas=None, metadata={}, diff_tag=' ')]"
       ]
      },
-     "execution_count": 17,
+     "execution_count": 16,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -546,7 +534,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 18,
+   "execution_count": 17,
    "metadata": {},
    "outputs": [
     {
@@ -561,7 +549,7 @@
        " 'local': {'upsert': [], 'delete': []}}"
       ]
      },
-     "execution_count": 18,
+     "execution_count": 17,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -572,7 +560,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 19,
+   "execution_count": 18,
    "metadata": {},
    "outputs": [
     {
@@ -587,7 +575,7 @@
        "  'delete': []}}"
       ]
      },
-     "execution_count": 19,
+     "execution_count": 18,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -598,7 +586,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 20,
+   "execution_count": 19,
    "metadata": {},
    "outputs": [
     {
@@ -613,7 +601,7 @@
        "  'delete': []}}"
       ]
      },
-     "execution_count": 20,
+     "execution_count": 19,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -624,14 +612,14 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 21,
+   "execution_count": 20,
    "metadata": {},
    "outputs": [
     {
      "name": "stderr",
      "output_type": "stream",
      "text": [
-      "\u001b[32m2024-11-23 23:14:16 INFO semantic_router.utils.logger local_only_mapper: {}\u001b[0m\n"
+      "\u001b[32m2024-11-23 23:47:11 INFO semantic_router.utils.logger local_only_mapper: {}\u001b[0m\n"
      ]
     },
     {
@@ -646,7 +634,7 @@
        " 'local': {'upsert': [], 'delete': []}}"
       ]
      },
-     "execution_count": 21,
+     "execution_count": 20,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -657,7 +645,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 22,
+   "execution_count": 21,
    "metadata": {},
    "outputs": [
     {
@@ -677,7 +665,7 @@
        "  'delete': []}}"
       ]
      },
-     "execution_count": 22,
+     "execution_count": 21,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -695,14 +683,14 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 23,
+   "execution_count": 22,
    "metadata": {},
    "outputs": [
     {
      "name": "stderr",
      "output_type": "stream",
      "text": [
-      "\u001b[33m2024-11-23 23:14:25 WARNING semantic_router.utils.logger TEMP | _remove_and_sync:\n",
+      "\u001b[33m2024-11-23 23:47:15 WARNING semantic_router.utils.logger TEMP | _remove_and_sync:\n",
       "chitchat: ['how are things going?', \"how's the weather today?\", \"let's go to the chippy\", 'lovely weather today', 'the weather is horrendous']\u001b[0m\n"
      ]
     }
@@ -714,16 +702,16 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 24,
+   "execution_count": 23,
    "metadata": {},
    "outputs": [
     {
      "data": {
       "text/plain": [
-       "False"
+       "True"
       ]
      },
-     "execution_count": 24,
+     "execution_count": 23,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -742,7 +730,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 25,
+   "execution_count": 24,
    "metadata": {},
    "outputs": [
     {
@@ -756,7 +744,7 @@
        " \"  politics: why don't you tell me about your political opinions\"]"
       ]
      },
-     "execution_count": 25,
+     "execution_count": 24,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -776,9 +764,16 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 26,
+   "execution_count": 25,
    "metadata": {},
    "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "\u001b[33m2024-11-23 23:47:23 WARNING semantic_router.utils.logger Local and remote route layers are already synchronized.\u001b[0m\n"
+     ]
+    },
     {
      "data": {
       "text/plain": [
@@ -790,7 +785,7 @@
        " \"  politics: why don't you tell me about your political opinions\"]"
       ]
      },
-     "execution_count": 26,
+     "execution_count": 25,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -798,6 +793,13 @@
    "source": [
     "rl.sync(sync_mode=\"local\")"
    ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "---"
+   ]
   }
  ],
  "metadata": {
diff --git a/semantic_router/__init__.py b/semantic_router/__init__.py
index 80f33f231ccb296fa8c9b81f1340d9bd37903a59..b2c0b88527d68d3a0d1fc54ceea003ee44a1d88a 100644
--- a/semantic_router/__init__.py
+++ b/semantic_router/__init__.py
@@ -1,6 +1,6 @@
-from semantic_router.routers import LayerConfig, RouteLayer, HybridRouteLayer
+from semantic_router.routers import RouterConfig, RouteLayer, HybridRouter
 from semantic_router.route import Route
 
-__all__ = ["RouteLayer", "HybridRouteLayer", "Route", "LayerConfig"]
+__all__ = ["RouteLayer", "HybridRouter", "Route", "RouterConfig"]
 
 __version__ = "0.1.0.dev2"
diff --git a/semantic_router/index/hybrid_local.py b/semantic_router/index/hybrid_local.py
index 9316487e33d193494fec0cfd7b1664951413d8be..a56463b30a56262ec61944ed9af86d50faf3ce59 100644
--- a/semantic_router/index/hybrid_local.py
+++ b/semantic_router/index/hybrid_local.py
@@ -5,7 +5,6 @@ from numpy.linalg import norm
 
 from semantic_router.schema import ConfigParameter, Utterance
 from semantic_router.index.local import LocalIndex
-from semantic_router.linear import similarity_matrix, top_scores
 from semantic_router.utils.logger import logger
 from typing import Any
 
@@ -104,7 +103,9 @@ class HybridLocalIndex(LocalIndex):
             # calculate sparse vec similarity
             sparse_norm = norm(self.sparse_index, axis=1)
             xq_s_norm = norm(xq_s)  # TODO: this used to be xq_s.T, should work without
-            sim_s = np.squeeze(np.dot(self.sparse_index, xq_s.T)) / (sparse_norm * xq_s_norm)
+            sim_s = np.squeeze(np.dot(self.sparse_index, xq_s.T)) / (
+                sparse_norm * xq_s_norm
+            )
             total_sim = sim_d + sim_s
             # get indices of top_k records
             top_k = min(top_k, total_sim.shape[0])
diff --git a/semantic_router/index/local.py b/semantic_router/index/local.py
index 420ad30f6ec29c0f1bfffbc432582b85e2f6d6fa..9d33163e010fdc116733830c06faad6236567486 100644
--- a/semantic_router/index/local.py
+++ b/semantic_router/index/local.py
@@ -43,9 +43,7 @@ class LocalIndex(BaseIndex):
             self.utterances = np.concatenate([self.utterances, utterances_arr])
 
     def _remove_and_sync(self, routes_to_delete: dict):
-        logger.warning(
-            f"Sync remove is not implemented for {self.__class__.__name__}."
-        )
+        logger.warning(f"Sync remove is not implemented for {self.__class__.__name__}.")
 
     def get_utterances(self) -> List[Utterance]:
         """
diff --git a/semantic_router/routers/__init__.py b/semantic_router/routers/__init__.py
index 4c8031ab50bd7512f23225c91deed5c1e0908c15..4ba619d5e023b420f4d96881e82866e65887fb4a 100644
--- a/semantic_router/routers/__init__.py
+++ b/semantic_router/routers/__init__.py
@@ -1,10 +1,10 @@
-from semantic_router.routers.base import BaseRouteLayer, LayerConfig
+from semantic_router.routers.base import BaseRouter, RouterConfig
 from semantic_router.routers.semantic import RouteLayer
-from semantic_router.routers.hybrid import HybridRouteLayer
+from semantic_router.routers.hybrid import HybridRouter
 
 __all__ = [
-    "BaseRouteLayer",
-    "LayerConfig",
+    "BaseRouter",
+    "RouterConfig",
     "RouteLayer",
-    "HybridRouteLayer",
+    "HybridRouter",
 ]
diff --git a/semantic_router/routers/base.py b/semantic_router/routers/base.py
index 4fd7b58b67889f33c46a3d2a09d87d3880d9eed3..69753e035846f9707dbc0ecfb809efa6cc254d0f 100644
--- a/semantic_router/routers/base.py
+++ b/semantic_router/routers/base.py
@@ -57,10 +57,10 @@ def is_valid(layer_config: str) -> bool:
         return False
 
 
-class LayerConfig:
+class RouterConfig:
     """
-    Generates a LayerConfig object that can be used for initializing a
-    RouteLayer.
+    Generates a RouterConfig object that can be used for initializing a
+    Routers.
     """
 
     routes: List[Route] = []
@@ -80,7 +80,7 @@ class LayerConfig:
                 if encode_type.value == self.encoder_type:
                     if self.encoder_type == EncoderType.HUGGINGFACE.value:
                         raise NotImplementedError(
-                            "HuggingFace encoder not supported by LayerConfig yet."
+                            "HuggingFace encoder not supported by RouterConfig yet."
                         )
                     encoder_name = EncoderDefault[encode_type.name].value[
                         "embedding_model"
@@ -91,7 +91,7 @@ class LayerConfig:
         self.routes = routes
 
     @classmethod
-    def from_file(cls, path: str) -> "LayerConfig":
+    def from_file(cls, path: str) -> "RouterConfig":
         logger.info(f"Loading route config from {path}")
         _, ext = os.path.splitext(path)
         with open(path, "r") as f:
@@ -143,7 +143,7 @@ class LayerConfig:
         encoder_type: str = "openai",
         encoder_name: Optional[str] = None,
     ):
-        """Initialize a LayerConfig from a list of tuples of routes and
+        """Initialize a RouterConfig from a list of tuples of routes and
         utterances.
 
         :param route_tuples: A list of tuples, each containing a route name and an
@@ -182,9 +182,9 @@ class LayerConfig:
         encoder_type: str = "openai",
         encoder_name: Optional[str] = None,
     ):
-        """Initialize a LayerConfig from a BaseIndex object.
+        """Initialize a RouterConfig from a BaseIndex object.
 
-        :param index: The index to initialize the LayerConfig from.
+        :param index: The index to initialize the RouterConfig from.
         :type index: BaseIndex
         :param encoder_type: The type of encoder to use, defaults to "openai".
         :type encoder_type: str, optional
@@ -275,7 +275,7 @@ class LayerConfig:
         )
 
 
-class BaseRouteLayer(BaseModel):
+class BaseRouter(BaseModel):
     encoder: BaseEncoder
     index: BaseIndex = Field(default_factory=BaseIndex)
     score_threshold: Optional[float] = Field(default=None)
@@ -365,7 +365,7 @@ class BaseRouteLayer(BaseModel):
     def _set_score_threshold(self):
         """Set the score threshold for the layer based on the encoder
         score threshold.
-        
+
         When no score threshold is used a default `None` value
         is used, which means that a route will always be returned when
         the layer is called."""
@@ -688,18 +688,18 @@ class BaseRouteLayer(BaseModel):
 
     @classmethod
     def from_json(cls, file_path: str):
-        config = LayerConfig.from_file(file_path)
+        config = RouterConfig.from_file(file_path)
         encoder = AutoEncoder(type=config.encoder_type, name=config.encoder_name).model
         return cls(encoder=encoder, routes=config.routes)
 
     @classmethod
     def from_yaml(cls, file_path: str):
-        config = LayerConfig.from_file(file_path)
+        config = RouterConfig.from_file(file_path)
         encoder = AutoEncoder(type=config.encoder_type, name=config.encoder_name).model
         return cls(encoder=encoder, routes=config.routes)
 
     @classmethod
-    def from_config(cls, config: LayerConfig, index: Optional[BaseIndex] = None):
+    def from_config(cls, config: RouterConfig, index: Optional[BaseIndex] = None):
         encoder = AutoEncoder(type=config.encoder_type, name=config.encoder_name).model
         return cls(encoder=encoder, routes=config.routes, index=index)
 
@@ -1115,8 +1115,8 @@ class BaseRouteLayer(BaseModel):
                     route.name, self.score_threshold
                 )
 
-    def to_config(self) -> LayerConfig:
-        return LayerConfig(
+    def to_config(self) -> RouterConfig:
+        return RouterConfig(
             encoder_type=self.encoder.type,
             encoder_name=self.encoder.name,
             routes=self.routes,
@@ -1226,7 +1226,7 @@ class BaseRouteLayer(BaseModel):
 
 
 def threshold_random_search(
-    route_layer: BaseRouteLayer,
+    route_layer: BaseRouter,
     search_range: Union[int, float],
 ) -> Dict[str, float]:
     """Performs a random search iteration given a route layer and a search range."""
diff --git a/semantic_router/routers/hybrid.py b/semantic_router/routers/hybrid.py
index 7ea3eddb4f518d5a5071f3b130a3d70879deea88..6e66142afd51691b9abdb3b3751c0f01938d184d 100644
--- a/semantic_router/routers/hybrid.py
+++ b/semantic_router/routers/hybrid.py
@@ -13,13 +13,13 @@ from semantic_router.route import Route
 from semantic_router.index.hybrid_local import HybridLocalIndex
 from semantic_router.schema import RouteChoice
 from semantic_router.utils.logger import logger
-from semantic_router.routers.base import BaseRouteLayer
+from semantic_router.routers.base import BaseRouter
 from semantic_router.llms import BaseLLM
 
 
-class HybridRouteLayer(BaseRouteLayer):
-    """A hybrid layer that uses both dense and sparse embeddings to classify routes.
-    """
+class HybridRouter(BaseRouter):
+    """A hybrid layer that uses both dense and sparse embeddings to classify routes."""
+
     # there are a few additional attributes for hybrid
     sparse_encoder: BM25Encoder = Field(default_factory=BM25Encoder)
     alpha: float = 0.3
@@ -74,7 +74,7 @@ class HybridRouteLayer(BaseRouteLayer):
     @validator("sparse_encoder", pre=True, always=True)
     def set_sparse_encoder(cls, v):
         return v if v is not None else BM25Encoder()
-    
+
     @validator("index", pre=True, always=True)
     def set_index(cls, v):
         return v if v is not None else HybridLocalIndex()
@@ -87,10 +87,10 @@ class HybridRouteLayer(BaseRouteLayer):
         # TODO: add alpha as a parameter
         # create dense query vector
         xq_d = np.array(self.encoder(text))
-        #xq_d = np.squeeze(xq_d)  # Reduce to 1d array.
+        # xq_d = np.squeeze(xq_d)  # Reduce to 1d array.
         # create sparse query vector
         xq_s = np.array(self.sparse_encoder(text))
-        #xq_s = np.squeeze(xq_s)
+        # xq_s = np.squeeze(xq_s)
         # convex scaling
         xq_d, xq_s = self._convex_scaling(xq_d, xq_s)
         return xq_d, xq_s
@@ -107,10 +107,10 @@ class HybridRouteLayer(BaseRouteLayer):
         dense_vec, sparse_vec = await asyncio.gather(dense_coro, sparse_coro)
         # create dense query vector
         xq_d = np.array(dense_vec)
-        #xq_d = np.squeeze(xq_d)  # reduce to 1d array
+        # xq_d = np.squeeze(xq_d)  # reduce to 1d array
         # create sparse query vector
         xq_s = np.array(sparse_vec)
-        #xq_s = np.squeeze(xq_s)
+        # xq_s = np.squeeze(xq_s)
         # convex scaling
         xq_d, xq_s = self._convex_scaling(xq_d, xq_s)
         return xq_d, xq_s
@@ -137,15 +137,18 @@ class HybridRouteLayer(BaseRouteLayer):
             vector=np.array(vector) if isinstance(vector, list) else vector,
             top_k=self.top_k,
             route_filter=route_filter,
-            sparse_vector=np.array(sparse_vector) if isinstance(sparse_vector, list) else sparse_vector,
+            sparse_vector=(
+                np.array(sparse_vector)
+                if isinstance(sparse_vector, list)
+                else sparse_vector
+            ),
+        )
+        top_class, top_class_scores = self._semantic_classify(
+            list(zip(scores, route_names))
         )
-        top_class, top_class_scores = self._semantic_classify(list(zip(scores, route_names)))
         passed = self._pass_threshold(top_class_scores, self.score_threshold)
         if passed:
-            return RouteChoice(
-                name=top_class,
-                similarity_score=max(top_class_scores)
-            )
+            return RouteChoice(name=top_class, similarity_score=max(top_class_scores))
         else:
             return RouteChoice()
 
diff --git a/semantic_router/routers/semantic.py b/semantic_router/routers/semantic.py
index 2104d431bffab7ed7240042ab14fa2ece0b8048c..951ef6f76f0c17b502844716ae967acd019c753c 100644
--- a/semantic_router/routers/semantic.py
+++ b/semantic_router/routers/semantic.py
@@ -1,13 +1,9 @@
-import importlib
 import json
-import os
 import random
-import hashlib
 from typing import Any, Dict, List, Optional, Tuple, Union
-from pydantic.v1 import validator, BaseModel, Field
+from pydantic.v1 import validator, Field
 
 import numpy as np
-import yaml  # type: ignore
 from tqdm.auto import tqdm
 
 from semantic_router.encoders import AutoEncoder, BaseEncoder, OpenAIEncoder
@@ -16,15 +12,13 @@ from semantic_router.index.local import LocalIndex
 from semantic_router.index.pinecone import PineconeIndex
 from semantic_router.llms import BaseLLM, OpenAILLM
 from semantic_router.route import Route
-from semantic_router.routers.base import BaseRouteLayer
+from semantic_router.routers.base import BaseRouter, RouterConfig
 from semantic_router.schema import (
     ConfigParameter,
-    EncoderType,
     RouteChoice,
     Utterance,
     UtteranceDiff,
 )
-from semantic_router.utils.defaults import EncoderDefault
 from semantic_router.utils.logger import logger
 
 
@@ -58,222 +52,7 @@ def is_valid(layer_config: str) -> bool:
         return False
 
 
-class LayerConfig:
-    """
-    Generates a LayerConfig object that can be used for initializing a
-    RouteLayer.
-    """
-
-    routes: List[Route] = []
-
-    def __init__(
-        self,
-        routes: List[Route] = [],
-        encoder_type: str = "openai",
-        encoder_name: Optional[str] = None,
-    ):
-        self.encoder_type = encoder_type
-        if encoder_name is None:
-            for encode_type in EncoderType:
-                if encode_type.value == self.encoder_type:
-                    if self.encoder_type == EncoderType.HUGGINGFACE.value:
-                        raise NotImplementedError(
-                            "HuggingFace encoder not supported by LayerConfig yet."
-                        )
-                    encoder_name = EncoderDefault[encode_type.name].value[
-                        "embedding_model"
-                    ]
-                    break
-            logger.info(f"Using default {encoder_type} encoder: {encoder_name}")
-        self.encoder_name = encoder_name
-        self.routes = routes
-
-    @classmethod
-    def from_file(cls, path: str) -> "LayerConfig":
-        logger.info(f"Loading route config from {path}")
-        _, ext = os.path.splitext(path)
-        with open(path, "r") as f:
-            if ext == ".json":
-                layer = json.load(f)
-            elif ext in [".yaml", ".yml"]:
-                layer = yaml.safe_load(f)
-            else:
-                raise ValueError(
-                    "Unsupported file type. Only .json and .yaml are supported"
-                )
-
-            if not is_valid(json.dumps(layer)):
-                raise Exception("Invalid config JSON or YAML")
-
-            encoder_type = layer["encoder_type"]
-            encoder_name = layer["encoder_name"]
-            routes = []
-            for route_data in layer["routes"]:
-                # Handle the 'llm' field specially if it exists
-                if "llm" in route_data and route_data["llm"] is not None:
-                    llm_data = route_data.pop(
-                        "llm"
-                    )  # Remove 'llm' from route_data and handle it separately
-                    # Use the module path directly from llm_data without modification
-                    llm_module_path = llm_data["module"]
-                    # Dynamically import the module and then the class from that module
-                    llm_module = importlib.import_module(llm_module_path)
-                    llm_class = getattr(llm_module, llm_data["class"])
-                    # Instantiate the LLM class with the provided model name
-                    llm = llm_class(name=llm_data["model"])
-                    # Reassign the instantiated llm object back to route_data
-                    route_data["llm"] = llm
-
-                # Dynamically create the Route object using the remaining route_data
-                route = Route(**route_data)
-                routes.append(route)
-
-            return cls(
-                encoder_type=encoder_type, encoder_name=encoder_name, routes=routes
-            )
-
-    @classmethod
-    def from_tuples(
-        cls,
-        route_tuples: List[
-            Tuple[str, str, Optional[List[Dict[str, Any]]], Dict[str, Any]]
-        ],
-        encoder_type: str = "openai",
-        encoder_name: Optional[str] = None,
-    ):
-        """Initialize a LayerConfig from a list of tuples of routes and
-        utterances.
-
-        :param route_tuples: A list of tuples, each containing a route name and an
-            associated utterance.
-        :type route_tuples: List[Tuple[str, str]]
-        :param encoder_type: The type of encoder to use, defaults to "openai".
-        :type encoder_type: str, optional
-        :param encoder_name: The name of the encoder to use, defaults to None.
-        :type encoder_name: Optional[str], optional
-        """
-        routes_dict: Dict[str, Route] = {}
-        # first create a dictionary of route names to Route objects
-        # TODO: duplicated code with BaseIndex.get_routes()
-        for route_name, utterance, function_schema, metadata in route_tuples:
-            # if the route is not in the dictionary, add it
-            if route_name not in routes_dict:
-                routes_dict[route_name] = Route(
-                    name=route_name,
-                    utterances=[utterance],
-                    function_schemas=function_schema,
-                    metadata=metadata,
-                )
-            else:
-                # otherwise, add the utterance to the route
-                routes_dict[route_name].utterances.append(utterance)
-        # then create a list of routes from the dictionary
-        routes: List[Route] = []
-        for route_name, route in routes_dict.items():
-            routes.append(route)
-        return cls(routes=routes, encoder_type=encoder_type, encoder_name=encoder_name)
-
-    @classmethod
-    def from_index(
-        cls,
-        index: BaseIndex,
-        encoder_type: str = "openai",
-        encoder_name: Optional[str] = None,
-    ):
-        """Initialize a LayerConfig from a BaseIndex object.
-
-        :param index: The index to initialize the LayerConfig from.
-        :type index: BaseIndex
-        :param encoder_type: The type of encoder to use, defaults to "openai".
-        :type encoder_type: str, optional
-        :param encoder_name: The name of the encoder to use, defaults to None.
-        :type encoder_name: Optional[str], optional
-        """
-        remote_routes = index.get_utterances()
-        return cls.from_tuples(
-            route_tuples=[utt.to_tuple() for utt in remote_routes],
-            encoder_type=encoder_type,
-            encoder_name=encoder_name,
-        )
-
-    def to_dict(self) -> Dict[str, Any]:
-        return {
-            "encoder_type": self.encoder_type,
-            "encoder_name": self.encoder_name,
-            "routes": [route.to_dict() for route in self.routes],
-        }
-
-    def to_file(self, path: str):
-        """Save the routes to a file in JSON or YAML format"""
-        logger.info(f"Saving route config to {path}")
-        _, ext = os.path.splitext(path)
-
-        # Check file extension before creating directories or files
-        if ext not in [".json", ".yaml", ".yml"]:
-            raise ValueError(
-                "Unsupported file type. Only .json and .yaml are supported"
-            )
-
-        dir_name = os.path.dirname(path)
-
-        # Create the directory if it doesn't exist and dir_name is not an empty string
-        if dir_name and not os.path.exists(dir_name):
-            os.makedirs(dir_name)
-
-        with open(path, "w") as f:
-            if ext == ".json":
-                json.dump(self.to_dict(), f, indent=4)
-            elif ext in [".yaml", ".yml"]:
-                yaml.safe_dump(self.to_dict(), f)
-
-    def to_utterances(self) -> List[Utterance]:
-        """Convert the routes to a list of Utterance objects.
-
-        :return: A list of Utterance objects.
-        :rtype: List[Utterance]
-        """
-        utterances = []
-        for route in self.routes:
-            utterances.extend(
-                [
-                    Utterance(
-                        route=route.name,
-                        utterance=x,
-                        function_schemas=route.function_schemas,
-                        metadata=route.metadata or {},
-                    )
-                    for x in route.utterances
-                ]
-            )
-        return utterances
-
-    def add(self, route: Route):
-        self.routes.append(route)
-        logger.info(f"Added route `{route.name}`")
-
-    def get(self, name: str) -> Optional[Route]:
-        for route in self.routes:
-            if route.name == name:
-                return route
-        logger.error(f"Route `{name}` not found")
-        return None
-
-    def remove(self, name: str):
-        if name not in [route.name for route in self.routes]:
-            logger.error(f"Route `{name}` not found")
-        else:
-            self.routes = [route for route in self.routes if route.name != name]
-            logger.info(f"Removed route `{name}`")
-
-    def get_hash(self) -> ConfigParameter:
-        layer = self.to_dict()
-        return ConfigParameter(
-            field="sr_hash",
-            value=hashlib.sha256(json.dumps(layer).encode()).hexdigest(),
-        )
-
-
-class RouteLayer(BaseRouteLayer):
+class RouteLayer(BaseRouter):
     index: BaseIndex = Field(default_factory=LocalIndex)
 
     @validator("index", pre=True, always=True)
@@ -655,18 +434,18 @@ class RouteLayer(BaseRouteLayer):
 
     @classmethod
     def from_json(cls, file_path: str):
-        config = LayerConfig.from_file(file_path)
+        config = RouterConfig.from_file(file_path)
         encoder = AutoEncoder(type=config.encoder_type, name=config.encoder_name).model
         return cls(encoder=encoder, routes=config.routes)
 
     @classmethod
     def from_yaml(cls, file_path: str):
-        config = LayerConfig.from_file(file_path)
+        config = RouterConfig.from_file(file_path)
         encoder = AutoEncoder(type=config.encoder_type, name=config.encoder_name).model
         return cls(encoder=encoder, routes=config.routes)
 
     @classmethod
-    def from_config(cls, config: LayerConfig, index: Optional[BaseIndex] = None):
+    def from_config(cls, config: RouterConfig, index: Optional[BaseIndex] = None):
         encoder = AutoEncoder(type=config.encoder_type, name=config.encoder_name).model
         return cls(encoder=encoder, routes=config.routes, index=index)
 
@@ -1069,8 +848,8 @@ class RouteLayer(BaseRouteLayer):
                     route.name, self.score_threshold
                 )
 
-    def to_config(self) -> LayerConfig:
-        return LayerConfig(
+    def to_config(self) -> RouterConfig:
+        return RouterConfig(
             encoder_type=self.encoder.type,
             encoder_name=self.encoder.name,
             routes=self.routes,
diff --git a/tests/unit/test_hybrid_layer.py b/tests/unit/test_hybrid_layer.py
index 0859fc8394fd71c0b23071c95e08b66d1049c945..3cba34caa8ca28f4c421117857b163047b0be4ff 100644
--- a/tests/unit/test_hybrid_layer.py
+++ b/tests/unit/test_hybrid_layer.py
@@ -8,7 +8,7 @@ from semantic_router.encoders import (
     OpenAIEncoder,
     TfidfEncoder,
 )
-from semantic_router.OLD_hybrid_layer import HybridRouteLayer
+from semantic_router.OLD_hybrid_layer import HybridRouter
 from semantic_router.route import Route
 
 
@@ -78,9 +78,9 @@ sparse_encoder = BM25Encoder(use_default_params=False)
 sparse_encoder.fit(["The quick brown fox", "jumps over the lazy dog", "Hello, world!"])
 
 
-class TestHybridRouteLayer:
+class TestHybridRouter:
     def test_initialization(self, openai_encoder, routes):
-        route_layer = HybridRouteLayer(
+        route_layer = HybridRouter(
             encoder=openai_encoder,
             sparse_encoder=sparse_encoder,
             routes=routes,
@@ -96,18 +96,18 @@ class TestHybridRouteLayer:
         assert len(set(route_layer.categories)) == 2
 
     def test_initialization_different_encoders(self, cohere_encoder, openai_encoder):
-        route_layer_cohere = HybridRouteLayer(
+        route_layer_cohere = HybridRouter(
             encoder=cohere_encoder, sparse_encoder=sparse_encoder
         )
         assert route_layer_cohere.score_threshold == 0.3
 
-        route_layer_openai = HybridRouteLayer(
+        route_layer_openai = HybridRouter(
             encoder=openai_encoder, sparse_encoder=sparse_encoder
         )
         assert route_layer_openai.score_threshold == 0.3
 
     def test_add_route(self, openai_encoder):
-        route_layer = HybridRouteLayer(
+        route_layer = HybridRouter(
             encoder=openai_encoder, sparse_encoder=sparse_encoder
         )
         route = Route(name="Route 3", utterances=["Yes", "No"])
@@ -117,7 +117,7 @@ class TestHybridRouteLayer:
         assert len(set(route_layer.categories)) == 1
 
     def test_add_multiple_routes(self, openai_encoder, routes):
-        route_layer = HybridRouteLayer(
+        route_layer = HybridRouter(
             encoder=openai_encoder, sparse_encoder=sparse_encoder
         )
         for route in routes:
@@ -127,20 +127,20 @@ class TestHybridRouteLayer:
         assert len(set(route_layer.categories)) == 2
 
     def test_query_and_classification(self, openai_encoder, routes):
-        route_layer = HybridRouteLayer(
+        route_layer = HybridRouter(
             encoder=openai_encoder, sparse_encoder=sparse_encoder, routes=routes
         )
         query_result = route_layer("Hello")
         assert query_result in ["Route 1", "Route 2"]
 
     def test_query_with_no_index(self, openai_encoder):
-        route_layer = HybridRouteLayer(
+        route_layer = HybridRouter(
             encoder=openai_encoder, sparse_encoder=sparse_encoder
         )
         assert route_layer("Anything") is None
 
     def test_semantic_classify(self, openai_encoder, routes):
-        route_layer = HybridRouteLayer(
+        route_layer = HybridRouter(
             encoder=openai_encoder, sparse_encoder=sparse_encoder, routes=routes
         )
         classification, score = route_layer._semantic_classify(
@@ -153,7 +153,7 @@ class TestHybridRouteLayer:
         assert score == [0.9]
 
     def test_semantic_classify_multiple_routes(self, openai_encoder, routes):
-        route_layer = HybridRouteLayer(
+        route_layer = HybridRouter(
             encoder=openai_encoder, sparse_encoder=sparse_encoder, routes=routes
         )
         classification, score = route_layer._semantic_classify(
@@ -167,21 +167,19 @@ class TestHybridRouteLayer:
         assert score == [0.9, 0.8]
 
     def test_pass_threshold(self, openai_encoder):
-        route_layer = HybridRouteLayer(
+        route_layer = HybridRouter(
             encoder=openai_encoder, sparse_encoder=sparse_encoder
         )
         assert not route_layer._pass_threshold([], 0.5)
         assert route_layer._pass_threshold([0.6, 0.7], 0.5)
 
     def test_failover_score_threshold(self, base_encoder):
-        route_layer = HybridRouteLayer(
-            encoder=base_encoder, sparse_encoder=sparse_encoder
-        )
+        route_layer = HybridRouter(encoder=base_encoder, sparse_encoder=sparse_encoder)
         assert base_encoder.score_threshold == 0.50
         assert route_layer.score_threshold == 0.50
 
     def test_add_route_tfidf(self, cohere_encoder, tfidf_encoder, routes):
-        hybrid_route_layer = HybridRouteLayer(
+        hybrid_route_layer = HybridRouter(
             encoder=cohere_encoder,
             sparse_encoder=tfidf_encoder,
             routes=routes[:-1],
@@ -195,7 +193,7 @@ class TestHybridRouteLayer:
 
     def test_setting_aggregation_methods(self, openai_encoder, routes):
         for agg in ["sum", "mean", "max"]:
-            route_layer = HybridRouteLayer(
+            route_layer = HybridRouter(
                 encoder=openai_encoder,
                 sparse_encoder=sparse_encoder,
                 routes=routes,
@@ -218,7 +216,7 @@ class TestHybridRouteLayer:
             {"route": "Route 3", "score": 1.0},
         ]
         for agg in ["sum", "mean", "max"]:
-            route_layer = HybridRouteLayer(
+            route_layer = HybridRouter(
                 encoder=openai_encoder,
                 sparse_encoder=sparse_encoder,
                 routes=routes,
diff --git a/tests/unit/test_router.py b/tests/unit/test_router.py
index 2b39410d2f400017865308b02f1766ac1b618b21..61731b29a88c042bf031c8a43c5885e52724c0c4 100644
--- a/tests/unit/test_router.py
+++ b/tests/unit/test_router.py
@@ -10,7 +10,7 @@ from semantic_router.encoders import BaseEncoder, CohereEncoder, OpenAIEncoder
 from semantic_router.index.local import LocalIndex
 from semantic_router.index.pinecone import PineconeIndex
 from semantic_router.index.qdrant import QdrantIndex
-from semantic_router.routers import LayerConfig, RouteLayer
+from semantic_router.routers import RouterConfig, RouteLayer
 from semantic_router.llms.base import BaseLLM
 from semantic_router.route import Route
 from platform import python_version
@@ -588,8 +588,8 @@ class TestRouteLayer:
             layer_json()
         )  # Assuming layer_json() returns a valid JSON string
 
-        # Load the LayerConfig from the temporary file
-        layer_config = LayerConfig.from_file(str(config_path))
+        # Load the RouterConfig from the temporary file
+        layer_config = RouterConfig.from_file(str(config_path))
 
         # Assertions to verify the loaded configuration
         assert layer_config.encoder_type == "cohere"
@@ -604,8 +604,8 @@ class TestRouteLayer:
             layer_yaml()
         )  # Assuming layer_yaml() returns a valid YAML string
 
-        # Load the LayerConfig from the temporary file
-        layer_config = LayerConfig.from_file(str(config_path))
+        # Load the RouterConfig from the temporary file
+        layer_config = RouterConfig.from_file(str(config_path))
 
         # Assertions to verify the loaded configuration
         assert layer_config.encoder_type == "cohere"
@@ -615,7 +615,7 @@ class TestRouteLayer:
 
     def test_from_file_invalid_path(self, index_cls):
         with pytest.raises(FileNotFoundError) as excinfo:
-            LayerConfig.from_file("nonexistent_path.json")
+            RouterConfig.from_file("nonexistent_path.json")
         assert "[Errno 2] No such file or directory: 'nonexistent_path.json'" in str(
             excinfo.value
         )
@@ -626,7 +626,7 @@ class TestRouteLayer:
         config_path.write_text(layer_json())
 
         with pytest.raises(ValueError) as excinfo:
-            LayerConfig.from_file(str(config_path))
+            RouterConfig.from_file(str(config_path))
         assert "Unsupported file type" in str(excinfo.value)
 
     def test_from_file_invalid_config(self, tmp_path, index_cls):
@@ -645,10 +645,10 @@ class TestRouteLayer:
 
         # Patch the is_valid function to return False for this test
         with patch("semantic_router.layer.is_valid", return_value=False):
-            # Attempt to load the LayerConfig from the temporary file
+            # Attempt to load the RouterConfig from the temporary file
             # and assert that it raises an exception due to invalid configuration
             with pytest.raises(Exception) as excinfo:
-                LayerConfig.from_file(str(config_path))
+                RouterConfig.from_file(str(config_path))
             assert "Invalid config JSON or YAML" in str(
                 excinfo.value
             ), "Loading an invalid configuration should raise an exception."
@@ -675,8 +675,8 @@ class TestRouteLayer:
         with open(config_path, "w") as file:
             file.write(llm_config_json)
 
-        # Load the LayerConfig from the temporary file
-        layer_config = LayerConfig.from_file(str(config_path))
+        # Load the RouterConfig from the temporary file
+        layer_config = RouterConfig.from_file(str(config_path))
 
         # Using BaseLLM because trying to create a usable Mock LLM is a nightmare.
         assert isinstance(
@@ -939,61 +939,61 @@ class TestLayerFit:
 # Add more tests for edge cases and error handling as needed.
 
 
-class TestLayerConfig:
+class TestRouterConfig:
     def test_init(self):
-        layer_config = LayerConfig()
+        layer_config = RouterConfig()
         assert layer_config.routes == []
 
     def test_to_file_json(self):
         route = Route(name="test", utterances=["utterance"])
-        layer_config = LayerConfig(routes=[route])
+        layer_config = RouterConfig(routes=[route])
         with patch("builtins.open", mock_open()) as mocked_open:
             layer_config.to_file("data/test_output.json")
             mocked_open.assert_called_once_with("data/test_output.json", "w")
 
     def test_to_file_yaml(self):
         route = Route(name="test", utterances=["utterance"])
-        layer_config = LayerConfig(routes=[route])
+        layer_config = RouterConfig(routes=[route])
         with patch("builtins.open", mock_open()) as mocked_open:
             layer_config.to_file("data/test_output.yaml")
             mocked_open.assert_called_once_with("data/test_output.yaml", "w")
 
     def test_to_file_invalid(self):
         route = Route(name="test", utterances=["utterance"])
-        layer_config = LayerConfig(routes=[route])
+        layer_config = RouterConfig(routes=[route])
         with pytest.raises(ValueError):
             layer_config.to_file("test_output.txt")
 
     def test_from_file_json(self):
         mock_json_data = layer_json()
         with patch("builtins.open", mock_open(read_data=mock_json_data)) as mocked_open:
-            layer_config = LayerConfig.from_file("data/test.json")
+            layer_config = RouterConfig.from_file("data/test.json")
             mocked_open.assert_called_once_with("data/test.json", "r")
-            assert isinstance(layer_config, LayerConfig)
+            assert isinstance(layer_config, RouterConfig)
 
     def test_from_file_yaml(self):
         mock_yaml_data = layer_yaml()
         with patch("builtins.open", mock_open(read_data=mock_yaml_data)) as mocked_open:
-            layer_config = LayerConfig.from_file("data/test.yaml")
+            layer_config = RouterConfig.from_file("data/test.yaml")
             mocked_open.assert_called_once_with("data/test.yaml", "r")
-            assert isinstance(layer_config, LayerConfig)
+            assert isinstance(layer_config, RouterConfig)
 
     def test_from_file_invalid(self):
         with open("test.txt", "w") as f:
             f.write("dummy content")
         with pytest.raises(ValueError):
-            LayerConfig.from_file("test.txt")
+            RouterConfig.from_file("test.txt")
         os.remove("test.txt")
 
     def test_to_dict(self):
         route = Route(name="test", utterances=["utterance"])
-        layer_config = LayerConfig(routes=[route])
+        layer_config = RouterConfig(routes=[route])
         assert layer_config.to_dict()["routes"] == [route.to_dict()]
 
     def test_add(self):
         route = Route(name="test", utterances=["utterance"])
         route2 = Route(name="test2", utterances=["utterance2"])
-        layer_config = LayerConfig()
+        layer_config = RouterConfig()
         layer_config.add(route)
         # confirm route added
         assert layer_config.routes == [route]
@@ -1003,17 +1003,17 @@ class TestLayerConfig:
 
     def test_get(self):
         route = Route(name="test", utterances=["utterance"])
-        layer_config = LayerConfig(routes=[route])
+        layer_config = RouterConfig(routes=[route])
         assert layer_config.get("test") == route
 
     def test_get_not_found(self):
         route = Route(name="test", utterances=["utterance"])
-        layer_config = LayerConfig(routes=[route])
+        layer_config = RouterConfig(routes=[route])
         assert layer_config.get("not_found") is None
 
     def test_remove(self):
         route = Route(name="test", utterances=["utterance"])
-        layer_config = LayerConfig(routes=[route])
+        layer_config = RouterConfig(routes=[route])
         layer_config.remove("test")
         assert layer_config.routes == []