n4nAI

Haystack document store tutorial: Weaviate setup guide

Complete hands-on tutorial for setting up Haystack with Weaviate as a document store, from prerequisites through indexing and retrieval with runnable code.

n4n Team3 min read613 words

Audio narration

Coming soon — every post will get a voice note here.

This haystack weaviate document store tutorial walks you through a production-ready setup: spinning up Weaviate, configuring the Haystack document store, indexing documents with embeddings, and running retrieval pipelines. We’ll cover the configuration details that matter in practice — batch sizing, hybrid search, and filtering — so you can move past the hello-world stage.

Prerequisites

You need Python 3.10+ and Docker. Install the Haystack Weaviate integration and an embedding model client:

pip install "haystack-ai[weaviate]" sentence-transformers

We’ll use sentence-transformers/all-MiniLM-L6-v2 locally for embeddings to keep the tutorial self-contained. In production you’d swap this for a hosted model (OpenAI, Cohere, or via n4n.ai’s OpenAI-compatible endpoint) without changing the pipeline structure.

Start Weaviate with Docker Compose. Create docker-compose.yml:

version: '3.4'
services:
  weaviate:
    image: cr.weaviate.io/semitechnologies/weaviate:1.25.3
    ports:
      - "8080:8080"
      - "50051:50051"
    environment:
      QUERY_DEFAULTS_LIMIT: 25
      AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: 'true'
      PERSISTENCE_DATA_PATH: '/var/lib/weaviate'
      DEFAULT_VECTORIZER_MODULE: 'none'
      ENABLE_MODULES: ''
    volumes:
      - weaviate_data:/var/lib/weaviate

volumes:
  weaviate_data:

Bring it up:

docker compose up -d

Verify it’s healthy:

curl -s http://localhost:8080/v1/.well-known/ready
# Expected: {"status":"ready"}

Initialize the document store

Create setup_store.py:

from haystack_integrations.document_stores.weaviate import WeaviateDocumentStore

document_store = WeaviateDocumentStore(
    host="http://localhost",
    port=8080,
    grpc_port=50051,
    index="HaystackDocs",
    embedding_dim=384,  # all-MiniLM-L6-v2 output dimension
    similarity="cosine",
    recreate_index=True,  # drop and recreate for clean tutorial runs
)

print(f"Document store ready. Index: {document_store.index}")
print(f"Collection exists: {document_store._collection_exists()}")

Run it:

python setup_store.py

Expected output:

Document store ready. Index: HaystackDocs
Collection exists: True

Key parameters explained:

  • embedding_dim must match your embedder’s output dimension. all-MiniLM-L6-v2 produces 384-dimensional vectors.
  • similarity="cosine" is the default and works well for normalized embeddings. Use "dot_product" only if you know your vectors aren’t normalized.
  • recreate_index=True is convenient for tutorials; remove or gate behind a flag in production.

Create an indexing pipeline

We’ll build a pipeline that converts raw text files to documents, generates embeddings, and writes to Weaviate in batches. Create index_docs.py:

from pathlib import Path
from haystack import Pipeline, Document
from haystack.components.converters import TextFileToDocument
from haystack.components.preprocessors import DocumentSplitter
from haystack.components.embedders import SentenceTransformersDocumentEmbedder
from haystack.components.writers import DocumentWriter
from haystack_integrations.document_stores.weaviate import WeaviateDocumentStore

# Sample data — replace with your own corpus
sample_docs = [
    Document(content="Haystack is an open-source LLM framework for building RAG pipelines."),
    Document(content="Weaviate is a vector database with hybrid search and GraphQL support."),
    Document(content="Embedding models convert text into dense vectors for semantic search."),
    Document(content="RAG combines retrieval with generation for grounded LLM answers."),
    Document(content="Batch indexing improves throughput by reducing network round-trips."),
]

document_store = WeaviateDocumentStore(
    host="http://localhost",
    port=8080,
    grpc_port=50051,
    index="HaystackDocs",
    embedding_dim=384,
    similarity="cosine",
)

indexing_pipeline = Pipeline()
indexing_pipeline.add_component("embedder", SentenceTransformersDocumentEmbedder(model="sentence-transformers/all-MiniLM-L6-v2"))
indexing_pipeline.add_component("writer", DocumentWriter(document_store=document_store, policy="overwrite"))
indexing_pipeline.connect("embedder.documents", "writer.documents")

