semantic-router 0.1.0.dev2__tar.gz → 0.1.0.dev4__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 (50) hide show
  1. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/LICENSE +1 -1
  2. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/PKG-INFO +12 -10
  3. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/pyproject.toml +10 -10
  4. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/__init__.py +1 -1
  5. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/encoders/aurelio.py +1 -1
  6. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/encoders/base.py +2 -2
  7. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/encoders/bedrock.py +37 -11
  8. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/encoders/clip.py +3 -2
  9. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/encoders/cohere.py +1 -1
  10. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/encoders/fastembed.py +1 -1
  11. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/encoders/huggingface.py +5 -3
  12. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/encoders/mistral.py +1 -1
  13. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/encoders/openai.py +9 -9
  14. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/encoders/vit.py +4 -3
  15. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/index/base.py +77 -10
  16. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/index/pinecone.py +21 -16
  17. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/index/postgres.py +3 -2
  18. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/index/qdrant.py +2 -2
  19. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/llms/base.py +3 -1
  20. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/llms/cohere.py +1 -1
  21. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/llms/llamacpp.py +1 -3
  22. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/llms/mistral.py +1 -3
  23. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/llms/ollama.py +5 -10
  24. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/llms/openai.py +9 -10
  25. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/llms/openrouter.py +8 -8
  26. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/llms/zure.py +5 -6
  27. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/route.py +2 -7
  28. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/routers/base.py +50 -19
  29. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/routers/hybrid.py +7 -7
  30. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/routers/semantic.py +0 -2
  31. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/schema.py +20 -7
  32. semantic_router-0.1.0.dev4/semantic_router/utils/__init__.py +0 -0
  33. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/utils/function_call.py +1 -1
  34. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/README.md +0 -0
  35. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/encoders/__init__.py +0 -0
  36. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/encoders/bm25.py +0 -0
  37. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/encoders/google.py +0 -0
  38. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/encoders/tfidf.py +0 -0
  39. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/encoders/zure.py +0 -0
  40. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/index/__init__.py +0 -0
  41. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/index/hybrid_local.py +0 -0
  42. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/index/local.py +0 -0
  43. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/linear.py +0 -0
  44. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/llms/__init__.py +0 -0
  45. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/llms/grammars/json.gbnf +0 -0
  46. /semantic_router-0.1.0.dev2/semantic_router/utils/__init__.py → /semantic_router-0.1.0.dev4/semantic_router/py.typed +0 -0
  47. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/routers/__init__.py +0 -0
  48. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/utils/defaults.py +0 -0
  49. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/utils/llm.py +0 -0
  50. {semantic_router-0.1.0.dev2 → semantic_router-0.1.0.dev4}/semantic_router/utils/logger.py +0 -0
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2023 Aurelio AI
3
+ Copyright (c) 2024 Aurelio AI
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,16 +1,18 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: semantic-router
3
- Version: 0.1.0.dev2
3
+ Version: 0.1.0.dev4
4
4
  Summary: Super fast semantic router for AI decision making
5
5
  License: MIT
6
6
  Author: Aurelio AI
7
7
  Author-email: hello@aurelio.ai
8
- Requires-Python: >=3.9,<3.13
8
+ Requires-Python: >=3.9,<3.14
9
9
  Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Programming Language :: Python :: 3
11
11
  Classifier: Programming Language :: Python :: 3.9
12
12
  Classifier: Programming Language :: Python :: 3.10
13
13
  Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
14
16
  Provides-Extra: bedrock
15
17
  Provides-Extra: cohere
16
18
  Provides-Extra: docs
@@ -30,17 +32,17 @@ Requires-Dist: botocore (>=1.34.110,<2.0.0) ; extra == "bedrock"
30
32
  Requires-Dist: cohere (>=5.9.4,<6.00) ; extra == "cohere"
31
33
  Requires-Dist: colorama (>=0.4.6,<0.5.0)
32
34
  Requires-Dist: colorlog (>=6.8.0,<7.0.0)
33
- Requires-Dist: fastembed (>=0.3.0,<0.4.0) ; extra == "fastembed"
35
+ Requires-Dist: fastembed (>=0.3.0,<0.4.0) ; (python_version < "3.13") and (extra == "fastembed")
34
36
  Requires-Dist: google-cloud-aiplatform (>=1.45.0,<2.0.0) ; extra == "google"
