Qdrant Alloy is a high-performance, configurable hybrid search pipeline that fuses dense, sparse, and late-interaction embeddings into a single powerful retrieval engine.
Built on Qdrant and FastEmbed, Alloy implements a robust "Retrieve & Rerank" strategy in a single unified workflow:
- Dense Embeddings: Capture semantic meaning and broad conceptual matches.
- Sparse Embeddings (SPLADE/BM25): Ensure precise keyword and lexical matching.
- Late Interaction (ColBERT): Perform fine-grained token-level reranking for superior relevance.
- 🔗 Tri-Vector Architecture: Seamlessly combines three vector strategies for state-of-the-art search quality.
- ⚙️ Fully Configurable: Swap embedding models, distance metrics, and vector parameters via simple Python config or YAML.
- 🏢 Multi-Tenant Native: Built-in support for partition-based multi-tenancy, perfect for SaaS applications.
- 🚀 Production Ready: Supports batch processing, sharding, and replication factors out of the box.
pip install qdrant-alloyRequires Python 3.11+
import uuid
from qdrant_client import QdrantClient, models
from fastembed import TextEmbedding, SparseTextEmbedding, LateInteractionTextEmbedding
from qdrant_client.models import Distance, VectorParams, SparseVectorParams, KeywordIndexParams
from qdrant_alloy import AlloyConfig, Alloy
# 1. Initialize Qdrant client
# Use ":memory:" for testing or a URL for production (e.g., Qdrant Cloud)
client = QdrantClient(":memory:")
# 2. Configure embedding models
# Dense (Semantic)
text_model = TextEmbedding("BAAI/bge-small-en-v1.5")
dense_params = VectorParams(size=text_model.dimensions, distance=Distance.COSINE)
# Sparse (Keyword)
sparse_model = SparseTextEmbedding("Qdrant/bm25")
sparse_params = SparseVectorParams(modifier=models.Modifier.IDF)
# Late Interaction (Reranking)
late_interaction_model = LateInteractionTextEmbedding("answerdotai/answerai-colbert-small-v1")
late_interaction_params = VectorParams(
size=late_interaction_model.dimensions,
distance=Distance.COSINE
)
# 3. Create pipeline configuration
pipeline_config = AlloyConfig(
text_embedding_config=(text_model, dense_params),
sparse_embedding_config=(sparse_model, sparse_params),
late_interaction_text_embedding_config=(late_interaction_model, late_interaction_params),
# Optional: Multi-tenant settings
partition_config=("tenant_id", KeywordIndexParams(minWordLength=1, maxWordLength=100)),
multi_tenant=True,
replication_factor=1,
shard_number=1,
)
# 4. Initialize the pipeline
pipeline = Alloy(
qdrant_client=client,
collection_name="documents",
alloy_config=pipeline_config,
)
# 5. Index documents
documents = [
"Alloy fuses multiple search technologies into a stronger whole.",
"Qdrant is a vector database for production-ready vector search.",
"Late interaction models like ColBERT provide superior reranking capabilities."
]
# Metadata payloads (required for multi-tenant setups)
payloads = [
{"tenant_id": "acme_corp", "category": "tech"},
{"tenant_id": "acme_corp", "category": "database"},
{"tenant_id": "acme_corp", "category": "ml"}
]
ids = [uuid.uuid4() for _ in range(len(documents))]
pipeline.insert_documents(documents=documents, payloads=payloads, document_ids=ids)
# 6. Search
# This performs dense+sparse retrieval followed by ColBERT reranking
results = pipeline.search(
query="How does hybrid search work?",
top_k=3,
partition_filter="acme_corp"
)
# 7. Process results
for result in results:
print(f"Score: {result.score:.4f}")
print(f"Document: {result.payload['document']}")
print("-" * 30)Alloy abstracts away the complexity of managing multiple embedding models and Qdrant query construction. When you run a search, Alloy performs a two-stage process:
Prefetch (Retrieval): The query is embedded into both Dense and Sparse vectors. Alloy queries Qdrant to retrieve the top candidates using these vectors. This casts a wide net (semantic) while preserving specific keywords (lexical).
Query (Rescoring): The retrieved candidates are immediately rescored using the Late Interaction (ColBERT) embeddings. This step examines the fine-grained interaction between query tokens and document tokens, surfacing the most relevant results to the top.
Alloy is model-agnostic and works with any model supported by fastembed:
- Dense: TextEmbedding (e.g., BAAI/bge-small-en-v1.5, intfloat/multilingual-e5-large)
- Sparse: SparseTextEmbedding (e.g., Qdrant/bm25, prithivida/Splade_PP_en_v1)
- Late Interaction: LateInteractionTextEmbedding (e.g., answerdotai/answerai-colbert-small-v1)
For production deployments, define your pipeline in YAML:
# config.yml
text_embedding:
model_name: "BAAI/bge-small-en-v1.5"
vector_params:
size: 384
distance: "Cosine"
sparse_embedding:
model_name: "Qdrant/bm25"
vector_params:
modifier: "IDF"
late_interaction_text_embedding:
model_name: "answerdotai/answerai-colbert-small-v1"
vector_params:
size: 128
distance: "Cosine"
# Optional multi-tenant settings
partition_config:
field_name: "tenant_id"
index_params:
type: "keyword"
minWordLength: 1
maxWordLength: 100
multi_tenant: true
replication_factor: 2
shard_number: 3Then load it in Python:
from qdrant_client import QdrantClient
from qdrant_alloy import create_alloy_from_yaml
client = QdrantClient("localhost")
config = create_alloy_from_yaml("config.yaml")
pipeline = Alloy(client, "my_collection", config)To contribute to Qdrant Alloy:
# Clone the repository
git clone https://github.com/DataParthenon/qdrant-alloy.git
cd qdrant-alloy
# Install development dependencies using uv
uv venv
uv pip install -e ".[dev]"
# Run tests
pytestThis project is licensed under the MIT License - see the LICENSE file for details.