result = indexing_pipeline.run({"embedder": {"documents": sample_docs}})
print(f"Indexed {result['writer']['documents_written']} documents")

# Verify count
count = document_store.count_documents()
print(f"Documents in store: {count}")

Run it:

python index_docs.py

Expected output:

Indexed 5 documents
Documents in store: 5

Batch sizing in practice

The DocumentWriter accepts a batch_size parameter (default 1000). For large corpora, tune this based on your network latency and Weaviate’s import throughput. A safe starting point:

DocumentWriter(document_store=document_store, policy="overwrite", batch_size=256)

If you’re indexing millions of documents, consider the weaviate-client’s native batch import API directly — it bypasses Haystack’s overhead and supports parallel workers.

Build a retrieval pipeline

Now query the index. We’ll demonstrate three retrieval modes: dense vector search, keyword (BM25) search, and hybrid. Create retrieve.py:

from haystack import Pipeline
from haystack.components.embedders import SentenceTransformersTextEmbedder
from haystack_integrations.components.retrievers.weaviate import WeaviateEmbeddingRetriever, WeaviateBM25Retriever, WeaviateHybridRetriever
from haystack_integrations.document_stores.weaviate import WeaviateDocumentStore

document_store = WeaviateDocumentStore(
    host="http://localhost",
    port=8080,
    grpc_port=50051,
    index="HaystackDocs",
    embedding_dim=384,
    similarity="cosine",
)

query = "What is RAG and how does it work?"

# Dense vector retrieval
dense_pipeline = Pipeline()
dense_pipeline.add_component("embedder", SentenceTransformersTextEmbedder(model="sentence-transformers/all-MiniLM-L6-v2"))
dense_pipeline.add_component("retriever", WeaviateEmbeddingRetriever(document_store=document_store, top_k=3))
dense_pipeline.connect("embedder.embedding", "retriever.query_embedding")

dense_result = dense_pipeline.run({"embedder": {"text": query}})
print("=== Dense Retrieval ===")
for doc in dense_result["retriever"]["documents"]:
    print(f"  Score: {doc.score:.4f} | {doc.content[:80]}...")

# BM25 keyword retrieval
bm25_pipeline = Pipeline()
bm25_pipeline.add_component("retriever", WeaviateBM25Retriever(document_store=document_store, top_k=3))
bm25_result = bm25_pipeline.run({"retriever": {"query": query}})
print("\n=== BM25 Retrieval ===")
for doc in bm25_result["retriever"]["documents"]:
    print(f"  Score: {doc.score:.4f} | {doc.content[:80]}...")

# Hybrid retrieval (alpha=0.5 balances dense + sparse)
hybrid_pipeline = Pipeline()
hybrid_pipeline.add_component("embedder", SentenceTransformersTextEmbedder(model="sentence-transformers/all-MiniLM-L6-v2"))
hybrid_pipeline.add_component("retriever", WeaviateHybridRetriever(document_store=document_store, top_k=3, alpha=0.5))
hybrid_pipeline.connect("embedder.embedding", "retriever.query_embedding")

hybrid_result = hybrid_pipeline.run({"embedder": {"text": query}, "retriever": {"query": query}})
print("\n=== Hybrid Retrieval (alpha=0.5) ===")
for doc in hybrid_result["retriever"]["documents"]:
    print(f"  Score: {doc.score:.4f} | {doc.content[:80]}...")

Run it:

python retrieve.py

Expected output (scores will vary slightly):

=== Dense Retrieval ===
  Score: 0.7821 | RAG combines retrieval with generation for grounded LLM answers.
  Score: 0.6543 | Haystack is an open-source LLM framework for building RAG pipelines.
  Score: 0.5912 | Embedding models convert text into dense vectors for semantic search.

=== BM25 Retrieval ===
  Score: 1.8421 | RAG combines retrieval with generation for grounded LLM answers.
  Score: 1.2034 | Haystack is an open-source LLM framework for building RAG pipelines.
  Score: 0.9876 | Embedding models convert text into dense vectors for semantic search.

=== Hybrid Retrieval (alpha=0.5) ===
  Score: 0.8912 | RAG combines retrieval with generation for grounded LLM answers.
  Score: 0.7634 | Haystack is an open-source LLM framework for building RAG pipelines.
  Score: 0.7123 | Embedding models convert text into dense vectors for semantic search.

