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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: leansearch
3
- Version: 0.2.0
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.38
17
- Requires-Dist: leanvec-scale<1.1,>=1.0.44
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.0"
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.44,<1.1",
18
- "leantext>=2.2.38,<2.3",
17
+ "leanvec-scale>=1.0.45,<1.1",
18
+ "leantext>=2.2.41,<2.3",
19
19
  "numpy",
20
20
  "orjson",
21
21
  ]
@@ -15,5 +15,5 @@ from ._collection import Collection, Snapshot
15
15
  from ._db import LeanSearch
16
16
  from ._table import Table
17
17
 
18
- __version__ = "0.2.0"
18
+ __version__ = "0.2.2"
19
19
  __all__ = ["LeanSearch", "Collection", "Snapshot", "Table", "__version__"]
@@ -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
- if len(explicit) <= _PROBE_IDS_DIRECTLY:
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 = 60,
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)`: both rankings fused by reciprocal rank (the same
676
- rule LeanVec uses for dense+sparse): score `sum(1 / (rrf_k + rank))`, higher is
677
- better; each hit also carries `text_score`/`text_rank` and
678
- `vector_score`/`vector_rank` where it was found.
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, rrf_k=rrf_k,
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
- text_hits = (self._text.search(query, k=fetch, filters=filters, collection=self.name,
728
- **text_opts) if self._has_text() else [])
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
- fused = rrf_fuse(text_hits, vec_hits, rrf_k)[start:total]
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, **opts):
323
- """`Collection.search`: text, vector (+ sparse), or both fused by RRF."""
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 by RRF (Collection.search)."""
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, fuzzy_dist=fuzzy_dist,
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; // fusion constant (default 60)
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"), **opts)
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