diff --git a/docs/examples/hybrid-layer.ipynb b/docs/examples/hybrid-layer.ipynb
index 589e13a1e4d3fc1d924c7bb851c19c56f9f30dd5..d3fb58c5978ea3da709d12eb850d16a846396e0b 100644
--- a/docs/examples/hybrid-layer.ipynb
+++ b/docs/examples/hybrid-layer.ipynb
@@ -151,39 +151,7 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "2067848296 1405\n",
-      "2212344012 2520\n",
-      "3313717465 206\n",
-      "3076736765 769\n",
-      "1778150425 4131\n",
-      "2067848296 1405\n",
-      "202708381 770\n",
-      "2212344012 2520\n",
-      "3374841595 2375\n",
-      "2067848296 1405\n",
-      "3508911095 2067\n",
-      "3454774732 not in encoder.idx_mapping\n",
-      "2379717389 3565\n",
-      "298452803 4356\n",
-      "1063320047 3369\n",
-      "4186256544 713\n",
-      "1846246980 858\n",
-      "3897916792 643\n",
-      "575623047 1476\n",
-      "3897916792 643\n"
-     ]
-    },
-    {
-     "ename": "ValueError",
-     "evalue": "all the input arrays must have same number of dimensions, but the array at index 0 has 2 dimension(s) and the array at index 1 has 1 dimension(s)",
-     "output_type": "error",
-     "traceback": [
-      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
-      "\u001b[0;31mValueError\u001b[0m                                Traceback (most recent call last)",
-      "\u001b[1;32m/Users/jamesbriggs/Documents/projects/aurelio-labs/semantic-router/docs/examples/hybrid-layer.ipynb Cell 14\u001b[0m line \u001b[0;36m3\n\u001b[1;32m      <a href='vscode-notebook-cell:/Users/jamesbriggs/Documents/projects/aurelio-labs/semantic-router/docs/examples/hybrid-layer.ipynb#X16sZmlsZQ%3D%3D?line=0'>1</a>\u001b[0m \u001b[39mfrom\u001b[39;00m \u001b[39msemantic_router\u001b[39;00m\u001b[39m.\u001b[39;00m\u001b[39mlayer\u001b[39;00m \u001b[39mimport\u001b[39;00m HybridDecisionLayer\n\u001b[0;32m----> <a href='vscode-notebook-cell:/Users/jamesbriggs/Documents/projects/aurelio-labs/semantic-router/docs/examples/hybrid-layer.ipynb#X16sZmlsZQ%3D%3D?line=2'>3</a>\u001b[0m dl \u001b[39m=\u001b[39m HybridDecisionLayer(encoder\u001b[39m=\u001b[39;49mencoder, decisions\u001b[39m=\u001b[39;49mdecisions)\n",
-      "File \u001b[0;32m~/Documents/projects/aurelio-labs/semantic-router/semantic_router/layer.py:137\u001b[0m, in \u001b[0;36mHybridDecisionLayer.__init__\u001b[0;34m(self, encoder, decisions, alpha)\u001b[0m\n\u001b[1;32m    134\u001b[0m \u001b[39mif\u001b[39;00m decisions:\n\u001b[1;32m    135\u001b[0m     \u001b[39m# initialize index now\u001b[39;00m\n\u001b[1;32m    136\u001b[0m     \u001b[39mfor\u001b[39;00m decision \u001b[39min\u001b[39;00m decisions:\n\u001b[0;32m--> 137\u001b[0m         \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49m_add_decision(decision\u001b[39m=\u001b[39;49mdecision)\n",
-      "File \u001b[0;32m~/Documents/projects/aurelio-labs/semantic-router/semantic_router/layer.py:156\u001b[0m, in \u001b[0;36mHybridDecisionLayer._add_decision\u001b[0;34m(self, decision)\u001b[0m\n\u001b[1;32m    154\u001b[0m sparse_embeds \u001b[39m=\u001b[39m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39msparse_encoder(decision\u001b[39m.\u001b[39mutterances)\n\u001b[1;32m    155\u001b[0m \u001b[39m# concatenate vectors to create hybrid vecs\u001b[39;00m\n\u001b[0;32m--> 156\u001b[0m embeds \u001b[39m=\u001b[39m np\u001b[39m.\u001b[39;49mconcatenate([\n\u001b[1;32m    157\u001b[0m     dense_embeds, sparse_embeds\n\u001b[1;32m    158\u001b[0m ], axis\u001b[39m=\u001b[39;49m\u001b[39m1\u001b[39;49m)\n\u001b[1;32m    160\u001b[0m \u001b[39m# create decision array\u001b[39;00m\n\u001b[1;32m    161\u001b[0m \u001b[39mif\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mcategories \u001b[39mis\u001b[39;00m \u001b[39mNone\u001b[39;00m:\n",
-      "\u001b[0;31mValueError\u001b[0m: all the input arrays must have same number of dimensions, but the array at index 0 has 2 dimension(s) and the array at index 1 has 1 dimension(s)"
+      "3454774732 not in encoder.idx_mapping\n"
      ]
     }
    ],
