diff --git a/docs/api-reference/rest/index.mdx b/docs/api-reference/rest/index.mdx
index 11c35e5c..6793f571 100644
--- a/docs/api-reference/rest/index.mdx
+++ b/docs/api-reference/rest/index.mdx
@@ -5,7 +5,7 @@ description: "API reference for LanceDB"
---
-[Lance REST Namespace](https://lance.org/format/namespace/rest/catalog-spec/?h=rest) spec
+[Lance REST Namespace](https://lance.org/format/namespace/) spec
is an OpenAPI protocol that enables reading, writing and managing Lance tables by connecting
those metadata services or building a custom metadata server in a standardized way.
diff --git a/docs/datasets/ade20k.mdx b/docs/datasets/ade20k.mdx
index e49b91c9..f0abeaed 100644
--- a/docs/datasets/ade20k.mdx
+++ b/docs/datasets/ade20k.mdx
@@ -272,7 +272,7 @@ The resulting `./ade20k-indoor-subset` is a first-class LanceDB database. Every
## Source & license
-Converted from [`1aurent/ADE20K`](https://huggingface.co/datasets/1aurent/ADE20K). ADE20K is released under the [BSD 3-Clause license](https://github.com/CSAILVision/ADE20K/blob/main/LICENSE) by the MIT CSAIL Computer Vision group.
+Converted from [`1aurent/ADE20K`](https://huggingface.co/datasets/1aurent/ADE20K). ADE20K is released under the [BSD 3-Clause license](https://ade20k.csail.mit.edu/terms/) by the MIT CSAIL Computer Vision group.
## Citation
diff --git a/docs/docs.json b/docs/docs.json
index 4e47e76b..e1e3d32a 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -122,6 +122,7 @@
"search/vector-search",
"search/multivector-search",
"search/full-text-search",
+ "search/fts-examples",
"search/hybrid-search",
"search/filtering",
"search/optimize-queries",
@@ -185,6 +186,7 @@
{
"group": "Running Jobs",
"pages": [
+ "geneva/jobs/index",
"geneva/jobs/backfilling",
"geneva/jobs/bulk-load-columns",
"geneva/jobs/materialized-views",
diff --git a/docs/geneva/index.mdx b/docs/geneva/index.mdx
index ea85711f..16662791 100644
--- a/docs/geneva/index.mdx
+++ b/docs/geneva/index.mdx
@@ -60,7 +60,7 @@ Visit the following pages to learn more about featuring engineering in LanceDB E
- **Get started**: [Getting Started](/geneva/getting-started) · [What is Feature Engineering?](/geneva/overview/) · [End-to-end example](/geneva/end-to-end)
- **UDFs**: [Using UDFs](/geneva/udfs/udfs) · [Blob helpers](/geneva/udfs/blobs/) · [Error handling](/geneva/udfs/error_handling) · [Advanced configuration](/geneva/udfs/advanced-configuration)
-- **Jobs**: [Backfilling](/geneva/jobs/backfilling/) · [Materialized views](/geneva/jobs/materialized-views/) · [Startup optimizations](/geneva/jobs/startup/) · [Advanced job configuration](/geneva/jobs/advanced-job-configuration/) · [Advanced execution contexts](/geneva/jobs/contexts/) · [Geneva console](/geneva/jobs/console) · [Performance](/geneva/jobs/performance/)
+- **Jobs**: [Job execution overview](/geneva/jobs/) · [Backfilling](/geneva/jobs/backfilling/) · [Materialized views](/geneva/jobs/materialized-views/) · [Startup optimizations](/geneva/jobs/startup/) · [Advanced job configuration](/geneva/jobs/advanced-job-configuration/) · [Advanced execution contexts](/geneva/jobs/contexts/) · [Geneva console](/geneva/jobs/console) · [Performance](/geneva/jobs/performance/)
- **Deployment**: [Deployment overview](/geneva/deployment/) · [Helm deployment](/geneva/deployment/helm/) · [Troubleshooting](/geneva/deployment/troubleshooting/)
## API Reference
diff --git a/docs/geneva/udfs/blobs.mdx b/docs/geneva/udfs/blobs.mdx
index 5d2752b1..6f055af0 100644
--- a/docs/geneva/udfs/blobs.mdx
+++ b/docs/geneva/udfs/blobs.mdx
@@ -5,7 +5,7 @@ description: Learn how to work with Lance Blobs in Geneva UDFs for handling larg
icon: splotch
---
-Geneva supports UDFs that take [Lance Blobs](https://lancedb.github.io/lance/guide/blob/) (large binary objects) as input and has the ability to write out columns with binaries encoded as Lance Blobs. Lance blobs are an optimization intended for large objects (1's MBs -> 100MB's) and provide a file-like object that lazily reads large binary objects.
+Geneva supports UDFs that take [Lance Blobs](https://docs.lancedb.com/tables/multimodal) (large binary objects) as input and has the ability to write out columns with binaries encoded as Lance Blobs. Lance blobs are an optimization intended for large objects (1's MBs -> 100MB's) and provide a file-like object that lazily reads large binary objects.
## Reading Blobs
diff --git a/docs/integrations/ai/llamaIndex.mdx b/docs/integrations/ai/llamaIndex.mdx
index a7c0fb38..3fba4473 100644
--- a/docs/integrations/ai/llamaIndex.mdx
+++ b/docs/integrations/ai/llamaIndex.mdx
@@ -37,7 +37,7 @@ For metadata filtering, you can use a Lance SQL-like string filter as demonstrat
### Hybrid Search
-For complete documentation, refer [here](https://lancedb.github.io/lancedb/hybrid_search/hybrid_search/). This example uses the `colbert` reranker. Make sure to install necessary dependencies for the reranker you choose.
+For complete documentation, refer [here](https://docs.lancedb.com/search/hybrid-search). This example uses the `colbert` reranker. Make sure to install necessary dependencies for the reranker you choose.
{PyFrameworksLlamaindexHybridSearch}
diff --git a/docs/search/fts-examples.mdx b/docs/search/fts-examples.mdx
new file mode 100644
index 00000000..17f13a2d
--- /dev/null
+++ b/docs/search/fts-examples.mdx
@@ -0,0 +1,561 @@
+---
+title: Full-Text Search Examples
+sidebarTitle: FTS Examples
+description: Worked examples of fuzzy search, boosting, boolean queries, and substring search with LanceDB full-text search.
+icon: "book-open"
+---
+
+These worked examples build on the concepts from the [Full-Text Search guide](/search/full-text-search). They walk through creating sample tables, building FTS indices, and running fuzzy, phrase, boosted, boolean, and substring queries.
+
+## Fuzzy Search and Boosting Example
+
+### Generate Data
+
+First, let's create a table with sample text data for testing fuzzy search:
+
+
+```python Python icon="python"
+import lancedb
+import numpy as np
+import pandas as pd
+import random
+
+# Connect to LanceDB
+db = lancedb.connect(
+ uri="db://your-project-slug",
+ api_key="your-api-key",
+ region="us-east-1"
+)
+
+# Generate 100 rows of random " " text
+table_name = "fts-fuzzy-boosting-test"
+vectors = [np.random.randn(128) for _ in range(100)]
+verbs = ("runs", "hits", "jumps", "drives", "barfs")
+adv = ("crazily.", "dutifully.", "foolishly.", "merrily.", "occasionally.")
+adj = ("adorable", "clueless", "dirty", "odd", "stupid")
+
+def sentence(nouns):
+ return " ".join(random.choice(words) for words in (nouns, verbs, adv, adj))
+
+text = [sentence(("puppy", "car")) for _ in range(100)]
+text2 = [sentence(("rabbit", "girl", "monkey")) for _ in range(100)]
+count = [random.randint(1, 10000) for _ in range(100)]
+```
+
+```typescript TypeScript icon="square-js"
+import * as lancedb from "@lancedb/lancedb"
+
+const db = await lancedb.connect({
+ uri: "db://your-project-slug",
+ apiKey: "your-api-key",
+ region: "us-east-1"
+});
+
+// Generate 100 rows of random " " text
+const tableName = "fts-fuzzy-boosting-test-ts";
+const n = 100;
+const verbs = ["runs", "hits", "jumps", "drives", "barfs"];
+const adverbs = ["crazily", "dutifully", "foolishly", "merrily", "occasionally"];
+const adjectives = ["adorable", "clueless", "dirty", "odd", "stupid"];
+
+const pick = (words: string[]) => words[Math.floor(Math.random() * words.length)];
+const sentence = (nouns: string[]) =>
+ [nouns, verbs, adverbs, adjectives].map(pick).join(" ");
+
+const vectors = Array.from({ length: n }, () =>
+ Array.from({ length: 128 }, () => Math.random() * 2 - 1)
+);
+const text = Array.from({ length: n }, () => sentence(["puppy", "car"]));
+const text2 = Array.from({ length: n }, () => sentence(["rabbit", "girl", "monkey"]));
+const count = Array.from({ length: n }, () => Math.floor(Math.random() * 10000) + 1);
+```
+
+
+### Create Table
+
+
+```python Python icon="python"
+# Create table with sample data
+table = db.create_table(
+ table_name,
+ data=pd.DataFrame({
+ "vector": vectors,
+ "id": [i % 2 for i in range(100)],
+ "text": text,
+ "text2": text2,
+ "count": count,
+ }),
+ mode="overwrite"
+)
+```
+
+```typescript TypeScript icon="square-js"
+// Create table with sample data
+const data = makeArrowTable(
+ vectors.map((vector, i) => ({
+ vector,
+ id: i % 2,
+ text: text[i],
+ text2: text2[i],
+ count: count[i],
+ }))
+);
+
+const table = await db.createTable(tableName, data, { mode: "overwrite" });
+```
+
+
+### Construct FTS Index
+
+Create a full-text search index on the first text column:
+
+
+```python Python icon="python"
+# Create FTS index on first text column
+table.create_fts_index("text")
+wait_for_index(table, "text_idx")
+```
+
+```typescript TypeScript icon="square-js"
+// Create FTS index on first text column
+await table.createIndex("text", { config: Index.fts() });
+await waitForIndex(table, "text_idx");
+```
+
+
+Then, create an index on the second text column:
+
+
+```python Python icon="python"
+# Create FTS index on second text column
+table.create_fts_index("text2")
+wait_for_index(table, "text2_idx")
+```
+
+```typescript TypeScript icon="square-js"
+// Create FTS index on second text column
+await table.createIndex("text2", { config: Index.fts() });
+await waitForIndex(table, "text2_idx");
+```
+
+
+### Basic and Fuzzy Search
+
+Now we can perform basic, fuzzy, and prefix match searches:
+
+#### Basic Exact Search
+
+
+```python Python icon="python"
+from lancedb.query import MatchQuery
+
+# Basic match (exact search)
+basic_match_results = (
+ table.search(MatchQuery("crazily", "text"))
+ .select(["id", "text"])
+ .limit(100)
+ .to_pandas()
+)
+```
+
+```typescript TypeScript icon="square-js"
+import { MatchQuery } from "@lancedb/lancedb";
+
+// Basic match (exact search)
+const basicMatchResults = await table.query()
+ .fullTextSearch(new MatchQuery("crazily", "text"))
+ .select(["id", "text"])
+ .limit(100)
+ .toArray();
+```
+
+
+#### Fuzzy Search with Typos
+
+
+```python Python icon="python"
+# Fuzzy match (allows typos)
+fuzzy_results = (
+ table.search(MatchQuery("craziou", "text", fuzziness=2))
+ .select(["id", "text"])
+ .limit(100)
+ .to_pandas()
+)
+```
+
+```typescript TypeScript icon="square-js"
+// Fuzzy match (allows typos)
+const fuzzyResults = await table.query()
+ .fullTextSearch(new MatchQuery("craziou", "text", {
+ fuzziness: 2,
+ }))
+ .select(["id", "text"])
+ .limit(100)
+ .toArray();
+```
+
+
+#### Prefix based Match
+
+Prefix-based match allows you to search for documents containing words that start with a specific prefix.
+
+
+```python Python icon="python"
+# Fuzzy match (allows typos)
+fuzzy_results = (
+ table.search(MatchQuery("cra", "text", prefix_length=3))
+ .select(["id", "text"])
+ .limit(100)
+ .to_pandas()
+)
+```
+
+```typescript TypeScript icon="square-js"
+// Fuzzy match (allows typos)
+const fuzzyResults = await table.query()
+ .fullTextSearch(new MatchQuery("cra", "text", {
+ prefixLength: 3,
+ }))
+ .select(["id", "text"])
+ .limit(100)
+ .toArray();
+```
+
+
+### Phrase Match
+
+Phrase matching enables you to search for exact sequences of words. Unlike regular text search
+which matches individual terms independently, phrase matching requires words to appear in the
+specified order with no intervening terms.
+
+
+Phrase queries are supported but only for a single column; providing multiple columns with a quoted phrase raises an error.
+
+
+Phrase matching is particularly useful for:
+
+- Searching for specific multi-word expressions
+- Matching exact titles or quotes
+- Finding precise word combinations in a specific order
+
+
+```python Python icon="python"
+# Exact phrase match
+from lancedb.query import PhraseQuery
+
+phrase_results = (
+ table.search(PhraseQuery("puppy runs", "text"))
+ .select(["id", "text"])
+ .limit(100)
+ .to_pandas()
+)
+```
+
+```typescript TypeScript icon="square-js"
+import { PhraseQuery } from "@lancedb/lancedb";
+
+// Exact phrase match
+const phraseResults = await table.query()
+ .fullTextSearch(new PhraseQuery("puppy runs", "text"))
+ .select(["id", "text"])
+ .limit(100)
+ .toArray();
+```
+
+
+#### Flexible Phrase Match
+To provide more flexible phrase matching, LanceDB supports the `slop` parameter. This allows you to match phrases where the terms appear close to each other, even if they are not directly adjacent or in the exact order, as long as they are within the specified `slop` value.
+
+For example, the phrase query "puppy merrily" would not return any results by default. However, if you set `slop=1`, it will match phrases like "puppy jumps merrily", "puppy runs merrily", and similar variations where one word appears between "puppy" and "merrily".
+
+
+```python Python icon="python"
+# Flexible phrase match with slop=1 for 'puppy merrily'
+from lancedb.query import PhraseQuery
+
+phrase_results = (
+ table.search(PhraseQuery("puppy merrily", "text", slop=1))
+ .select(["id", "text"])
+ .limit(100)
+ .to_pandas()
+)
+```
+
+```typescript TypeScript icon="square-js"
+import { PhraseQuery } from "@lancedb/lancedb";
+
+// Flexible phrase match with slop=1 for 'puppy runs'
+const phraseResults = await table.query()
+ .fullTextSearch(new PhraseQuery("puppy runs", "text", { slop: 1 }))
+ .select(["id", "text"])
+ .limit(100)
+ .toArray();
+```
+
+
+### Search with Boosting
+
+Boosting allows you to control the relative importance of different search terms or fields
+in your queries. This feature is particularly useful when you need to:
+
+* Prioritize matches in certain columns
+* Promote specific terms while demoting others
+* Fine-tune relevance scoring for better search results
+
+| Parameter | Type | Default | Description |
+| -------------- | ----- | -------- | ------------------------------------------------------------------ |
+| positive | Query | required | The primary query terms to match and promote in results |
+| negative | Query | required | Terms to demote in the search results |
+| negative_boost | float | 0.5 | Multiplier for negative matches (lower values = stronger demotion) |
+
+
+```python Python icon="python"
+from lancedb.query import MatchQuery, BoostQuery, MultiMatchQuery
+
+# Boost data with 'runs' in text more than 'puppy' in text
+boosting_results = (
+ table.search(
+ BoostQuery(
+ MatchQuery("runs", "text"),
+ MatchQuery("puppy", "text"),
+ negative_boost=0.2,
+ ),
+ )
+ .select(["id", "text"])
+ .limit(100)
+ .to_pandas()
+)
+
+# Search across both text and text2
+multi_match_results = (
+ table.search(MultiMatchQuery("crazily", ["text", "text2"]))
+ .select(["id", "text", "text2"])
+ .limit(100)
+ .to_pandas()
+)
+
+# Search with field boosting
+multi_match_boosting_results = (
+ table.search(
+ MultiMatchQuery("crazily", ["text", "text2"], boosts=[1.0, 2.0]),
+ )
+ .select(["id", "text", "text2"])
+ .limit(100)
+ .to_pandas()
+)
+```
+
+```typescript TypeScript icon="square-js"
+import { MatchQuery, BoostQuery, MultiMatchQuery } from "@lancedb/lancedb";
+
+// Boosting Example
+const boostingResults = await table.query()
+ .fullTextSearch(new BoostQuery(new MatchQuery("runs", "text"), new MatchQuery("puppy", "text"), {
+ negativeBoost: 0.2,
+ }))
+ .select(["id", "text"])
+ .limit(100)
+ .toArray();
+
+// Search across both text fields
+const multiMatchResults = await table.query()
+ .fullTextSearch(new MultiMatchQuery("crazily", ["text", "text2"]))
+ .select(["id", "text", "text2"])
+ .limit(100)
+ .toArray();
+
+// Search with field boosting
+const multiMatchBoostingResults = await table.query()
+ .fullTextSearch(new MultiMatchQuery("crazily", ["text", "text2"], {
+ boosts: [1.0, 2.0],
+ }))
+ .select(["id", "text", "text2"])
+ .limit(100)
+ .toArray();
+```
+
+
+
+- Use fuzzy search when handling user input that may contain typos or variations
+- Apply field boosting to prioritize matches in more important columns
+- Combine fuzzy search with boosting for robust and precise search results
+
+**Recommendations for optimal FTS performance:**
+
+- Create full-text search indices on text columns that will be frequently searched
+- For hybrid search combining text and vectors, see our [hybrid search guide](/search/hybrid-search/)
+- For performance benchmarks, check our [benchmark results](/enterprise/benchmarks/)
+- For complex queries, use SQL to combine FTS with other filter conditions
+
+
+### Boolean Queries
+LanceDB supports boolean logic in full-text search, allowing you to combine multiple queries using `and` and `or` operators. This is useful when you want to match documents that satisfy multiple conditions (intersection) or at least one of several conditions (union).
+
+#### Combining Two Match Queries
+
+In Python, you can combine two MatchQuery objects using either the `and` function or the `&` operator (e.g., `MatchQuery("puppy", "text") and MatchQuery("merrily", "text")`); both methods are supported and yield the same result. Similarly, you can use either the `or` function or the `|` operator to perform an or query.
+
+In TypeScript, boolean queries are constructed using the `BooleanQuery` class with a list of [Occur, subquery] pairs. For example, to perform an AND query:
+
+```sql SQL icon="code"
+BooleanQuery([
+[Occur.Must, new MatchQuery("puppy", "text")],
+[Occur.Must, new MatchQuery("merrily", "text")],
+])
+```
+
+This approach allows you to specify complex boolean logic by combining multiple subqueries with different Occur values (such as `Must`, `Should`, or `MustNot`).
+
+
+**Which queries are allowed?**
+
+A boolean query must include at least one `SHOULD` or `MUST` clause. Queries that contain only a `MUST_NOT` clause are not allowed.
+
+
+
+```python Python icon="python"
+from lancedb.query import MatchQuery
+
+# Example: Find documents containing both "puppy" and "merrily"
+and_query = MatchQuery("puppy", "text") & MatchQuery("merrily", "text")
+and_results = (
+ table.search(and_query)
+ .select(["id", "text"])
+ .limit(100)
+ .to_pandas()
+)
+
+# Example: Find documents containing either "puppy" or "merrily"
+or_query = MatchQuery("puppy", "text") | MatchQuery("merrily", "text")
+or_results = (
+ table.search(or_query)
+ .select(["id", "text"])
+ .limit(100)
+ .to_pandas()
+)
+```
+
+```typescript TypeScript icon="square-js" expandable=true
+import { MatchQuery, BooleanQuery, Occur } from "@lancedb/lancedb";
+
+// Flexible boolean queries with MatchQuery
+
+// Find documents containing both "puppy" and "merrily"
+const mustResults = await table
+ .search(
+ new BooleanQuery([
+ [Occur.Must, new MatchQuery("puppy", "text")],
+ [Occur.Must, new MatchQuery("merrily", "text")],
+ ]),
+ )
+ .select(["id", "text"])
+ .limit(100)
+ .toArray();
+
+// Find documents containing either "puppy" or "merrily"
+const shouldResults = await table
+ .search(
+ new BooleanQuery([
+ [Occur.Should, new MatchQuery("puppy", "text")],
+ [Occur.Should, new MatchQuery("merrily", "text")],
+ ]),
+ )
+ .select(["id", "text"])
+ .limit(100)
+ .toArray();
+```
+
+
+
+**How to use booleans?**
+
+- Use `and`/`&`(Python), `Occur.Must`(Typescript) for intersection (documents must match all queries).
+- Use `or`/`|`(Python), `Occur.Should`(Typescript) for union (documents must match at least one query).
+
+
+## Substring Search Example
+
+LanceDB supports searching for substrings in text columns using n-gram tokenization. This is useful for finding partial matches within text content.
+
+### Setting Up the Table
+
+First, create a table with sample text data and configure n-gram tokenization:
+
+
+```python Python icon="python"
+import pyarrow as pa
+import lancedb
+
+db = lancedb.connect(":memory:")
+
+data = pa.table({"text": ["hello world", "lance database", "lance is cool"]})
+table = db.create_table("test", data=data)
+table.create_fts_index("text", base_tokenizer="ngram")
+```
+
+
+### Basic Substring Search
+
+With the default n-gram settings (minimum length of 3), you can search for substrings of length 3 or more:
+
+
+```python Python icon="python"
+results = table.search("lan", query_type="fts").limit(10).to_list()
+assert len(results) == 2
+assert set(r["text"] for r in results) == {"lance database", "lance is cool"}
+
+results = (
+ table.search("nce", query_type="fts").limit(10).to_list()
+) # spellchecker:disable-line
+assert len(results) == 2
+assert set(r["text"] for r in results) == {"lance database", "lance is cool"}
+```
+
+
+### Handling Short Substrings
+
+By default, the minimum n-gram length is 3, so shorter substrings like "la" won't match:
+
+
+```python Python icon="python"
+results = table.search("la", query_type="fts").limit(10).to_list()
+assert len(results) == 0
+```
+
+
+### Customizing N-gram Parameters
+
+You can customize the n-gram behavior by adjusting the minimum length and using prefix-only matching:
+
+
+```python Python icon="python"
+table.create_fts_index(
+ "text",
+ base_tokenizer="ngram",
+ replace=True,
+ ngram_min_length=2,
+ prefix_only=True,
+)
+```
+
+
+### Testing Custom N-gram Settings
+
+With the new settings, you can now search for shorter substrings and use prefix-only matching:
+
+
+```python Python icon="python"
+results = table.search("lan", query_type="fts").limit(10).to_list()
+assert len(results) == 2
+assert set(r["text"] for r in results) == {"lance database", "lance is cool"}
+
+results = (
+ table.search("nce", query_type="fts").limit(10).to_list()
+) # spellchecker:disable-line
+assert len(results) == 0
+
+results = table.search("la", query_type="fts").limit(10).to_list()
+assert len(results) == 2
+assert set(r["text"] for r in results) == {"lance database", "lance is cool"}
+```
+
diff --git a/docs/search/full-text-search.mdx b/docs/search/full-text-search.mdx
index bad353e5..9764d461 100644
--- a/docs/search/full-text-search.mdx
+++ b/docs/search/full-text-search.mdx
@@ -324,8 +324,7 @@ to find similar terms within a specified edit distance.
| fuzziness | int | 0 | Maximum edit distance allowed for each term. If not specified, automatically set based on term length: 0 for length ≤ 2, 1 for length ≤ 5, 2 for length > 5 |
| max_expansions | int | 50 | Maximum number of terms to consider for fuzzy matching. Higher values may improve recall but increase search time |
-Let's create a sample table and build full-text search indices to demonstrate
-fuzzy search capabilities and relevance boosting features.
+For a complete walkthrough that creates a sample table and demonstrates fuzzy search and relevance boosting, see the [fuzzy search example](/search/fts-examples#fuzzy-search-and-boosting-example).
### Search for Substring
@@ -338,605 +337,9 @@ LanceDB supports searching for substrings in the text column, you can set the `b
| prefix_only | bool | false | Whether to only search for prefixes of the n-grams |
-## Example: Fuzzy Search
+## More Examples
-### Generate Data
-
-First, let's create a table with sample text data for testing fuzzy search:
-
-
-```python Python icon="python"
-import lancedb
-import numpy as np
-import pandas as pd
-import random
-
-# Connect to LanceDB
-db = lancedb.connect(
- uri="db://your-project-slug",
- api_key="your-api-key",
- region="us-east-1"
-)
-
-# Generate sample data
-table_name = "fts-fuzzy-boosting-test"
-vectors = [np.random.randn(128) for _ in range(100)]
-text_nouns = ("puppy", "car")
-text2_nouns = ("rabbit", "girl", "monkey")
-verbs = ("runs", "hits", "jumps", "drives", "barfs")
-adv = ("crazily.", "dutifully.", "foolishly.", "merrily.", "occasionally.")
-adj = ("adorable", "clueless", "dirty", "odd", "stupid")
-
-# Generate random text combinations
-text = [
- " ".join([
- text_nouns[random.randrange(0, len(text_nouns))],
- verbs[random.randrange(0, 5)],
- adv[random.randrange(0, 5)],
- adj[random.randrange(0, 5)],
- ])
- for _ in range(100)
-]
-text2 = [
- " ".join([
- text2_nouns[random.randrange(0, len(text2_nouns))],
- verbs[random.randrange(0, 5)],
- adv[random.randrange(0, 5)],
- adj[random.randrange(0, 5)],
- ])
- for _ in range(100)
-]
-count = [random.randint(1, 10000) for _ in range(100)]
-```
-
-```typescript TypeScript icon="square-js"
-import * as lancedb from "@lancedb/lancedb"
-
-const db = await lancedb.connect({
- uri: "db://your-project-slug",
- apiKey: "your-api-key",
- region: "us-east-1"
-});
-
-// Generate sample data
-const tableName = "fts-fuzzy-boosting-test-ts";
-const n = 100;
-const vectors = Array.from({ length: n }, () =>
- Array.from({ length: 128 }, () => Math.random() * 2 - 1)
-);
-
-const textNouns = ["puppy", "car"];
-const text2Nouns = ["rabbit", "girl", "monkey"];
-const verbs = ["runs", "hits", "jumps", "drives", "barfs"];
-const adverbs = ["crazily", "dutifully", "foolishly", "merrily", "occasionally"];
-const adjectives = ["adorable", "clueless", "dirty", "odd", "stupid"];
-
-// Generate random text combinations
-const generateText = (nouns: string[]) => {
- const noun = nouns[Math.floor(Math.random() * nouns.length)];
- const verb = verbs[Math.floor(Math.random() * verbs.length)];
- const adv = adverbs[Math.floor(Math.random() * adverbs.length)];
- const adj = adjectives[Math.floor(Math.random() * adjectives.length)];
- return `${noun} ${verb} ${adv} ${adj}`;
-};
-
-const text = Array.from({ length: n }, () => generateText(textNouns));
-const text2 = Array.from({ length: n }, () => generateText(text2Nouns));
-const count = Array.from({ length: n }, () => Math.floor(Math.random() * 10000) + 1);
-```
-
-
-### Create Table
-
-
-```python Python icon="python"
-# Create table with sample data
-table = db.create_table(
- table_name,
- data=pd.DataFrame({
- "vector": vectors,
- "id": [i % 2 for i in range(100)],
- "text": text,
- "text2": text2,
- "count": count,
- }),
- mode="overwrite"
-)
-```
-
-```typescript TypeScript icon="square-js"
-// Create table with sample data
-const data = makeArrowTable(
- vectors.map((vector, i) => ({
- vector,
- id: i % 2,
- text: text[i],
- text2: text2[i],
- count: count[i],
- }))
-);
-
-const table = await db.createTable(tableName, data, { mode: "overwrite" });
-```
-
-
-### Construct FTS Index
-
-Create a full-text search index on the first text column:
-
-
-```python Python icon="python"
-# Create FTS index on first text column
-table.create_fts_index("text")
-wait_for_index(table, "text_idx")
-```
-
-```typescript TypeScript icon="square-js"
-// Create FTS index on first text column
-await table.createIndex("text", { config: Index.fts() });
-await waitForIndex(table, "text_idx");
-```
-
-
-Then, create an index on the second text column:
-
-
-```python Python icon="python"
-# Create FTS index on second text column
-table.create_fts_index("text2")
-wait_for_index(table, "text2_idx")
-```
-
-```typescript TypeScript icon="square-js"
-// Create FTS index on second text column
-await table.createIndex("text2", { config: Index.fts() });
-await waitForIndex(table, "text2_idx");
-```
-
-
-### Basic and Fuzzy Search
-
-Now we can perform basic, fuzzy, and prefix match searches:
-
-#### Basic Exact Search
-
-
-```python Python icon="python"
-from lancedb.query import MatchQuery
-
-# Basic match (exact search)
-basic_match_results = (
- table.search(MatchQuery("crazily", "text"))
- .select(["id", "text"])
- .limit(100)
- .to_pandas()
-)
-```
-
-```typescript TypeScript icon="square-js"
-import { MatchQuery } from "@lancedb/lancedb";
-
-// Basic match (exact search)
-const basicMatchResults = await table.query()
- .fullTextSearch(new MatchQuery("crazily", "text"))
- .select(["id", "text"])
- .limit(100)
- .toArray();
-```
-
-
-#### Fuzzy Search with Typos
-
-
-```python Python icon="python"
-# Fuzzy match (allows typos)
-fuzzy_results = (
- table.search(MatchQuery("craziou", "text", fuzziness=2))
- .select(["id", "text"])
- .limit(100)
- .to_pandas()
-)
-```
-
-```typescript TypeScript icon="square-js"
-// Fuzzy match (allows typos)
-const fuzzyResults = await table.query()
- .fullTextSearch(new MatchQuery("craziou", "text", {
- fuzziness: 2,
- }))
- .select(["id", "text"])
- .limit(100)
- .toArray();
-```
-
-
-#### Prefix based Match
-
-Prefix-based match allows you to search for documents containing words that start with a specific prefix.
-
-
-```python Python icon="python"
-# Fuzzy match (allows typos)
-fuzzy_results = (
- table.search(MatchQuery("cra", "text", prefix_length=3))
- .select(["id", "text"])
- .limit(100)
- .to_pandas()
-)
-```
-
-```typescript TypeScript icon="square-js"
-// Fuzzy match (allows typos)
-const fuzzyResults = await table.query()
- .fullTextSearch(new MatchQuery("cra", "text", {
- prefixLength: 3,
- }))
- .select(["id", "text"])
- .limit(100)
- .toArray();
-```
-
-
-### Phrase Match
-
-Phrase matching enables you to search for exact sequences of words. Unlike regular text search
-which matches individual terms independently, phrase matching requires words to appear in the
-specified order with no intervening terms.
-
-
-Phrase queries are supported but only for a single column; providing multiple columns with a quoted phrase raises an error.
-
-
-Phrase matching is particularly useful for:
-
-- Searching for specific multi-word expressions
-- Matching exact titles or quotes
-- Finding precise word combinations in a specific order
-
-
-```python Python icon="python"
-# Exact phrase match
-from lancedb.query import PhraseQuery
-
-print("\n1. Exact phrase match for 'puppy runs':")
-phrase_results = (
- table.search(PhraseQuery("puppy runs", "text"))
- .select(["id", "text"])
- .limit(100)
- .to_pandas()
-)
-```
-
-```typescript TypeScript icon="square-js"
-import { PhraseQuery } from "@lancedb/lancedb";
-
-// Exact phrase match
-console.log("\n1. Exact phrase match for 'puppy runs':");
-const phraseResults = await table.query()
- .fullTextSearch(new PhraseQuery("puppy runs", "text"))
- .select(["id", "text"])
- .limit(100)
- .toArray();
-```
-
-
-#### Flexible Phrase Match
-To provide more flexible phrase matching, LanceDB supports the `slop` parameter. This allows you to match phrases where the terms appear close to each other, even if they are not directly adjacent or in the exact order, as long as they are within the specified `slop` value.
-
-For example, the phrase query "puppy merrily" would not return any results by default. However, if you set `slop=1`, it will match phrases like "puppy jumps merrily", "puppy runs merrily", and similar variations where one word appears between "puppy" and "merrily".
-
-
-```python Python icon="python"
-# Flexible phrase match with slop=1 for 'puppy merrily'
-from lancedb.query import PhraseQuery
-
-print("\n1. Flexible phrase match for 'puppy merrily' with slop=1:")
-phrase_results = (
- table.search(PhraseQuery("puppy merrily", "text", slop=1))
- .select(["id", "text"])
- .limit(100)
- .to_pandas()
-)
-```
-
-```typescript TypeScript icon="square-js"
-import { PhraseQuery } from "@lancedb/lancedb";
-
-// Flexible phrase match with slop=1 for 'puppy runs'
-console.log("\n1. Flexible phrase match for 'puppy runs' with slop=1:");
-const phraseResults = await table.query()
- .fullTextSearch(new PhraseQuery("puppy runs", "text", { slop: 1 }))
- .select(["id", "text"])
- .limit(100)
- .toArray();
-```
-
-
-### Search with Boosting
-
-Boosting allows you to control the relative importance of different search terms or fields
-in your queries. This feature is particularly useful when you need to:
-
-* Prioritize matches in certain columns
-* Promote specific terms while demoting others
-* Fine-tune relevance scoring for better search results
-
-| Parameter | Type | Default | Description |
-| -------------- | ----- | -------- | ------------------------------------------------------------------ |
-| positive | Query | required | The primary query terms to match and promote in results |
-| negative | Query | required | Terms to demote in the search results |
-| negative_boost | float | 0.5 | Multiplier for negative matches (lower values = stronger demotion) |
-
-
-```python Python icon="python"
-from lancedb.query import MatchQuery, BoostQuery, MultiMatchQuery
-
-# Boost data with 'runs' in text more than 'puppy' in text
-print("\n2. Boosting data with 'runs' in text:")
-boosting_results = (
- table.search(
- BoostQuery(
- MatchQuery("runs", "text"),
- MatchQuery("puppy", "text"),
- negative_boost=0.2,
- ),
- )
- .select(["id", "text"])
- .limit(100)
- .to_pandas()
-)
-
-"""Test searching across multiple fields."""
-print("\n=== Multi Match Query Examples ===")
-# Search across both text and text2
-print("\n1. Searching 'crazily' in both text and text2:")
-multi_match_results = (
- table.search(MultiMatchQuery("crazily", ["text", "text2"]))
- .select(["id", "text", "text2"])
- .limit(100)
- .to_pandas()
-)
-
-# Search with field boosting
-print("\n2. Searching with boosted text2 field:")
-multi_match_boosting_results = (
- table.search(
- MultiMatchQuery("crazily", ["text", "text2"], boosts=[1.0, 2.0]),
- )
- .select(["id", "text", "text2"])
- .limit(100)
- .to_pandas()
-)
-```
-
-```typescript TypeScript icon="square-js"
-import { MatchQuery, BoostQuery, MultiMatchQuery } from "@lancedb/lancedb";
-
-// Boosting Example
-console.log("\n2. Boosting data with 'runs' in text:");
-const boostingResults = await table.query()
- .fullTextSearch(new BoostQuery(new MatchQuery("runs", "text"), new MatchQuery("puppy", "text"), {
- negativeBoost: 0.2,
- }))
- .select(["id", "text"])
- .limit(100)
- .toArray();
-
-// Multi Match Query Examples
-console.log("\n=== Multi Match Query Examples ===");
-
-// Search across both text fields
-console.log("\n1. Searching 'crazily' in both text and text2:");
-const multiMatchResults = await table.query()
- .fullTextSearch(new MultiMatchQuery("crazily", ["text", "text2"]))
- .select(["id", "text", "text2"])
- .limit(100)
- .toArray();
-
-// Search with field boosting
-console.log("\n2. Searching with boosted text2 field:");
-const multiMatchBoostingResults = await table.query()
- .fullTextSearch(new MultiMatchQuery("crazily", ["text", "text2"], {
- boosts: [1.0, 2.0],
- }))
- .select(["id", "text", "text2"])
- .limit(100)
- .toArray();
-```
-
-
-
-- Use fuzzy search when handling user input that may contain typos or variations
-- Apply field boosting to prioritize matches in more important columns
-- Combine fuzzy search with boosting for robust and precise search results
-
-**Recommendations for optimal FTS performance:**
-
-- Create full-text search indices on text columns that will be frequently searched
-- For hybrid search combining text and vectors, see our [hybrid search guide](/search/hybrid-search/)
-- For performance benchmarks, check our [benchmark results](/enterprise/benchmarks/)
-- For complex queries, use SQL to combine FTS with other filter conditions
-
-
-### Boolean Queries
-LanceDB supports boolean logic in full-text search, allowing you to combine multiple queries using `and` and `or` operators. This is useful when you want to match documents that satisfy multiple conditions (intersection) or at least one of several conditions (union).
-
-#### Combining Two Match Queries
-
-In Python, you can combine two MatchQuery objects using either the `and` function or the `&` operator (e.g., `MatchQuery("puppy", "text") and MatchQuery("merrily", "text")`); both methods are supported and yield the same result. Similarly, you can use either the `or` function or the `|` operator to perform an or query.
-
-In TypeScript, boolean queries are constructed using the `BooleanQuery` class with a list of [Occur, subquery] pairs. For example, to perform an AND query:
-
-```sql SQL icon="code"
-BooleanQuery([
-[Occur.Must, new MatchQuery("puppy", "text")],
-[Occur.Must, new MatchQuery("merrily", "text")],
-])
-```
-
-This approach allows you to specify complex boolean logic by combining multiple subqueries with different Occur values (such as `Must`, `Should`, or `MustNot`).
-
-
-**Which queries are allowed?**
-
-A boolean query must include at least one `SHOULD` or `MUST` clause. Queries that contain only a `MUST_NOT` clause are not allowed.
-
-
-
-```python Python icon="python"
-from lancedb.query import MatchQuery
-
-# Example: Find documents containing both "puppy" and "merrily"
-and_query = MatchQuery("puppy", "text") & MatchQuery("merrily", "text")
-and_results = (
- table.search(and_query)
- .select(["id", "text"])
- .limit(100)
- .to_pandas()
-)
-print("\nDocuments containing both 'puppy' and 'merrily':")
-print(and_results)
-
-# Example: Find documents containing either "puppy" or "merrily"
-or_query = MatchQuery("puppy", "text") | MatchQuery("merrily", "text")
-or_results = (
- table.search(or_query)
- .select(["id", "text"])
- .limit(100)
- .to_pandas()
-)
-print("\nDocuments containing either 'puppy' OR 'merrily':")
-print(or_results)
-```
-
-```typescript TypeScript icon="square-js" expandable=true
-import { MatchQuery, BooleanQuery, Occur } from "@lancedb/lancedb";
-
-// Flexible boolean queries with MatchQuery
-
-// Find documents containing both "puppy" and "merrily"
-const mustResults = await table
- .search(
- new BooleanQuery([
- [Occur.Must, new MatchQuery("puppy", "text")],
- [Occur.Must, new MatchQuery("merrily", "text")],
- ]),
- )
- .select(["id", "text"])
- .limit(100)
- .toArray();
-console.log("\nDocuments containing both 'puppy' and 'merrily':");
-console.log(mustResults);
-
-// Find documents containing either "puppy" or "merrily"
-const shouldResults = await table
- .search(
- new BooleanQuery([
- [Occur.Should, new MatchQuery("puppy", "text")],
- [Occur.Should, new MatchQuery("merrily", "text")],
- ]),
- )
- .select(["id", "text"])
- .limit(100)
- .toArray();
-console.log("\nDocuments containing either 'puppy' or 'merrily':");
-console.log(shouldResults);
-```
-
-
-
-**How to use booleans?**
-
-- Use `and`/`&`(Python), `Occur.Must`(Typescript) for intersection (documents must match all queries).
-- Use `or`/`|`(Python), `Occur.Should`(Typescript) for union (documents must match at least one query).
-
-
-## Example: Substring Search
-
-LanceDB supports searching for substrings in text columns using n-gram tokenization. This is useful for finding partial matches within text content.
-
-### Setting Up the Table
-
-First, create a table with sample text data and configure n-gram tokenization:
-
-
-```python Python icon="python"
-import pyarrow as pa
-import lancedb
-
-db = lancedb.connect(":memory:")
-
-data = pa.table({"text": ["hello world", "lance database", "lance is cool"]})
-table = db.create_table("test", data=data)
-table.create_fts_index("text", base_tokenizer="ngram")
-```
-
-
-### Basic Substring Search
-
-With the default n-gram settings (minimum length of 3), you can search for substrings of length 3 or more:
-
-
-```python Python icon="python"
-results = table.search("lan", query_type="fts").limit(10).to_list()
-assert len(results) == 2
-assert set(r["text"] for r in results) == {"lance database", "lance is cool"}
-
-results = (
- table.search("nce", query_type="fts").limit(10).to_list()
-) # spellchecker:disable-line
-assert len(results) == 2
-assert set(r["text"] for r in results) == {"lance database", "lance is cool"}
-```
-
-
-### Handling Short Substrings
-
-By default, the minimum n-gram length is 3, so shorter substrings like "la" won't match:
-
-
-```python Python icon="python"
-results = table.search("la", query_type="fts").limit(10).to_list()
-assert len(results) == 0
-```
-
-
-### Customizing N-gram Parameters
-
-You can customize the n-gram behavior by adjusting the minimum length and using prefix-only matching:
-
-
-```python Python icon="python"
-table.create_fts_index(
- "text",
- base_tokenizer="ngram",
- replace=True,
- ngram_min_length=2,
- prefix_only=True,
-)
-```
-
-
-### Testing Custom N-gram Settings
-
-With the new settings, you can now search for shorter substrings and use prefix-only matching:
-
-
-```python Python icon="python"
-results = table.search("lan", query_type="fts").limit(10).to_list()
-assert len(results) == 2
-assert set(r["text"] for r in results) == {"lance database", "lance is cool"}
-
-results = (
- table.search("nce", query_type="fts").limit(10).to_list()
-) # spellchecker:disable-line
-assert len(results) == 0
-
-results = table.search("la", query_type="fts").limit(10).to_list()
-assert len(results) == 2
-assert set(r["text"] for r in results) == {"lance database", "lance is cool"}
-```
-
+For complete worked examples of fuzzy search, prefix matching, phrase matching, boosting, boolean queries, and substring search — including sample data generation and index setup — see [Full-Text Search Examples](/search/fts-examples).
## Full-Text Search on Array Fields
diff --git a/docs/search/hybrid-search.mdx b/docs/search/hybrid-search.mdx
index d5785f36..9ac30e47 100644
--- a/docs/search/hybrid-search.mdx
+++ b/docs/search/hybrid-search.mdx
@@ -344,7 +344,7 @@ The choice gets baked into both sub-queries, so the vector and FTS halves see th
## More on Reranking
-You can perform hybrid search in LanceDB by combining the results of semantic and full-text search via a reranking algorithm of your choice. LanceDB comes with [**built-in rerankers**](https://lancedb.github.io/lancedb/reranking/) and you can implement your own **custom reranker** as well.
+You can perform hybrid search in LanceDB by combining the results of semantic and full-text search via a reranking algorithm of your choice. LanceDB comes with [**built-in rerankers**](https://docs.lancedb.com/reranking) and you can implement your own **custom reranker** as well.
By default, LanceDB uses `RRFReranker()`, which uses reciprocal rank fusion score, to combine and rerank the results of semantic and full-text search. You can customize the hyperparameters as needed or write your own custom reranker. Here's how you can use any of the available rerankers:
diff --git a/docs/static/assets/images/demos/video_demo_hero.png b/docs/static/assets/images/demos/video_demo_hero.png
index 510c94b0..89722dfa 100644
Binary files a/docs/static/assets/images/demos/video_demo_hero.png and b/docs/static/assets/images/demos/video_demo_hero.png differ
diff --git a/docs/tutorials/agents/time-travel-rag/index.mdx b/docs/tutorials/agents/time-travel-rag/index.mdx
index 2893d034..fb29888b 100644
--- a/docs/tutorials/agents/time-travel-rag/index.mdx
+++ b/docs/tutorials/agents/time-travel-rag/index.mdx
@@ -40,7 +40,7 @@ To make this use case realistic, we'll use a perfect real-world dataset: The U.S
It contains all new rules, proposed rules, and notices from federal agencies. It is the canonical source for regulatory changes, and it's updated every business day. It even has a public API, allowing us to simulate the real-time ingestion of new documents.
-An example output of the workflow defined in [main.py](https://github.com/lancedb/blog-lancedb/blob/main/content/docs/tutorials/rag/time-travel-rag/main.py)
+An example output of the workflow defined in main.py
is shown below.
```bash main.py expandable