semantic-router 0.1.0.dev1__tar.gz → 0.1.0.dev2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/PKG-INFO +5 -5
  2. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/pyproject.toml +7 -6
  3. semantic_router-0.1.0.dev2/semantic_router/__init__.py +6 -0
  4. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/encoders/__init__.py +10 -5
  5. semantic_router-0.1.0.dev2/semantic_router/encoders/aurelio.py +47 -0
  6. semantic_router-0.1.0.dev2/semantic_router/encoders/base.py +53 -0
  7. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/encoders/bedrock.py +3 -3
  8. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/encoders/bm25.py +26 -20
  9. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/encoders/clip.py +2 -2
  10. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/encoders/cohere.py +2 -2
  11. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/encoders/fastembed.py +2 -2
  12. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/encoders/google.py +3 -3
  13. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/encoders/huggingface.py +4 -4
  14. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/encoders/mistral.py +2 -2
  15. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/encoders/openai.py +2 -2
  16. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/encoders/tfidf.py +24 -11
  17. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/encoders/vit.py +2 -2
  18. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/encoders/zure.py +2 -2
  19. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/index/__init__.py +4 -0
  20. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/index/base.py +4 -2
  21. semantic_router-0.1.0.dev2/semantic_router/index/hybrid_local.py +212 -0
  22. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/index/local.py +27 -11
  23. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/index/pinecone.py +63 -28
  24. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/index/postgres.py +17 -5
  25. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/index/qdrant.py +3 -1
  26. semantic_router-0.1.0.dev2/semantic_router/routers/__init__.py +10 -0
  27. semantic_router-0.1.0.dev1/semantic_router/layer.py → semantic_router-0.1.0.dev2/semantic_router/routers/base.py +267 -218
  28. semantic_router-0.1.0.dev2/semantic_router/routers/hybrid.py +254 -0
  29. semantic_router-0.1.0.dev2/semantic_router/routers/semantic.py +87 -0
  30. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/schema.py +63 -0
  31. semantic_router-0.1.0.dev1/semantic_router/__init__.py +0 -7
  32. semantic_router-0.1.0.dev1/semantic_router/encoders/base.py +0 -22
  33. semantic_router-0.1.0.dev1/semantic_router/hybrid_layer.py +0 -220
  34. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/LICENSE +0 -0
  35. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/README.md +0 -0
  36. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/linear.py +0 -0
  37. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/llms/__init__.py +0 -0
  38. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/llms/base.py +0 -0
  39. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/llms/cohere.py +0 -0
  40. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/llms/grammars/json.gbnf +0 -0
  41. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/llms/llamacpp.py +0 -0
  42. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/llms/mistral.py +0 -0
  43. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/llms/ollama.py +0 -0
  44. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/llms/openai.py +0 -0
  45. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/llms/openrouter.py +0 -0
  46. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/llms/zure.py +0 -0
  47. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/route.py +0 -0
  48. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/utils/__init__.py +0 -0
  49. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/utils/defaults.py +0 -0
  50. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/utils/function_call.py +0 -0
  51. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/utils/llm.py +0 -0
  52. {semantic_router-0.1.0.dev1 → semantic_router-0.1.0.dev2}/semantic_router/utils/logger.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: semantic-router
3
- Version: 0.1.0.dev1
3
+ Version: 0.1.0.dev2
4
4
  Summary: Super fast semantic router for AI decision making
5
5
  License: MIT
6
6
  Author: Aurelio AI
@@ -21,10 +21,10 @@ Provides-Extra: local
21
21
  Provides-Extra: mistralai
22
22
  Provides-Extra: pinecone
23
23
  Provides-Extra: postgres
24
- Provides-Extra: processing
25
24
  Provides-Extra: qdrant
26
25
  Provides-Extra: vision
27
- Requires-Dist: aiohttp (>=3.9.5,<4.0.0)
26
+ Requires-Dist: aiohttp (>=3.10.11,<4.0.0)
27
+ Requires-Dist: aurelio-sdk (>=0.0.16,<0.0.17)
28
28
  Requires-Dist: boto3 (>=1.34.98,<2.0.0) ; extra == "bedrock"
29
29
  Requires-Dist: botocore (>=1.34.110,<2.0.0) ; extra == "bedrock"
30
30
  Requires-Dist: cohere (>=5.9.4,<6.00) ; extra == "cohere"
@@ -33,14 +33,13 @@ Requires-Dist: colorlog (>=6.8.0,<7.0.0)
33
33
  Requires-Dist: fastembed (>=0.3.0,<0.4.0) ; extra == "fastembed"