@@ -195,9 +163,24 @@
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 6,
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "ename": "AxisError",
+     "evalue": "axis 1 is out of bounds for array of dimension 1",
+     "output_type": "error",
+     "traceback": [
+      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+      "\u001b[0;31mAxisError\u001b[0m                                 Traceback (most recent call last)",
+      "\u001b[1;32m/Users/jamesbriggs/Documents/projects/aurelio-labs/semantic-router/docs/examples/hybrid-layer.ipynb Cell 15\u001b[0m line \u001b[0;36m1\n\u001b[0;32m----> <a href='vscode-notebook-cell:/Users/jamesbriggs/Documents/projects/aurelio-labs/semantic-router/docs/examples/hybrid-layer.ipynb#X20sZmlsZQ%3D%3D?line=0'>1</a>\u001b[0m dl(\u001b[39m\"\u001b[39;49m\u001b[39mdon\u001b[39;49m\u001b[39m'\u001b[39;49m\u001b[39mt you love politics?\u001b[39;49m\u001b[39m\"\u001b[39;49m)\n",
+      "File \u001b[0;32m~/Documents/projects/aurelio-labs/semantic-router/semantic_router/layer.py:141\u001b[0m, in \u001b[0;36mHybridDecisionLayer.__call__\u001b[0;34m(self, text)\u001b[0m\n\u001b[1;32m    140\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39m__call__\u001b[39m(\u001b[39mself\u001b[39m, text: \u001b[39mstr\u001b[39m) \u001b[39m-\u001b[39m\u001b[39m>\u001b[39m \u001b[39mstr\u001b[39m \u001b[39m|\u001b[39m \u001b[39mNone\u001b[39;00m:\n\u001b[0;32m--> 141\u001b[0m     results \u001b[39m=\u001b[39m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49m_query(text)\n\u001b[1;32m    142\u001b[0m     top_class, top_class_scores \u001b[39m=\u001b[39m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_semantic_classify(results)\n\u001b[1;32m    143\u001b[0m     passed \u001b[39m=\u001b[39m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39m_pass_threshold(top_class_scores, \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mscore_threshold)\n",
+      "File \u001b[0;32m~/Documents/projects/aurelio-labs/semantic-router/semantic_router/layer.py:204\u001b[0m, in \u001b[0;36mHybridDecisionLayer._query\u001b[0;34m(self, text, top_k)\u001b[0m\n\u001b[1;32m    202\u001b[0m sim_d \u001b[39m=\u001b[39m np\u001b[39m.\u001b[39mdot(\u001b[39mself\u001b[39m\u001b[39m.\u001b[39mindex, xq_d\u001b[39m.\u001b[39mT) \u001b[39m/\u001b[39m (index_norm \u001b[39m*\u001b[39m xq_d_norm)\n\u001b[1;32m    203\u001b[0m \u001b[39m# calculate sparse vec similarity\u001b[39;00m\n\u001b[0;32m--> 204\u001b[0m sparse_norm \u001b[39m=\u001b[39m norm(\u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49msparse_index, axis\u001b[39m=\u001b[39;49m\u001b[39m1\u001b[39;49m)\n\u001b[1;32m    205\u001b[0m xq_s_norm \u001b[39m=\u001b[39m norm(xq_s\u001b[39m.\u001b[39mT)\n\u001b[1;32m    206\u001b[0m sim_s \u001b[39m=\u001b[39m np\u001b[39m.\u001b[39mdot(\u001b[39mself\u001b[39m\u001b[39m.\u001b[39msparse_index, xq_s\u001b[39m.\u001b[39mT) \u001b[39m/\u001b[39m (sparse_norm \u001b[39m*\u001b[39m xq_s_norm)\n",
+      "File \u001b[0;32m~/opt/anaconda3/envs/decision-layer/lib/python3.11/site-packages/numpy/linalg/linalg.py:2583\u001b[0m, in \u001b[0;36mnorm\u001b[0;34m(x, ord, axis, keepdims)\u001b[0m\n\u001b[1;32m   2580\u001b[0m \u001b[39melif\u001b[39;00m \u001b[39mord\u001b[39m \u001b[39mis\u001b[39;00m \u001b[39mNone\u001b[39;00m \u001b[39mor\u001b[39;00m \u001b[39mord\u001b[39m \u001b[39m==\u001b[39m \u001b[39m2\u001b[39m:\n\u001b[1;32m   2581\u001b[0m     \u001b[39m# special case for speedup\u001b[39;00m\n\u001b[1;32m   2582\u001b[0m     s \u001b[39m=\u001b[39m (x\u001b[39m.\u001b[39mconj() \u001b[39m*\u001b[39m x)\u001b[39m.\u001b[39mreal\n\u001b[0;32m-> 2583\u001b[0m     \u001b[39mreturn\u001b[39;00m sqrt(add\u001b[39m.\u001b[39;49mreduce(s, axis\u001b[39m=\u001b[39;49maxis, keepdims\u001b[39m=\u001b[39;49mkeepdims))\n\u001b[1;32m   2584\u001b[0m \u001b[39m# None of the str-type keywords for ord ('fro', 'nuc')\u001b[39;00m\n\u001b[1;32m   2585\u001b[0m \u001b[39m# are valid for vectors\u001b[39;00m\n\u001b[1;32m   2586\u001b[0m \u001b[39melif\u001b[39;00m \u001b[39misinstance\u001b[39m(\u001b[39mord\u001b[39m, \u001b[39mstr\u001b[39m):\n",
+      "\u001b[0;31mAxisError\u001b[0m: axis 1 is out of bounds for array of dimension 1"
+     ]
+    }
+   ],
    "source": [
     "dl(\"don't you love politics?\")"
    ]
