leansearch 0.2.0__tar.gz → 0.2.2__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.
- {leansearch-0.2.0 → leansearch-0.2.2}/PKG-INFO +3 -3
- {leansearch-0.2.0 → leansearch-0.2.2}/pyproject.toml +3 -3
- {leansearch-0.2.0 → leansearch-0.2.2}/src/leansearch/__init__.py +1 -1
- {leansearch-0.2.0 → leansearch-0.2.2}/src/leansearch/_collection.py +54 -13
- leansearch-0.2.2/src/leansearch/_fusion.py +148 -0
- {leansearch-0.2.0 → leansearch-0.2.2}/src/leansearch/server/_service.py +17 -2
- {leansearch-0.2.0 → leansearch-0.2.2}/src/leansearch/server/client.py +4 -2
- {leansearch-0.2.0 → leansearch-0.2.2}/src/leansearch/server/leansearch.proto +4 -1
- leansearch-0.2.2/src/leansearch/server/leansearch_pb2.py +108 -0
- {leansearch-0.2.0 → leansearch-0.2.2}/src/leansearch/server/serve.py +3 -1
- {leansearch-0.2.0 → leansearch-0.2.2}/src/leansearch/server/serve_grpc.py +2 -1
- leansearch-0.2.0/src/leansearch/_fusion.py +0 -51
- leansearch-0.2.0/src/leansearch/server/leansearch_pb2.py +0 -108
- {leansearch-0.2.0 → leansearch-0.2.2}/.gitignore +0 -0
- {leansearch-0.2.0 → leansearch-0.2.2}/LICENSE +0 -0
- {leansearch-0.2.0 → leansearch-0.2.2}/src/leansearch/_db.py +0 -0
- {leansearch-0.2.0 → leansearch-0.2.2}/src/leansearch/_table.py +0 -0
- {leansearch-0.2.0 → leansearch-0.2.2}/src/leansearch/_vectors.py +0 -0
- {leansearch-0.2.0 → leansearch-0.2.2}/src/leansearch/server/__init__.py +0 -0
- {leansearch-0.2.0 → leansearch-0.2.2}/src/leansearch/server/cli.py +0 -0
- {leansearch-0.2.0 → leansearch-0.2.2}/src/leansearch/server/leansearch_pb2_grpc.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: leansearch
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Embedded full-text and vector search in one library (LeanText + LeanVec).
|
|
5
5
|
Author-email: Carlo Moro <cnmoro@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -13,8 +13,8 @@ Classifier: Programming Language :: Python :: 3
|
|
|
13
13
|
Classifier: Topic :: Database
|
|
14
14
|
Classifier: Topic :: Text Processing :: Indexing
|
|
15
15
|
Requires-Python: >=3.8
|
|
16
|
-
Requires-Dist: leantext<2.3,>=2.2.
|
|
17
|
-
Requires-Dist: leanvec-scale<1.1,>=1.0.
|
|
16
|
+
Requires-Dist: leantext<2.3,>=2.2.41
|
|
17
|
+
Requires-Dist: leanvec-scale<1.1,>=1.0.45
|
|
18
18
|
Requires-Dist: numpy
|
|
19
19
|
Requires-Dist: orjson
|
|
20
20
|
Provides-Extra: grpc
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "leansearch"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.2"
|
|
8
8
|
description = "Embedded full-text and vector search in one library (LeanText + LeanVec)."
|
|
9
9
|
requires-python = ">=3.8"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -14,8 +14,8 @@ dependencies = [
|
|
|
14
14
|
# so it pins a compatible release range checked by tests/test_internals.py.
|
|
15
15
|
# The servers (leansearch.server) reuse both engines' server guardrails, which ship
|
|
16
16
|
# in their wheels from these releases on.
|
|
17
|
-
"leanvec-scale>=1.0.
|
|
18
|
-
"leantext>=2.2.
|
|
17
|
+
"leanvec-scale>=1.0.45,<1.1",
|
|
18
|
+
"leantext>=2.2.41,<2.3",
|
|
19
19
|
"numpy",
|
|
20
20
|
"orjson",
|
|
21
21
|
]
|
|
@@ -12,12 +12,15 @@ import time
|
|
|
12
12
|
from typing import Any, Dict, Iterable, List, Optional, Sequence
|
|
13
13
|
|
|
14
14
|
from . import _vectors
|
|
15
|
-
from ._fusion import rrf_fuse
|
|
15
|
+
from ._fusion import check_fusion, relative_fuse, rrf_fuse
|
|
16
16
|
|
|
17
17
|
# Keyword arguments each engine's search accepts, used to split a hybrid call's options.
|
|
18
18
|
_TEXT_SEARCH_OPTIONS = frozenset({
|
|
19
19
|
"fuzzy_dist", "title_boost", "body_boost", "return_content", "return_highlight",
|
|
20
20
|
"highlight_buffer", "match_any", "force_fresh", "max_result_bytes"})
|
|
21
|
+
# Text options only LeanText's ranked `search` takes: a hybrid query passing one uses that
|
|
22
|
+
# ranking on the text side instead of BM25.
|
|
23
|
+
_RANKED_ONLY_OPTIONS = frozenset({"fuzzy_dist", "title_boost", "body_boost"})
|
|
21
24
|
_VECTOR_SEARCH_OPTIONS = frozenset({
|
|
22
25
|
"include_metadata", "ensure_target_k", "nprobe", "rerank", "max_result_bytes"})
|
|
23
26
|
|
|
@@ -456,7 +459,15 @@ class Collection:
|
|
|
456
459
|
if text_rows:
|
|
457
460
|
t._check_field_sizes([docs[i].get("text") or "" for i in text_rows],
|
|
458
461
|
prepared)
|
|
459
|
-
|
|
462
|
+
probe = getattr(t, "_exists_for_write", None)
|
|
463
|
+
if probe is not None:
|
|
464
|
+
# leantext >= 2.2.41: answered by the writable handle under the write
|
|
465
|
+
# lock, not by a read-only handle that must replay this process's writes.
|
|
466
|
+
f = {"id": {"$in": explicit}}
|
|
467
|
+
if probe(f, collection=self.name):
|
|
468
|
+
sizes_ok()
|
|
469
|
+
t.delete_by_filter(f, collection=self.name)
|
|
470
|
+
elif len(explicit) <= _PROBE_IDS_DIRECTLY:
|
|
460
471
|
old = [i for i in explicit if t.get_content(i, collection=self.name) is not None]
|
|
461
472
|
if old:
|
|
462
473
|
sizes_ok()
|
|
@@ -660,8 +671,9 @@ class Collection:
|
|
|
660
671
|
|
|
661
672
|
# ---- search --------------------------------------------------------------------
|
|
662
673
|
def search(self, query: Optional[str] = None, *, vector=None, sparse=None, k: int = 10,
|
|
663
|
-
filters: Optional[Dict[str, Any]] = None, rrf_k: float =
|
|
674
|
+
filters: Optional[Dict[str, Any]] = None, rrf_k: Optional[float] = None,
|
|
664
675
|
page: Optional[int] = None, page_size: Optional[int] = None,
|
|
676
|
+
fusion: Optional[str] = None, alpha: Optional[float] = None,
|
|
665
677
|
**options) -> List[Dict[str, Any]]:
|
|
666
678
|
"""Search by text, by vector, or by both.
|
|
667
679
|
|
|
@@ -672,10 +684,20 @@ class Collection:
|
|
|
672
684
|
(`LeanVecDB.search`, hybrid dense+sparse when `sparse` is given); options
|
|
673
685
|
`nprobe`, `rerank`, `ensure_target_k`, `include_metadata`. Score: squared L2
|
|
674
686
|
distance, lower is better (dense+sparse: RRF, higher is better).
|
|
675
|
-
- `search("words", vector=v)`:
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
687
|
+
- `search("words", vector=v)`: hybrid. The text side is BM25 (LeanText's
|
|
688
|
+
`search_bm25`, any query word matches unless `match_any=False`); with
|
|
689
|
+
`fuzzy_dist`, `title_boost` or `body_boost` it is LeanText's `search` ranking
|
|
690
|
+
instead. Each side ranks max(10 x depth, 100) candidates under the same filter,
|
|
691
|
+
and the two rankings are fused:
|
|
692
|
+
`fusion="relative"` (default): each side's scores are min-max normalized to
|
|
693
|
+
[0, 1] against its top 100 hits (deeper hits clamp to 0) and a document scores `(1 - alpha) * text + alpha * vector`, `alpha`
|
|
694
|
+
default 0.75 (the vector side's weight).
|
|
695
|
+
`fusion="rrf"` (also chosen by passing `rrf_k`): reciprocal rank, the rule
|
|
696
|
+
LeanVec uses for dense+sparse, score `sum(1 / (rrf_k + rank))`, `rrf_k`
|
|
697
|
+
default 60.
|
|
698
|
+
Higher is better; each hit also carries `text_score`/`text_rank` and
|
|
699
|
+
`vector_score`/`vector_rank` where it was found. A text-only or vector-only
|
|
700
|
+
search ignores `fusion` and `alpha` (as a text-only search ignores `rrf_k`).
|
|
679
701
|
|
|
680
702
|
`page`/`page_size` (0-based) return one page of the ranking instead of the top k.
|
|
681
703
|
`k` defaults to 10 here for every kind of query (`LeanVecDB.search` alone defaults
|
|
@@ -696,20 +718,24 @@ class Collection:
|
|
|
696
718
|
if has_vector_query and not has_text_query:
|
|
697
719
|
return self._vec.search(vector, k=k, filters=self._vector_filter(filters),
|
|
698
720
|
collection=self.name,
|
|
699
|
-
query_sparse_embedding=sparse,
|
|
721
|
+
query_sparse_embedding=sparse,
|
|
722
|
+
rrf_k=60 if rrf_k is None else rrf_k,
|
|
700
723
|
page_size=page_size,
|
|
701
724
|
page_index=page if page_size is not None else None,
|
|
702
725
|
**options)
|
|
703
726
|
return self._hybrid(query, vector, sparse, k, filters, rrf_k, page, page_size,
|
|
704
|
-
options)
|
|
727
|
+
options, fusion, alpha)
|
|
705
728
|
|
|
706
|
-
def _hybrid(self, query, vector, sparse, k, filters, rrf_k, page, page_size, options
|
|
729
|
+
def _hybrid(self, query, vector, sparse, k, filters, rrf_k, page, page_size, options,
|
|
730
|
+
fusion=None, alpha=None):
|
|
707
731
|
# Both engines are needed: without one, the "fused" ranking silently was the
|
|
708
732
|
# other engine's alone (a vector-only search on a text-only store raises).
|
|
709
733
|
self._text, self._vec
|
|
710
734
|
unknown = set(options) - _TEXT_SEARCH_OPTIONS - _VECTOR_SEARCH_OPTIONS
|
|
711
735
|
if unknown:
|
|
712
736
|
raise TypeError(f"unexpected search options {sorted(unknown)}")
|
|
737
|
+
fusion, alpha = check_fusion(fusion, alpha, rrf_k)
|
|
738
|
+
rrf_k = 60 if rrf_k is None else rrf_k
|
|
713
739
|
text_opts = {o: v for o, v in options.items() if o in _TEXT_SEARCH_OPTIONS}
|
|
714
740
|
vec_opts = {o: v for o, v in options.items() if o in _VECTOR_SEARCH_OPTIONS}
|
|
715
741
|
if page_size is not None:
|
|
@@ -724,13 +750,28 @@ class Collection:
|
|
|
724
750
|
fetch = max(total * 10, 100)
|
|
725
751
|
if filters:
|
|
726
752
|
filters = self._text._sanitize_filter(filters) # once, for both engines
|
|
727
|
-
|
|
728
|
-
|
|
753
|
+
if self._has_text():
|
|
754
|
+
if _RANKED_ONLY_OPTIONS.intersection(text_opts):
|
|
755
|
+
text_hits = self._text.search(query, k=fetch, filters=filters,
|
|
756
|
+
collection=self.name, **text_opts)
|
|
757
|
+
else:
|
|
758
|
+
text_opts.setdefault("match_any", True)
|
|
759
|
+
text_hits = self._text.search_bm25(query=query, k=fetch, filters=filters,
|
|
760
|
+
collection=self.name, **text_opts)
|
|
761
|
+
else:
|
|
762
|
+
text_hits = []
|
|
729
763
|
vec_hits = (self._vec.search(vector, k=fetch, filters=self._vector_filter(filters),
|
|
730
764
|
collection=self.name,
|
|
731
765
|
query_sparse_embedding=sparse, rrf_k=rrf_k, **vec_opts)
|
|
732
766
|
if self._has_vectors() else [])
|
|
733
|
-
|
|
767
|
+
if fusion == "rrf":
|
|
768
|
+
fused = rrf_fuse(text_hits, vec_hits, rrf_k)[start:total]
|
|
769
|
+
else:
|
|
770
|
+
# LeanVec fuses dense + sparse (RRF, higher is better) only for a non-empty
|
|
771
|
+
# sparse query; otherwise its scores are distances (lower is better).
|
|
772
|
+
sparse_fused = sparse is not None and bool(_vectors._to_sparse(sparse))
|
|
773
|
+
fused = relative_fuse(text_hits, vec_hits, alpha,
|
|
774
|
+
vector_higher_is_better=sparse_fused)[start:total]
|
|
734
775
|
if vec_opts.get("include_metadata") is False:
|
|
735
776
|
for h in fused:
|
|
736
777
|
h.pop("metadata", None)
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""Reciprocal-rank fusion of a text ranking and a vector ranking."""
|
|
2
|
+
from typing import Any, Dict, List
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def rrf_fuse(text_hits: List[Dict[str, Any]], vector_hits: List[Dict[str, Any]],
|
|
6
|
+
rrf_k: float) -> List[Dict[str, Any]]:
|
|
7
|
+
"""Fuse two rankings the way LeanVec fuses dense and sparse: each list adds
|
|
8
|
+
`1 / (rrf_k + rank)` (rank from 0) to a document's score; the result is sorted by
|
|
9
|
+
score, descending, and ties keep first-seen order (text hits, then vector-only hits).
|
|
10
|
+
|
|
11
|
+
A fused hit is the text hit's fields (title, content, highlight, metadata) or, for a
|
|
12
|
+
document only the vector search found, the vector hit's, plus `score` (the fused
|
|
13
|
+
score) and `text_score`/`text_rank`/`vector_score`/`vector_rank` where it was found.
|
|
14
|
+
The hit dicts are annotated in place: callers pass the lists an engine search just
|
|
15
|
+
returned, which nothing else holds.
|
|
16
|
+
"""
|
|
17
|
+
import math
|
|
18
|
+
rrf_k = float(rrf_k)
|
|
19
|
+
if not math.isfinite(rrf_k) or rrf_k <= 0:
|
|
20
|
+
raise ValueError(f"rrf_k must be a finite positive number, got {rrf_k!r}")
|
|
21
|
+
fused: Dict[str, Dict[str, Any]] = {}
|
|
22
|
+
for rank, h in enumerate(text_hits):
|
|
23
|
+
rid = h["id"]
|
|
24
|
+
if rid not in fused: # the engines return each id once; keep the first
|
|
25
|
+
h["text_score"] = h.get("score")
|
|
26
|
+
h["text_rank"] = rank
|
|
27
|
+
h["score"] = 1.0 / (rrf_k + rank)
|
|
28
|
+
fused[rid] = h
|
|
29
|
+
seen = set()
|
|
30
|
+
for rank, h in enumerate(vector_hits):
|
|
31
|
+
rid = h["id"]
|
|
32
|
+
if rid in seen:
|
|
33
|
+
continue
|
|
34
|
+
seen.add(rid)
|
|
35
|
+
hit = fused.get(rid)
|
|
36
|
+
if hit is None:
|
|
37
|
+
h["vector_score"] = h.get("score")
|
|
38
|
+
h["vector_rank"] = rank
|
|
39
|
+
h["score"] = 1.0 / (rrf_k + rank)
|
|
40
|
+
fused[rid] = h
|
|
41
|
+
else:
|
|
42
|
+
hit["vector_score"] = h.get("score")
|
|
43
|
+
hit["vector_rank"] = rank
|
|
44
|
+
hit["score"] += 1.0 / (rrf_k + rank)
|
|
45
|
+
out = list(fused.values())
|
|
46
|
+
out.sort(key=_neg_score) # stable: ties keep first-seen order
|
|
47
|
+
return out
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _neg_score(h):
|
|
51
|
+
return -h["score"]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# Hybrid fusion defaults (Collection.search with a text query AND a vector).
|
|
55
|
+
DEFAULT_FUSION = "relative"
|
|
56
|
+
DEFAULT_ALPHA = 0.75 # weight of the vector side in relative-score fusion
|
|
57
|
+
FUSIONS = ("relative", "rrf")
|
|
58
|
+
NORM_WINDOW = 100 # relative fusion normalizes against each side's top 100
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def check_fusion(fusion, alpha, rrf_k):
|
|
62
|
+
"""Resolve the fusion method and its weight.
|
|
63
|
+
|
|
64
|
+
`fusion=None` means the default, relative-score fusion, unless `rrf_k` is given: an
|
|
65
|
+
explicit `rrf_k` keeps the earlier reciprocal-rank fusion. `alpha` (0..1) is the
|
|
66
|
+
vector side's weight and only applies to relative-score fusion."""
|
|
67
|
+
import math
|
|
68
|
+
if fusion is None:
|
|
69
|
+
fusion = "rrf" if rrf_k is not None else DEFAULT_FUSION
|
|
70
|
+
if fusion not in FUSIONS:
|
|
71
|
+
raise ValueError(f"fusion must be one of {FUSIONS}, got {fusion!r}")
|
|
72
|
+
if alpha is None:
|
|
73
|
+
alpha = DEFAULT_ALPHA
|
|
74
|
+
else:
|
|
75
|
+
if fusion != "relative":
|
|
76
|
+
raise ValueError("alpha applies to fusion='relative' only")
|
|
77
|
+
try:
|
|
78
|
+
alpha = float(alpha)
|
|
79
|
+
except (TypeError, ValueError):
|
|
80
|
+
raise ValueError(f"alpha must be a number in [0, 1], got {alpha!r}") from None
|
|
81
|
+
if not math.isfinite(alpha) or not 0.0 <= alpha <= 1.0:
|
|
82
|
+
raise ValueError(f"alpha must be a number in [0, 1], got {alpha!r}")
|
|
83
|
+
return fusion, alpha
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _minmax(hits, higher_is_better, window):
|
|
87
|
+
"""Each hit's score mapped to [0, 1] against the best and the worst score among the
|
|
88
|
+
list's first `window` hits (best = 1; deeper hits clamp to 0). The window is fixed
|
|
89
|
+
(not the read depth), so a hit's normalized score does not depend on how deep each
|
|
90
|
+
side was read or which page was asked for. A window whose scores are all equal maps
|
|
91
|
+
to 1, except all-zero BM25 scores (no text signal), which map to 0."""
|
|
92
|
+
if not hits:
|
|
93
|
+
return []
|
|
94
|
+
xs = [float(h.get("score")) for h in hits]
|
|
95
|
+
ref = xs[:window]
|
|
96
|
+
lo, hi = min(ref), max(ref)
|
|
97
|
+
span = hi - lo
|
|
98
|
+
if not span > 0:
|
|
99
|
+
if higher_is_better and not hi > 0:
|
|
100
|
+
# e.g. an empty text query browsing a filter: every BM25 score is 0, no signal
|
|
101
|
+
return [0.0] * len(xs)
|
|
102
|
+
return [1.0 if i < window else 0.0 for i in range(len(xs))]
|
|
103
|
+
if higher_is_better:
|
|
104
|
+
return [min(1.0, max(0.0, (x - lo) / span)) for x in xs]
|
|
105
|
+
return [min(1.0, max(0.0, (hi - x) / span)) for x in xs]
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def relative_fuse(text_hits: List[Dict[str, Any]], vector_hits: List[Dict[str, Any]],
|
|
109
|
+
alpha: float, vector_higher_is_better: bool,
|
|
110
|
+
window: int = NORM_WINDOW) -> List[Dict[str, Any]]:
|
|
111
|
+
"""Relative-score (convex) fusion: each list's scores are min-max normalized to
|
|
112
|
+
[0, 1] over its first `window` hits (best hit = 1, see `_minmax`), and a document
|
|
113
|
+
scores `(1 - alpha) * text + alpha * vector` (a side that did not return the
|
|
114
|
+
document adds 0). Sorted by that score, descending;
|
|
115
|
+
ties keep first-seen order (text hits, then vector-only hits).
|
|
116
|
+
|
|
117
|
+
Text scores are BM25 (higher is better); vector scores are distances (lower is
|
|
118
|
+
better) unless the vector side itself fused dense and sparse by RRF. Same hit fields
|
|
119
|
+
as `rrf_fuse`: `score` (fused) plus `text_score`/`text_rank` and
|
|
120
|
+
`vector_score`/`vector_rank` where the document was found."""
|
|
121
|
+
wt, wv = 1.0 - alpha, alpha
|
|
122
|
+
fused: Dict[str, Dict[str, Any]] = {}
|
|
123
|
+
for rank, (h, x) in enumerate(zip(text_hits, _minmax(text_hits, True, window))):
|
|
124
|
+
rid = h["id"]
|
|
125
|
+
if rid not in fused:
|
|
126
|
+
h["text_score"] = h.get("score")
|
|
127
|
+
h["text_rank"] = rank
|
|
128
|
+
h["score"] = wt * x
|
|
129
|
+
fused[rid] = h
|
|
130
|
+
seen = set()
|
|
131
|
+
for rank, (h, x) in enumerate(zip(vector_hits, _minmax(vector_hits, vector_higher_is_better, window))):
|
|
132
|
+
rid = h["id"]
|
|
133
|
+
if rid in seen:
|
|
134
|
+
continue
|
|
135
|
+
seen.add(rid)
|
|
136
|
+
hit = fused.get(rid)
|
|
137
|
+
if hit is None:
|
|
138
|
+
h["vector_score"] = h.get("score")
|
|
139
|
+
h["vector_rank"] = rank
|
|
140
|
+
h["score"] = wv * x
|
|
141
|
+
fused[rid] = h
|
|
142
|
+
else:
|
|
143
|
+
hit["vector_score"] = h.get("score")
|
|
144
|
+
hit["vector_rank"] = rank
|
|
145
|
+
hit["score"] += wv * x
|
|
146
|
+
out = list(fused.values())
|
|
147
|
+
out.sort(key=_neg_score)
|
|
148
|
+
return out
|
|
@@ -23,6 +23,7 @@ from leanvec.server import serve_guardrails as _vg
|
|
|
23
23
|
from leantext_server import serve_guardrails as _tg
|
|
24
24
|
|
|
25
25
|
from .. import LeanSearch
|
|
26
|
+
from .._fusion import check_fusion
|
|
26
27
|
|
|
27
28
|
GuardrailError = _vg.GuardrailError
|
|
28
29
|
InvalidRequestError = _vg.InvalidRequestError
|
|
@@ -319,8 +320,10 @@ class Service:
|
|
|
319
320
|
tg.check_fanout(many, per)
|
|
320
321
|
|
|
321
322
|
def search(self, collection, *, query=None, vector=None, sparse=None, k=None,
|
|
322
|
-
filters=None, page=None, page_size=None, rrf_k=None,
|
|
323
|
-
|
|
323
|
+
filters=None, page=None, page_size=None, rrf_k=None, fusion=None,
|
|
324
|
+
alpha=None, **opts):
|
|
325
|
+
"""`Collection.search`: text, vector (+ sparse), or both fused (relative score by
|
|
326
|
+
default, or RRF)."""
|
|
324
327
|
unknown = set(opts) - set(_TEXT_OPTIONS) - set(_VECTOR_OPTIONS)
|
|
325
328
|
if unknown:
|
|
326
329
|
raise InvalidRequestError(f"unknown search options {sorted(unknown)}")
|
|
@@ -359,11 +362,23 @@ class Service:
|
|
|
359
362
|
if rrf_k is not None:
|
|
360
363
|
# finite and > 0: rrf_k <= 0 divides by zero inside the fusion
|
|
361
364
|
rrf_k = vg.check_rrf_k(rrf_k)
|
|
365
|
+
if has_text:
|
|
366
|
+
try:
|
|
367
|
+
check_fusion(fusion or None, alpha, rrf_k)
|
|
368
|
+
except ValueError as e:
|
|
369
|
+
raise InvalidRequestError(str(e)) from None
|
|
370
|
+
else:
|
|
371
|
+
fusion = alpha = None # a vector-only search ignores them, as the library does
|
|
362
372
|
else:
|
|
363
373
|
rrf_k = None # a text-only search ignores it, as the library does
|
|
374
|
+
fusion = alpha = None
|
|
364
375
|
call["max_result_bytes"] = vg.result_byte_budget()
|
|
365
376
|
if rrf_k is not None:
|
|
366
377
|
call["rrf_k"] = rrf_k
|
|
378
|
+
if fusion:
|
|
379
|
+
call["fusion"] = fusion
|
|
380
|
+
if alpha is not None:
|
|
381
|
+
call["alpha"] = alpha
|
|
367
382
|
return self.db[_name(collection)].search(
|
|
368
383
|
query, vector=vector, sparse=sparse, k=k, filters=filters, page=page,
|
|
369
384
|
page_size=page_size, **call)
|
|
@@ -293,6 +293,7 @@ class LeanSearchClient:
|
|
|
293
293
|
def search(self, query: Optional[str] = None, *, vector=None, sparse=None, k: int = 10,
|
|
294
294
|
filters: Optional[Dict[str, Any]] = None, page: Optional[int] = None,
|
|
295
295
|
page_size: Optional[int] = None, rrf_k: Optional[float] = None,
|
|
296
|
+
fusion: Optional[str] = None, alpha: Optional[float] = None,
|
|
296
297
|
fuzzy_dist: Optional[int] = None, title_boost: Optional[float] = None,
|
|
297
298
|
body_boost: Optional[float] = None, return_content: Optional[bool] = None,
|
|
298
299
|
return_highlight: Optional[bool] = None,
|
|
@@ -301,12 +302,13 @@ class LeanSearchClient:
|
|
|
301
302
|
ensure_target_k: Optional[bool] = None,
|
|
302
303
|
include_metadata: Optional[bool] = None, force_fresh: Optional[bool] = None,
|
|
303
304
|
collection: str = "default", timeout=None) -> List[Dict[str, Any]]:
|
|
304
|
-
"""Search by text, by vector (+ sparse), or both fused
|
|
305
|
+
"""Search by text, by vector (+ sparse), or both fused (Collection.search)."""
|
|
305
306
|
req = pb.SearchRequest(collection=collection, vector=_vec(vector),
|
|
306
307
|
filters_json=_filters(filters),
|
|
307
308
|
nprobe=nprobe or 0, rerank=rerank or 0)
|
|
308
309
|
_set(req, k=k, page_size=page_size, force_fresh=force_fresh,
|
|
309
|
-
query=query, page=page, rrf_k=rrf_k,
|
|
310
|
+
query=query, page=page, rrf_k=rrf_k, fusion=fusion, alpha=alpha,
|
|
311
|
+
fuzzy_dist=fuzzy_dist,
|
|
310
312
|
title_boost=title_boost, body_boost=body_boost, return_content=return_content,
|
|
311
313
|
return_highlight=return_highlight, highlight_buffer=highlight_buffer,
|
|
312
314
|
match_any=match_any, ensure_target_k=ensure_target_k,
|
|
@@ -154,7 +154,7 @@ message SearchRequest {
|
|
|
154
154
|
string filters_json = 6;
|
|
155
155
|
optional int32 page = 7; // 0-based page of the ranking (with page_size)
|
|
156
156
|
optional int32 page_size = 8; // unset -> top k, no paging
|
|
157
|
-
optional double rrf_k = 9; //
|
|
157
|
+
optional double rrf_k = 9; // RRF constant (default 60); set -> fusion rrf
|
|
158
158
|
// text options
|
|
159
159
|
optional int32 fuzzy_dist = 10;
|
|
160
160
|
optional double title_boost = 11;
|
|
@@ -169,6 +169,9 @@ message SearchRequest {
|
|
|
169
169
|
optional bool ensure_target_k = 19;
|
|
170
170
|
optional bool include_metadata = 20; // default true
|
|
171
171
|
optional bool force_fresh = 21; // text: reload from disk before searching
|
|
172
|
+
// hybrid fusion (text query + vector): "relative" (default; rrf when rrf_k is set) or "rrf"
|
|
173
|
+
optional string fusion = 22;
|
|
174
|
+
optional double alpha = 23; // relative fusion: vector weight in [0, 1], default 0.75
|
|
172
175
|
}
|
|
173
176
|
|
|
174
177
|
message QueryRequest {
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
+
# NO CHECKED-IN PROTOBUF GENCODE
|
|
4
|
+
# source: leansearch.proto
|
|
5
|
+
# Protobuf Python Version: 7.35.0
|
|
6
|
+
"""Generated protocol buffer code."""
|
|
7
|
+
from google.protobuf import descriptor as _descriptor
|
|
8
|
+
from google.protobuf import descriptor_pool as _descriptor_pool
|
|
9
|
+
from google.protobuf import runtime_version as _runtime_version
|
|
10
|
+
from google.protobuf import symbol_database as _symbol_database
|
|
11
|
+
from google.protobuf.internal import builder as _builder
|
|
12
|
+
_runtime_version.ValidateProtobufRuntimeVersion(
|
|
13
|
+
_runtime_version.Domain.PUBLIC,
|
|
14
|
+
7,
|
|
15
|
+
35,
|
|
16
|
+
0,
|
|
17
|
+
'',
|
|
18
|
+
'leansearch.proto'
|
|
19
|
+
)
|
|
20
|
+
# @@protoc_insertion_point(imports)
|
|
21
|
+
|
|
22
|
+
_sym_db = _symbol_database.Default()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x10leansearch.proto\x12\nleansearch\"\x07\n\x05\x45mpty\"/\n\x0cSparseVector\x12\x0f\n\x07indices\x18\x01 \x03(\r\x12\x0e\n\x06values\x18\x02 \x03(\x02\"\x0f\n\rHealthRequest\"1\n\x0bHealthReply\x12\r\n\x05ready\x18\x01 \x01(\x08\x12\x13\n\x0blimits_json\x18\x02 \x01(\t\"\'\n\x11\x43ollectionRequest\x12\x12\n\ncollection\x18\x01 \x01(\t\"\x1a\n\tListReply\x12\r\n\x05names\x18\x01 \x03(\t\"\x1b\n\nCountReply\x12\r\n\x05\x63ount\x18\x01 \x01(\x03\"\x19\n\tJsonReply\x12\x0c\n\x04json\x18\x01 \x01(\t\"\x1d\n\x0bStatusReply\x12\x0e\n\x06status\x18\x01 \x01(\t\"\x15\n\x07IdReply\x12\n\n\x02id\x18\x01 \x01(\t\"\x17\n\x08IdsReply\x12\x0b\n\x03ids\x18\x01 \x03(\t\"\x1d\n\x0b\x45xistsReply\x12\x0e\n\x06\x65xists\x18\x01 \x01(\x08\" \n\x0bSearchReply\x12\x11\n\thits_json\x18\x01 \x01(\t\"\xa1\x01\n\x08\x44ocument\x12\n\n\x02id\x18\x01 \x01(\t\x12\x11\n\x04text\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x12\n\x05title\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x0e\n\x06vector\x18\x04 \x03(\x02\x12(\n\x06sparse\x18\x05 \x01(\x0b\x32\x18.leansearch.SparseVector\x12\x15\n\rmetadata_json\x18\x06 \x01(\tB\x07\n\x05_textB\x08\n\x06_title\"\\\n\nAddRequest\x12\x12\n\ncollection\x18\x01 \x01(\t\x12&\n\x08\x64ocument\x18\x02 \x01(\x0b\x32\x14.leansearch.Document\x12\x12\n\nskip_index\x18\x03 \x03(\t\"a\n\x0e\x41\x64\x64ManyRequest\x12\x12\n\ncollection\x18\x01 \x01(\t\x12\'\n\tdocuments\x18\x02 \x03(\x0b\x32\x14.leansearch.Document\x12\x12\n\nskip_index\x18\x03 \x03(\t\"E\n\nGetRequest\x12\x12\n\ncollection\x18\x01 \x01(\t\x12\x0b\n\x03ids\x18\x02 \x03(\t\x12\x16\n\x0einclude_vector\x18\x03 \x01(\x08\"0\n\x0e\x43ontentRequest\x12\x12\n\ncollection\x18\x01 \x01(\t\x12\n\n\x02id\x18\x02 \x01(\t\"0\n\x0c\x43ontentReply\x12\x14\n\x07\x63ontent\x18\x01 \x01(\tH\x00\x88\x01\x01\x42\n\n\x08_content\"0\n\rDeleteRequest\x12\x12\n\ncollection\x18\x01 \x01(\t\x12\x0b\n\x03ids\x18\x02 \x03(\t\"9\n\rFilterRequest\x12\x12\n\ncollection\x18\x01 \x01(\t\x12\x14\n\x0c\x66ilters_json\x18\x02 \x01(\t\"V\n\x0cItemsRequest\x12\x12\n\ncollection\x18\x01 \x01(\t\x12\x14\n\x0c\x66ilters_json\x18\x02 \x01(\t\x12\x12\n\x05limit\x18\x03 \x01(\x05H\x00\x88\x01\x01\x42\x08\n\x06_limit\"\xad\x06\n\rSearchRequest\x12\x12\n\ncollection\x18\x01 \x01(\t\x12\x12\n\x05query\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x0e\n\x06vector\x18\x03 \x03(\x02\x12(\n\x06sparse\x18\x04 \x01(\x0b\x32\x18.leansearch.SparseVector\x12\x0e\n\x01k\x18\x05 \x01(\x05H\x01\x88\x01\x01\x12\x14\n\x0c\x66ilters_json\x18\x06 \x01(\t\x12\x11\n\x04page\x18\x07 \x01(\x05H\x02\x88\x01\x01\x12\x16\n\tpage_size\x18\x08 \x01(\x05H\x03\x88\x01\x01\x12\x12\n\x05rrf_k\x18\t \x01(\x01H\x04\x88\x01\x01\x12\x17\n\nfuzzy_dist\x18\n \x01(\x05H\x05\x88\x01\x01\x12\x18\n\x0btitle_boost\x18\x0b \x01(\x01H\x06\x88\x01\x01\x12\x17\n\nbody_boost\x18\x0c \x01(\x01H\x07\x88\x01\x01\x12\x1b\n\x0ereturn_content\x18\r \x01(\x08H\x08\x88\x01\x01\x12\x1d\n\x10return_highlight\x18\x0e \x01(\x08H\t\x88\x01\x01\x12\x1d\n\x10highlight_buffer\x18\x0f \x01(\x05H\n\x88\x01\x01\x12\x16\n\tmatch_any\x18\x10 \x01(\x08H\x0b\x88\x01\x01\x12\x0e\n\x06nprobe\x18\x11 \x01(\x05\x12\x0e\n\x06rerank\x18\x12 \x01(\x05\x12\x1c\n\x0f\x65nsure_target_k\x18\x13 \x01(\x08H\x0c\x88\x01\x01\x12\x1d\n\x10include_metadata\x18\x14 \x01(\x08H\r\x88\x01\x01\x12\x18\n\x0b\x66orce_fresh\x18\x15 \x01(\x08H\x0e\x88\x01\x01\x12\x13\n\x06\x66usion\x18\x16 \x01(\tH\x0f\x88\x01\x01\x12\x12\n\x05\x61lpha\x18\x17 \x01(\x01H\x10\x88\x01\x01\x42\x08\n\x06_queryB\x04\n\x02_kB\x07\n\x05_pageB\x0c\n\n_page_sizeB\x08\n\x06_rrf_kB\r\n\x0b_fuzzy_distB\x0e\n\x0c_title_boostB\r\n\x0b_body_boostB\x11\n\x0f_return_contentB\x13\n\x11_return_highlightB\x13\n\x11_highlight_bufferB\x0c\n\n_match_anyB\x12\n\x10_ensure_target_kB\x13\n\x11_include_metadataB\x0e\n\x0c_force_freshB\t\n\x07_fusionB\x08\n\x06_alpha\"\xcd\x03\n\x0cQueryRequest\x12\x12\n\ncollection\x18\x01 \x01(\t\x12\r\n\x05query\x18\x02 \x01(\t\x12\x0e\n\x01k\x18\x03 \x01(\x05H\x00\x88\x01\x01\x12\x14\n\x0c\x66ilters_json\x18\x04 \x01(\t\x12\x11\n\x04page\x18\x05 \x01(\x05H\x01\x88\x01\x01\x12\x16\n\tpage_size\x18\x06 \x01(\x05H\x02\x88\x01\x01\x12\x18\n\x0btitle_boost\x18\x07 \x01(\x01H\x03\x88\x01\x01\x12\x17\n\nbody_boost\x18\x08 \x01(\x01H\x04\x88\x01\x01\x12\x18\n\x10\x64\x65\x66\x61ult_operator\x18\t \x01(\t\x12\x1b\n\x0ereturn_content\x18\n \x01(\x08H\x05\x88\x01\x01\x12\x1d\n\x10return_highlight\x18\x0b \x01(\x08H\x06\x88\x01\x01\x12\x1d\n\x10highlight_buffer\x18\x0c \x01(\x05H\x07\x88\x01\x01\x12\x18\n\x0b\x66orce_fresh\x18\r \x01(\x08H\x08\x88\x01\x01\x42\x04\n\x02_kB\x07\n\x05_pageB\x0c\n\n_page_sizeB\x0e\n\x0c_title_boostB\r\n\x0b_body_boostB\x11\n\x0f_return_contentB\x13\n\x11_return_highlightB\x13\n\x11_highlight_bufferB\x0e\n\x0c_force_fresh\"\x86\x03\n\x0b\x42m25Request\x12\x12\n\ncollection\x18\x01 \x01(\t\x12\r\n\x05query\x18\x02 \x01(\t\x12\x0e\n\x01k\x18\x03 \x01(\x05H\x00\x88\x01\x01\x12\x14\n\x0c\x66ilters_json\x18\x04 \x01(\t\x12\x11\n\x04page\x18\x05 \x01(\x05H\x01\x88\x01\x01\x12\x16\n\tpage_size\x18\x06 \x01(\x05H\x02\x88\x01\x01\x12\x1b\n\x0ereturn_content\x18\x07 \x01(\x08H\x03\x88\x01\x01\x12\x1d\n\x10return_highlight\x18\x08 \x01(\x08H\x04\x88\x01\x01\x12\x1d\n\x10highlight_buffer\x18\t \x01(\x05H\x05\x88\x01\x01\x12\x16\n\tmatch_any\x18\n \x01(\x08H\x06\x88\x01\x01\x12\x18\n\x0b\x66orce_fresh\x18\x0b \x01(\x08H\x07\x88\x01\x01\x42\x04\n\x02_kB\x07\n\x05_pageB\x0c\n\n_page_sizeB\x11\n\x0f_return_contentB\x13\n\x11_return_highlightB\x13\n\x11_highlight_bufferB\x0c\n\n_match_anyB\x0e\n\x0c_force_fresh\"\x88\x04\n\x11SearchManyRequest\x12\x12\n\ncollection\x18\x01 \x01(\t\x12\x0f\n\x07queries\x18\x02 \x03(\t\x12\x0e\n\x01k\x18\x03 \x01(\x05H\x00\x88\x01\x01\x12\x14\n\x0c\x66ilters_json\x18\x04 \x01(\t\x12\x11\n\x04page\x18\x05 \x01(\x05H\x01\x88\x01\x01\x12\x16\n\tpage_size\x18\x06 \x01(\x05H\x02\x88\x01\x01\x12\x17\n\nfuzzy_dist\x18\x07 \x01(\x05H\x03\x88\x01\x01\x12\x18\n\x0btitle_boost\x18\x08 \x01(\x01H\x04\x88\x01\x01\x12\x17\n\nbody_boost\x18\t \x01(\x01H\x05\x88\x01\x01\x12\x1b\n\x0ereturn_content\x18\n \x01(\x08H\x06\x88\x01\x01\x12\x1d\n\x10return_highlight\x18\x0b \x01(\x08H\x07\x88\x01\x01\x12\x1d\n\x10highlight_buffer\x18\x0c \x01(\x05H\x08\x88\x01\x01\x12\x16\n\tmatch_any\x18\r \x01(\x08H\t\x88\x01\x01\x12\x18\n\x0b\x66orce_fresh\x18\x0e \x01(\x08H\n\x88\x01\x01\x42\x04\n\x02_kB\x07\n\x05_pageB\x0c\n\n_page_sizeB\r\n\x0b_fuzzy_distB\x0e\n\x0c_title_boostB\r\n\x0b_body_boostB\x11\n\x0f_return_contentB\x13\n\x11_return_highlightB\x13\n\x11_highlight_bufferB\x0c\n\n_match_anyB\x0e\n\x0c_force_fresh\"M\n\x12ValueCountsRequest\x12\x12\n\ncollection\x18\x01 \x01(\t\x12\r\n\x05\x66ield\x18\x02 \x01(\t\x12\x14\n\x0c\x66ilters_json\x18\x03 \x01(\t\"\x9f\x01\n\x11TopByGroupRequest\x12\x12\n\ncollection\x18\x01 \x01(\t\x12\x10\n\x08group_by\x18\x02 \x01(\t\x12\x0f\n\x07sort_by\x18\x03 \x01(\t\x12\x14\n\x0c\x66ilters_json\x18\x04 \x01(\t\x12\x15\n\routput_fields\x18\x05 \x03(\t\x12\x17\n\ndescending\x18\x06 \x01(\x08H\x00\x88\x01\x01\x42\r\n\x0b_descending\"[\n\x0cPca3dRequest\x12\x12\n\ncollection\x18\x01 \x01(\t\x12\x12\n\nbatch_size\x18\x02 \x01(\x05\x12\x14\n\x0c\x66ilters_json\x18\x03 \x01(\t\x12\r\n\x05limit\x18\x04 \x01(\x05\"P\n\nPca3dPoint\x12\n\n\x02id\x18\x01 \x01(\t\x12\t\n\x01x\x18\x02 \x01(\x01\x12\t\n\x01y\x18\x03 \x01(\x01\x12\t\n\x01z\x18\x04 \x01(\x01\x12\x15\n\rmetadata_json\x18\x05 \x01(\t\"2\n\rVerifyRequest\x12\x12\n\ncollection\x18\x01 \x01(\t\x12\r\n\x05\x66lush\x18\x02 \x01(\x08\"!\n\x0bLvqlRequest\x12\x12\n\nquery_json\x18\x01 \x01(\t\"L\n\x10TableRowsRequest\x12\r\n\x05table\x18\x01 \x01(\t\x12\x11\n\trows_json\x18\x02 \x01(\t\x12\x16\n\x0evector_columns\x18\x03 \x03(\t\"6\n\x11TableQueryRequest\x12\r\n\x05table\x18\x01 \x01(\t\x12\x12\n\nquery_json\x18\x02 \x01(\t\"o\n\x12TableSearchRequest\x12\r\n\x05table\x18\x01 \x01(\t\x12\x0e\n\x06\x63olumn\x18\x02 \x01(\t\x12\x0e\n\x06vector\x18\x03 \x03(\x02\x12\x0e\n\x01k\x18\x04 \x01(\x05H\x00\x88\x01\x01\x12\x14\n\x0c\x66ilters_json\x18\x05 \x01(\tB\x04\n\x02_k\"2\n\x11TableCountRequest\x12\r\n\x05table\x18\x01 \x01(\t\x12\x0e\n\x06\x63olumn\x18\x02 \x01(\t2\xb9\x10\n\nLeanSearch\x12<\n\x06Health\x12\x19.leansearch.HealthRequest\x1a\x17.leansearch.HealthReply\x12;\n\x0fListCollections\x12\x11.leansearch.Empty\x1a\x15.leansearch.ListReply\x12>\n\x05\x43ount\x12\x1d.leansearch.CollectionRequest\x1a\x16.leansearch.CountReply\x12=\n\x05Stats\x12\x1d.leansearch.CollectionRequest\x1a\x15.leansearch.JsonReply\x12@\n\x08\x44\x65scribe\x12\x1d.leansearch.CollectionRequest\x1a\x15.leansearch.JsonReply\x12\x32\n\x03\x41\x64\x64\x12\x16.leansearch.AddRequest\x1a\x13.leansearch.IdReply\x12;\n\x07\x41\x64\x64Many\x12\x1a.leansearch.AddManyRequest\x1a\x14.leansearch.IdsReply\x12\x34\n\x03Get\x12\x16.leansearch.GetRequest\x1a\x15.leansearch.JsonReply\x12?\n\x07\x43ontent\x12\x1a.leansearch.ContentRequest\x1a\x18.leansearch.ContentReply\x12;\n\x06\x44\x65lete\x12\x19.leansearch.DeleteRequest\x1a\x16.leansearch.CountReply\x12@\n\x0b\x44\x65leteWhere\x12\x19.leansearch.FilterRequest\x1a\x16.leansearch.CountReply\x12>\n\x05\x43lear\x12\x1d.leansearch.CollectionRequest\x1a\x16.leansearch.CountReply\x12<\n\x06\x45xists\x12\x19.leansearch.FilterRequest\x1a\x17.leansearch.ExistsReply\x12\x38\n\x05Items\x12\x18.leansearch.ItemsRequest\x1a\x15.leansearch.JsonReply\x12<\n\x06Search\x12\x19.leansearch.SearchRequest\x1a\x17.leansearch.SearchReply\x12:\n\x05Query\x12\x18.leansearch.QueryRequest\x1a\x17.leansearch.SearchReply\x12\x38\n\x04\x42m25\x12\x17.leansearch.Bm25Request\x1a\x17.leansearch.SearchReply\x12\x42\n\nSearchMany\x12\x1d.leansearch.SearchManyRequest\x1a\x15.leansearch.JsonReply\x12\x44\n\x0bValueCounts\x12\x1e.leansearch.ValueCountsRequest\x1a\x15.leansearch.JsonReply\x12\x42\n\nTopByGroup\x12\x1d.leansearch.TopByGroupRequest\x1a\x15.leansearch.JsonReply\x12;\n\x05Pca3d\x12\x18.leansearch.Pca3dRequest\x1a\x16.leansearch.Pca3dPoint0\x01\x12\x41\n\x07Persist\x12\x1d.leansearch.CollectionRequest\x1a\x17.leansearch.StatusReply\x12\x46\n\x0c\x46lushBarrier\x12\x1d.leansearch.CollectionRequest\x1a\x17.leansearch.StatusReply\x12\x41\n\x07Refresh\x12\x1d.leansearch.CollectionRequest\x1a\x17.leansearch.StatusReply\x12@\n\x06Vacuum\x12\x1d.leansearch.CollectionRequest\x1a\x17.leansearch.StatusReply\x12:\n\x06Verify\x12\x19.leansearch.VerifyRequest\x1a\x15.leansearch.JsonReply\x12\x36\n\x04Lvql\x12\x17.leansearch.LvqlRequest\x1a\x15.leansearch.JsonReply\x12\x36\n\nListTables\x12\x11.leansearch.Empty\x1a\x15.leansearch.JsonReply\x12@\n\x08TableAdd\x12\x1c.leansearch.TableRowsRequest\x1a\x16.leansearch.CountReply\x12\x44\n\x0cTableReplace\x12\x1c.leansearch.TableRowsRequest\x1a\x16.leansearch.CountReply\x12\x42\n\nTableQuery\x12\x1d.leansearch.TableQueryRequest\x1a\x15.leansearch.JsonReply\x12\x44\n\x0bTableSearch\x12\x1e.leansearch.TableSearchRequest\x1a\x15.leansearch.JsonReply\x12\x43\n\nTableCount\x12\x1d.leansearch.TableCountRequest\x1a\x16.leansearch.CountReplyb\x06proto3')
|
|
28
|
+
|
|
29
|
+
_globals = globals()
|
|
30
|
+
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
31
|
+
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'leansearch_pb2', _globals)
|
|
32
|
+
if not _descriptor._USE_C_DESCRIPTORS:
|
|
33
|
+
DESCRIPTOR._loaded_options = None
|
|
34
|
+
_globals['_EMPTY']._serialized_start=32
|
|
35
|
+
_globals['_EMPTY']._serialized_end=39
|
|
36
|
+
_globals['_SPARSEVECTOR']._serialized_start=41
|
|
37
|
+
_globals['_SPARSEVECTOR']._serialized_end=88
|
|
38
|
+
_globals['_HEALTHREQUEST']._serialized_start=90
|
|
39
|
+
_globals['_HEALTHREQUEST']._serialized_end=105
|
|
40
|
+
_globals['_HEALTHREPLY']._serialized_start=107
|
|
41
|
+
_globals['_HEALTHREPLY']._serialized_end=156
|
|
42
|
+
_globals['_COLLECTIONREQUEST']._serialized_start=158
|
|
43
|
+
_globals['_COLLECTIONREQUEST']._serialized_end=197
|
|
44
|
+
_globals['_LISTREPLY']._serialized_start=199
|
|
45
|
+
_globals['_LISTREPLY']._serialized_end=225
|
|
46
|
+
_globals['_COUNTREPLY']._serialized_start=227
|
|
47
|
+
_globals['_COUNTREPLY']._serialized_end=254
|
|
48
|
+
_globals['_JSONREPLY']._serialized_start=256
|
|
49
|
+
_globals['_JSONREPLY']._serialized_end=281
|
|
50
|
+
_globals['_STATUSREPLY']._serialized_start=283
|
|
51
|
+
_globals['_STATUSREPLY']._serialized_end=312
|
|
52
|
+
_globals['_IDREPLY']._serialized_start=314
|
|
53
|
+
_globals['_IDREPLY']._serialized_end=335
|
|
54
|
+
_globals['_IDSREPLY']._serialized_start=337
|
|
55
|
+
_globals['_IDSREPLY']._serialized_end=360
|
|
56
|
+
_globals['_EXISTSREPLY']._serialized_start=362
|
|
57
|
+
_globals['_EXISTSREPLY']._serialized_end=391
|
|
58
|
+
_globals['_SEARCHREPLY']._serialized_start=393
|
|
59
|
+
_globals['_SEARCHREPLY']._serialized_end=425
|
|
60
|
+
_globals['_DOCUMENT']._serialized_start=428
|
|
61
|
+
_globals['_DOCUMENT']._serialized_end=589
|
|
62
|
+
_globals['_ADDREQUEST']._serialized_start=591
|
|
63
|
+
_globals['_ADDREQUEST']._serialized_end=683
|
|
64
|
+
_globals['_ADDMANYREQUEST']._serialized_start=685
|
|
65
|
+
_globals['_ADDMANYREQUEST']._serialized_end=782
|
|
66
|
+
_globals['_GETREQUEST']._serialized_start=784
|
|
67
|
+
_globals['_GETREQUEST']._serialized_end=853
|
|
68
|
+
_globals['_CONTENTREQUEST']._serialized_start=855
|
|
69
|
+
_globals['_CONTENTREQUEST']._serialized_end=903
|
|
70
|
+
_globals['_CONTENTREPLY']._serialized_start=905
|
|
71
|
+
_globals['_CONTENTREPLY']._serialized_end=953
|
|
72
|
+
_globals['_DELETEREQUEST']._serialized_start=955
|
|
73
|
+
_globals['_DELETEREQUEST']._serialized_end=1003
|
|
74
|
+
_globals['_FILTERREQUEST']._serialized_start=1005
|
|
75
|
+
_globals['_FILTERREQUEST']._serialized_end=1062
|
|
76
|
+
_globals['_ITEMSREQUEST']._serialized_start=1064
|
|
77
|
+
_globals['_ITEMSREQUEST']._serialized_end=1150
|
|
78
|
+
_globals['_SEARCHREQUEST']._serialized_start=1153
|
|
79
|
+
_globals['_SEARCHREQUEST']._serialized_end=1966
|
|
80
|
+
_globals['_QUERYREQUEST']._serialized_start=1969
|
|
81
|
+
_globals['_QUERYREQUEST']._serialized_end=2430
|
|
82
|
+
_globals['_BM25REQUEST']._serialized_start=2433
|
|
83
|
+
_globals['_BM25REQUEST']._serialized_end=2823
|
|
84
|
+
_globals['_SEARCHMANYREQUEST']._serialized_start=2826
|
|
85
|
+
_globals['_SEARCHMANYREQUEST']._serialized_end=3346
|
|
86
|
+
_globals['_VALUECOUNTSREQUEST']._serialized_start=3348
|
|
87
|
+
_globals['_VALUECOUNTSREQUEST']._serialized_end=3425
|
|
88
|
+
_globals['_TOPBYGROUPREQUEST']._serialized_start=3428
|
|
89
|
+
_globals['_TOPBYGROUPREQUEST']._serialized_end=3587
|
|
90
|
+
_globals['_PCA3DREQUEST']._serialized_start=3589
|
|
91
|
+
_globals['_PCA3DREQUEST']._serialized_end=3680
|
|
92
|
+
_globals['_PCA3DPOINT']._serialized_start=3682
|
|
93
|
+
_globals['_PCA3DPOINT']._serialized_end=3762
|
|
94
|
+
_globals['_VERIFYREQUEST']._serialized_start=3764
|
|
95
|
+
_globals['_VERIFYREQUEST']._serialized_end=3814
|
|
96
|
+
_globals['_LVQLREQUEST']._serialized_start=3816
|
|
97
|
+
_globals['_LVQLREQUEST']._serialized_end=3849
|
|
98
|
+
_globals['_TABLEROWSREQUEST']._serialized_start=3851
|
|
99
|
+
_globals['_TABLEROWSREQUEST']._serialized_end=3927
|
|
100
|
+
_globals['_TABLEQUERYREQUEST']._serialized_start=3929
|
|
101
|
+
_globals['_TABLEQUERYREQUEST']._serialized_end=3983
|
|
102
|
+
_globals['_TABLESEARCHREQUEST']._serialized_start=3985
|
|
103
|
+
_globals['_TABLESEARCHREQUEST']._serialized_end=4096
|
|
104
|
+
_globals['_TABLECOUNTREQUEST']._serialized_start=4098
|
|
105
|
+
_globals['_TABLECOUNTREQUEST']._serialized_end=4148
|
|
106
|
+
_globals['_LEANSEARCH']._serialized_start=4151
|
|
107
|
+
_globals['_LEANSEARCH']._serialized_end=6256
|
|
108
|
+
# @@protoc_insertion_point(module_scope)
|
|
@@ -385,6 +385,8 @@ class SearchReq(_TextOpts):
|
|
|
385
385
|
page: Optional[int] = None
|
|
386
386
|
page_size: Optional[int] = None
|
|
387
387
|
rrf_k: Optional[float] = None
|
|
388
|
+
fusion: Optional[str] = None
|
|
389
|
+
alpha: Optional[float] = None
|
|
388
390
|
fuzzy_dist: Optional[int] = None
|
|
389
391
|
title_boost: Optional[float] = None
|
|
390
392
|
body_boost: Optional[float] = None
|
|
@@ -560,7 +562,7 @@ def search(req: SearchReq):
|
|
|
560
562
|
return {"hits": _call(lambda: svc().search(
|
|
561
563
|
req.collection, query=req.query, vector=req.vector, sparse=req.sparse, k=req.k,
|
|
562
564
|
filters=req.filters, page=req.page, page_size=req.page_size, rrf_k=req.rrf_k,
|
|
563
|
-
**opts))}
|
|
565
|
+
fusion=req.fusion, alpha=req.alpha, **opts))}
|
|
564
566
|
|
|
565
567
|
|
|
566
568
|
@app.post("/query")
|
|
@@ -297,7 +297,8 @@ class LeanSearchServicer(pb_grpc.LeanSearchServicer):
|
|
|
297
297
|
r.collection, query=r.query if r.HasField("query") else None,
|
|
298
298
|
vector=list(r.vector) if len(r.vector) else None, sparse=_sparse(r.sparse),
|
|
299
299
|
k=_has(r, "k"), filters=_json(r.filters_json), page=_has(r, "page"),
|
|
300
|
-
page_size=_has(r, "page_size"), rrf_k=_has(r, "rrf_k"),
|
|
300
|
+
page_size=_has(r, "page_size"), rrf_k=_has(r, "rrf_k"),
|
|
301
|
+
fusion=_has(r, "fusion"), alpha=_has(r, "alpha"), **opts)
|
|
301
302
|
return pb.SearchReply(hits_json=S.dumps(hits))
|
|
302
303
|
return self._run(context, pb.SearchReply, run,
|
|
303
304
|
S.json_weight(request.filters_json)
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
"""Reciprocal-rank fusion of a text ranking and a vector ranking."""
|
|
2
|
-
from typing import Any, Dict, List
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
def rrf_fuse(text_hits: List[Dict[str, Any]], vector_hits: List[Dict[str, Any]],
|
|
6
|
-
rrf_k: float) -> List[Dict[str, Any]]:
|
|
7
|
-
"""Fuse two rankings the way LeanVec fuses dense and sparse: each list adds
|
|
8
|
-
`1 / (rrf_k + rank)` (rank from 0) to a document's score; the result is sorted by
|
|
9
|
-
score, descending, and ties keep first-seen order (text hits, then vector-only hits).
|
|
10
|
-
|
|
11
|
-
A fused hit is the text hit's fields (title, content, highlight, metadata) or, for a
|
|
12
|
-
document only the vector search found, the vector hit's, plus `score` (the fused
|
|
13
|
-
score) and `text_score`/`text_rank`/`vector_score`/`vector_rank` where it was found.
|
|
14
|
-
The hit dicts are annotated in place: callers pass the lists an engine search just
|
|
15
|
-
returned, which nothing else holds.
|
|
16
|
-
"""
|
|
17
|
-
import math
|
|
18
|
-
rrf_k = float(rrf_k)
|
|
19
|
-
if not math.isfinite(rrf_k) or rrf_k <= 0:
|
|
20
|
-
raise ValueError(f"rrf_k must be a finite positive number, got {rrf_k!r}")
|
|
21
|
-
fused: Dict[str, Dict[str, Any]] = {}
|
|
22
|
-
for rank, h in enumerate(text_hits):
|
|
23
|
-
rid = h["id"]
|
|
24
|
-
if rid not in fused: # the engines return each id once; keep the first
|
|
25
|
-
h["text_score"] = h.get("score")
|
|
26
|
-
h["text_rank"] = rank
|
|
27
|
-
h["score"] = 1.0 / (rrf_k + rank)
|
|
28
|
-
fused[rid] = h
|
|
29
|
-
seen = set()
|
|
30
|
-
for rank, h in enumerate(vector_hits):
|
|
31
|
-
rid = h["id"]
|
|
32
|
-
if rid in seen:
|
|
33
|
-
continue
|
|
34
|
-
seen.add(rid)
|
|
35
|
-
hit = fused.get(rid)
|
|
36
|
-
if hit is None:
|
|
37
|
-
h["vector_score"] = h.get("score")
|
|
38
|
-
h["vector_rank"] = rank
|
|
39
|
-
h["score"] = 1.0 / (rrf_k + rank)
|
|
40
|
-
fused[rid] = h
|
|
41
|
-
else:
|
|
42
|
-
hit["vector_score"] = h.get("score")
|
|
43
|
-
hit["vector_rank"] = rank
|
|
44
|
-
hit["score"] += 1.0 / (rrf_k + rank)
|
|
45
|
-
out = list(fused.values())
|
|
46
|
-
out.sort(key=_neg_score) # stable: ties keep first-seen order
|
|
47
|
-
return out
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def _neg_score(h):
|
|
51
|
-
return -h["score"]
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
-
# NO CHECKED-IN PROTOBUF GENCODE
|
|
4
|
-
# source: leansearch.proto
|
|
5
|
-
# Protobuf Python Version: 7.35.0
|
|
6
|
-
"""Generated protocol buffer code."""
|
|
7
|
-
from google.protobuf import descriptor as _descriptor
|
|
8
|
-
from google.protobuf import descriptor_pool as _descriptor_pool
|
|
9
|
-
from google.protobuf import runtime_version as _runtime_version
|
|
10
|
-
from google.protobuf import symbol_database as _symbol_database
|
|
11
|
-
from google.protobuf.internal import builder as _builder
|
|
12
|
-
_runtime_version.ValidateProtobufRuntimeVersion(
|
|
13
|
-
_runtime_version.Domain.PUBLIC,
|
|
14
|
-
7,
|
|
15
|
-
35,
|
|
16
|
-
0,
|
|
17
|
-
'',
|
|
18
|
-
'leansearch.proto'
|
|
19
|
-
)
|
|
20
|
-
# @@protoc_insertion_point(imports)
|
|
21
|
-
|
|
22
|
-
_sym_db = _symbol_database.Default()
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x10leansearch.proto\x12\nleansearch\"\x07\n\x05\x45mpty\"/\n\x0cSparseVector\x12\x0f\n\x07indices\x18\x01 \x03(\r\x12\x0e\n\x06values\x18\x02 \x03(\x02\"\x0f\n\rHealthRequest\"1\n\x0bHealthReply\x12\r\n\x05ready\x18\x01 \x01(\x08\x12\x13\n\x0blimits_json\x18\x02 \x01(\t\"\'\n\x11\x43ollectionRequest\x12\x12\n\ncollection\x18\x01 \x01(\t\"\x1a\n\tListReply\x12\r\n\x05names\x18\x01 \x03(\t\"\x1b\n\nCountReply\x12\r\n\x05\x63ount\x18\x01 \x01(\x03\"\x19\n\tJsonReply\x12\x0c\n\x04json\x18\x01 \x01(\t\"\x1d\n\x0bStatusReply\x12\x0e\n\x06status\x18\x01 \x01(\t\"\x15\n\x07IdReply\x12\n\n\x02id\x18\x01 \x01(\t\"\x17\n\x08IdsReply\x12\x0b\n\x03ids\x18\x01 \x03(\t\"\x1d\n\x0b\x45xistsReply\x12\x0e\n\x06\x65xists\x18\x01 \x01(\x08\" \n\x0bSearchReply\x12\x11\n\thits_json\x18\x01 \x01(\t\"\xa1\x01\n\x08\x44ocument\x12\n\n\x02id\x18\x01 \x01(\t\x12\x11\n\x04text\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x12\n\x05title\x18\x03 \x01(\tH\x01\x88\x01\x01\x12\x0e\n\x06vector\x18\x04 \x03(\x02\x12(\n\x06sparse\x18\x05 \x01(\x0b\x32\x18.leansearch.SparseVector\x12\x15\n\rmetadata_json\x18\x06 \x01(\tB\x07\n\x05_textB\x08\n\x06_title\"\\\n\nAddRequest\x12\x12\n\ncollection\x18\x01 \x01(\t\x12&\n\x08\x64ocument\x18\x02 \x01(\x0b\x32\x14.leansearch.Document\x12\x12\n\nskip_index\x18\x03 \x03(\t\"a\n\x0e\x41\x64\x64ManyRequest\x12\x12\n\ncollection\x18\x01 \x01(\t\x12\'\n\tdocuments\x18\x02 \x03(\x0b\x32\x14.leansearch.Document\x12\x12\n\nskip_index\x18\x03 \x03(\t\"E\n\nGetRequest\x12\x12\n\ncollection\x18\x01 \x01(\t\x12\x0b\n\x03ids\x18\x02 \x03(\t\x12\x16\n\x0einclude_vector\x18\x03 \x01(\x08\"0\n\x0e\x43ontentRequest\x12\x12\n\ncollection\x18\x01 \x01(\t\x12\n\n\x02id\x18\x02 \x01(\t\"0\n\x0c\x43ontentReply\x12\x14\n\x07\x63ontent\x18\x01 \x01(\tH\x00\x88\x01\x01\x42\n\n\x08_content\"0\n\rDeleteRequest\x12\x12\n\ncollection\x18\x01 \x01(\t\x12\x0b\n\x03ids\x18\x02 \x03(\t\"9\n\rFilterRequest\x12\x12\n\ncollection\x18\x01 \x01(\t\x12\x14\n\x0c\x66ilters_json\x18\x02 \x01(\t\"V\n\x0cItemsRequest\x12\x12\n\ncollection\x18\x01 \x01(\t\x12\x14\n\x0c\x66ilters_json\x18\x02 \x01(\t\x12\x12\n\x05limit\x18\x03 \x01(\x05H\x00\x88\x01\x01\x42\x08\n\x06_limit\"\xef\x05\n\rSearchRequest\x12\x12\n\ncollection\x18\x01 \x01(\t\x12\x12\n\x05query\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x0e\n\x06vector\x18\x03 \x03(\x02\x12(\n\x06sparse\x18\x04 \x01(\x0b\x32\x18.leansearch.SparseVector\x12\x0e\n\x01k\x18\x05 \x01(\x05H\x01\x88\x01\x01\x12\x14\n\x0c\x66ilters_json\x18\x06 \x01(\t\x12\x11\n\x04page\x18\x07 \x01(\x05H\x02\x88\x01\x01\x12\x16\n\tpage_size\x18\x08 \x01(\x05H\x03\x88\x01\x01\x12\x12\n\x05rrf_k\x18\t \x01(\x01H\x04\x88\x01\x01\x12\x17\n\nfuzzy_dist\x18\n \x01(\x05H\x05\x88\x01\x01\x12\x18\n\x0btitle_boost\x18\x0b \x01(\x01H\x06\x88\x01\x01\x12\x17\n\nbody_boost\x18\x0c \x01(\x01H\x07\x88\x01\x01\x12\x1b\n\x0ereturn_content\x18\r \x01(\x08H\x08\x88\x01\x01\x12\x1d\n\x10return_highlight\x18\x0e \x01(\x08H\t\x88\x01\x01\x12\x1d\n\x10highlight_buffer\x18\x0f \x01(\x05H\n\x88\x01\x01\x12\x16\n\tmatch_any\x18\x10 \x01(\x08H\x0b\x88\x01\x01\x12\x0e\n\x06nprobe\x18\x11 \x01(\x05\x12\x0e\n\x06rerank\x18\x12 \x01(\x05\x12\x1c\n\x0f\x65nsure_target_k\x18\x13 \x01(\x08H\x0c\x88\x01\x01\x12\x1d\n\x10include_metadata\x18\x14 \x01(\x08H\r\x88\x01\x01\x12\x18\n\x0b\x66orce_fresh\x18\x15 \x01(\x08H\x0e\x88\x01\x01\x42\x08\n\x06_queryB\x04\n\x02_kB\x07\n\x05_pageB\x0c\n\n_page_sizeB\x08\n\x06_rrf_kB\r\n\x0b_fuzzy_distB\x0e\n\x0c_title_boostB\r\n\x0b_body_boostB\x11\n\x0f_return_contentB\x13\n\x11_return_highlightB\x13\n\x11_highlight_bufferB\x0c\n\n_match_anyB\x12\n\x10_ensure_target_kB\x13\n\x11_include_metadataB\x0e\n\x0c_force_fresh\"\xcd\x03\n\x0cQueryRequest\x12\x12\n\ncollection\x18\x01 \x01(\t\x12\r\n\x05query\x18\x02 \x01(\t\x12\x0e\n\x01k\x18\x03 \x01(\x05H\x00\x88\x01\x01\x12\x14\n\x0c\x66ilters_json\x18\x04 \x01(\t\x12\x11\n\x04page\x18\x05 \x01(\x05H\x01\x88\x01\x01\x12\x16\n\tpage_size\x18\x06 \x01(\x05H\x02\x88\x01\x01\x12\x18\n\x0btitle_boost\x18\x07 \x01(\x01H\x03\x88\x01\x01\x12\x17\n\nbody_boost\x18\x08 \x01(\x01H\x04\x88\x01\x01\x12\x18\n\x10\x64\x65\x66\x61ult_operator\x18\t \x01(\t\x12\x1b\n\x0ereturn_content\x18\n \x01(\x08H\x05\x88\x01\x01\x12\x1d\n\x10return_highlight\x18\x0b \x01(\x08H\x06\x88\x01\x01\x12\x1d\n\x10highlight_buffer\x18\x0c \x01(\x05H\x07\x88\x01\x01\x12\x18\n\x0b\x66orce_fresh\x18\r \x01(\x08H\x08\x88\x01\x01\x42\x04\n\x02_kB\x07\n\x05_pageB\x0c\n\n_page_sizeB\x0e\n\x0c_title_boostB\r\n\x0b_body_boostB\x11\n\x0f_return_contentB\x13\n\x11_return_highlightB\x13\n\x11_highlight_bufferB\x0e\n\x0c_force_fresh\"\x86\x03\n\x0b\x42m25Request\x12\x12\n\ncollection\x18\x01 \x01(\t\x12\r\n\x05query\x18\x02 \x01(\t\x12\x0e\n\x01k\x18\x03 \x01(\x05H\x00\x88\x01\x01\x12\x14\n\x0c\x66ilters_json\x18\x04 \x01(\t\x12\x11\n\x04page\x18\x05 \x01(\x05H\x01\x88\x01\x01\x12\x16\n\tpage_size\x18\x06 \x01(\x05H\x02\x88\x01\x01\x12\x1b\n\x0ereturn_content\x18\x07 \x01(\x08H\x03\x88\x01\x01\x12\x1d\n\x10return_highlight\x18\x08 \x01(\x08H\x04\x88\x01\x01\x12\x1d\n\x10highlight_buffer\x18\t \x01(\x05H\x05\x88\x01\x01\x12\x16\n\tmatch_any\x18\n \x01(\x08H\x06\x88\x01\x01\x12\x18\n\x0b\x66orce_fresh\x18\x0b \x01(\x08H\x07\x88\x01\x01\x42\x04\n\x02_kB\x07\n\x05_pageB\x0c\n\n_page_sizeB\x11\n\x0f_return_contentB\x13\n\x11_return_highlightB\x13\n\x11_highlight_bufferB\x0c\n\n_match_anyB\x0e\n\x0c_force_fresh\"\x88\x04\n\x11SearchManyRequest\x12\x12\n\ncollection\x18\x01 \x01(\t\x12\x0f\n\x07queries\x18\x02 \x03(\t\x12\x0e\n\x01k\x18\x03 \x01(\x05H\x00\x88\x01\x01\x12\x14\n\x0c\x66ilters_json\x18\x04 \x01(\t\x12\x11\n\x04page\x18\x05 \x01(\x05H\x01\x88\x01\x01\x12\x16\n\tpage_size\x18\x06 \x01(\x05H\x02\x88\x01\x01\x12\x17\n\nfuzzy_dist\x18\x07 \x01(\x05H\x03\x88\x01\x01\x12\x18\n\x0btitle_boost\x18\x08 \x01(\x01H\x04\x88\x01\x01\x12\x17\n\nbody_boost\x18\t \x01(\x01H\x05\x88\x01\x01\x12\x1b\n\x0ereturn_content\x18\n \x01(\x08H\x06\x88\x01\x01\x12\x1d\n\x10return_highlight\x18\x0b \x01(\x08H\x07\x88\x01\x01\x12\x1d\n\x10highlight_buffer\x18\x0c \x01(\x05H\x08\x88\x01\x01\x12\x16\n\tmatch_any\x18\r \x01(\x08H\t\x88\x01\x01\x12\x18\n\x0b\x66orce_fresh\x18\x0e \x01(\x08H\n\x88\x01\x01\x42\x04\n\x02_kB\x07\n\x05_pageB\x0c\n\n_page_sizeB\r\n\x0b_fuzzy_distB\x0e\n\x0c_title_boostB\r\n\x0b_body_boostB\x11\n\x0f_return_contentB\x13\n\x11_return_highlightB\x13\n\x11_highlight_bufferB\x0c\n\n_match_anyB\x0e\n\x0c_force_fresh\"M\n\x12ValueCountsRequest\x12\x12\n\ncollection\x18\x01 \x01(\t\x12\r\n\x05\x66ield\x18\x02 \x01(\t\x12\x14\n\x0c\x66ilters_json\x18\x03 \x01(\t\"\x9f\x01\n\x11TopByGroupRequest\x12\x12\n\ncollection\x18\x01 \x01(\t\x12\x10\n\x08group_by\x18\x02 \x01(\t\x12\x0f\n\x07sort_by\x18\x03 \x01(\t\x12\x14\n\x0c\x66ilters_json\x18\x04 \x01(\t\x12\x15\n\routput_fields\x18\x05 \x03(\t\x12\x17\n\ndescending\x18\x06 \x01(\x08H\x00\x88\x01\x01\x42\r\n\x0b_descending\"[\n\x0cPca3dRequest\x12\x12\n\ncollection\x18\x01 \x01(\t\x12\x12\n\nbatch_size\x18\x02 \x01(\x05\x12\x14\n\x0c\x66ilters_json\x18\x03 \x01(\t\x12\r\n\x05limit\x18\x04 \x01(\x05\"P\n\nPca3dPoint\x12\n\n\x02id\x18\x01 \x01(\t\x12\t\n\x01x\x18\x02 \x01(\x01\x12\t\n\x01y\x18\x03 \x01(\x01\x12\t\n\x01z\x18\x04 \x01(\x01\x12\x15\n\rmetadata_json\x18\x05 \x01(\t\"2\n\rVerifyRequest\x12\x12\n\ncollection\x18\x01 \x01(\t\x12\r\n\x05\x66lush\x18\x02 \x01(\x08\"!\n\x0bLvqlRequest\x12\x12\n\nquery_json\x18\x01 \x01(\t\"L\n\x10TableRowsRequest\x12\r\n\x05table\x18\x01 \x01(\t\x12\x11\n\trows_json\x18\x02 \x01(\t\x12\x16\n\x0evector_columns\x18\x03 \x03(\t\"6\n\x11TableQueryRequest\x12\r\n\x05table\x18\x01 \x01(\t\x12\x12\n\nquery_json\x18\x02 \x01(\t\"o\n\x12TableSearchRequest\x12\r\n\x05table\x18\x01 \x01(\t\x12\x0e\n\x06\x63olumn\x18\x02 \x01(\t\x12\x0e\n\x06vector\x18\x03 \x03(\x02\x12\x0e\n\x01k\x18\x04 \x01(\x05H\x00\x88\x01\x01\x12\x14\n\x0c\x66ilters_json\x18\x05 \x01(\tB\x04\n\x02_k\"2\n\x11TableCountRequest\x12\r\n\x05table\x18\x01 \x01(\t\x12\x0e\n\x06\x63olumn\x18\x02 \x01(\t2\xb9\x10\n\nLeanSearch\x12<\n\x06Health\x12\x19.leansearch.HealthRequest\x1a\x17.leansearch.HealthReply\x12;\n\x0fListCollections\x12\x11.leansearch.Empty\x1a\x15.leansearch.ListReply\x12>\n\x05\x43ount\x12\x1d.leansearch.CollectionRequest\x1a\x16.leansearch.CountReply\x12=\n\x05Stats\x12\x1d.leansearch.CollectionRequest\x1a\x15.leansearch.JsonReply\x12@\n\x08\x44\x65scribe\x12\x1d.leansearch.CollectionRequest\x1a\x15.leansearch.JsonReply\x12\x32\n\x03\x41\x64\x64\x12\x16.leansearch.AddRequest\x1a\x13.leansearch.IdReply\x12;\n\x07\x41\x64\x64Many\x12\x1a.leansearch.AddManyRequest\x1a\x14.leansearch.IdsReply\x12\x34\n\x03Get\x12\x16.leansearch.GetRequest\x1a\x15.leansearch.JsonReply\x12?\n\x07\x43ontent\x12\x1a.leansearch.ContentRequest\x1a\x18.leansearch.ContentReply\x12;\n\x06\x44\x65lete\x12\x19.leansearch.DeleteRequest\x1a\x16.leansearch.CountReply\x12@\n\x0b\x44\x65leteWhere\x12\x19.leansearch.FilterRequest\x1a\x16.leansearch.CountReply\x12>\n\x05\x43lear\x12\x1d.leansearch.CollectionRequest\x1a\x16.leansearch.CountReply\x12<\n\x06\x45xists\x12\x19.leansearch.FilterRequest\x1a\x17.leansearch.ExistsReply\x12\x38\n\x05Items\x12\x18.leansearch.ItemsRequest\x1a\x15.leansearch.JsonReply\x12<\n\x06Search\x12\x19.leansearch.SearchRequest\x1a\x17.leansearch.SearchReply\x12:\n\x05Query\x12\x18.leansearch.QueryRequest\x1a\x17.leansearch.SearchReply\x12\x38\n\x04\x42m25\x12\x17.leansearch.Bm25Request\x1a\x17.leansearch.SearchReply\x12\x42\n\nSearchMany\x12\x1d.leansearch.SearchManyRequest\x1a\x15.leansearch.JsonReply\x12\x44\n\x0bValueCounts\x12\x1e.leansearch.ValueCountsRequest\x1a\x15.leansearch.JsonReply\x12\x42\n\nTopByGroup\x12\x1d.leansearch.TopByGroupRequest\x1a\x15.leansearch.JsonReply\x12;\n\x05Pca3d\x12\x18.leansearch.Pca3dRequest\x1a\x16.leansearch.Pca3dPoint0\x01\x12\x41\n\x07Persist\x12\x1d.leansearch.CollectionRequest\x1a\x17.leansearch.StatusReply\x12\x46\n\x0c\x46lushBarrier\x12\x1d.leansearch.CollectionRequest\x1a\x17.leansearch.StatusReply\x12\x41\n\x07Refresh\x12\x1d.leansearch.CollectionRequest\x1a\x17.leansearch.StatusReply\x12@\n\x06Vacuum\x12\x1d.leansearch.CollectionRequest\x1a\x17.leansearch.StatusReply\x12:\n\x06Verify\x12\x19.leansearch.VerifyRequest\x1a\x15.leansearch.JsonReply\x12\x36\n\x04Lvql\x12\x17.leansearch.LvqlRequest\x1a\x15.leansearch.JsonReply\x12\x36\n\nListTables\x12\x11.leansearch.Empty\x1a\x15.leansearch.JsonReply\x12@\n\x08TableAdd\x12\x1c.leansearch.TableRowsRequest\x1a\x16.leansearch.CountReply\x12\x44\n\x0cTableReplace\x12\x1c.leansearch.TableRowsRequest\x1a\x16.leansearch.CountReply\x12\x42\n\nTableQuery\x12\x1d.leansearch.TableQueryRequest\x1a\x15.leansearch.JsonReply\x12\x44\n\x0bTableSearch\x12\x1e.leansearch.TableSearchRequest\x1a\x15.leansearch.JsonReply\x12\x43\n\nTableCount\x12\x1d.leansearch.TableCountRequest\x1a\x16.leansearch.CountReplyb\x06proto3')
|
|
28
|
-
|
|
29
|
-
_globals = globals()
|
|
30
|
-
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
31
|
-
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'leansearch_pb2', _globals)
|
|
32
|
-
if not _descriptor._USE_C_DESCRIPTORS:
|
|
33
|
-
DESCRIPTOR._loaded_options = None
|
|
34
|
-
_globals['_EMPTY']._serialized_start=32
|
|
35
|
-
_globals['_EMPTY']._serialized_end=39
|
|
36
|
-
_globals['_SPARSEVECTOR']._serialized_start=41
|
|
37
|
-
_globals['_SPARSEVECTOR']._serialized_end=88
|
|
38
|
-
_globals['_HEALTHREQUEST']._serialized_start=90
|
|
39
|
-
_globals['_HEALTHREQUEST']._serialized_end=105
|
|
40
|
-
_globals['_HEALTHREPLY']._serialized_start=107
|
|
41
|
-
_globals['_HEALTHREPLY']._serialized_end=156
|
|
42
|
-
_globals['_COLLECTIONREQUEST']._serialized_start=158
|
|
43
|
-
_globals['_COLLECTIONREQUEST']._serialized_end=197
|
|
44
|
-
_globals['_LISTREPLY']._serialized_start=199
|
|
45
|
-
_globals['_LISTREPLY']._serialized_end=225
|
|
46
|
-
_globals['_COUNTREPLY']._serialized_start=227
|
|
47
|
-
_globals['_COUNTREPLY']._serialized_end=254
|
|
48
|
-
_globals['_JSONREPLY']._serialized_start=256
|
|
49
|
-
_globals['_JSONREPLY']._serialized_end=281
|
|
50
|
-
_globals['_STATUSREPLY']._serialized_start=283
|
|
51
|
-
_globals['_STATUSREPLY']._serialized_end=312
|
|
52
|
-
_globals['_IDREPLY']._serialized_start=314
|
|
53
|
-
_globals['_IDREPLY']._serialized_end=335
|
|
54
|
-
_globals['_IDSREPLY']._serialized_start=337
|
|
55
|
-
_globals['_IDSREPLY']._serialized_end=360
|
|
56
|
-
_globals['_EXISTSREPLY']._serialized_start=362
|
|
57
|
-
_globals['_EXISTSREPLY']._serialized_end=391
|
|
58
|
-
_globals['_SEARCHREPLY']._serialized_start=393
|
|
59
|
-
_globals['_SEARCHREPLY']._serialized_end=425
|
|
60
|
-
_globals['_DOCUMENT']._serialized_start=428
|
|
61
|
-
_globals['_DOCUMENT']._serialized_end=589
|
|
62
|
-
_globals['_ADDREQUEST']._serialized_start=591
|
|
63
|
-
_globals['_ADDREQUEST']._serialized_end=683
|
|
64
|
-
_globals['_ADDMANYREQUEST']._serialized_start=685
|
|
65
|
-
_globals['_ADDMANYREQUEST']._serialized_end=782
|
|
66
|
-
_globals['_GETREQUEST']._serialized_start=784
|
|
67
|
-
_globals['_GETREQUEST']._serialized_end=853
|
|
68
|
-
_globals['_CONTENTREQUEST']._serialized_start=855
|
|
69
|
-
_globals['_CONTENTREQUEST']._serialized_end=903
|
|
70
|
-
_globals['_CONTENTREPLY']._serialized_start=905
|
|
71
|
-
_globals['_CONTENTREPLY']._serialized_end=953
|
|
72
|
-
_globals['_DELETEREQUEST']._serialized_start=955
|
|
73
|
-
_globals['_DELETEREQUEST']._serialized_end=1003
|
|
74
|
-
_globals['_FILTERREQUEST']._serialized_start=1005
|
|
75
|
-
_globals['_FILTERREQUEST']._serialized_end=1062
|
|
76
|
-
_globals['_ITEMSREQUEST']._serialized_start=1064
|
|
77
|
-
_globals['_ITEMSREQUEST']._serialized_end=1150
|
|
78
|
-
_globals['_SEARCHREQUEST']._serialized_start=1153
|
|
79
|
-
_globals['_SEARCHREQUEST']._serialized_end=1904
|
|
80
|
-
_globals['_QUERYREQUEST']._serialized_start=1907
|
|
81
|
-
_globals['_QUERYREQUEST']._serialized_end=2368
|
|
82
|
-
_globals['_BM25REQUEST']._serialized_start=2371
|
|
83
|
-
_globals['_BM25REQUEST']._serialized_end=2761
|
|
84
|
-
_globals['_SEARCHMANYREQUEST']._serialized_start=2764
|
|
85
|
-
_globals['_SEARCHMANYREQUEST']._serialized_end=3284
|
|
86
|
-
_globals['_VALUECOUNTSREQUEST']._serialized_start=3286
|
|
87
|
-
_globals['_VALUECOUNTSREQUEST']._serialized_end=3363
|
|
88
|
-
_globals['_TOPBYGROUPREQUEST']._serialized_start=3366
|
|
89
|
-
_globals['_TOPBYGROUPREQUEST']._serialized_end=3525
|
|
90
|
-
_globals['_PCA3DREQUEST']._serialized_start=3527
|
|
91
|
-
_globals['_PCA3DREQUEST']._serialized_end=3618
|
|
92
|
-
_globals['_PCA3DPOINT']._serialized_start=3620
|
|
93
|
-
_globals['_PCA3DPOINT']._serialized_end=3700
|
|
94
|
-
_globals['_VERIFYREQUEST']._serialized_start=3702
|
|
95
|
-
_globals['_VERIFYREQUEST']._serialized_end=3752
|
|
96
|
-
_globals['_LVQLREQUEST']._serialized_start=3754
|
|
97
|
-
_globals['_LVQLREQUEST']._serialized_end=3787
|
|
98
|
-
_globals['_TABLEROWSREQUEST']._serialized_start=3789
|
|
99
|
-
_globals['_TABLEROWSREQUEST']._serialized_end=3865
|
|
100
|
-
_globals['_TABLEQUERYREQUEST']._serialized_start=3867
|
|
101
|
-
_globals['_TABLEQUERYREQUEST']._serialized_end=3921
|
|
102
|
-
_globals['_TABLESEARCHREQUEST']._serialized_start=3923
|
|
103
|
-
_globals['_TABLESEARCHREQUEST']._serialized_end=4034
|
|
104
|
-
_globals['_TABLECOUNTREQUEST']._serialized_start=4036
|
|
105
|
-
_globals['_TABLECOUNTREQUEST']._serialized_end=4086
|
|
106
|
-
_globals['_LEANSEARCH']._serialized_start=4089
|
|
107
|
-
_globals['_LEANSEARCH']._serialized_end=6194
|
|
108
|
-
# @@protoc_insertion_point(module_scope)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|