34
34
  Requires-Dist: google-cloud-aiplatform (>=1.45.0,<2.0.0) ; extra == "google"
35
35
  Requires-Dist: llama-cpp-python (>=0.2.28,<0.2.86) ; extra == "local"
36
- Requires-Dist: matplotlib (>=3.8.3,<4.0.0) ; extra == "processing"
37
36
  Requires-Dist: mistralai (>=0.0.12,<0.1.0) ; extra == "mistralai"
38
37
  Requires-Dist: numpy (>=1.25.2,<2.0.0)
39
38
  Requires-Dist: openai (>=1.10.0,<2.0.0)
40
39
  Requires-Dist: pillow (>=10.2.0,<11.0.0) ; extra == "vision"
41
40
  Requires-Dist: pinecone (>=5.0.0) ; extra == "pinecone"
42
41
  Requires-Dist: pinecone-text (>=0.7.1,<0.10.0) ; extra == "hybrid"
43
- Requires-Dist: psycopg2 (>=2.9.9,<3.0.0) ; extra == "postgres"
42
+ Requires-Dist: psycopg2-binary (>=2.9.9,<3.0.0)
44
43
  Requires-Dist: pydantic (>=2.5.3,<3.0.0)
45
44
  Requires-Dist: pyyaml (>=6.0.1,<7.0.0)
46
45
  Requires-Dist: qdrant-client (>=1.11.1,<2.0.0) ; extra == "qdrant"
@@ -52,6 +51,7 @@ Requires-Dist: tiktoken (>=0.6.0,<1.0.0)
52
51
  Requires-Dist: tokenizers (>=0.19) ; extra == "local"
53
52
  Requires-Dist: torch (>=2.1.0,<2.6.0) ; extra == "local" or extra == "vision"
54
53
  Requires-Dist: torchvision (>=0.17.0,<0.18.0) ; extra == "vision"
54
+ Requires-Dist: tornado (>=6.4.2,<7.0.0)
55
55
  Requires-Dist: transformers (>=4.36.2) ; extra == "local" or extra == "vision"
56
56
  Description-Content-Type: text/markdown
57
57
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "semantic-router"
3
- version = "0.1.0.dev1"
3
+ version = "0.1.0.dev2"
4
4
  description = "Super fast semantic router for AI decision making"
5
5
  authors = ["Aurelio AI <hello@aurelio.ai>"]
6
6
  readme = "README.md"
@@ -16,6 +16,7 @@ mistralai= {version = ">=0.0.12,<0.1.0", optional = true}
16
16
  numpy = "^1.25.2"
17
17
  colorlog = "^6.8.0"
18
18
  pyyaml = "^6.0.1"
19
+ aurelio-sdk = {version = "^0.0.16"}
19
20
  pinecone-text = {version = ">=0.7.1,<0.10.0", optional = true}
20
21
  torch = {version = ">=2.1.0,<2.6.0", optional = true}
21
22
  transformers = {version = ">=4.36.2", optional = true}
@@ -27,24 +28,23 @@ regex = ">=2023.12.25"
27
28
  torchvision = { version = ">=0.17.0,<0.18.0", optional = true}
28
29
  pillow = { version = ">=10.2.0,<11.0.0", optional = true}
29
30
  tiktoken = ">=0.6.0,<1.0.0"
30
- matplotlib = { version = "^3.8.3", optional = true}
31
31
  qdrant-client = {version = "^1.11.1", optional = true}
32
32
  google-cloud-aiplatform = {version = "^1.45.0", optional = true}
33
33
  requests-mock = "^1.12.1"
34
34
  boto3 = { version = "^1.34.98", optional = true }
35
35
  botocore = {version = "^1.34.110", optional = true}
36
- aiohttp = "^3.9.5"
36
+ aiohttp = "^3.10.11"
37
37
  fastembed = {version = "^0.3.0", optional = true}
38
- psycopg2 = {version = "^2.9.9", optional = true}
38
+ psycopg2-binary = {version = "^2.9.9", optional = true}
39
39
  sphinx = {version = "^7.0.0", optional = true}
40
40
  sphinxawesome-theme = {version = "^5.2.0", optional = true}
41
+ tornado = {version = "^6.4.2", optional = true}
41
42
 
42
43
  [tool.poetry.extras]
43
44
  hybrid = ["pinecone-text"]
44
45
  local = ["torch", "transformers", "tokenizers", "huggingface-hub", "llama-cpp-python"]
45
46
  pinecone = ["pinecone"]
46
47
  vision = ["torch", "torchvision", "transformers", "pillow"]
47
- processing = ["matplotlib"]
48
48
  mistralai = ["mistralai"]
49
49
  qdrant = ["qdrant-client"]
50
50
  google = ["google-cloud-aiplatform"]
@@ -57,10 +57,11 @@ cohere = ["cohere"]
57
57
  [tool.poetry.group.dev.dependencies]
58
58
  ipykernel = "^6.25.0"
59
59
  ruff = "^0.1.5"
60
- pytest = "^7.4.3"
60
+ pytest = "^8.2"
61
61
  pytest-mock = "^3.12.0"
62
62
  pytest-cov = "^4.1.0"
63
63
  pytest-xdist = "^3.5.0"
64
+ pytest-asyncio = "^0.24.0"
64
65
  mypy = "^1.7.1"
65
66
  types-pyyaml = "^6.0.12.12"
66
67
  types-requests = "^2.31.0"
@@ -0,0 +1,6 @@
1
+ from semantic_router.routers import RouterConfig, SemanticRouter, HybridRouter
2
+ from semantic_router.route import Route
3
+
4
+ __all__ = ["SemanticRouter", "HybridRouter", "Route", "RouterConfig"]
5
+
6
+ __version__ = "0.1.0.dev2"
@@ -1,6 +1,7 @@
1
1
  from typing import List, Optional
2
2
 
3
- from semantic_router.encoders.base import BaseEncoder
3
+ from semantic_router.encoders.aurelio import AurelioSparseEncoder
4
+ from semantic_router.encoders.base import DenseEncoder, SparseEncoder
4
5
  from semantic_router.encoders.bedrock import BedrockEncoder
5
6
  from semantic_router.encoders.bm25 import BM25Encoder
6
7
  from semantic_router.encoders.clip import CLIPEncoder
@@ -14,10 +15,12 @@ from semantic_router.encoders.openai import OpenAIEncoder
14
15
  from semantic_router.encoders.tfidf import TfidfEncoder
15
16
  from semantic_router.encoders.vit import VitEncoder
16
17
  from semantic_router.encoders.zure import AzureOpenAIEncoder
17
- from semantic_router.schema import EncoderType
18
+ from semantic_router.schema import EncoderType, SparseEmbedding
18
19
 