35
- Requires-Dist: llama-cpp-python (>=0.2.28,<0.2.86) ; extra == "local"
37
+ Requires-Dist: llama-cpp-python (>=0.2.28,<0.2.86) ; (python_version < "3.13") and (extra == "local")
36
38
  Requires-Dist: mistralai (>=0.0.12,<0.1.0) ; extra == "mistralai"
37
39
  Requires-Dist: numpy (>=1.25.2,<2.0.0)
38
40
  Requires-Dist: openai (>=1.10.0,<2.0.0)
39
- Requires-Dist: pillow (>=10.2.0,<11.0.0) ; extra == "vision"
41
+ Requires-Dist: pillow (>=10.2.0,<11.0.0) ; (python_version < "3.13") and (extra == "vision")
40
42
  Requires-Dist: pinecone (>=5.0.0) ; extra == "pinecone"
41
43
  Requires-Dist: pinecone-text (>=0.7.1,<0.10.0) ; extra == "hybrid"
42
44
  Requires-Dist: psycopg2-binary (>=2.9.9,<3.0.0)
43
- Requires-Dist: pydantic (>=2.5.3,<3.0.0)
45
+ Requires-Dist: pydantic (>=2.10.2,<3.0.0)
44
46
  Requires-Dist: pyyaml (>=6.0.1,<7.0.0)
45
47
  Requires-Dist: qdrant-client (>=1.11.1,<2.0.0) ; extra == "qdrant"
46
48
  Requires-Dist: regex (>=2023.12.25)
@@ -48,11 +50,11 @@ Requires-Dist: requests-mock (>=1.12.1,<2.0.0)
48
50
  Requires-Dist: sphinx (>=7.0.0,<8.0.0) ; extra == "docs"
49
51
  Requires-Dist: sphinxawesome-theme (>=5.2.0,<6.0.0) ; extra == "docs"
50
52
  Requires-Dist: tiktoken (>=0.6.0,<1.0.0)
51
- Requires-Dist: tokenizers (>=0.19) ; extra == "local"
52
- Requires-Dist: torch (>=2.1.0,<2.6.0) ; extra == "local" or extra == "vision"
53
- Requires-Dist: torchvision (>=0.17.0,<0.18.0) ; extra == "vision"
53
+ Requires-Dist: tokenizers (>=0.19) ; (python_version < "3.13") and (extra == "local")
54
+ Requires-Dist: torch (>=2.1.0,<2.6.0) ; (python_version < "3.13") and (extra == "local" or extra == "vision")
55
+ Requires-Dist: torchvision (>=0.17.0,<0.18.0) ; (python_version < "3.13") and (extra == "vision")
54
56
  Requires-Dist: tornado (>=6.4.2,<7.0.0)
55
- Requires-Dist: transformers (>=4.36.2) ; extra == "local" or extra == "vision"
57
+ Requires-Dist: transformers (>=4.36.2) ; (python_version < "3.13") and (extra == "local" or extra == "vision")
56
58
  Description-Content-Type: text/markdown
57
59
 