@@ -207,10 +190,7 @@
    "execution_count": null,
    "metadata": {},
    "outputs": [],
-   "source": [
-    "if 3454774732 in encoder.idx_mapping:\n",
-    "    print(\"yes\")"
-   ]
+   "source": []
   },
   {
    "cell_type": "code",
@@ -220,11 +200,13 @@
    "source": []
   },
   {
-   "cell_type": "code",
-   "execution_count": null,
+   "cell_type": "markdown",
    "metadata": {},
-   "outputs": [],
-   "source": []
+   "source": [
+    "---\n",
+    "\n",
+    "#### Testing"
+   ]
   },
   {
    "cell_type": "code",
@@ -347,6 +329,15 @@
     "sparse_vec.shape"
    ]
   },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "#### Finish Testing\n",
+    "\n",
+    "---"
+   ]
+  },
   {
    "cell_type": "markdown",
    "metadata": {},
diff --git a/semantic_router/encoders/bm25.py b/semantic_router/encoders/bm25.py
index 344f0820ae354645331dc6512456ffe65c60113b..c419ac8ff65c061c4e7f1829d2d23d5aee4b37ea 100644
--- a/semantic_router/encoders/bm25.py
+++ b/semantic_router/encoders/bm25.py
@@ -17,7 +17,7 @@ class BM25Encoder(BaseEncoder):
 
     def __call__(self, docs: list[str]) -> list[list[float]]:
         if len(docs) == 1:
-            sparse_dicts = self.model.encode_query(docs[0])
+            sparse_dicts = self.model.encode_queries(docs)
         elif len(docs) > 1:
             sparse_dicts = self.model.encode_documents(docs)
         else:
@@ -29,7 +29,6 @@ class BM25Encoder(BaseEncoder):
             values = output["values"]
             for idx, val in zip(indices, values):
                 if idx in self.idx_mapping:
-                    print(idx, self.idx_mapping[idx])
                     position = self.idx_mapping[idx]
                     embeds[position] = val
                 else:
diff --git a/semantic_router/layer.py b/semantic_router/layer.py
index e8b71576c4accd5936621f6da2889411caf2fe97..070c9c0bd37ab3d096e8d85753a93318eb68ce1d 100644
--- a/semantic_router/layer.py
+++ b/semantic_router/layer.py
@@ -111,6 +111,7 @@ class DecisionLayer:
 
 class HybridDecisionLayer:
     index = None
+    sparse_index = None
     categories = None
     score_threshold = 0.82
 
@@ -150,19 +151,19 @@ class HybridDecisionLayer:
 
     def _add_decision(self, decision: Decision):
         # create embeddings
-        dense_embeds = self.encoder(decision.utterances) * self.alpha
-        sparse_embeds = self.sparse_encoder(decision.utterances) * (1 - self.alpha)
-        # concatenate vectors to create hybrid vecs
-        embeds = np.concatenate([
-            dense_embeds, sparse_embeds
-        ], axis=1)
+        dense_embeds = np.array(
+            self.encoder(decision.utterances)
+        )  # * self.alpha
+        sparse_embeds = np.array(
+            self.sparse_encoder(decision.utterances)
+        )  # * (1 - self.alpha)
 
         # create decision array
         if self.categories is None:
-            self.categories = np.array([decision.name] * len(embeds))
+            self.categories = np.array([decision.name] * len(decision.utterances))
             self.utterances = np.array(decision.utterances)
         else:
-            str_arr = np.array([decision.name] * len(embeds))
+            str_arr = np.array([decision.name] * len(decision.utterances))
             self.categories = np.concatenate([self.categories, str_arr])
             self.utterances = np.concatenate([
                 self.utterances,
@@ -170,17 +171,15 @@ class HybridDecisionLayer:
             ])
         # create utterance array (the dense index)
         if self.index is None:
-            self.index = np.array(dense_embeds)
+            self.index = dense_embeds
         else:
-            embed_arr = np.array(dense_embeds)
-            self.index = np.concatenate([self.index, embed_arr])
+            self.index = np.concatenate([self.index, dense_embeds])
         # create sparse utterance array
         if self.sparse_index is None:
-            self.sparse_index = np.array(sparse_embeds)
+            self.sparse_index = sparse_embeds
         else:
-            sparse_embed_arr = np.array(sparse_embeds)
             self.sparse_index = np.concatenate([
-                self.sparse_index, sparse_embed_arr
+                self.sparse_index, sparse_embeds
             ])
 
     def _query(self, text: str, top_k: int = 5):
@@ -195,17 +194,21 @@ class HybridDecisionLayer:
         xq_s = np.squeeze(xq_s)
         # convex scaling
         xq_d, xq_s = self._convex_scaling(xq_d, xq_s)
-        # concatenate to create single hybrid vec
-        xq = np.concatenate([xq_d, xq_s], axis=1)
 
         if self.index is not None:
+            # calculate dense vec similarity
             index_norm = norm(self.index, axis=1)
-            xq_norm = norm(xq.T)
-            sim = np.dot(self.index, xq.T) / (index_norm * xq_norm)
+            xq_d_norm = norm(xq_d.T)
+            sim_d = np.dot(self.index, xq_d.T) / (index_norm * xq_d_norm)
+            # calculate sparse vec similarity
+            sparse_norm = norm(self.sparse_index, axis=1)
+            xq_s_norm = norm(xq_s.T)
+            sim_s = 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, sim.shape[0])
-            idx = np.argpartition(sim, -top_k)[-top_k:]
-            scores = sim[idx]
+            top_k = min(top_k, total_sim.shape[0])
+            idx = np.argpartition(total_sim, -top_k)[-top_k:]
+            scores = total_sim[idx]
             # get the utterance categories (decision names)
             decisions = self.categories[idx] if self.categories is not None else []
             return [
@@ -216,8 +219,8 @@ class HybridDecisionLayer:
         
     def _convex_scaling(self, dense: list[float], sparse: list[float]):
         # scale sparse and dense vecs
-        dense = dense * self.alpha
-        sparse = sparse * (1 - self.alpha)
+        dense = np.array(dense) * self.alpha
+        sparse = np.array(sparse) * (1 - self.alpha)
         return dense, sparse
 
     def _semantic_classify(self, query_results: list[dict]) -> tuple[str, list[float]]: