c56bef871b
CodeQL / Analyze (python) (push) Has been cancelled
Update Platform Components Table / update (push) Has been cancelled
Docker image release / Build base image (push) Has been cancelled
Sync docs with Docusaurus / sync (push) Has been cancelled
Tests / Check if changed (push) Has been cancelled
Tests / format (push) Has been cancelled
Tests / check-imports (push) Has been cancelled
Tests / Unit / macos-latest (push) Has been cancelled
Tests / Unit / ubuntu-latest (push) Has been cancelled
Tests / Unit / windows-latest (push) Has been cancelled
Tests / mypy (push) Has been cancelled
Tests / Integration / ubuntu-latest (push) Has been cancelled
Tests / Integration / macos-latest (push) Has been cancelled
Tests / Integration / windows-latest (push) Has been cancelled
Tests / notify-slack-on-failure (push) Has been cancelled
Tests / Mark tests as completed (push) Has been cancelled
214 lines
8.1 KiB
Plaintext
214 lines
8.1 KiB
Plaintext
---
|
|
title: "MultiRetriever"
|
|
id: multiretriever
|
|
slug: "/multiretriever"
|
|
description: "Runs multiple text retrievers in parallel and combines their results using reciprocal rank fusion or deduplication."
|
|
---
|
|
|
|
# MultiRetriever
|
|
|
|
Runs multiple text retrievers in parallel and combines their results using reciprocal rank fusion or deduplication.
|
|
|
|
:::warning[Experimental]
|
|
|
|
`MultiRetriever` is experimental and may change or be removed in future releases without prior deprecation notice. An `ExperimentalWarning` is printed when initializing this component.
|
|
|
|
:::
|
|
|
|
<div className="key-value-table">
|
|
|
|
| | |
|
|
| --- | --- |
|
|
| **Most common position in a pipeline** | After query input, before a [`ChatPromptBuilder`](../builders/chatpromptbuilder.mdx) in RAG pipelines |
|
|
| **Mandatory init variables** | `retrievers`: A dictionary mapping names to text retrievers (implementing the `TextRetriever` protocol) |
|
|
| **Optional init variables** | `join_mode`: `"reciprocal_rank_fusion"` (default) or `"concatenate"` |
|
|
| **Mandatory run variables** | `query`: A query string |
|
|
| **Output variables** | `documents`: A merged list of retrieved documents |
|
|
| **API reference** | [Retrievers](/reference/retrievers-api) |
|
|
| **GitHub link** | https://github.com/deepset-ai/haystack/blob/main/haystack/components/retrievers/multi_retriever.py |
|
|
| **Package name** | `haystack-ai` |
|
|
|
|
</div>
|
|
|
|
## Overview
|
|
|
|
`MultiRetriever` composes any number of text retrievers into a single component. All retrievers are queried in parallel using a thread pool, and their results are merged before being returned.
|
|
|
|
The component:
|
|
- Queries all retrievers concurrently for better performance
|
|
- Merges results across retrievers using the configured `join_mode`
|
|
- Supports selectively enabling retrievers at runtime via `active_retrievers`
|
|
|
|
All retrievers passed to `MultiRetriever` must implement the `TextRetriever` protocol — their `run` method must accept a text `query`, `filters`, and `top_k`. Use [`TextEmbeddingRetriever`](textembeddingretriever.mdx) to wrap an embedding-based retriever so it can be used with this component.
|
|
|
|
### Join modes
|
|
|
|
The `join_mode` parameter controls how results from multiple retrievers are merged:
|
|
|
|
- **`reciprocal_rank_fusion`** (default): Assigns scores based on each document's rank across retrieval lists using the [Reciprocal Rank Fusion](https://plg.uwaterloo.ca/~gvcormac/cormacksigir09-rrf.pdf) algorithm. Documents appearing highly ranked in multiple lists receive higher scores. Results are deduplicated and returned in descending score order. This is the recommended mode when combining retrievers with incomparable scores, such as BM25 and embedding retrievers.
|
|
- **`concatenate`**: Combines all results into a single list and deduplicates.
|
|
|
|
## Usage
|
|
|
|
### On its own
|
|
|
|
This example sets up a `MultiRetriever` combining a BM25 retriever and an embedding-based retriever (wrapped with `TextEmbeddingRetriever`). Both are queried in parallel and the results are merged using reciprocal rank fusion.
|
|
|
|
The examples on this page use Sentence Transformers embedders that have moved to the `sentence-transformers-haystack` package. Install it to run the examples:
|
|
|
|
```shell
|
|
pip install sentence-transformers-haystack
|
|
```
|
|
|
|
```python
|
|
from haystack import Document
|
|
from haystack.document_stores.in_memory import InMemoryDocumentStore
|
|
from haystack.document_stores.types import DuplicatePolicy
|
|
from haystack_integrations.components.embedders.sentence_transformers import (
|
|
SentenceTransformersDocumentEmbedder,
|
|
SentenceTransformersTextEmbedder,
|
|
)
|
|
from haystack.components.retrievers import (
|
|
InMemoryBM25Retriever,
|
|
InMemoryEmbeddingRetriever,
|
|
)
|
|
from haystack.components.retrievers import MultiRetriever, TextEmbeddingRetriever
|
|
from haystack.components.writers import DocumentWriter
|
|
|
|
documents = [
|
|
Document(
|
|
content="Renewable energy is energy that is collected from renewable resources.",
|
|
),
|
|
Document(
|
|
content="Solar energy is a type of green energy that is harnessed from the sun.",
|
|
),
|
|
Document(
|
|
content="Wind energy is another type of green energy that is generated by wind turbines.",
|
|
),
|
|
]
|
|
|
|
doc_store = InMemoryDocumentStore()
|
|
doc_embedder = SentenceTransformersDocumentEmbedder(
|
|
model="sentence-transformers/all-MiniLM-L6-v2",
|
|
)
|
|
doc_writer = DocumentWriter(document_store=doc_store, policy=DuplicatePolicy.SKIP)
|
|
doc_writer.run(documents=doc_embedder.run(documents)["documents"])
|
|
|
|
retriever = MultiRetriever(
|
|
retrievers={
|
|
"bm25": InMemoryBM25Retriever(document_store=doc_store),
|
|
"embedding": TextEmbeddingRetriever(
|
|
retriever=InMemoryEmbeddingRetriever(document_store=doc_store),
|
|
text_embedder=SentenceTransformersTextEmbedder(
|
|
model="sentence-transformers/all-MiniLM-L6-v2",
|
|
),
|
|
),
|
|
},
|
|
top_k=3,
|
|
)
|
|
|
|
result = retriever.run(query="green energy sources")
|
|
for doc in result["documents"]:
|
|
print(doc.content)
|
|
```
|
|
|
|
### Selecting retrievers at runtime
|
|
|
|
Use the `active_retrievers` parameter to run only a subset of retrievers. Names must match the keys in the `retrievers` dictionary. Building on the example above:
|
|
|
|
```python
|
|
# Run only the BM25 retriever
|
|
result = retriever.run(query="green energy sources", active_retrievers=["bm25"])
|
|
for doc in result["documents"]:
|
|
print(doc.content)
|
|
```
|
|
|
|
### In a RAG pipeline
|
|
|
|
This RAG pipeline uses `MultiRetriever` to combine BM25 and embedding retrieval before generating an answer with an LLM.
|
|
|
|
```python
|
|
from haystack import Document, Pipeline
|
|
from haystack.document_stores.in_memory import InMemoryDocumentStore
|
|
from haystack.document_stores.types import DuplicatePolicy
|
|
from haystack.components.builders import ChatPromptBuilder
|
|
from haystack_integrations.components.embedders.sentence_transformers import (
|
|
SentenceTransformersDocumentEmbedder,
|
|
SentenceTransformersTextEmbedder,
|
|
)
|
|
from haystack.components.generators.chat import OpenAIChatGenerator
|
|
from haystack.components.retrievers import (
|
|
InMemoryBM25Retriever,
|
|
InMemoryEmbeddingRetriever,
|
|
)
|
|
from haystack.components.retrievers import MultiRetriever, TextEmbeddingRetriever
|
|
from haystack.components.writers import DocumentWriter
|
|
from haystack.dataclasses import ChatMessage
|
|
|
|
documents = [
|
|
Document(
|
|
content="Renewable energy is energy that is collected from renewable resources.",
|
|
),
|
|
Document(
|
|
content="Solar energy is a type of green energy that is harnessed from the sun.",
|
|
),
|
|
Document(
|
|
content="Wind energy is another type of green energy that is generated by wind turbines.",
|
|
),
|
|
]
|
|
|
|
doc_store = InMemoryDocumentStore()
|
|
doc_embedder = SentenceTransformersDocumentEmbedder(
|
|
model="sentence-transformers/all-MiniLM-L6-v2",
|
|
)
|
|
doc_writer = DocumentWriter(document_store=doc_store, policy=DuplicatePolicy.SKIP)
|
|
doc_writer.run(documents=doc_embedder.run(documents)["documents"])
|
|
|
|
prompt_template = [
|
|
ChatMessage.from_system(
|
|
"You are a helpful assistant that answers questions based on the provided documents.",
|
|
),
|
|
ChatMessage.from_user(
|
|
"Given these documents, answer the question.\nDocuments:\n"
|
|
"{% for doc in documents %}{{ doc.content }}\n{% endfor %}\n"
|
|
"Question: {{ question }}",
|
|
),
|
|
]
|
|
|
|
pipeline = Pipeline()
|
|
pipeline.add_component(
|
|
"retriever",
|
|
MultiRetriever(
|
|
retrievers={
|
|
"bm25": InMemoryBM25Retriever(document_store=doc_store),
|
|
"embedding": TextEmbeddingRetriever(
|
|
retriever=InMemoryEmbeddingRetriever(document_store=doc_store),
|
|
text_embedder=SentenceTransformersTextEmbedder(
|
|
model="sentence-transformers/all-MiniLM-L6-v2",
|
|
),
|
|
),
|
|
},
|
|
top_k=3,
|
|
),
|
|
)
|
|
pipeline.add_component(
|
|
"prompt_builder",
|
|
ChatPromptBuilder(
|
|
template=prompt_template,
|
|
required_variables=["documents", "question"],
|
|
),
|
|
)
|
|
pipeline.add_component("llm", OpenAIChatGenerator())
|
|
|
|
pipeline.connect("retriever.documents", "prompt_builder.documents")
|
|
pipeline.connect("prompt_builder.prompt", "llm.messages")
|
|
|
|
result = pipeline.run(
|
|
{
|
|
"retriever": {"query": "green energy sources"},
|
|
"prompt_builder": {"question": "What types of green energy exist?"},
|
|
},
|
|
)
|
|
print(result["llm"]["replies"][0].text)
|
|
```
|