diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 4be33361..33f6c85f 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -57,16 +57,6 @@ plugins: - https://arrow.apache.org/docs/objects.inv - https://pandas.pydata.org/docs/objects.inv - mkdocs-jupyter - - ultralytics: - verbose: True - enabled: True - default_image: "assets/lancedb_and_lance.png" # Default image for all pages - add_image: True # Automatically add meta image - add_keywords: True # Add page keywords in the header tag - add_share_buttons: True # Add social share buttons - add_authors: False # Display page authors - add_desc: False - add_dates: False markdown_extensions: - admonition @@ -104,6 +94,14 @@ nav: - Overview: hybrid_search/hybrid_search.md - Comparing Rerankers: hybrid_search/eval.md - Airbnb financial data example: notebooks/hybrid_search.ipynb + - Reranking: + - Quickstart: reranking/index.md + - Cohere Reranker: reranking/cohere.md + - Linear Combination Reranker: reranking/linear_combination.md + - Cross Encoder Reranker: reranking/cross_encoder.md + - ColBERT Reranker: reranking/colbert.md + - OpenAI Reranker: reranking/openai.md + - Building Custom Rerankers: reranking/custom_reranker.md - Filtering: sql.md - Versioning & Reproducibility: notebooks/reproducibility.ipynb - Configuring Storage: guides/storage.md @@ -170,6 +168,14 @@ nav: - Overview: hybrid_search/hybrid_search.md - Comparing Rerankers: hybrid_search/eval.md - Airbnb financial data example: notebooks/hybrid_search.ipynb + - Reranking: + - Quickstart: reranking/index.md + - Cohere Reranker: reranking/cohere.md + - Linear Combination Reranker: reranking/linear_combination.md + - Cross Encoder Reranker: reranking/cross_encoder.md + - ColBERT Reranker: reranking/colbert.md + - OpenAI Reranker: reranking/openai.md + - Building Custom Rerankers: reranking/custom_reranker.md - Filtering: sql.md - Versioning & Reproducibility: notebooks/reproducibility.ipynb - Configuring Storage: guides/storage.md diff --git a/docs/requirements.txt b/docs/requirements.txt index 7f34591e..e5b8bbd3 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,5 +2,4 @@ mkdocs==1.5.3 mkdocs-jupyter==0.24.1 mkdocs-material==9.5.3 mkdocstrings[python]==0.20.0 -pydantic -mkdocs-ultralytics-plugin==0.0.44 \ No newline at end of file +pydantic \ No newline at end of file diff --git a/docs/src/reranking/cohere.md b/docs/src/reranking/cohere.md new file mode 100644 index 00000000..50b72e56 --- /dev/null +++ b/docs/src/reranking/cohere.md @@ -0,0 +1,75 @@ +# Cohere Reranker + +This re-ranker uses the [Cohere](https://cohere.ai/) API to rerank the search results. You can use this re-ranker by passing `CohereReranker()` to the `rerank()` method. Note that you'll either need to set the `COHERE_API_KEY` environment variable or pass the `api_key` argument to use this re-ranker. + + +!!! note + Supported Query Types: Hybrid, Vector, FTS + + +```python +import numpy +import lancedb +from lancedb.embeddings import get_registry +from lancedb.pydantic import LanceModel, Vector +from lancedb.rerankers import CohereReranker + +embedder = get_registry().get("sentence-transformers").create() +db = lancedb.connect("~/.lancedb") + +class Schema(LanceModel): + text: str = embedder.SourceField() + vector: Vector(embedder.ndims()) = embedder.VectorField() + +data = [ + {"text": "hello world"}, + {"text": "goodbye world"} + ] +tbl = db.create_table("test", schema=Schema, mode="overwrite") +tbl.add(data) +reranker = CohereReranker(api_key="key") + +# Run vector search with a reranker +result = tbl.search("hello").rerank(reranker=reranker).to_list() + +# Run FTS search with a reranker +result = tbl.search("hello", query_type="fts").rerank(reranker=reranker).to_list() + +# Run hybrid search with a reranker +tbl.create_fts_index("text", replace=True) +result = tbl.search("hello", query_type="hybrid").rerank(reranker=reranker).to_list() + +``` + +Accepted Arguments +---------------- +| Argument | Type | Default | Description | +| --- | --- | --- | --- | +| `model_name` | `str` | `"rerank-english-v2.0"` | The name of the reranker model to use. Available cohere models are: rerank-english-v2.0, rerank-multilingual-v2.0 | +| `column` | `str` | `"text"` | The name of the column to use as input to the cross encoder model. | +| `top_n` | `str` | `None` | The number of results to return. If None, will return all results. | +| `api_key` | `str` | `None` | The API key for the Cohere API. If not provided, the `COHERE_API_KEY` environment variable is used. | +| `return_score` | str | `"relevance"` | Options are "relevance" or "all". The type of score to return. If "relevance", will return only the `_relevance_score. If "all" is supported, will return relevance score along with the vector and/or fts scores depending on query type | + + + +## Supported Scores for each query type +You can specify the type of scores you want the reranker to return. The following are the supported scores for each query type: + +### Hybrid Search +|`return_score`| Status | Description | +| --- | --- | --- | +| `relevance` | ✅ Supported | Returns only have the `_relevance_score` column | +| `all` | ❌ Not Supported | Returns have vector(`_distance`) and FTS(`score`) along with Hybrid Search score(`_relevance_score`) | + +### Vector Search +|`return_score`| Status | Description | +| --- | --- | --- | +| `relevance` | ✅ Supported | Returns only have the `_relevance_score` column | +| `all` | ✅ Supported | Returns have vector(`_distance`) along with Hybrid Search score(`_relevance_score`) | + +### FTS Search +|`return_score`| Status | Description | +| --- | --- | --- | +| `relevance` | ✅ Supported | Returns only have the `_relevance_score` column | +| `all` | ✅ Supported | Returns have FTS(`score`) along with Hybrid Search score(`_relevance_score`) | \ No newline at end of file diff --git a/docs/src/reranking/colbert.md b/docs/src/reranking/colbert.md new file mode 100644 index 00000000..ace5a9a8 --- /dev/null +++ b/docs/src/reranking/colbert.md @@ -0,0 +1,71 @@ +# ColBERT Reranker + +This re-ranker uses ColBERT model to rerank the search results. You can use this re-ranker by passing `ColbertReranker()` to the `rerank()` method. +!!! note + Supported Query Types: Hybrid, Vector, FTS + + +```python +import numpy +import lancedb +from lancedb.embeddings import get_registry +from lancedb.pydantic import LanceModel, Vector +from lancedb.rerankers import ColbertReranker + +embedder = get_registry().get("sentence-transformers").create() +db = lancedb.connect("~/.lancedb") + +class Schema(LanceModel): + text: str = embedder.SourceField() + vector: Vector(embedder.ndims()) = embedder.VectorField() + +data = [ + {"text": "hello world"}, + {"text": "goodbye world"} + ] +tbl = db.create_table("test", schema=Schema, mode="overwrite") +tbl.add(data) +reranker = ColbertReranker() + +# Run vector search with a reranker +result = tbl.search("hello").rerank(reranker=reranker).to_list() + +# Run FTS search with a reranker +result = tbl.search("hello", query_type="fts").rerank(reranker=reranker).to_list() + +# Run hybrid search with a reranker +tbl.create_fts_index("text", replace=True) +result = tbl.search("hello", query_type="hybrid").rerank(reranker=reranker).to_list() + +``` + +Accepted Arguments +---------------- +| Argument | Type | Default | Description | +| --- | --- | --- | --- | +| `model_name` | `str` | `"colbert-ir/colbertv2.0"` | The name of the reranker model to use.| +| `column` | `str` | `"text"` | The name of the column to use as input to the cross encoder model. | +| `device` | `str` | `None` | The device to use for the cross encoder model. If None, will use "cuda" if available, otherwise "cpu". | +| `return_score` | str | `"relevance"` | Options are "relevance" or "all". The type of score to return. If "relevance", will return only the `_relevance_score. If "all" is supported, will return relevance score along with the vector and/or fts scores depending on query type | + + +## Supported Scores for each query type +You can specify the type of scores you want the reranker to return. The following are the supported scores for each query type: + +### Hybrid Search +|`return_score`| Status | Description | +| --- | --- | --- | +| `relevance` | ✅ Supported | Returns only have the `_relevance_score` column | +| `all` | ❌ Not Supported | Returns have vector(`_distance`) and FTS(`score`) along with Hybrid Search score(`_relevance_score`) | + +### Vector Search +|`return_score`| Status | Description | +| --- | --- | --- | +| `relevance` | ✅ Supported | Returns only have the `_relevance_score` column | +| `all` | ✅ Supported | Returns have vector(`_distance`) along with Hybrid Search score(`_relevance_score`) | + +### FTS Search +|`return_score`| Status | Description | +| --- | --- | --- | +| `relevance` | ✅ Supported | Returns only have the `_relevance_score` column | +| `all` | ✅ Supported | Returns have FTS(`score`) along with Hybrid Search score(`_relevance_score`) | \ No newline at end of file diff --git a/docs/src/reranking/cross_encoder.md b/docs/src/reranking/cross_encoder.md new file mode 100644 index 00000000..d6e3c54f --- /dev/null +++ b/docs/src/reranking/cross_encoder.md @@ -0,0 +1,70 @@ +# Cross Encoder Reranker + +This re-ranker uses Cross Encoder models from sentence-transformers to rerank the search results. You can use this re-ranker by passing `CrossEncoderReranker()` to the `rerank()` method. +!!! note + Supported Query Types: Hybrid, Vector, FTS + + +```python +import numpy +import lancedb +from lancedb.embeddings import get_registry +from lancedb.pydantic import LanceModel, Vector +from lancedb.rerankers import CrossEncoderReranker + +embedder = get_registry().get("sentence-transformers").create() +db = lancedb.connect("~/.lancedb") + +class Schema(LanceModel): + text: str = embedder.SourceField() + vector: Vector(embedder.ndims()) = embedder.VectorField() + +data = [ + {"text": "hello world"}, + {"text": "goodbye world"} + ] +tbl = db.create_table("test", schema=Schema, mode="overwrite") +tbl.add(data) +reranker = CrossEncoderReranker() + +# Run vector search with a reranker +result = tbl.search("hello").rerank(reranker=reranker).to_list() + +# Run FTS search with a reranker +result = tbl.search("hello", query_type="fts").rerank(reranker=reranker).to_list() + +# Run hybrid search with a reranker +tbl.create_fts_index("text", replace=True) +result = tbl.search("hello", query_type="hybrid").rerank(reranker=reranker).to_list() + +``` + +Accepted Arguments +---------------- +| Argument | Type | Default | Description | +| --- | --- | --- | --- | +| `model_name` | `str` | `""cross-encoder/ms-marco-TinyBERT-L-6"` | The name of the reranker model to use.| +| `column` | `str` | `"text"` | The name of the column to use as input to the cross encoder model. | +| `device` | `str` | `None` | The device to use for the cross encoder model. If None, will use "cuda" if available, otherwise "cpu". | +| `return_score` | str | `"relevance"` | Options are "relevance" or "all". The type of score to return. If "relevance", will return only the `_relevance_score. If "all" is supported, will return relevance score along with the vector and/or fts scores depending on query type | + +## Supported Scores for each query type +You can specify the type of scores you want the reranker to return. The following are the supported scores for each query type: + +### Hybrid Search +|`return_score`| Status | Description | +| --- | --- | --- | +| `relevance` | ✅ Supported | Returns only have the `_relevance_score` column | +| `all` | ❌ Not Supported | Returns have vector(`_distance`) and FTS(`score`) along with Hybrid Search score(`_relevance_score`) | + +### Vector Search +|`return_score`| Status | Description | +| --- | --- | --- | +| `relevance` | ✅ Supported | Returns only have the `_relevance_score` column | +| `all` | ✅ Supported | Returns have vector(`_distance`) along with Hybrid Search score(`_relevance_score`) | + +### FTS Search +|`return_score`| Status | Description | +| --- | --- | --- | +| `relevance` | ✅ Supported | Returns only have the `_relevance_score` column | +| `all` | ✅ Supported | Returns have FTS(`score`) along with Hybrid Search score(`_relevance_score`) | \ No newline at end of file diff --git a/docs/src/reranking/custom_reranker.md b/docs/src/reranking/custom_reranker.md new file mode 100644 index 00000000..0fc00eb4 --- /dev/null +++ b/docs/src/reranking/custom_reranker.md @@ -0,0 +1,88 @@ +## Building Custom Rerankers +You can build your own custom reranker by subclassing the `Reranker` class and implementing the `rerank_hybrid()` method. Optionally, you can also implement the `rerank_vector()` and `rerank_fts()` methods if you want to support reranking for vector and FTS search separately. +Here's an example of a custom reranker that combines the results of semantic and full-text search using a linear combination of the scores. + +The `Reranker` base interface comes with a `merge_results()` method that can be used to combine the results of semantic and full-text search. This is a vanilla merging algorithm that simply concatenates the results and removes the duplicates without taking the scores into consideration. It only keeps the first copy of the row encountered. This works well in cases that don't require the scores of semantic and full-text search to combine the results. If you want to use the scores or want to support `return_score="all"`, you'll need to implement your own merging algorithm. + +```python + +from lancedb.rerankers import Reranker +import pyarrow as pa + +class MyReranker(Reranker): + def __init__(self, param1, param2, ..., return_score="relevance"): + super().__init__(return_score) + self.param1 = param1 + self.param2 = param2 + + def rerank_hybrid(self, query: str, vector_results: pa.Table, fts_results: pa.Table): + # Use the built-in merging function + combined_result = self.merge_results(vector_results, fts_results) + + # Do something with the combined results + # ... + + # Return the combined results + return combined_result + + def rerank_vector(self, query: str, vector_results: pa.Table): + # Do something with the vector results + # ... + + # Return the vector results + return vector_results + + def rerank_fts(self, query: str, fts_results: pa.Table): + # Do something with the FTS results + # ... + + # Return the FTS results + return fts_results + +``` + +### Example of a Custom Reranker +For the sake of simplicity let's build custom reranker that just enchances the Cohere Reranker by accepting a filter query, and accept other CohereReranker params as kwags. + +```python + +from typing import List, Union +import pandas as pd +from lancedb.rerankers import CohereReranker + +class ModifiedCohereReranker(CohereReranker): + def __init__(self, filters: Union[str, List[str]], **kwargs): + super().__init__(**kwargs) + filters = filters if isinstance(filters, list) else [filters] + self.filters = filters + + def rerank_hybrid(self, query: str, vector_results: pa.Table, fts_results: pa.Table)-> pa.Table: + combined_result = super().rerank_hybrid(query, vector_results, fts_results) + df = combined_result.to_pandas() + for filter in self.filters: + df = df.query("not text.str.contains(@filter)") + + return pa.Table.from_pandas(df) + + def rerank_vector(self, query: str, vector_results: pa.Table)-> pa.Table: + vector_results = super().rerank_vector(query, vector_results) + df = vector_results.to_pandas() + for filter in self.filters: + df = df.query("not text.str.contains(@filter)") + + return pa.Table.from_pandas(df) + + def rerank_fts(self, query: str, fts_results: pa.Table)-> pa.Table: + fts_results = super().rerank_fts(query, fts_results) + df = fts_results.to_pandas() + for filter in self.filters: + df = df.query("not text.str.contains(@filter)") + + return pa.Table.from_pandas(df) + +``` + +!!! tip + The `vector_results` and `fts_results` are pyarrow tables. Lean more about pyarrow tables [here](https://arrow.apache.org/docs/python). It can be convered to other data types like pandas dataframe, pydict, pylist etc. + + For example, You can convert them to pandas dataframes using `to_pandas()` method and perform any operations you want. After you are done, you can convert the dataframe back to pyarrow table using `pa.Table.from_pandas()` method and return it. \ No newline at end of file diff --git a/docs/src/reranking/index.md b/docs/src/reranking/index.md new file mode 100644 index 00000000..20199524 --- /dev/null +++ b/docs/src/reranking/index.md @@ -0,0 +1,60 @@ +Reranking is the process of reordering a list of items based on some criteria. In the context of search, reranking is used to reorder the search results returned by a search engine based on some criteria. This can be useful when the initial ranking of the search results is not satisfactory or when the user has provided additional information that can be used to improve the ranking of the search results. + +LanceDB comes with some built-in rerankers. Some of the rerankers that are available in LanceDB are: + +| Reranker | Description | Supported Query Types | +| --- | --- | --- | +| `LinearCombinationReranker` | Reranks search results based on a linear combination of FTS and vector search scores | Hybrid | +| `CohereReranker` | Uses cohere Reranker API to rerank results | Vector, FTS, Hybrid | +| `CrossEncoderReranker` | Uses a cross-encoder model to rerank search results | Vector, FTS, Hybrid | +| `ColbertReranker` | Uses a colbert model to rerank search results | Vector, FTS, Hybrid | +| `OpenaiReranker`(Experimental) | Uses OpenAI's chat model to rerank search results | Vector, FTS, Hybrid | + + +## Using a Reranker +Using rerankers is optional for vector and FTS. However, for hybrid search, rerankers are required. To use a reranker, you need to create an instance of the reranker and pass it to the `rerank` method of the query builder. + +```python +import numpy +import lancedb +from lancedb.embeddings import get_registry +from lancedb.pydantic import LanceModel, Vector +from lancedb.rerankers import CohereReranker + +embedder = get_registry().get("sentence-transformers").create() +db = lancedb.connect("~/.lancedb") + +class Schema(LanceModel): + text: str = embedder.SourceField() + vector: Vector(embedder.ndims()) = embedder.VectorField() + +data = [ + {"text": "hello world"}, + {"text": "goodbye world"} + ] +tbl = db.create_table("test", data) +reranker = CohereReranker(api_key="your_api_key") + +# Run vector search with a reranker +result = tbl.query("hello").rerank(reranker).to_list() + +# Run FTS search with a reranker +result = tbl.query("hello", query_type="fts").rerank(reranker).to_list() + +# Run hybrid search with a reranker +tbl.create_fts_index("text") +result = tbl.query("hello", query_type="hybrid").rerank(reranker).to_list() +``` + +## Available Rerankers +LanceDB comes with some built-in rerankers. Here are some of the rerankers that are available in LanceDB: + +- [Cohere Reranker](./cohere.md) +- [Cross Encoder Reranker](./cross_encoder.md) +- [ColBERT Reranker](./colbert.md) +- [OpenAI Reranker](./openai.md) +- [Linear Combination Reranker](./linear_combination.md) + +## Creating Custom Rerankers + +LanceDB also you to create custom rerankers by extending the base `Reranker` class. The custom reranker should implement the `rerank` method that takes a list of search results and returns a reranked list of search results. This is covered in more detail in the [Creating Custom Rerankers](./custom_reranker.md) section. \ No newline at end of file diff --git a/docs/src/reranking/linear_combination.md b/docs/src/reranking/linear_combination.md new file mode 100644 index 00000000..4a27907c --- /dev/null +++ b/docs/src/reranking/linear_combination.md @@ -0,0 +1,52 @@ +# Linear Combination Reranker + +This is the default re-ranker used by LanceDB hybrid search. It combines the results of semantic and full-text search using a linear combination of the scores. The weights for the linear combination can be specified. It defaults to 0.7, i.e, 70% weight for semantic search and 30% weight for full-text search. + +!!! note + Supported Query Types: Hybrid + + +```python +import numpy +import lancedb +from lancedb.embeddings import get_registry +from lancedb.pydantic import LanceModel, Vector +from lancedb.rerankers import LinearCombinationReranker + +embedder = get_registry().get("sentence-transformers").create() +db = lancedb.connect("~/.lancedb") + +class Schema(LanceModel): + text: str = embedder.SourceField() + vector: Vector(embedder.ndims()) = embedder.VectorField() + +data = [ + {"text": "hello world"}, + {"text": "goodbye world"} + ] +tbl = db.create_table("test", schema=Schema, mode="overwrite") +tbl.add(data) +reranker = LinearCombinationReranker() + +# Run hybrid search with a reranker +tbl.create_fts_index("text", replace=True) +result = tbl.search("hello", query_type="hybrid").rerank(reranker=reranker).to_list() + +``` + +Accepted Arguments +---------------- +| Argument | Type | Default | Description | +| --- | --- | --- | --- | +| `weight` | `float` | `0.7` | The weight to use for the semantic search score. The weight for the full-text search score is `1 - weights`. | +| `return_score` | str | `"relevance"` | Options are "relevance" or "all". The type of score to return. If "relevance", will return only the `_relevance_score. If "all", will return all scores from the vector and FTS search along with the relevance score. | + + +## Supported Scores for each query type +You can specify the type of scores you want the reranker to return. The following are the supported scores for each query type: + +### Hybrid Search +|`return_score`| Status | Description | +| --- | --- | --- | +| `relevance` | ✅ Supported | Returns only have the `_relevance_score` column | +| `all` | ✅ Supported | Returns have vector(`_distance`) and FTS(`score`) along with Hybrid Search score(`_distance`) | \ No newline at end of file diff --git a/docs/src/reranking/openai.md b/docs/src/reranking/openai.md new file mode 100644 index 00000000..ec935910 --- /dev/null +++ b/docs/src/reranking/openai.md @@ -0,0 +1,73 @@ +# OpenAI Reranker (Experimental) + +This re-ranker uses OpenAI chat model to rerank the search results. You can use this re-ranker by passing `OpenAI()` to the `rerank()` method. +!!! note + Supported Query Types: Hybrid, Vector, FTS + +!!! warning + This re-ranker is experimental. OpenAI doesn't have a dedicated reranking model, so we are using the chat model for reranking. + +```python +import numpy +import lancedb +from lancedb.embeddings import get_registry +from lancedb.pydantic import LanceModel, Vector +from lancedb.rerankers import OpenaiReranker + +embedder = get_registry().get("sentence-transformers").create() +db = lancedb.connect("~/.lancedb") + +class Schema(LanceModel): + text: str = embedder.SourceField() + vector: Vector(embedder.ndims()) = embedder.VectorField() + +data = [ + {"text": "hello world"}, + {"text": "goodbye world"} + ] +tbl = db.create_table("test", schema=Schema, mode="overwrite") +tbl.add(data) +reranker = OpenaiReranker() + +# Run vector search with a reranker +result = tbl.search("hello").rerank(reranker=reranker).to_list() + +# Run FTS search with a reranker +result = tbl.search("hello", query_type="fts").rerank(reranker=reranker).to_list() + +# Run hybrid search with a reranker +tbl.create_fts_index("text", replace=True) +result = tbl.search("hello", query_type="hybrid").rerank(reranker=reranker).to_list() + +``` + +Accepted Arguments +---------------- +| Argument | Type | Default | Description | +| --- | --- | --- | --- | +| `model_name` | `str` | `"gpt-4-turbo-preview"` | The name of the reranker model to use.| +| `column` | `str` | `"text"` | The name of the column to use as input to the cross encoder model. | +| `return_score` | str | `"relevance"` | Options are "relevance" or "all". The type of score to return. If "relevance", will return only the `_relevance_score. If "all" is supported, will return relevance score along with the vector and/or fts scores depending on query type | +| `api_key` | str | `None` | The API key to use. If None, will use the OPENAI_API_KEY environment variable. + + +## Supported Scores for each query type +You can specify the type of scores you want the reranker to return. The following are the supported scores for each query type: + +### Hybrid Search +|`return_score`| Status | Description | +| --- | --- | --- | +| `relevance` | ✅ Supported | Returns only have the `_relevance_score` column | +| `all` | ❌ Not Supported | Returns have vector(`_distance`) and FTS(`score`) along with Hybrid Search score(`_relevance_score`) | + +### Vector Search +|`return_score`| Status | Description | +| --- | --- | --- | +| `relevance` | ✅ Supported | Returns only have the `_relevance_score` column | +| `all` | ✅ Supported | Returns have vector(`_distance`) along with Hybrid Search score(`_relevance_score`) | + +### FTS Search +|`return_score`| Status | Description | +| --- | --- | --- | +| `relevance` | ✅ Supported | Returns only have the `_relevance_score` column | +| `all` | ✅ Supported | Returns have FTS(`score`) along with Hybrid Search score(`_relevance_score`) | \ No newline at end of file diff --git a/docs/test/md_testing.py b/docs/test/md_testing.py index 305e3668..eef77bcd 100644 --- a/docs/test/md_testing.py +++ b/docs/test/md_testing.py @@ -15,6 +15,7 @@ excluded_globs = [ "../src/ann_indexes.md", "../src/basic.md", "../src/hybrid_search/hybrid_search.md", + "../src/reranking/*.md", ] python_prefix = "py"