19
20
  __all__ = [
20
- "BaseEncoder",
21
+ "AurelioSparseEncoder",
22
+ "DenseEncoder",
23
+ "SparseEncoder",
21
24
  "AzureOpenAIEncoder",
22
25
  "CohereEncoder",
23
26
  "OpenAIEncoder",
@@ -37,7 +40,7 @@ __all__ = [
37
40
  class AutoEncoder:
38
41
  type: EncoderType
39
42
  name: Optional[str]
40
- model: BaseEncoder
43
+ model: DenseEncoder | SparseEncoder
41
44
 
42
45
  def __init__(self, type: str, name: Optional[str]):
43
46
  self.type = EncoderType(type)
@@ -49,6 +52,8 @@ class AutoEncoder:
49
52
  self.model = CohereEncoder(name=name)
50
53
  elif self.type == EncoderType.OPENAI:
51
54
  self.model = OpenAIEncoder(name=name)
55
+ elif self.type == EncoderType.AURELIO:
56
+ self.model = AurelioSparseEncoder(name=name)
52
57
  elif self.type == EncoderType.BM25:
53
58
  if name is None:
54
59
  name = "bm25"
@@ -74,5 +79,5 @@ class AutoEncoder:
74
79
  else:
75
80
  raise ValueError(f"Encoder type '{type}' not supported")
76
81
 
77
- def __call__(self, texts: List[str]) -> List[List[float]]:
82
+ def __call__(self, texts: List[str]) -> List[List[float]] | List[SparseEmbedding]:
78
83
  return self.model(texts)
@@ -0,0 +1,47 @@
1
+ import os
2
+ from typing import Any, List, Optional
3
+ from pydantic.v1 import Field
4
+
5
+ from aurelio_sdk import AurelioClient, AsyncAurelioClient, EmbeddingResponse
6
+
7
+ from semantic_router.encoders.base import SparseEncoder
8
+ from semantic_router.schema import SparseEmbedding
9
+
10
+
11
+ class AurelioSparseEncoder(SparseEncoder):
12
+ model: Optional[Any] = None
13
+ client: AurelioClient = Field(default_factory=AurelioClient, exclude=True)
14
+ async_client: AsyncAurelioClient = Field(
15
+ default_factory=AsyncAurelioClient, exclude=True
16
+ )
17
+ type: str = "sparse"
18
+
19
+ def __init__(
20
+ self,
21
+ name: str | None = None,
22
+ api_key: Optional[str] = None,
23
+ ):
24
+ if name is None:
25
+ name = "bm25"
26
+ super().__init__(name=name)
27
+ if api_key is None:
28
+ api_key = os.getenv("AURELIO_API_KEY")
29
+ if api_key is None:
30
+ raise ValueError("AURELIO_API_KEY environment variable is not set.")
31
+ self.client = AurelioClient(api_key=api_key)
32
+ self.async_client = AsyncAurelioClient(api_key=api_key)
33
+
34
+ def __call__(self, docs: list[str]) -> list[SparseEmbedding]:
35
+ res: EmbeddingResponse = self.client.embedding(input=docs, model=self.name)
36
+ embeds = [SparseEmbedding.from_aurelio(r.embedding) for r in res.data]
37
+ return embeds
38
+
39
+ async def acall(self, docs: list[str]) -> list[SparseEmbedding]:
40
+ res: EmbeddingResponse = await self.async_client.embedding(
41
+ input=docs, model=self.name
42
+ )
43
+ embeds = [SparseEmbedding.from_aurelio(r.embedding) for r in res.data]
44
+ return embeds
45
+
46
+ def fit(self, docs: List[str]):
47
+ raise NotImplementedError("AurelioSparseEncoder does not support fit.")
@@ -0,0 +1,53 @@
1
+ from typing import Any, Coroutine, List, Optional
2
+
3
+ from pydantic.v1 import BaseModel, Field, validator
4
+ import numpy as np
5
+
6
+ from semantic_router.schema import SparseEmbedding
7
+
8
+
9
+ class DenseEncoder(BaseModel):
10
+ name: str
11
+ score_threshold: Optional[float] = None
12
+ type: str = Field(default="base")
13
+
14
+ class Config:
15
+ arbitrary_types_allowed = True
16
+
17
+ @validator("score_threshold", pre=True, always=True)
18
+ def set_score_threshold(cls, v):
19
+ return float(v) if v is not None else None
20
+
21
+ def __call__(self, docs: List[Any]) -> List[List[float]]:
22
+ raise NotImplementedError("Subclasses must implement this method")
23
+
24
+ def acall(self, docs: List[Any]) -> Coroutine[Any, Any, List[List[float]]]:
25
+ raise NotImplementedError("Subclasses must implement this method")
26
+
27
+
28
+ class SparseEncoder(BaseModel):
29
+ name: str
30
+ type: str = Field(default="base")
31
+
32
+ class Config:
33
+ arbitrary_types_allowed = True
34
+
35
+ def __call__(self, docs: List[str]) -> List[SparseEmbedding]:
36
+ raise NotImplementedError("Subclasses must implement this method")
37
+
38
+ async def acall(self, docs: List[str]) -> list[SparseEmbedding]:
39
+ raise NotImplementedError("Subclasses must implement this method")
40
+
41
+ def _array_to_sparse_embeddings(
42
+ self, sparse_arrays: np.ndarray
43
+ ) -> List[SparseEmbedding]:
44
+ """Consumes several sparse vectors containing zero-values and returns a compact array."""
45
+ if sparse_arrays.ndim != 2:
46
+ raise ValueError(f"Expected a 2D array, got a {sparse_arrays.ndim}D array.")
47
+ # get coordinates of non-zero values
48
+ coords = np.nonzero(sparse_arrays)
49
+ # create compact array
50
+ compact_array = np.array([coords[0], coords[1], sparse_arrays[coords]]).T
51
+ arr_range = range(compact_array[:, 0].max().astype(int) + 1)
52
+ arrs = [compact_array[compact_array[:, 0] == i, :][:, 1:3] for i in arr_range]
53
+ return [SparseEmbedding.from_compact_array(arr) for arr in arrs]
@@ -1,7 +1,7 @@
1
1
  """
2
2
  This module provides the BedrockEncoder class for generating embeddings using Amazon's Bedrock Platform.
3
3
 
4
- The BedrockEncoder class is a subclass of BaseEncoder and utilizes the TextEmbeddingModel from the
4
+ The BedrockEncoder class is a subclass of DenseEncoder and utilizes the TextEmbeddingModel from the
5
5
  Amazon's Bedrock Platform to generate embeddings for given documents. It requires an AWS Access Key ID
6
6
  and AWS Secret Access Key and supports customization of the pre-trained model, score threshold, and region.
7
7
 
@@ -21,12 +21,12 @@ from typing import List, Optional, Any
21
21
  import os
22
22
  from time import sleep
23
23
  import tiktoken
24
- from semantic_router.encoders import BaseEncoder
24
+ from semantic_router.encoders import DenseEncoder
25
25
  from semantic_router.utils.defaults import EncoderDefault
26
26
  from semantic_router.utils.logger import logger
27
27
 
28
28
 
29
- class BedrockEncoder(BaseEncoder):
29
+ class BedrockEncoder(DenseEncoder):
30
30
  client: Any = None
31
31
  type: str = "bedrock"
32
32
  input_type: Optional[str] = "search_query"
@@ -1,21 +1,24 @@
1
1
  from typing import Any, Dict, List, Optional
2
2
 
3
- from semantic_router.encoders import BaseEncoder
3
+ from semantic_router.encoders.tfidf import TfidfEncoder
4
4
  from semantic_router.utils.logger import logger
5
+ from semantic_router.schema import SparseEmbedding
6
+ from semantic_router.route import Route
5
7
 
6
8
 
7
- class BM25Encoder(BaseEncoder):
9
+ class BM25Encoder(TfidfEncoder):
8
10
  model: Optional[Any] = None
9
11
  idx_mapping: Optional[Dict[int, int]] = None
10
12
  type: str = "sparse"
11
13
 
12
14
  def __init__(
13
15
  self,
14
- name: str = "bm25",
15
- score_threshold: float = 0.82,
16
+ name: str | None = None,
16
17
  use_default_params: bool = True,
17
18
  ):
18
- super().__init__(name=name, score_threshold=score_threshold)
19
+ if name is None:
20
+ name = "bm25"
21
+ super().__init__(name=name)
19
22
  try:
20
23
  from pinecone_text.sparse import BM25Encoder as encoder
21
24
  except ImportError:
@@ -32,6 +35,7 @@ class BM25Encoder(BaseEncoder):
32
35
  self._set_idx_mapping()
33
36
 
34
37
  def _set_idx_mapping(self):
38
+ # TODO JB: this is training the model somehow - not sure how...
35
39
  params = self.model.get_params()
36
40
  doc_freq = params["doc_freq"]
37
41
  if isinstance(doc_freq, dict):
@@ -40,8 +44,20 @@ class BM25Encoder(BaseEncoder):
40
44
  else:
41
45
  raise TypeError("Expected a dictionary for 'doc_freq'")
42
46
 
43
- def __call__(self, docs: List[str]) -> List[List[float]]:
44
- if self.model is None or self.idx_mapping is None:
47
+ def fit(self, routes: List[Route]):
48
+ """Trains the encoder weights on the provided routes.
49
+
50
+ :param routes: List of routes to train the encoder on.
51
+ :type routes: List[Route]
52
+ """
53
+ self._fit_validate(routes=routes)
54
+ if self.model is None:
55
+ raise ValueError("Model is not initialized.")
56
+ utterances = [utterance for route in routes for utterance in route.utterances]
57
+ self.model.fit(corpus=utterances)
58
+
59
+ def __call__(self, docs: List[str]) -> list[SparseEmbedding]:
60
+ if self.model is None:
45
61
  raise ValueError("Model or index mapping is not initialized.")
46
62
  if len(docs) == 1:
47
63
  sparse_dicts = self.model.encode_queries(docs)
@@ -50,18 +66,8 @@ class BM25Encoder(BaseEncoder):
50
66
  else:
51
67
  raise ValueError("No documents to encode.")
52
68
 
53
- embeds = [[0.0] * len(self.idx_mapping)] * len(docs)
69
+ embeds = []
54
70
  for i, output in enumerate(sparse_dicts):
55
- indices = output["indices"]
56
- values = output["values"]
57
- for idx, val in zip(indices, values):
58
- if idx in self.idx_mapping:
59
- position = self.idx_mapping[idx]
60
- embeds[i][position] = val
71
+ if isinstance(output, dict):
72
+ embeds.append(SparseEmbedding.from_pinecone_dict(output))
61
73
  return embeds
62
-
63
- def fit(self, docs: List[str]):
64
- if self.model is None:
65
- raise ValueError("Model is not initialized.")
66
- self.model.fit(docs)
67
- self._set_idx_mapping()
@@ -3,10 +3,10 @@ from typing import Any, List, Optional
3
3
  import numpy as np
4
4
  from pydantic.v1 import PrivateAttr
5
5
  from typing import Dict
6
- from semantic_router.encoders import BaseEncoder
6
+ from semantic_router.encoders import DenseEncoder
7
7
 
8
8
 
9
- class CLIPEncoder(BaseEncoder):
9
+ class CLIPEncoder(DenseEncoder):
10
10
  name: str = "openai/clip-vit-base-patch16"
11
11
  type: str = "huggingface"
12
12
  score_threshold: float = 0.2
@@ -3,11 +3,11 @@ from typing import Any, List, Optional
3
3
 
4
4
  from pydantic.v1 import PrivateAttr
5
5
 
6
- from semantic_router.encoders import BaseEncoder
6
+ from semantic_router.encoders import DenseEncoder
7
7
  from semantic_router.utils.defaults import EncoderDefault
8
8
 
9
9
 
10
- class CohereEncoder(BaseEncoder):
10
+ class CohereEncoder(DenseEncoder):
11
11
  _client: Any = PrivateAttr()
12
12
  _embed_type: Any = PrivateAttr()
13
13
  type: str = "cohere"
@@ -3,10 +3,10 @@ from typing import Any, List, Optional
3
3
  import numpy as np
4
4
  from pydantic.v1 import PrivateAttr
5
5
 
6
- from semantic_router.encoders import BaseEncoder
6
+ from semantic_router.encoders import DenseEncoder
7
7
 
8
8
 
9
- class FastEmbedEncoder(BaseEncoder):
9
+ class FastEmbedEncoder(DenseEncoder):
10
10
  type: str = "fastembed"
11
11
  name: str = "BAAI/bge-small-en-v1.5"
12
12
  max_length: int = 512
@@ -1,7 +1,7 @@
1
1
  """
2
2
  This module provides the GoogleEncoder class for generating embeddings using Google's AI Platform.
3
3
 
4
- The GoogleEncoder class is a subclass of BaseEncoder and utilizes the TextEmbeddingModel from the
4
+ The GoogleEncoder class is a subclass of DenseEncoder and utilizes the TextEmbeddingModel from the
5
5
  Google AI Platform to generate embeddings for given documents. It requires a Google Cloud project ID
6
6
  and supports customization of the pre-trained model, score threshold, location, and API endpoint.
7
7
 
@@ -19,11 +19,11 @@ Classes:
19
19
  import os
20
20
  from typing import Any, List, Optional
21
21
 
22
- from semantic_router.encoders import BaseEncoder
22
+ from semantic_router.encoders import DenseEncoder
23
23
  from semantic_router.utils.defaults import EncoderDefault
24
24
 
25
25
 
26
- class GoogleEncoder(BaseEncoder):
26
+ class GoogleEncoder(DenseEncoder):
27
27
  """GoogleEncoder class for generating embeddings using Google's AI Platform.
28
28
 
29
29
  Attributes:
@@ -1,7 +1,7 @@
1
1
  """
2
2
  This module provides the HFEndpointEncoder class to embeddings models using Huggingface's endpoint.
3
3
 
4
- The HFEndpointEncoder class is a subclass of BaseEncoder and utilizes a specified Huggingface
4
+ The HFEndpointEncoder class is a subclass of DenseEncoder and utilizes a specified Huggingface
5
5
  endpoint to generate embeddings for given documents. It requires the URL of the Huggingface
6
6
  API endpoint and an API key for authentication. The class supports customization of the score
7
7
  threshold for filtering or processing the embeddings.
@@ -27,11 +27,11 @@ from typing import Any, List, Optional, Dict
27
27
 
28
28
  from pydantic.v1 import PrivateAttr
29
29
 
30
- from semantic_router.encoders import BaseEncoder
30
+ from semantic_router.encoders import DenseEncoder
31
31
  from semantic_router.utils.logger import logger
32
32
 
33
33
 
34
- class HuggingFaceEncoder(BaseEncoder):
34
+ class HuggingFaceEncoder(DenseEncoder):
35
35
  name: str = "sentence-transformers/all-MiniLM-L6-v2"
36
36
  type: str = "huggingface"
37
37
  score_threshold: float = 0.5
@@ -140,7 +140,7 @@ class HuggingFaceEncoder(BaseEncoder):
140
140
  return self._torch.max(token_embeddings, 1)[0]
141
141
 
142
142
 
143
- class HFEndpointEncoder(BaseEncoder):
143
+ class HFEndpointEncoder(DenseEncoder):
144
144
  """
145
145
  A class to encode documents using a Hugging Face transformer model endpoint.
146
146
 
@@ -6,11 +6,11 @@ from typing import Any, List, Optional
6
6
 
7
7
  from pydantic.v1 import PrivateAttr
8
8
 
9
- from semantic_router.encoders import BaseEncoder
9
+ from semantic_router.encoders import DenseEncoder
10
10
  from semantic_router.utils.defaults import EncoderDefault
11
11
 
12
12
 
13
- class MistralEncoder(BaseEncoder):
13
+ class MistralEncoder(DenseEncoder):
14
14
  """Class to encode text using MistralAI"""
15
15
 
16
16
  _client: Any = PrivateAttr()
@@ -10,7 +10,7 @@ from openai._types import NotGiven
10
10
  from openai.types import CreateEmbeddingResponse
11
11
  import tiktoken
12
12
 
13
- from semantic_router.encoders import BaseEncoder
13
+ from semantic_router.encoders import DenseEncoder
14
14
  from semantic_router.schema import EncoderInfo
15
15
  from semantic_router.utils.defaults import EncoderDefault
16
16
  from semantic_router.utils.logger import logger
@@ -35,7 +35,7 @@ model_configs = {
35
35
  }
36
36
 
37
37
 
38
- class OpenAIEncoder(BaseEncoder):
38
+ class OpenAIEncoder(DenseEncoder):
39
39
  client: Optional[openai.Client]
40
40
  async_client: Optional[openai.AsyncClient]
41
41
  dimensions: Union[int, NotGiven] = NotGiven()
@@ -3,24 +3,25 @@ from collections import Counter
3
3
  from typing import Dict, List
4
4
 
5
5
  import numpy as np
6
- from numpy import ndarray
7
- from numpy.linalg import norm
8
6
 
9
- from semantic_router.encoders import BaseEncoder
7
+ from semantic_router.encoders import SparseEncoder
10
8
  from semantic_router.route import Route
9
+ from semantic_router.schema import SparseEmbedding
11
10
 
12
11
 
13
- class TfidfEncoder(BaseEncoder):
14
- idf: ndarray = np.array([])
12
+ class TfidfEncoder(SparseEncoder):
13
+ idf: np.ndarray = np.array([])
14
+ # TODO: add option to use default params like with BM25Encoder
15
15
  word_index: Dict = {}
16
16
 
17
- def __init__(self, name: str = "tfidf", score_threshold: float = 0.82):
18
- # TODO default score_threshold not thoroughly tested, should optimize
19
- super().__init__(name=name, score_threshold=score_threshold)
17
+ def __init__(self, name: str | None = None):
18
+ if name is None:
19
+ name = "tfidf"
20
+ super().__init__(name=name)
20
21
  self.word_index = {}
21
22
  self.idf = np.array([])
22
23
 
23
- def __call__(self, docs: List[str]) -> List[List[float]]:
24
+ def __call__(self, docs: List[str]) -> list[SparseEmbedding]:
24
25
  if len(self.word_index) == 0 or self.idf.size == 0:
25
26
  raise ValueError("Vectorizer is not initialized.")
26
27
  if len(docs) == 0:
@@ -29,16 +30,28 @@ class TfidfEncoder(BaseEncoder):
29
30
  docs = [self._preprocess(doc) for doc in docs]
30
31
  tf = self._compute_tf(docs)
31
32
  tfidf = tf * self.idf
32
- return tfidf.tolist()
33
+ return self._array_to_sparse_embeddings(tfidf)
33
34
 
34
35
  def fit(self, routes: List[Route]):
36
+ """Trains the encoder weights on the provided routes.
37
+
38
+ :param routes: List of routes to train the encoder on.
39
+ :type routes: List[Route]
40
+ """
41
+ self._fit_validate(routes=routes)
35
42
  docs = []
36
43
  for route in routes:
37
44
  for doc in route.utterances:
38
45
  docs.append(self._preprocess(doc)) # type: ignore
39
46
  self.word_index = self._build_word_index(docs)
47
+ if len(self.word_index) == 0:
48
+ raise ValueError(f"Too little data to fit {self.__class__.__name__}.")
40
49
  self.idf = self._compute_idf(docs)
41
50
 
51
+ def _fit_validate(self, routes: List[Route]):
52
+ if not isinstance(routes, list) or not isinstance(routes[0], Route):
53
+ raise TypeError("`routes` parameter must be a list of Route objects.")
54
+
42
55
  def _build_word_index(self, docs: List[str]) -> Dict:
43
56
  words = set()
44
57
  for doc in docs:
@@ -57,7 +70,7 @@ class TfidfEncoder(BaseEncoder):
57
70
  if word in self.word_index:
58
71
  tf[i, self.word_index[word]] = count
59
72
  # L2 normalization
60
- tf = tf / norm(tf, axis=1, keepdims=True)
73
+ tf = tf / np.linalg.norm(tf, axis=1, keepdims=True)
61
74
  return tf
62
75
 
63
76
  def _compute_idf(self, docs: List[str]) -> np.ndarray:
@@ -2,10 +2,10 @@ from typing import Any, List, Optional, Dict
2
2
 
3
3
  from pydantic.v1 import PrivateAttr
4
4
 
5
- from semantic_router.encoders import BaseEncoder
5
+ from semantic_router.encoders import DenseEncoder
6
6
 
7
7
 
8
- class VitEncoder(BaseEncoder):
8
+ class VitEncoder(DenseEncoder):
9
9
  name: str = "google/vit-base-patch16-224"
10
10
  type: str = "huggingface"
11
11
  score_threshold: float = 0.5
@@ -8,12 +8,12 @@ from openai._types import NotGiven
8
8
  from openai import OpenAIError
9
9
  from openai.types import CreateEmbeddingResponse
10
10
 
11
- from semantic_router.encoders import BaseEncoder
11
+ from semantic_router.encoders import DenseEncoder
12
12
  from semantic_router.utils.defaults import EncoderDefault
13
13
  from semantic_router.utils.logger import logger
14
14
 
15
15
 
16
- class AzureOpenAIEncoder(BaseEncoder):
16
+ class AzureOpenAIEncoder(DenseEncoder):
17
17
  client: Optional[openai.AzureOpenAI] = None
18
18
  async_client: Optional[openai.AsyncAzureOpenAI] = None
19
19
  dimensions: Union[int, NotGiven] = NotGiven()
@@ -1,11 +1,15 @@
1
1
  from semantic_router.index.base import BaseIndex
2
+ from semantic_router.index.hybrid_local import HybridLocalIndex
2
3
  from semantic_router.index.local import LocalIndex
3
4
  from semantic_router.index.pinecone import PineconeIndex
5
+ from semantic_router.index.postgres import PostgresIndex
4
6
  from semantic_router.index.qdrant import QdrantIndex
5
7
 
6
8
  __all__ = [
7
9
  "BaseIndex",
10
+ "HybridLocalIndex",
8
11
  "LocalIndex",
9
12
  "QdrantIndex",
10
13
  "PineconeIndex",
14
+ "PostgresIndex",
11
15
  ]
@@ -4,7 +4,7 @@ import json
4
4
  import numpy as np
5
5
  from pydantic.v1 import BaseModel
6
6
 
7
- from semantic_router.schema import ConfigParameter, Utterance
7
+ from semantic_router.schema import ConfigParameter, SparseEmbedding, Utterance
8
8
  from semantic_router.route import Route
9
9
  from semantic_router.utils.logger import logger
10
10
 
@@ -18,12 +18,12 @@ class BaseIndex(BaseModel):
18
18
 
19
19
  # You can define common attributes here if there are any.
20
20
  # For example, a placeholder for the index attribute:
21
- index: Optional[Any] = None
22
21
  routes: Optional[np.ndarray] = None
23
22
  utterances: Optional[np.ndarray] = None
24
23
  dimensions: Union[int, None] = None
25
24
  type: str = "base"
26
25
  init_async_index: bool = False
26
+ index: Optional[Any] = None
27
27
 
28
28
  def add(
29
29
  self,
@@ -108,6 +108,7 @@ class BaseIndex(BaseModel):
108
108
  vector: np.ndarray,
109
109
  top_k: int = 5,
110
110
  route_filter: Optional[List[str]] = None,
111
+ sparse_vector: dict[int, float] | SparseEmbedding | None = None,
111
112
  ) -> Tuple[np.ndarray, List[str]]:
112
113
  """
113
114
  Search the index for the query_vector and return top_k results.
@@ -120,6 +121,7 @@ class BaseIndex(BaseModel):
120
121
  vector: np.ndarray,
121
122
  top_k: int = 5,
122
123
  route_filter: Optional[List[str]] = None,
124
+ sparse_vector: dict[int, float] | SparseEmbedding | None = None,
123
125
  ) -> Tuple[np.ndarray, List[str]]:
124
126
  """
125
127
  Search the index for the query_vector and return top_k results.