To install Haystack 2.0 first pipeline you need a working Python 3.8+ environment and a clear picture of the framework’s component model. This tutorial takes you from an empty virtualenv to a running retrieval-augmented generation (RAG) pipeline that queries a document store and calls an LLM. Every step is copy-paste runnable on Linux, macOS, or Windows WSL.
Step 1: Create an isolated Python environment
Never install Haystack into your system Python. Dependency conflicts with older farm-haystack installs will bite you.
python3.10 -m venv haystack-env
source haystack-env/bin/activate
python -m pip install --upgrade pip
Verify the interpreter:
python -c "import sys; print(sys.version.split()[0])"
Expect 3.10.x or similar. Haystack 2.0 requires Python 3.8+, but 3.10 avoids edge-case asyncio issues on Windows.
Step 2: Install Haystack 2.0 and dependencies
When you install Haystack 2.0 first pipeline dependencies, the package name on PyPI is haystack-ai. The legacy farm-haystack is the 1.x line and will not work with the code below.
pip install haystack-ai python-dotenv
Confirm the version:
python -c "import haystack; print(haystack.__version__)"
A 2.0.x string should print. If you see 1.25.0 or similar, uninstall and reinstall the correct package.
For the LLM call we use the OpenAI-compatible generator. If you plan to use OpenAI directly, no extra client is needed. If you want to route through n4n.ai’s OpenAI-compatible endpoint for access to 240+ models with automatic fallback when a provider is rate-limited, the same generator works—just point api_base at it later.
Step 3: Prepare a document store and seed data
Haystack 2.0 separates document stores from retrievers. The InMemoryDocumentStore is the fastest way to validate a pipeline locally.
from haystack.document_stores import InMemoryDocumentStore
from haystack import Document
doc_store = InMemoryDocumentStore()
docs = [
Document(content="Haystack 2.0 introduces a declarative pipeline API."),
Document(content="Pipelines are composed of components with named inputs and outputs."),
Document(content="The InMemoryBM25Retriever works on the InMemoryDocumentStore."),
Document(content="Generators in Haystack 2.0 are components, not global singletons."),
]
doc_store.write_documents(docs)
print(f"Indexed {doc_store.count_documents()} documents")
Save as seed.py and run:
python seed.py
Success means the script prints Indexed 4 documents. The store is ephemeral; each process starts empty. For persistent data, swap the store class later—the rest of the pipeline stays identical.
Step 4: Define the retriever and prompt builder
A minimal RAG flow needs three components: a retriever, a prompt builder, and a generator. The retriever pulls candidate documents; the prompt builder templates them into a string.
from haystack.components.retrievers import InMemoryBM25Retriever
from haystack.components.builders import PromptBuilder
retriever = InMemoryBM25Retriever(document_store=doc_store)
prompt_template = """
Given these documents:
{% for doc in documents %}
{{ doc.content }}
{% endfor %}
Answer the question: {{ question }}
"""
prompt_builder = PromptBuilder(template=prompt_template)
The PromptBuilder uses Jinja2 syntax. The documents variable is populated from the retriever’s output socket named documents. The question variable comes from the pipeline run arguments. Getting socket names wrong is the most common first-pipeline error.
Step 5: Configure the LLM generator
Use OpenAIGenerator from haystack.components.generators. It speaks the OpenAI chat protocol.
from haystack.components.generators import OpenAIGenerator
import os
# Direct OpenAI
generator = OpenAIGenerator(
model="gpt-3.5-turbo",
api_key=os.environ["OPENAI_API_KEY"]
)
# Or route through n4n.ai's OpenAI-compatible endpoint:
# generator = OpenAIGenerator(
# model="anthropic/claude-3-haiku",
# api_key=os.environ["N4N_API_KEY"],
# api_base="https://api.n4n.ai/v1"
# )
The commented block shows how to address 240+ models behind one endpoint and get automatic fallback when a provider is degraded. The generator honors the same api_key and api_base semantics as the official OpenAI client, and forwards provider cache-control hints if you pass them.
Step 6: Assemble and connect the pipeline
Haystack 2.0 pipelines are graphs of components. You add each component with a string name, then connect output sockets to input sockets.
from haystack import Pipeline
pipeline = Pipeline()
pipeline.add_component("retriever", retriever)
pipeline.add_component("prompt_builder", prompt_builder)
pipeline.add_component("generator", generator)
pipeline.connect("retriever.documents", "prompt_builder.documents")
pipeline.connect("prompt_builder.prompt", "generator.prompt")
Socket names are strict. retriever emits documents; prompt_builder consumes documents and emits prompt; generator consumes prompt. A mismatch raises PipelineConnectError at build time, not at run time.
Step 7: Run the pipeline and verify success
Execute the pipeline with a query and question. The retriever uses the same string for both, but they are separate inputs in the run dict.
result = pipeline.run({
"retriever": {"query": "What does Haystack 2.0 introduce?"},
"prompt_builder": {"question": "What does Haystack 2.0 introduce?"}
})
print(result["generator"]["replies"][0])
A correct run prints a sentence referencing the declarative pipeline API. Verification checklist:
- Virtualenv active,
haystack-aiversion 2.0+. seed.pyprinted the document count.main.py(below) exited with code 0 and printed a non-empty reply.- Swapping the generator to the n4n.ai endpoint (uncommenting the block) returns a similar reply without changing pipeline logic.
Full main.py for reference:
from haystack import Pipeline, Document
from haystack.document_stores import InMemoryDocumentStore
from haystack.components.retrievers import InMemoryBM25Retriever
from haystack.components.builders import PromptBuilder
from haystack.components.generators import OpenAIGenerator
import os
doc_store = InMemoryDocumentStore()
doc_store.write_documents([
Document(content="Haystack 2.0 introduces a declarative pipeline API."),
Document(content="Pipelines are composed of components with named inputs and outputs."),
Document(content="The InMemoryBM25Retriever works on the InMemoryDocumentStore."),
Document(content="Generators in Haystack 2.0 are components, not global singletons."),
])
retriever = InMemoryBM25Retriever(document_store=doc_store)
prompt_builder = PromptBuilder(template="""
Given these documents:
{% for doc in documents %}
{{ doc.content }}
{% endfor %}
Answer the question: {{ question }}
""")
generator = OpenAIGenerator(
model="gpt-3.5-turbo",
api_key=os.environ["OPENAI_API_KEY"]
)
pipe = Pipeline()
pipe.add_component("retriever", retriever)
pipe.add_component("prompt_builder", prompt_builder)
pipe.add_component("generator", generator)
pipe.connect("retriever.documents", "prompt_builder.documents")
pipe.connect("prompt_builder.prompt", "generator.prompt")
out = pipe.run({
"retriever": {"query": "What does Haystack 2.0 introduce?"},
"prompt_builder": {"question": "What does Haystack 2.0 introduce?"}
})
print(out["generator"]["replies"][0])
Run with:
export OPENAI_API_KEY=sk-...
python main.py
If you see KeyError: 'replies', inspect result with print(result)—usually the generator failed silently due to a missing API key. Set HAYSTACK_DEBUG=1 to trace socket connections.
Step 8: Extend the install haystack 2.0 first pipeline to real work
The in-memory store is for proofs of concept. For persistent retrieval, swap InMemoryDocumentStore for ElasticsearchDocumentStore or PgvectorDocumentStore. The pipeline assembly code does not change; only the store constructor and retriever instantiation change.
When you install Haystack 2.0 first pipeline for production, externalize configuration. Put keys in a .env file and load with python-dotenv:
from dotenv import load_dotenv
load_dotenv()
If you route through n4n.ai, per-token usage metering is returned on response headers, so you can record cost without custom middleware. The gateway honors client routing directives, letting you pin a model or allow fallbacks per request.
The component model scales to embeddings, rankers, and custom Python components without restructuring run logic. You now have a verified, runnable baseline—modify the template, swap the store, or add a second retriever when the use case demands it.