Haystack applications combine document storage, retrieval, and language-model components in pipelines. Moving a Haystack 1.x application to 2.x requires changes to its Python dependencies and pipeline code, even when the documents and search queries stay the same.

The 1.x distribution is named farm-haystack, while 2.x uses haystack-ai. Both provide the haystack import, so they need separate Python environments. Haystack 1.x has reached end of life; an upgrade to 2.x should leave the old environment available until the replacement passes the application's checks.

A keyword-search pipeline makes the migration small enough to compare directly. The in-memory document store needs no external database or API key, and matching queries can reveal changes in retrieval before a larger application switches over. Persistent stores, model-backed readers, and generators need their own compatible 2.x components and integration packages.

Steps to migrate Haystack 1.x code to Haystack 2.x:

  1. Save a copy of the working 1.x project and its locked dependencies.

    Replacing dependencies can break the old application. A separate 2.x environment preserves the working interpreter for rollback. For persistent document stores, a data backup and a separate migration index are also needed before any reindexing or schema change.

  2. Identify the node registration and retrieval calls in the 1.x script.
    from haystack import Pipeline
    from haystack.document_stores import InMemoryDocumentStore
    from haystack.nodes import BM25Retriever
     
    store = InMemoryDocumentStore(use_bm25=True)
    store.write_documents([
        {"content": "Paris is the capital of France."},
        {"content": "Berlin is the capital of Germany."},
        {"content": "Rome is the capital of Italy."},
    ])
    pipeline = Pipeline()
    pipeline.add_node(BM25Retriever(document_store=store), "retriever", ["Query"])
    for query in ["France", "Germany"]:
        result = pipeline.run(query=query, params={"retriever": {"top_k": 1}})
        print(f"{query}: {result['documents'][0].content}")

    This is the starting 1.x keyword-search application. Its Query root supplies the query, params supplies the node's settings, and the result exposes documents at the top level.

  3. Run the 1.x script with the existing project's Python interpreter.
    $ python search_v1.py
    France: Paris is the capital of France.
    Germany: Berlin is the capital of Germany.

    The query results are the comparison point for the replacement. Other applications need reference queries against their own documents and expected answers.

  4. Create a separate Python environment for Haystack 2.x.
    $ python3 -m venv .venv-v2

    Python 3.10 or later and its venv support are required for Haystack 2.31. The explicit interpreter paths keep subsequent installations and runs in the new environment.

  5. Replace the farm-haystack entry in the copied project's requirements.txt with a 2.x dependency.
    haystack-ai>=2,<3

    Other application dependencies remain in the file. The upper bound matters because an unconstrained haystack-ai installation now selects 3.x. If the project uses pyproject.toml or a lockfile manager, the equivalent change belongs in that dependency workflow.

  6. Install the copied project's requirements in the 2.x environment.
    $ .venv-v2/bin/python -m pip install -r requirements.txt

    The empty environment avoids overlapping farm-haystack and haystack-ai files without uninstalling the working 1.x package. Additional integrations are needed only for components the migrated application actually uses.
    Related: How to install Haystack with pip

  7. Create migrated_pipeline.py in your text editor with the 2.x imports.
    from haystack import Document, Pipeline
    from haystack.components.retrievers.in_memory import InMemoryBM25Retriever
    from haystack.document_stores.in_memory import InMemoryDocumentStore

    The retriever moves from haystack.nodes to haystack.components, and the document-store import now names its in_memory module.

  8. Append the document-store setup after the imports in migrated_pipeline.py.
    store = InMemoryDocumentStore(bm25_algorithm="BM25Okapi")
    store.write_documents([
        Document(content="Paris is the capital of France."),
        Document(content="Berlin is the capital of Germany."),
        Document(content="Rome is the capital of Italy."),
    ])

    The documents become Document objects. Setting BM25Okapi retains the 1.x store's default algorithm; the 2.x default is BM25L. Matching the algorithm alone does not guarantee identical ranking on a larger corpus.

  9. Append the retriever registration after the store setup in migrated_pipeline.py.
    pipeline = Pipeline()
    pipeline.add_component("retriever", InMemoryBM25Retriever(document_store=store))

    add_component() replaces add_node() and takes the component name first. A single retriever needs no connection. For multiple components, connect(“source.output_name”, “target.input_name”) replaces the old inputs wiring; the socket names and types must match.

  10. Append the query loop at the end of migrated_pipeline.py.
    for query in ["France", "Germany"]:
        result = pipeline.run({"retriever": {"query": query, "top_k": 1}})
        print(f"{query}: {result['retriever']['documents'][0].content}")

    The 2.x run() call groups inputs under the component name instead of passing a top-level query and params. Output is also grouped under retriever, so the old result[“documents”] lookup must change.

  11. Confirm that the saved 2.x script returns the same documents for both queries.
    $ .venv-v2/bin/python migrated_pipeline.py
    France: Paris is the capital of France.
    Germany: Berlin is the capital of Germany.

    Both queries return the same document contents as the 1.x pipeline. An import error, missing result key, empty result, or different document needs investigation before switching the application to the 2.x environment. The old interpreter, source copy, and data backup provide the rollback path.

Share this guide