58
60
  [![Semantic Router](https://i.ibb.co.com/g423grt/semantic-router-banner.png)](https://aurelio.ai)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "semantic-router"
3
- version = "0.1.0.dev2"
3
+ version = "0.1.0.dev4"
4
4
  description = "Super fast semantic router for AI decision making"
5
5
  authors = ["Aurelio AI <hello@aurelio.ai>"]
6
6
  readme = "README.md"
@@ -8,8 +8,8 @@ packages = [{include = "semantic_router"}]
8
8
  license = "MIT"
9
9
 
10
10
  [tool.poetry.dependencies]
11
- python = ">=3.9,<3.13"
12
- pydantic = "^2.5.3"
11
+ python = ">=3.9,<3.14"
12
+ pydantic = "^2.10.2"
13
13
  openai = ">=1.10.0,<2.0.0"
14
14
  cohere = {version = ">=5.9.4,<6.00", optional = true}
15
15
  mistralai= {version = ">=0.0.12,<0.1.0", optional = true}
@@ -18,15 +18,15 @@ colorlog = "^6.8.0"
18
18
  pyyaml = "^6.0.1"
19
19
  aurelio-sdk = {version = "^0.0.16"}
20
20
  pinecone-text = {version = ">=0.7.1,<0.10.0", optional = true}
21
- torch = {version = ">=2.1.0,<2.6.0", optional = true}
22
- transformers = {version = ">=4.36.2", optional = true}
23
- tokenizers = {version = ">=0.19", optional = true}
24
- llama-cpp-python = {version = ">=0.2.28,<0.2.86", optional = true}
21
+ torch = {version = ">=2.1.0,<2.6.0", optional = true, python = "<3.13" }
22
+ transformers = {version = ">=4.36.2", optional = true, python = "<3.13" }
23
+ tokenizers = {version = ">=0.19", optional = true, python = "<3.13" }
24
+ llama-cpp-python = {version = ">=0.2.28,<0.2.86", optional = true, python = "<3.13" }
25
25
  colorama = "^0.4.6"
26
26
  pinecone = {version=">=5.0.0", optional = true}
27
27
  regex = ">=2023.12.25"
28
- torchvision = { version = ">=0.17.0,<0.18.0", optional = true}
29
- pillow = { version = ">=10.2.0,<11.0.0", optional = true}
28
+ torchvision = { version = ">=0.17.0,<0.18.0", optional = true, python = "<3.13" }
29
+ pillow = { version = ">=10.2.0,<11.0.0", optional = true, python = "<3.13" }
30
30
  tiktoken = ">=0.6.0,<1.0.0"
31
31
  qdrant-client = {version = "^1.11.1", optional = true}
32
32
  google-cloud-aiplatform = {version = "^1.45.0", optional = true}
@@ -34,7 +34,7 @@ requests-mock = "^1.12.1"
34
34
  boto3 = { version = "^1.34.98", optional = true }
35
35
  botocore = {version = "^1.34.110", optional = true}
36
36
  aiohttp = "^3.10.11"
37
- fastembed = {version = "^0.3.0", optional = true}
37
+ fastembed = {version = "^0.3.0", optional = true, python = "<3.13" }
38
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}
@@ -3,4 +3,4 @@ from semantic_router.route import Route
3
3
 
4
4
  __all__ = ["SemanticRouter", "HybridRouter", "Route", "RouterConfig"]
5
5
 
6
- __version__ = "0.1.0.dev2"
6
+ __version__ = "0.1.0.dev4"
@@ -1,6 +1,6 @@
1
1
  import os
2
2
  from typing import Any, List, Optional
3
- from pydantic.v1 import Field
3
+ from pydantic import Field
4
4
 
5
5
  from aurelio_sdk import AurelioClient, AsyncAurelioClient, EmbeddingResponse
6
6
 
@@ -1,6 +1,6 @@
1
1
  from typing import Any, Coroutine, List, Optional
2
2
 
3
- from pydantic.v1 import BaseModel, Field, validator
3
+ from pydantic import BaseModel, Field, field_validator
4
4
  import numpy as np
5
5
 
6
6
  from semantic_router.schema import SparseEmbedding
@@ -14,7 +14,7 @@ class DenseEncoder(BaseModel):
14
14
  class Config:
15
15
  arbitrary_types_allowed = True
16
16
 
17
- @validator("score_threshold", pre=True, always=True)
17
+ @field_validator("score_threshold")
18
18
  def set_score_threshold(cls, v):
19
19
  return float(v) if v is not None else None
20
20
 
@@ -17,7 +17,7 @@ Classes:
17
17
  """
18
18
 
19
19
  import json
20
- from typing import List, Optional, Any
20
+ from typing import Dict, List, Optional, Any, Union
21
21
  import os
22
22
  from time import sleep
23
23
  import tiktoken
@@ -138,11 +138,14 @@ class BedrockEncoder(DenseEncoder):
138
138
  ) from err
139
139
  return bedrock_client
140
140
 
141
- def __call__(self, docs: List[str]) -> List[List[float]]:
141
+ def __call__(
142
+ self, docs: List[Union[str, Dict]], model_kwargs: Optional[Dict] = None
143
+ ) -> List[List[float]]:
142
144
  """Generates embeddings for the given documents.
143
145
 
144
146
  Args:
145
147
  docs: A list of strings representing the documents to embed.
148
+ model_kwargs: A dictionary of model-specific inference parameters.
146
149
 
147
150
  Returns:
148
151
  A list of lists, where each inner list contains the embedding values for a
@@ -168,13 +171,29 @@ class BedrockEncoder(DenseEncoder):
168
171
  embeddings = []
169
172
  if self.name and "amazon" in self.name:
170
173
  for doc in docs:
171
- embedding_body = json.dumps(
172
- {
173
- "inputText": doc,
174
- }
175
- )
174
+
175
+ embedding_body = {}
176
+
177
+ if isinstance(doc, dict):
178
+ embedding_body["inputText"] = doc.get("text")
179
+ embedding_body["inputImage"] = doc.get(
180
+ "image"
181
+ ) # expects a base64-encoded image
182
+ else:
183
+ embedding_body["inputText"] = doc
184
+
185
+ # Add model-specific inference parameters
186
+ if model_kwargs:
187
+ embedding_body = embedding_body | model_kwargs
188
+
189
+ # Clean up null values
190
+ embedding_body = {k: v for k, v in embedding_body.items() if v}
191
+
192
+ # Format payload
193
+ embedding_body_payload: str = json.dumps(embedding_body)
194
+
176
195
  response = self.client.invoke_model(
177
- body=embedding_body,
196
+ body=embedding_body_payload,
178
197
  modelId=self.name,
179
198
  accept="application/json",
180
199
  contentType="application/json",
@@ -184,9 +203,16 @@ class BedrockEncoder(DenseEncoder):
184
203
  elif self.name and "cohere" in self.name:
185
204
  chunked_docs = self.chunk_strings(docs)
186
205
  for chunk in chunked_docs:
187
- chunk = json.dumps(
188
- {"texts": chunk, "input_type": self.input_type}
189
- )
206
+ chunk = {"texts": chunk, "input_type": self.input_type}
207
+
208
+ # Add model-specific inference parameters
209
+ # Note: if specified, input_type will be overwritten by model_kwargs
210
+ if model_kwargs:
211
+ chunk = chunk | model_kwargs
212
+
213
+ # Format payload
214
+ chunk = json.dumps(chunk)
215
+
190
216
  response = self.client.invoke_model(
191
217
  body=chunk,
192
218
  modelId=self.name,
@@ -1,7 +1,7 @@
1
1
  from typing import Any, List, Optional
2
2
 
3
3
  import numpy as np
4
- from pydantic.v1 import PrivateAttr
4
+ from pydantic import PrivateAttr
5
5
  from typing import Dict
6
6
  from semantic_router.encoders import DenseEncoder
7
7
 
@@ -9,7 +9,6 @@ from semantic_router.encoders import DenseEncoder
9
9
  class CLIPEncoder(DenseEncoder):
10
10
  name: str = "openai/clip-vit-base-patch16"
11
11
  type: str = "huggingface"
12
- score_threshold: float = 0.2
13
12
  tokenizer_kwargs: Dict = {}
14
13
  processor_kwargs: Dict = {}
15
14
  model_kwargs: Dict = {}
@@ -21,6 +20,8 @@ class CLIPEncoder(DenseEncoder):
21
20
  _Image: Any = PrivateAttr()
22
21
 
23
22
  def __init__(self, **data):
23
+ if data.get("score_threshold") is None:
24
+ data["score_threshold"] = 0.2
24
25
  super().__init__(**data)
25
26
  self._tokenizer, self._processor, self._model = self._initialize_hf_model()
26
27
 
@@ -1,7 +1,7 @@
1
1
  import os
2
2
  from typing import Any, List, Optional
3
3
 
4
- from pydantic.v1 import PrivateAttr
4
+ from pydantic import PrivateAttr
5
5
 
6
6
  from semantic_router.encoders import DenseEncoder
7
7
  from semantic_router.utils.defaults import EncoderDefault
@@ -1,7 +1,7 @@
1
1
  from typing import Any, List, Optional
2
2
 
3
3
  import numpy as np
4
- from pydantic.v1 import PrivateAttr
4
+ from pydantic import PrivateAttr
5
5
 
6
6
  from semantic_router.encoders import DenseEncoder
7
7
 
@@ -25,7 +25,7 @@ import time
25
25
  import os
26
26
  from typing import Any, List, Optional, Dict
27
27
 
28
- from pydantic.v1 import PrivateAttr
28
+ from pydantic import PrivateAttr
29
29
 
30
30
  from semantic_router.encoders import DenseEncoder
31
31
  from semantic_router.utils.logger import logger
@@ -34,7 +34,6 @@ from semantic_router.utils.logger import logger
34
34
  class HuggingFaceEncoder(DenseEncoder):
35
35
  name: str = "sentence-transformers/all-MiniLM-L6-v2"
36
36
  type: str = "huggingface"
37
- score_threshold: float = 0.5
38
37
  tokenizer_kwargs: Dict = {}
39
38
  model_kwargs: Dict = {}
40
39
  device: Optional[str] = None
@@ -43,6 +42,8 @@ class HuggingFaceEncoder(DenseEncoder):
43
42
  _torch: Any = PrivateAttr()
44
43
 
45
44
  def __init__(self, **data):
45
+ if data.get("score_threshold") is None:
46
+ data["score_threshold"] = 0.5
46
47
  super().__init__(**data)
47
48
  self._tokenizer, self._model = self._initialize_hf_model()
48
49
 
@@ -153,7 +154,6 @@ class HFEndpointEncoder(DenseEncoder):
153
154
  name: str = "hugging_face_custom_endpoint"
154
155
  huggingface_url: Optional[str] = None
155
156
  huggingface_api_key: Optional[str] = None
156
- score_threshold: float = 0.8
157
157
 
158
158
  def __init__(
159
159
  self,
@@ -180,6 +180,8 @@ class HFEndpointEncoder(DenseEncoder):
180
180
  """
181
181
  huggingface_url = huggingface_url or os.getenv("HF_API_URL")
182
182
  huggingface_api_key = huggingface_api_key or os.getenv("HF_API_KEY")
183
+ if score_threshold is None:
184
+ score_threshold = 0.8
183
185
 
184
186
  super().__init__(name=name, score_threshold=score_threshold) # type: ignore
185
187
 
@@ -4,7 +4,7 @@ import os
4
4
  from time import sleep
5
5
  from typing import Any, List, Optional
6
6
 
7
- from pydantic.v1 import PrivateAttr
7
+ from pydantic import PrivateAttr
8
8
 
9
9
  from semantic_router.encoders import DenseEncoder
10
10
  from semantic_router.utils.defaults import EncoderDefault
@@ -2,7 +2,7 @@ from asyncio import sleep as asleep
2
2
  import os
3
3
  from time import sleep
4
4
  from typing import Any, List, Optional, Union
5
- from pydantic.v1 import PrivateAttr
5
+ from pydantic import PrivateAttr
6
6
 
7
7
  import openai
8
8
  from openai import OpenAIError
@@ -36,8 +36,8 @@ model_configs = {
36
36
 
37
37
 
38
38
  class OpenAIEncoder(DenseEncoder):
39
- client: Optional[openai.Client]
40
- async_client: Optional[openai.AsyncClient]
39
+ _client: Optional[openai.Client] = PrivateAttr(default=None)
40
+ _async_client: Optional[openai.AsyncClient] = PrivateAttr(default=None)
41
41
  dimensions: Union[int, NotGiven] = NotGiven()
42
42
  token_limit: int = 8192 # default value, should be replaced by config
43
43
  _token_encoder: Any = PrivateAttr()
@@ -77,10 +77,10 @@ class OpenAIEncoder(DenseEncoder):
77
77
  if max_retries is not None:
78
78
  self.max_retries = max_retries
79
79
  try:
80
- self.client = openai.Client(
80
+ self._client = openai.Client(
81
81
  base_url=base_url, api_key=api_key, organization=openai_org_id
82
82
  )
83
- self.async_client = openai.AsyncClient(
83
+ self._async_client = openai.AsyncClient(
84
84
  base_url=base_url, api_key=api_key, organization=openai_org_id
85
85
  )
86
86
  except Exception as e:
@@ -103,7 +103,7 @@ class OpenAIEncoder(DenseEncoder):
103
103
  False and a document exceeds the token limit, an error will be
104
104
  raised.
105
105
  :return: List of embeddings for each document."""
106
- if self.client is None:
106
+ if self._client is None:
107
107
  raise ValueError("OpenAI client is not initialized.")
108
108
  embeds = None
109
109
 
@@ -114,7 +114,7 @@ class OpenAIEncoder(DenseEncoder):
114
114
  # Exponential backoff
115
115
  for j in range(self.max_retries + 1):
116
116
  try:
117
- embeds = self.client.embeddings.create(
117
+ embeds = self._client.embeddings.create(
118
118
  input=docs,
119
119
  model=self.name,
120
120
  dimensions=self.dimensions,
@@ -160,7 +160,7 @@ class OpenAIEncoder(DenseEncoder):
160
160
  return text
161
161
 
162
162
  async def acall(self, docs: List[str], truncate: bool = True) -> List[List[float]]:
163
- if self.async_client is None:
163
+ if self._async_client is None:
164
164
  raise ValueError("OpenAI async client is not initialized.")
165
165
  embeds = None
166
166
 
@@ -171,7 +171,7 @@ class OpenAIEncoder(DenseEncoder):
171
171
  # Exponential backoff
172
172
  for j in range(self.max_retries + 1):
173
173
  try:
174
- embeds = await self.async_client.embeddings.create(
174
+ embeds = await self._async_client.embeddings.create(
175
175
  input=docs,
176
176
  model=self.name,
177
177
  dimensions=self.dimensions,
@@ -1,6 +1,6 @@
1
- from typing import Any, List, Optional, Dict
1
+ from typing import Any, Dict, List, Optional
2
2
 
3
- from pydantic.v1 import PrivateAttr
3
+ from pydantic import PrivateAttr
4
4
 
5
5
  from semantic_router.encoders import DenseEncoder
6
6
 
@@ -8,7 +8,6 @@ from semantic_router.encoders import DenseEncoder
8
8
  class VitEncoder(DenseEncoder):
9
9
  name: str = "google/vit-base-patch16-224"
10
10
  type: str = "huggingface"
11
- score_threshold: float = 0.5
12
11
  processor_kwargs: Dict = {}
13
12
  model_kwargs: Dict = {}
14
13
  device: Optional[str] = None
@@ -19,6 +18,8 @@ class VitEncoder(DenseEncoder):
19
18
  _Image: Any = PrivateAttr()
20
19
 
21
20
  def __init__(self, **data):
21
+ if data.get("score_threshold") is None:
22
+ data["score_threshold"] = 0.5
22
23
  super().__init__(**data)
23
24
  self._processor, self._model = self._initialize_hf_model()
24
25
 
@@ -1,8 +1,10 @@
1
+ from datetime import datetime
2
+ import time
1
3
  from typing import Any, List, Optional, Tuple, Union, Dict
2
4
  import json
3
5
 
4
6
  import numpy as np
5
- from pydantic.v1 import BaseModel
7
+ from pydantic import BaseModel
6
8
 
7
9
  from semantic_router.schema import ConfigParameter, SparseEmbedding, Utterance
8
10
  from semantic_router.route import Route
@@ -157,26 +159,91 @@ class BaseIndex(BaseModel):
157
159
  logger.warning("This method should be implemented by subclasses.")
158
160
  self.index = None
159
161
 
160
- def _read_hash(self) -> ConfigParameter:
161
- """
162
- Read the hash of the previously written index.
162
+ def _read_config(self, field: str, scope: str | None = None) -> ConfigParameter:
163
+ """Read a config parameter from the index.
163
164
 
164
- This method should be implemented by subclasses.
165
+ :param field: The field to read.
166
+ :type field: str
167
+ :param scope: The scope to read.
168
+ :type scope: str | None
169
+ :return: The config parameter that was read.
170
+ :rtype: ConfigParameter
165
171
  """
166
172
  logger.warning("This method should be implemented by subclasses.")
167
173
  return ConfigParameter(
168
- field="sr_hash",
174
+ field=field,
169
175
  value="",
170
- namespace="",
176
+ scope=scope,
171
177
  )
172
178
 
173
- def _write_config(self, config: ConfigParameter):
179
+ def _read_hash(self) -> ConfigParameter:
180
+ """Read the hash of the previously written index.
181
+
182
+ :return: The config parameter that was read.
183
+ :rtype: ConfigParameter
174
184
  """
175
- Write a config parameter to the index.
185
+ return self._read_config(field="sr_hash")
176
186
 
177
- This method should be implemented by subclasses.
187
+ def _write_config(self, config: ConfigParameter) -> ConfigParameter:
188
+ """Write a config parameter to the index.
189
+
190
+ :param config: The config parameter to write.
191
+ :type config: ConfigParameter
192
+ :return: The config parameter that was written.
193
+ :rtype: ConfigParameter
178
194
  """
179
195
  logger.warning("This method should be implemented by subclasses.")
196
+ return config
197
+
198
+ def lock(
199
+ self, value: bool, wait: int = 0, scope: str | None = None
200
+ ) -> ConfigParameter:
201
+ """Lock/unlock the index for a given scope (if applicable). If index
202
+ already locked/unlocked, raises ValueError.
203
+
204
+ :param scope: The scope to lock.
205
+ :type scope: str | None
206
+ :param wait: The number of seconds to wait for the index to be unlocked, if
207
+ set to 0, will raise an error if index is already locked/unlocked.
208
+ :type wait: int
209
+ :return: The config parameter that was locked.
210
+ :rtype: ConfigParameter
211
+ """
212
+ start_time = datetime.now()
213
+ while True:
214
+ if self._is_locked(scope=scope) != value:
215
+ # in this case, we can set the lock value
216
+ break
217
+ if (datetime.now() - start_time).total_seconds() < wait:
218
+ # wait for 2.5 seconds before checking again
219
+ time.sleep(2.5)
220
+ else:
221
+ raise ValueError(
222
+ f"Index is already {'locked' if value else 'unlocked'}."
223
+ )
224
+ lock_param = ConfigParameter(
225
+ field="sr_lock",
226
+ value=str(value),
227
+ scope=scope,
228
+ )
229
+ self._write_config(lock_param)
230
+ return lock_param
231
+
232
+ def _is_locked(self, scope: str | None = None) -> bool:
233
+ """Check if the index is locked for a given scope (if applicable).
234
+
235
+ :param scope: The scope to check.
236
+ :type scope: str | None
237
+ :return: True if the index is locked, False otherwise.
238
+ :rtype: bool
239
+ """
240
+ lock_config = self._read_config(field="sr_lock", scope=scope)
241
+ if lock_config.value == "True":
242
+ return True
243
+ elif lock_config.value == "False" or not lock_config.value:
244
+ return False
245
+ else:
246
+ raise ValueError(f"Invalid lock value: {lock_config.value}")
180
247
 
181
248
  def _get_all(self, prefix: Optional[str] = None, include_metadata: bool = False):
182
249
  """
@@ -8,7 +8,7 @@ import json
8
8
  from typing import Any, Dict, List, Optional, Union, Tuple
9
9
 
10
10
  import numpy as np
11
- from pydantic.v1 import BaseModel, Field
11
+ from pydantic import BaseModel, Field
12
12
 
13
13
  from semantic_router.index.base import BaseIndex
14
14
  from semantic_router.schema import ConfigParameter, SparseEmbedding
@@ -405,39 +405,43 @@ class PineconeIndex(BaseIndex):
405
405
  route_names = [result["metadata"]["sr_route"] for result in results["matches"]]
406
406
  return np.array(scores), route_names
407
407
 
408
- def _read_hash(self) -> ConfigParameter:
408
+ def _read_config(self, field: str, scope: str | None = None) -> ConfigParameter:
409
+ scope = scope or self.namespace
409
410
  if self.index is None:
410
411
  return ConfigParameter(
411
- field="sr_hash",
412
+ field=field,
412
413
  value="",
413
- namespace=self.namespace,
414
+ scope=scope,
414
415
  )
415
- hash_id = f"sr_hash#{self.namespace}"
416
- hash_record = self.index.fetch(
417
- ids=[hash_id],
416
+ config_id = f"{field}#{scope}"
417
+ config_record = self.index.fetch(
418
+ ids=[config_id],
418
419
  namespace="sr_config",
419
420
  )
420
- if hash_record["vectors"]:
421
+ if config_record["vectors"]:
421
422
  return ConfigParameter(
422
- field="sr_hash",
423
- value=hash_record["vectors"][hash_id]["metadata"]["value"],
424
- created_at=hash_record["vectors"][hash_id]["metadata"]["created_at"],
425
- namespace=self.namespace,
423
+ field=field,
424
+ value=config_record["vectors"][config_id]["metadata"]["value"],
425
+ created_at=config_record["vectors"][config_id]["metadata"][
426
+ "created_at"
427
+ ],
428
+ scope=scope,
426
429
  )
427
430
  else:
428
- logger.warning("Configuration for hash parameter not found in index.")
431
+ logger.warning(f"Configuration for {field} parameter not found in index.")
429
432
  return ConfigParameter(
430
- field="sr_hash",
433
+ field=field,
431
434
  value="",
432
- namespace=self.namespace,
435
+ scope=scope,
433
436
  )
434
437
 
435
- def _write_config(self, config: ConfigParameter) -> None:
438
+ def _write_config(self, config: ConfigParameter) -> ConfigParameter:
436
439
  """Method to write a config parameter to the remote Pinecone index.
437
440
 
438
441
  :param config: The config parameter to write to the index.
439
442
  :type config: ConfigParameter
440
443
  """
444
+ config.scope = config.scope or self.namespace
441
445
  if self.index is None:
442
446
  raise ValueError("Index has not been initialized.")
443
447
  if self.dimensions is None:
@@ -446,6 +450,7 @@ class PineconeIndex(BaseIndex):
446
450
  vectors=[config.to_pinecone(dimensions=self.dimensions)],
447
451
  namespace="sr_config",
448
452
  )
453
+ return config
449
454
 
450
455
  async def aquery(
451
456
  self,
@@ -103,7 +103,6 @@ class PostgresIndex(BaseIndex):
103
103
  connection_string: Optional[str] = None
104
104
  index_prefix: str = "semantic_router_"
105
105
  index_name: str = "index"
106
- dimensions: int = 1536
107
106
  metric: Metric = Metric.COSINE
108
107
  namespace: Optional[str] = ""
109
108
  conn: Optional["psycopg2.extensions.connection"] = None
@@ -115,9 +114,9 @@ class PostgresIndex(BaseIndex):
115
114
  connection_string: Optional[str] = None,
116
115
  index_prefix: str = "semantic_router_",
117
116
  index_name: str = "index",
118
- dimensions: int = 1536,
119
117
  metric: Metric = Metric.COSINE,
120
118
  namespace: Optional[str] = "",
119
+ dimensions: int | None = None,
121
120
  ):
122
121
  """
123
122
  Initializes the Postgres index with the specified parameters.
@@ -135,6 +134,8 @@ class PostgresIndex(BaseIndex):
135
134
  :param namespace: An optional namespace for the index.
136
135
  :type namespace: Optional[str]
137
136
  """
137
+ if dimensions is None:
138
+ dimensions = 1536
138
139
  super().__init__()
139
140
  # try and import psycopg2
140
141
  try:
@@ -1,7 +1,7 @@
1
1
  from typing import Any, Dict, List, Optional, Tuple, Union
2
2
 
3
3
  import numpy as np
4
- from pydantic.v1 import Field
4
+ from pydantic import Field
5
5
 
6
6
  from semantic_router.index.base import BaseIndex
7
7
  from semantic_router.schema import ConfigParameter, Metric, SparseEmbedding, Utterance
@@ -40,7 +40,7 @@ class QdrantIndex(BaseIndex):
40
40
  default=6334,
41
41
  description="Port of the gRPC interface.",
42
42
  )
43
- prefer_grpc: bool = Field(
43
+ prefer_grpc: Optional[bool] = Field(
44
44
  default=None,
45
45
  description="Whether to use gPRC interface whenever possible in methods",
46
46
  )