fix(search): validate effort examples and hybrid queries

This commit is contained in:
Yang Cen
2026-10-11 17:38:14 +08:00
parent 13853cb169
commit 1e545e9835
3 changed files with 69 additions and 5 deletions
+32
View File
@@ -646,6 +646,38 @@ rerank(reranker): VectorQuery
***
### searchEffort()
```ts
searchEffort(searchEffort): VectorQuery
```
Set the initial IVF search effort to a finite value in [0, 1].
The default, null, uses Auto (internally 0.5). Zero starts at the caller
minimum; one searches all partitions within the caller maximum. Later
probing may continue when too few candidates are found.
Any explicit effort, including 0.5, overrides nprobes in either call order.
Explicit minimum and maximum bounds still apply. This is not a recall
guarantee and does not change refinement or HNSW ef. The MemWAL LSM
scanner does not yet support this option.
#### Parameters
* **searchEffort**: `null` \| `number`
#### Returns
[`VectorQuery`](VectorQuery.md)
#### Example
```ts
table.vectorSearch([0.1, 0.2]).searchEffort(0.75).limit(10);
```
***
### select()
```ts
+21 -4
View File
@@ -1734,6 +1734,9 @@ class LanceVectorQueryBuilder(LanceQueryBuilder):
Examples
--------
>>> import lancedb
>>> db = lancedb.connect("memory://")
>>> table = db.create_table("effort_vectors", [{"vector": [0.1, 0.2]}])
>>> query = table.search([0.1, 0.2]).search_effort(0.75)
"""
self._search_effort = search_effort
@@ -2562,11 +2565,17 @@ class LanceHybridQueryBuilder(LanceQueryBuilder):
Examples
--------
>>> query = table.search([0.1, 0.2]).search_effort(0.75)
>>> import lancedb
>>> db = lancedb.connect("memory://")
>>> table = db.create_table(
... "effort_hybrid", [{"vector": [0.1, 0.2], "text": "dog"}]
... )
>>> query = (
... table.search(query_type="hybrid").vector([0.1, 0.2])
... .text("dog").search_effort(0.75)
... )
"""
self._search_effort = search_effort
if self._vector_query is not None:
self._vector_query.search_effort(search_effort)
return self
def minimum_nprobes(self, minimum_nprobes: int) -> LanceHybridQueryBuilder:
@@ -3790,7 +3799,15 @@ class AsyncVectorQueryBase:
Examples
--------
>>> query = table.vector_search([0.1, 0.2]).search_effort(0.75)
>>> import asyncio
>>> import lancedb
>>> async def build_query():
... db = await lancedb.connect_async("memory://")
... table = await db.create_table(
... "effort_async", [{"vector": [0.1, 0.2]}]
... )
... return table.vector_search([0.1, 0.2]).search_effort(0.75)
>>> query = asyncio.run(build_query())
"""
self._inner.search_effort(search_effort)
return self
+16 -1
View File
@@ -2284,6 +2284,7 @@ def test_search_effort_probe_budget(tmp_path, monkeypatch, metric, effort, probe
data = pa.table(
{
"id": np.arange(512),
"text": ["dog"] * 512,
"vector": pa.FixedSizeListArray.from_arrays(pa.array(vectors.ravel()), 16),
}
)
@@ -2298,7 +2299,8 @@ def test_search_effort_probe_budget(tmp_path, monkeypatch, metric, effort, probe
return table.search(vector).distance_type(metric).search_effort(value).limit(8)
def partitions(query):
values = re.findall(r"partitions_searched=(\d+)", query.analyze_plan())
vector_plan = query.analyze_plan().split("FTS Search Plan:", 1)[0]
values = re.findall(r"partitions_searched=(\d+)", vector_plan)
assert values
return sum(map(int, values))
@@ -2335,6 +2337,19 @@ def test_search_effort_probe_budget(tmp_path, monkeypatch, metric, effort, probe
assert partitions(filtered) <= 2
assert all(row["id"] % 8 == 0 for row in filtered.to_list())
if metric == "l2" and effort == 0.5:
table.create_index("text", config=FTS())
hybrid = (
table.search(query_type="hybrid")
.vector(vector)
.text("dog")
.nprobes(1)
.search_effort(0.5)
.limit(8)
)
assert partitions(hybrid) == 4
assert partitions(hybrid.search_effort(None)) == 1
@pytest.mark.parametrize(
"effort", [-0.1, 1.1, float("nan"), float("inf"), -float("inf")]