semantic-router 0.1.0.dev3__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.
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/PKG-INFO +1 -1
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/pyproject.toml +1 -1
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/__init__.py +1 -1
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/encoders/bedrock.py +37 -11
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/index/base.py +76 -9
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/index/pinecone.py +20 -15
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/routers/base.py +49 -18
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/routers/hybrid.py +6 -6
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/routers/semantic.py +0 -2
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/schema.py +6 -5
- semantic_router-0.1.0.dev4/semantic_router/utils/__init__.py +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/LICENSE +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/README.md +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/encoders/__init__.py +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/encoders/aurelio.py +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/encoders/base.py +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/encoders/bm25.py +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/encoders/clip.py +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/encoders/cohere.py +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/encoders/fastembed.py +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/encoders/google.py +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/encoders/huggingface.py +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/encoders/mistral.py +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/encoders/openai.py +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/encoders/tfidf.py +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/encoders/vit.py +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/encoders/zure.py +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/index/__init__.py +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/index/hybrid_local.py +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/index/local.py +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/index/postgres.py +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/index/qdrant.py +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/linear.py +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/llms/__init__.py +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/llms/base.py +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/llms/cohere.py +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/llms/grammars/json.gbnf +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/llms/llamacpp.py +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/llms/mistral.py +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/llms/ollama.py +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/llms/openai.py +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/llms/openrouter.py +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/llms/zure.py +0 -0
- /semantic_router-0.1.0.dev3/semantic_router/utils/__init__.py → /semantic_router-0.1.0.dev4/semantic_router/py.typed +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/route.py +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/routers/__init__.py +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/utils/defaults.py +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/utils/function_call.py +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/utils/llm.py +0 -0
- {semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/utils/logger.py +0 -0
{semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/encoders/bedrock.py
RENAMED
|
@@ -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__(
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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=
|
|
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 =
|
|
188
|
-
|
|
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,3 +1,5 @@
|
|
|
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
|
|
|
@@ -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
|
|
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
|
-
|
|
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=
|
|
174
|
+
field=field,
|
|
169
175
|
value="",
|
|
170
|
-
|
|
176
|
+
scope=scope,
|
|
171
177
|
)
|
|
172
178
|
|
|
173
|
-
def
|
|
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
|
-
|
|
185
|
+
return self._read_config(field="sr_hash")
|
|
176
186
|
|
|
177
|
-
|
|
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
|
"""
|
|
@@ -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
|
|
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=
|
|
412
|
+
field=field,
|
|
412
413
|
value="",
|
|
413
|
-
|
|
414
|
+
scope=scope,
|
|
414
415
|
)
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
ids=[
|
|
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
|
|
421
|
+
if config_record["vectors"]:
|
|
421
422
|
return ConfigParameter(
|
|
422
|
-
field=
|
|
423
|
-
value=
|
|
424
|
-
created_at=
|
|
425
|
-
|
|
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
|
|
431
|
+
logger.warning(f"Configuration for {field} parameter not found in index.")
|
|
429
432
|
return ConfigParameter(
|
|
430
|
-
field=
|
|
433
|
+
field=field,
|
|
431
434
|
value="",
|
|
432
|
-
|
|
435
|
+
scope=scope,
|
|
433
436
|
)
|
|
434
437
|
|
|
435
|
-
def _write_config(self, config: ConfigParameter) ->
|
|
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,
|
|
@@ -4,6 +4,7 @@ import os
|
|
|
4
4
|
import random
|
|
5
5
|
import hashlib
|
|
6
6
|
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
|
7
|
+
from typing_extensions import deprecated
|
|
7
8
|
from pydantic import BaseModel, Field
|
|
8
9
|
|
|
9
10
|
import numpy as np
|
|
@@ -280,6 +281,20 @@ class RouterConfig:
|
|
|
280
281
|
)
|
|
281
282
|
|
|
282
283
|
|
|
284
|
+
def xq_reshape(xq: List[float] | np.ndarray) -> np.ndarray:
|
|
285
|
+
# convert to numpy array if not already
|
|
286
|
+
if not isinstance(xq, np.ndarray):
|
|
287
|
+
xq = np.array(xq)
|
|
288
|
+
# check if vector is 1D and expand to 2D if necessary
|
|
289
|
+
if len(xq.shape) == 1:
|
|
290
|
+
xq = np.expand_dims(xq, axis=0)
|
|
291
|
+
if xq.shape[0] != 1:
|
|
292
|
+
raise ValueError(
|
|
293
|
+
f"Expected (1, x) dimensional input for query, got {xq.shape}."
|
|
294
|
+
)
|
|
295
|
+
return xq
|
|
296
|
+
|
|
297
|
+
|
|
283
298
|
class BaseRouter(BaseModel):
|
|
284
299
|
encoder: DenseEncoder = Field(default_factory=OpenAIEncoder)
|
|
285
300
|
index: BaseIndex = Field(default_factory=BaseIndex)
|
|
@@ -402,7 +417,7 @@ class BaseRouter(BaseModel):
|
|
|
402
417
|
def __call__(
|
|
403
418
|
self,
|
|
404
419
|
text: Optional[str] = None,
|
|
405
|
-
vector: Optional[List[float]] = None,
|
|
420
|
+
vector: Optional[List[float] | np.ndarray] = None,
|
|
406
421
|
simulate_static: bool = False,
|
|
407
422
|
route_filter: Optional[List[str]] = None,
|
|
408
423
|
) -> RouteChoice:
|
|
@@ -411,6 +426,9 @@ class BaseRouter(BaseModel):
|
|
|
411
426
|
if text is None:
|
|
412
427
|
raise ValueError("Either text or vector must be provided")
|
|
413
428
|
vector = self._encode(text=[text])
|
|
429
|
+
# convert to numpy array if not already
|
|
430
|
+
vector = xq_reshape(vector)
|
|
431
|
+
# calculate semantics
|
|
414
432
|
route, top_class_scores = self._retrieve_top_route(vector, route_filter)
|
|
415
433
|
passed = self._check_threshold(top_class_scores, route)
|
|
416
434
|
if passed and route is not None and not simulate_static:
|
|
@@ -444,7 +462,7 @@ class BaseRouter(BaseModel):
|
|
|
444
462
|
async def acall(
|
|
445
463
|
self,
|
|
446
464
|
text: Optional[str] = None,
|
|
447
|
-
vector: Optional[List[float]] = None,
|
|
465
|
+
vector: Optional[List[float] | np.ndarray] = None,
|
|
448
466
|
simulate_static: bool = False,
|
|
449
467
|
route_filter: Optional[List[str]] = None,
|
|
450
468
|
) -> RouteChoice:
|
|
@@ -453,7 +471,9 @@ class BaseRouter(BaseModel):
|
|
|
453
471
|
if text is None:
|
|
454
472
|
raise ValueError("Either text or vector must be provided")
|
|
455
473
|
vector = await self._async_encode(text=[text])
|
|
456
|
-
|
|
474
|
+
# convert to numpy array if not already
|
|
475
|
+
vector = xq_reshape(vector)
|
|
476
|
+
# calculate semantics
|
|
457
477
|
route, top_class_scores = await self._async_retrieve_top_route(
|
|
458
478
|
vector, route_filter
|
|
459
479
|
)
|
|
@@ -483,19 +503,21 @@ class BaseRouter(BaseModel):
|
|
|
483
503
|
# if no route passes threshold, return empty route choice
|
|
484
504
|
return RouteChoice()
|
|
485
505
|
|
|
506
|
+
# TODO: add multiple routes return to __call__ and acall
|
|
507
|
+
@deprecated("This method is deprecated. Use `__call__` instead.")
|
|
486
508
|
def retrieve_multiple_routes(
|
|
487
509
|
self,
|
|
488
510
|
text: Optional[str] = None,
|
|
489
|
-
vector: Optional[List[float]] = None,
|
|
511
|
+
vector: Optional[List[float] | np.ndarray] = None,
|
|
490
512
|
) -> List[RouteChoice]:
|
|
491
513
|
if vector is None:
|
|
492
514
|
if text is None:
|
|
493
515
|
raise ValueError("Either text or vector must be provided")
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
516
|
+
vector = self._encode(text=[text])
|
|
517
|
+
# convert to numpy array if not already
|
|
518
|
+
vector = xq_reshape(vector)
|
|
497
519
|
# get relevant utterances
|
|
498
|
-
results = self._retrieve(xq=
|
|
520
|
+
results = self._retrieve(xq=vector)
|
|
499
521
|
# decide most relevant routes
|
|
500
522
|
categories_with_scores = self._semantic_classify_multiple_routes(results)
|
|
501
523
|
return [
|
|
@@ -514,16 +536,14 @@ class BaseRouter(BaseModel):
|
|
|
514
536
|
# return route_choices
|
|
515
537
|
|
|
516
538
|
def _retrieve_top_route(
|
|
517
|
-
self, vector:
|
|
539
|
+
self, vector: np.ndarray, route_filter: Optional[List[str]] = None
|
|
518
540
|
) -> Tuple[Optional[Route], List[float]]:
|
|
519
541
|
"""
|
|
520
542
|
Retrieve the top matching route based on the given vector.
|
|
521
543
|
Returns a tuple of the route (if any) and the scores of the top class.
|
|
522
544
|
"""
|
|
523
545
|
# get relevant results (scores and routes)
|
|
524
|
-
results = self._retrieve(
|
|
525
|
-
xq=np.array(vector), top_k=self.top_k, route_filter=route_filter
|
|
526
|
-
)
|
|
546
|
+
results = self._retrieve(xq=vector, top_k=self.top_k, route_filter=route_filter)
|
|
527
547
|
# decide most relevant routes
|
|
528
548
|
top_class, top_class_scores = self._semantic_classify(results)
|
|
529
549
|
# TODO do we need this check?
|
|
@@ -531,11 +551,11 @@ class BaseRouter(BaseModel):
|
|
|
531
551
|
return route, top_class_scores
|
|
532
552
|
|
|
533
553
|
async def _async_retrieve_top_route(
|
|
534
|
-
self, vector:
|
|
554
|
+
self, vector: np.ndarray, route_filter: Optional[List[str]] = None
|
|
535
555
|
) -> Tuple[Optional[Route], List[float]]:
|
|
536
556
|
# get relevant results (scores and routes)
|
|
537
557
|
results = await self._async_retrieve(
|
|
538
|
-
xq=
|
|
558
|
+
xq=vector, top_k=self.top_k, route_filter=route_filter
|
|
539
559
|
)
|
|
540
560
|
# decide most relevant routes
|
|
541
561
|
top_class, top_class_scores = await self._async_semantic_classify(results)
|
|
@@ -543,7 +563,7 @@ class BaseRouter(BaseModel):
|
|
|
543
563
|
route = self.check_for_matching_routes(top_class)
|
|
544
564
|
return route, top_class_scores
|
|
545
565
|
|
|
546
|
-
def sync(self, sync_mode: str, force: bool = False) -> List[str]:
|
|
566
|
+
def sync(self, sync_mode: str, force: bool = False, wait: int = 0) -> List[str]:
|
|
547
567
|
"""Runs a sync of the local routes with the remote index.
|
|
548
568
|
|
|
549
569
|
:param sync_mode: The mode to sync the routes with the remote index.
|
|
@@ -551,6 +571,10 @@ class BaseRouter(BaseModel):
|
|
|
551
571
|
:param force: Whether to force the sync even if the local and remote
|
|
552
572
|
hashes already match. Defaults to False.
|
|
553
573
|
:type force: bool, optional
|
|
574
|
+
:param wait: The number of seconds to wait for the index to be unlocked
|
|
575
|
+
before proceeding with the sync. If set to 0, will raise an error if
|
|
576
|
+
index is already locked/unlocked.
|
|
577
|
+
:type wait: int
|
|
554
578
|
:return: A list of diffs describing the addressed differences between
|
|
555
579
|
the local and remote route layers.
|
|
556
580
|
:rtype: List[str]
|
|
@@ -565,7 +589,9 @@ class BaseRouter(BaseModel):
|
|
|
565
589
|
remote_utterances=local_utterances,
|
|
566
590
|
)
|
|
567
591
|
return diff.to_utterance_str()
|
|
568
|
-
# otherwise we continue with the sync, first
|
|
592
|
+
# otherwise we continue with the sync, first locking the index
|
|
593
|
+
_ = self.index.lock(value=True, wait=wait)
|
|
594
|
+
# first creating a diff
|
|
569
595
|
local_utterances = self.to_config().to_utterances()
|
|
570
596
|
remote_utterances = self.index.get_utterances()
|
|
571
597
|
diff = UtteranceDiff.from_utterances(
|
|
@@ -576,6 +602,8 @@ class BaseRouter(BaseModel):
|
|
|
576
602
|
sync_strategy = diff.get_sync_strategy(sync_mode=sync_mode)
|
|
577
603
|
# and execute
|
|
578
604
|
self._execute_sync_strategy(sync_strategy)
|
|
605
|
+
# unlock index after sync
|
|
606
|
+
_ = self.index.lock(value=False)
|
|
579
607
|
return diff.to_utterance_str()
|
|
580
608
|
|
|
581
609
|
def _execute_sync_strategy(self, strategy: Dict[str, Dict[str, List[Utterance]]]):
|
|
@@ -781,6 +809,9 @@ class BaseRouter(BaseModel):
|
|
|
781
809
|
:param route_name: the name of the route to be deleted
|
|
782
810
|
:type str:
|
|
783
811
|
"""
|
|
812
|
+
# ensure index is not locked
|
|
813
|
+
if self.index._is_locked():
|
|
814
|
+
raise ValueError("Index is locked. Cannot delete route.")
|
|
784
815
|
current_local_hash = self._get_hash()
|
|
785
816
|
current_remote_hash = self.index._read_hash()
|
|
786
817
|
if current_remote_hash.value == "":
|
|
@@ -928,7 +959,7 @@ class BaseRouter(BaseModel):
|
|
|
928
959
|
"""Given a query vector, retrieve the top_k most similar records."""
|
|
929
960
|
# get scores and routes
|
|
930
961
|
scores, routes = self.index.query(
|
|
931
|
-
vector=xq, top_k=top_k, route_filter=route_filter
|
|
962
|
+
vector=xq[0], top_k=top_k, route_filter=route_filter
|
|
932
963
|
)
|
|
933
964
|
return [{"route": d, "score": s.item()} for d, s in zip(routes, scores)]
|
|
934
965
|
|
|
@@ -938,7 +969,7 @@ class BaseRouter(BaseModel):
|
|
|
938
969
|
"""Given a query vector, retrieve the top_k most similar records."""
|
|
939
970
|
# get scores and routes
|
|
940
971
|
scores, routes = await self.index.aquery(
|
|
941
|
-
vector=xq, top_k=top_k, route_filter=route_filter
|
|
972
|
+
vector=xq[0], top_k=top_k, route_filter=route_filter
|
|
942
973
|
)
|
|
943
974
|
return [{"route": d, "score": s.item()} for d, s in zip(routes, scores)]
|
|
944
975
|
|
|
@@ -14,7 +14,7 @@ from semantic_router.route import Route
|
|
|
14
14
|
from semantic_router.index import BaseIndex, HybridLocalIndex
|
|
15
15
|
from semantic_router.schema import RouteChoice, SparseEmbedding, Utterance
|
|
16
16
|
from semantic_router.utils.logger import logger
|
|
17
|
-
from semantic_router.routers.base import BaseRouter
|
|
17
|
+
from semantic_router.routers.base import BaseRouter, xq_reshape
|
|
18
18
|
from semantic_router.llms import BaseLLM
|
|
19
19
|
|
|
20
20
|
|
|
@@ -197,18 +197,19 @@ class HybridRouter(BaseRouter):
|
|
|
197
197
|
def __call__(
|
|
198
198
|
self,
|
|
199
199
|
text: Optional[str] = None,
|
|
200
|
-
vector: Optional[List[float]] = None,
|
|
200
|
+
vector: Optional[List[float] | np.ndarray] = None,
|
|
201
201
|
simulate_static: bool = False,
|
|
202
202
|
route_filter: Optional[List[str]] = None,
|
|
203
203
|
sparse_vector: dict[int, float] | SparseEmbedding | None = None,
|
|
204
204
|
) -> RouteChoice:
|
|
205
|
-
vector_arr: np.ndarray | None = None
|
|
206
205
|
potential_sparse_vector: List[SparseEmbedding] | None = None
|
|
207
206
|
# if no vector provided, encode text to get vector
|
|
208
207
|
if vector is None:
|
|
209
208
|
if text is None:
|
|
210
209
|
raise ValueError("Either text or vector must be provided")
|
|
211
|
-
|
|
210
|
+
vector, potential_sparse_vector = self._encode(text=[text])
|
|
211
|
+
# convert to numpy array if not already
|
|
212
|
+
vector = xq_reshape(vector)
|
|
212
213
|
if sparse_vector is None:
|
|
213
214
|
if text is None:
|
|
214
215
|
raise ValueError("Either text or sparse_vector must be provided")
|
|
@@ -217,10 +218,9 @@ class HybridRouter(BaseRouter):
|
|
|
217
218
|
)
|
|
218
219
|
if sparse_vector is None:
|
|
219
220
|
raise ValueError("Sparse vector is required for HybridLocalIndex.")
|
|
220
|
-
vector_arr = vector_arr if vector_arr is not None else np.array(vector)
|
|
221
221
|
# TODO: add alpha as a parameter
|
|
222
222
|
scores, route_names = self.index.query(
|
|
223
|
-
vector=
|
|
223
|
+
vector=vector,
|
|
224
224
|
top_k=self.top_k,
|
|
225
225
|
route_filter=route_filter,
|
|
226
226
|
sparse_vector=sparse_vector,
|
{semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/routers/semantic.py
RENAMED
|
@@ -40,14 +40,12 @@ class SemanticRouter(BaseRouter):
|
|
|
40
40
|
"""Given some text, encode it."""
|
|
41
41
|
# create query vector
|
|
42
42
|
xq = np.array(self.encoder(text))
|
|
43
|
-
xq = np.squeeze(xq) # Reduce to 1d array.
|
|
44
43
|
return xq
|
|
45
44
|
|
|
46
45
|
async def _async_encode(self, text: list[str]) -> Any:
|
|
47
46
|
"""Given some text, encode it."""
|
|
48
47
|
# create query vector
|
|
49
48
|
xq = np.array(await self.encoder.acall(docs=text))
|
|
50
|
-
xq = np.squeeze(xq) # Reduce to 1d array.
|
|
51
49
|
return xq
|
|
52
50
|
|
|
53
51
|
def add(self, routes: List[Route] | Route):
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from datetime import datetime
|
|
1
|
+
from datetime import datetime, timezone
|
|
2
2
|
from difflib import Differ
|
|
3
3
|
from enum import Enum
|
|
4
4
|
import json
|
|
@@ -62,12 +62,13 @@ class Message(BaseModel):
|
|
|
62
62
|
class ConfigParameter(BaseModel):
|
|
63
63
|
field: str
|
|
64
64
|
value: str
|
|
65
|
-
|
|
66
|
-
created_at: str = Field(
|
|
65
|
+
scope: Optional[str] = None
|
|
66
|
+
created_at: str = Field(
|
|
67
|
+
default_factory=lambda: datetime.now(timezone.utc).isoformat()
|
|
68
|
+
)
|
|
67
69
|
|
|
68
70
|
def to_pinecone(self, dimensions: int):
|
|
69
|
-
|
|
70
|
-
namespace = ""
|
|
71
|
+
namespace = self.scope or ""
|
|
71
72
|
return {
|
|
72
73
|
"id": f"{self.field}#{namespace}",
|
|
73
74
|
"values": [0.1] * dimensions,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/encoders/__init__.py
RENAMED
|
File without changes
|
{semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/encoders/aurelio.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/encoders/cohere.py
RENAMED
|
File without changes
|
{semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/encoders/fastembed.py
RENAMED
|
File without changes
|
{semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/encoders/google.py
RENAMED
|
File without changes
|
{semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/encoders/huggingface.py
RENAMED
|
File without changes
|
{semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/encoders/mistral.py
RENAMED
|
File without changes
|
{semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/encoders/openai.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/index/hybrid_local.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/llms/grammars/json.gbnf
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/llms/openrouter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/routers/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{semantic_router-0.1.0.dev3 → semantic_router-0.1.0.dev4}/semantic_router/utils/function_call.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|