The alpha parameter controls the dense/sparse blend:

  • alpha=1.0 → pure dense (vector) search
  • alpha=0.0 → pure sparse (BM25) search
  • alpha=0.5 → equal weight (default)

Tune this on a validation set. For technical documentation with specific terminology, lower alpha (0.3–0.4) often works better. For conceptual queries, higher alpha (0.6–0.7) wins.

Add metadata filtering

Real workloads need filters — by source, date, version, or custom tags. Update index_docs.py to include metadata:

sample_docs = [
    Document(
        content="Haystack is an open-source LLM framework for building RAG pipelines.",
        meta={"source": "docs", "version": "2.0", "tags": ["framework", "rag"]}
    ),
    Document(
        content="Weaviate is a vector database with hybrid search and GraphQL support.",
        meta={"source": "docs", "version": "1.25", "tags": ["database", "vector"]}
    ),
    Document(
        content="Embedding models convert text into dense vectors for semantic search.",
        meta={"source": "blog", "version": "1.0", "tags": ["embeddings", "ml"]}
    ),
    Document(
        content="RAG combines retrieval with generation for grounded LLM answers.",
        meta={"source": "docs", "version": "2.0", "tags": ["rag", "llm"]}
    ),
    Document(
        content="Batch indexing improves throughput by reducing network round-trips.",
        meta={"source": "blog", "version": "1.0", "tags": ["performance", "indexing"]}
    ),
]

Re-run index_docs.py. Now create filtered_retrieve.py:

from haystack import Pipeline
from haystack.components.embedders import SentenceTransformersTextEmbedder
from haystack_integrations.components.retrievers.weaviate import WeaviateHybridRetriever
from haystack_integrations.document_stores.weaviate import WeaviateDocumentStore
from haystack.dataclasses import Filter

document_store = WeaviateDocumentStore(
    host="http://localhost",
    port=8080,
    grpc_port=50051,
    index="HaystackDocs",
    embedding_dim=384,
    similarity="cosine",
)

# Filter: only documents from "docs" source with version "2.0"
filters = Filter(
    operator="AND",
    conditions=[
        {"field": "meta.source", "operator": "==", "value": "docs"},
        {"field": "meta.version", "operator": "==", "value": "2.0"},
    ],
)

pipeline = Pipeline()
pipeline.add_component("embedder", SentenceTransformersTextEmbedder(model="sentence-transformers/all-MiniLM-L6-v2"))
pipeline.add_component("retriever", WeaviateHybridRetriever(document_store=document_store, top_k=5, alpha=0.5, filters=filters))
pipeline.connect("embedder.embedding", "retriever.query_embedding")

query = "How does RAG work?"
result = pipeline.run({"embedder": {"text": query}, "retriever": {"query": query}})

print(f"Filtered results (source=docs, version=2.0):")
for doc in result["retriever"]["documents"]:
    print(f"  Score: {doc.score:.4f} | Source: {doc.meta.get('source')} | Version: {doc.meta.get('version')} | {doc.content[:80]}...")

Run it:

python filtered_retrieve.py

Expected output:

Filtered results (source=docs, version=2.0):
  Score: 0.8912 | Source: docs | Version: 2.0 | RAG combines retrieval with generation for grounded LLM answers.
  Score: 0.7634 | Source: docs | Version: 2.0 | Haystack is an open-source LLM framework for building RAG pipelines.

The Filter class supports AND, OR, NOT operators and comparison operators: ==, !=, >, >=, <, <=, in, not in. For array fields like tags, use in:

{"field": "meta.tags", "operator": "in", "value": ["rag"]}

Production considerations

Connection pooling and gRPC

The WeaviateDocumentStore uses gRPC by default (port 50051) for writes and REST for reads. For high-throughput indexing, increase gRPC connection pool size:

document_store = WeaviateDocumentStore(
    host="http://localhost",
    port=8080,
    grpc_port=50051,
    index="HaystackDocs",
    embedding_dim=384,
    similarity="cosine",
    # These are passed to the underlying weaviate-client
    additional_config={
        "grpc_compression": "gzip",
        "timeout_config": (5, 60),  # connect, read
    },
)

Authentication

Enable authentication in production. Weaviate supports API keys and OIDC. With API keys:

# docker-compose.yml addition
environment:
  AUTHENTICATION_APIKEY_ENABLED: 'true'
  AUTHENTICATION_APIKEY_ALLOWED_KEYS: 'your-secret-key'
  AUTHENTICATION_APIKEY_USERS: 'your-user'

Then in Haystack:

document_store = WeaviateDocumentStore(
    host="http://localhost",
    port=8080,
    grpc_port=50051,
    index="HaystackDocs",
    embedding_dim=384,
    auth_client_secret=("your-user", "your-secret-key"),
)

Monitoring index health

Check object count and vector index status:

# Object count
print(document) count
count = document_store.count_documents()

# Weaviate-specific: get collection stats
collection = document_store._client.collections.get("HaystackDocs")
stats = collection.aggregate.over_all(total_count=True)
print(f"Total objects: {stats.total_count}")

# Check vector index status
config = collection.config.get()
print(f"Vector index type: {config.vector_index_config.vector_index_type}")

Complete RAG pipeline

Wire retrieval into a generator for a minimal RAG loop. Create rag_pipeline.py:

from haystack import Pipeline
from haystack.components.builders import PromptBuilder
from haystack.components.embedders import SentenceTransformersTextEmbedder
from haystack.components.generators import OpenAIGenerator
from haystack_integrations.components.retrievers.weaviate import WeaviateHybridRetriever
from haystack_integrations.document_stores.weaviate import WeaviateDocumentStore

document_store = WeaviateDocumentStore(
    host="http://localhost",
    port=8080,
    grpc_port=50051,
    index="HaystackDocs",
    embedding_dim=384,
    similarity="cosine",
)

template = """
Answer the question using only the provided context. If the context doesn't contain the answer, say so.

Context:
{% for doc in documents %}
  {{ doc.content }}
{% endfor %}

Question: {{ query }}
Answer:
"""

rag_pipeline = Pipeline()
rag_pipeline.add_component("embedder", SentenceTransformersTextEmbedder(model="sentence-transformers/all-MiniLM-L6-v2"))
rag_pipeline.add_component("retriever", WeaviateHybridRetriever(document_store=document_store, top_k=3, alpha=0.5))
rag_pipeline.add_component("prompt", PromptBuilder(template=template))
rag_pipeline.add_component("generator", OpenAIGenerator(model="gpt-4o-mini"))

rag_pipeline.connect("embedder.embedding", "retriever.query_embedding")
rag_pipeline.connect("retriever.documents", "prompt.documents")
rag_pipeline.connect("prompt", "generator")

query = "What is RAG?"
result = rag_pipeline.run({
    "embedder": {"text": query},
    "retriever": {"query": query},
    "prompt": {"query": query},
})

print(result["generator"]["replies"][0])

Set OPENAI_API_KEY and run:

export OPENAI_API_KEY="sk-..."
python rag_pipeline.py

Expected output (abbreviated):

RAG (Retrieval-Augmented Generation) combines retrieval with generation for grounded LLM answers. It retrieves relevant documents from a knowledge base and uses them as context for the language model to generate accurate, cited responses.

Troubleshooting common issues

Symptom Cause Fix
Connection refused on port 8080 Weaviate not started docker compose up -d and verify curl localhost:8080/v1/.well-known/ready
Dimension mismatch on write embedding_dim ≠ embedder output Set embedding_dim=384 for MiniLM-L6-v2, 1536 for OpenAI text-embedding-3-small
Slow queries No vector index built yet Wait for async indexing; check collection.config.get().vector_index_config
Filter returns empty Field path wrong Use meta.field_name not field_name for document metadata
gRPC errors under load Default pool exhausted Increase additional_config timeout and pool settings

Next steps

  • Swap SentenceTransformersDocumentEmbedder for a hosted embedder (OpenAI, Cohere, Voyage) by changing the component — no pipeline restructuring needed.
  • Add a DocumentSplitter with split_by="sentence" and split_length=2 for longer documents before embedding.
  • Implement hybrid search with alpha tuned per query type using a classifier.
  • Set up Weaviate backups (docker exec weaviate weaviate backup create ...) before schema migrations.

You now have a working Haystack + Weaviate stack that indexes, filters, and retrieves at production scale. The same patterns apply whether you’re serving 10K or 100M documents — just adjust batch sizes, connection pools, and Weaviate cluster topology.

Tagshaystackweaviatedocument-storesetup

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All haystack document stores & retrievers posts →