Haystack applications depend on both component interfaces and the data passed between them. Moving from Haystack 2 to 3 keeps the pipeline model, but removed classes, relocated integrations, and changed defaults can break an application that still installs successfully.

The Python package remains haystack-ai. Separate environments let the existing application stay on Haystack 2 while its replacement is tested on Haystack 3.4. The starting point is a working Haystack 2 application in an active Python 3.12 environment on Linux or macOS, with its pip dependencies declared in requirements.txt at the project root and a test suite or repeatable entry point to run afterwards.

An offline language-classification pipeline provides a repeatable comparison without an API key or model download. It exercises a component moved out of core, while the application's own imports, retrieval, generation, and stored-data behavior need their own import check and test run before a production switch.

Steps to migrate from Haystack 2 to 3:

  1. Record the dependencies from the working Haystack 2 environment.
    $ python -m pip freeze > requirements-haystack2.txt
  2. Create a migration copy of the application source, serialized pipelines, and document-store data.

    A dependency downgrade cannot reverse changed document IDs or writes to a document store. The migration copy must use disposable data until regression tests pass; the untouched original source, environment, data, and requirements-haystack2.txt are the rollback path.

  3. Change into the migration copy's project directory.

    Subsequent file edits and commands apply to this copy. It includes requirements.txt and the captured requirements-haystack2.txt.

  4. Create a separate environment for the Haystack 2 comparison.
    $ python3.12 -m venv .venv-haystack2-check

    Haystack 3.4.1 requires Python 3.11 or later, and python3.11 or python3.13 can replace python3.12 in both environments.
    Related: How to create a virtualenv for Haystack

  5. Activate the comparison environment.
    $ . .venv-haystack2-check/bin/activate
  6. Install the original dependencies and the baseline classifier's optional dependency in the comparison environment.
    $ python -m pip install -r requirements-haystack2.txt \
        "langdetect==1.0.9"

    The original environment stays untouched. The added langdetect package is needed by the Haystack 2 language classifier even when the application's own pipeline does not use it.

  7. Create migration_check.py in the application directory with these imports and input documents.
    migration_check.py
    import json
     
    from haystack import Document, Pipeline
    from haystack.components.classifiers import DocumentLanguageClassifier
     
    documents = [Document(
        content="Haystack pipelines connect search components.",
    )]
  8. Append the pipeline run and the two printed fields at the end of migration_check.py.
    migration_check.py
    pipeline = Pipeline()
    pipeline.add_component("language", DocumentLanguageClassifier())
    result = pipeline.run({"language": {"documents": documents}})
    document = result["language"]["documents"][0]
    print(json.dumps({"content": document.content,
                      "language": document.meta["language"]}))
  9. Save migration_check.py.
  10. Capture the pipeline result under Haystack 2.
    $ python migration_check.py > haystack2.json

    A FutureWarning about DocumentLanguageClassifier moving out of core is harmless on recent Haystack 2 releases. The JSON file contains the document content and detected language, and a traceback leaves no usable baseline in it.

  11. Create a separate Python environment for Haystack 3.
    $ python3.12 -m venv .venv-haystack3
  12. Activate the Haystack 3 environment in the current shell.
    $ . .venv-haystack3/bin/activate
  13. Open requirements.txt at the migration copy's project root in your text editor.
  14. Replace the existing haystack-ai requirement in requirements.txt with haystack-ai==3.4.1.

    A requirements.txt written by pip freeze also pins Haystack 2's own dependencies, and older pins such as anyio, certifi, and openai conflict with Haystack 3.4.1. A file like that keeps only the application's direct dependencies, because requirements-haystack2.txt already holds the full set for rollback. A short hand-written file keeps its other entries.

  15. Add langdetect-haystack==2.0.0 to requirements.txt if it has no entry for that integration.

    An existing langdetect-haystack entry instead becomes langdetect-haystack==2.0.0. Components moved out of core need both their new package and their new import path. Other integrations used by the application may also need version changes specified in the upstream migration guide. Projects using another dependency or lock tool need the equivalent declaration updated and their lock regenerated with that tool.

  16. Save requirements.txt.
  17. Install the complete migrated application dependency set.
    $ python -m pip install -r requirements.txt

    This installs the retained application requirements as well as Haystack and the relocated classifier. A ResolutionImpossible error names a retained pin that conflicts with Haystack 3.4.1. For an application packaged separately from its requirements file, its normal installation command is also required in this environment.

  18. Open migration_check.py in your text editor.
  19. Replace the DocumentLanguageClassifier import line with the Haystack 3 import.
    from haystack_integrations.components.classifiers.langdetect import DocumentLanguageClassifier
  20. Save the updated migration_check.py.
  21. Check that the candidate environment has compatible dependencies.
    $ python -m pip check
    No broken requirements found.
  22. Confirm the installed Haystack version in the active environment.
    $ python -c 'import haystack; print(haystack.__version__)'
    3.4.1
  23. Run the migrated sample into a separate result file.
    $ python migration_check.py > haystack3.json

    This imports the relocated classifier and runs it through a Haystack 3 pipeline. A traceback or a nonzero exit status is a failed migration check.

  24. Compare the migrated sample output with the Haystack 2 baseline.
    $ diff -s haystack2.json haystack3.json
    Files haystack2.json and haystack3.json are identical

    The two files are identical when the content and detected language match; different output makes diff return a nonzero status and print both lines. This match proves the Haystack 3 environment, not the application, and it does not test generated document IDs. Haystack 3 changes those IDs for documents with nonempty metadata, so a store that relies on them needs its documents reingested or their previous IDs passed explicitly.

  25. Search the application source for Haystack imports and removed names. Example output:
    $ grep -rnIE 'AsyncPipeline|haystack\.|haystack_experimental' \
        --exclude-dir='.venv*' .
    ./application.py:4:from haystack import AsyncPipeline, Document
    ./application.py:5:from haystack.components.builders import PromptBuilder
    ./application.py:6:from haystack.components.classifiers import DocumentLanguageClassifier
    ./application.py:7:from haystack.components.generators import OpenAIGenerator
    ./application.py:8:from haystack.components.retrievers.in_memory import InMemoryBM25Retriever
    ./application.py:9:from haystack.document_stores.in_memory import InMemoryDocumentStore
    ./application.py:26:    pipeline = AsyncPipeline()

    The search also reads serialized pipeline YAML, whose stored type paths use the same module names. Each matching line needs checking against the mappings in the next step, and modules with no mapping, such as haystack.components.builders here, stay as they are.

  26. Adapt the application's affected imports and API calls to the Haystack 3 interfaces.

    The upstream migration guide and complete migration mappings cover less common components and Agent changes. Only mappings used by the application need changes.

    Asynchronous pipelines. AsyncPipeline becomes Pipeline with run_async or run_async_generator for concurrent execution, because Pipeline.run is sequential and does not accept concurrency_limit.

    Legacy generators. OpenAIGenerator and AzureOpenAIGenerator become OpenAIChatGenerator and AzureOpenAIChatGenerator from haystack.components.generators.chat. The removed Hugging Face generators have integration replacements in the complete mappings. Replies are ChatMessage objects; text is in reply.text and metadata in reply.meta. The separate meta output socket and its connections are removed. A PromptBuilder string can still feed a ChatGenerator's messages input.

    Tool execution. The removed ToolInvoker loop belongs in Agent, while Tool.invoke handles an explicitly managed single call. Agent runtime prompts and state mappings also have breaking changes in the complete mappings.

    Sentence Transformers. The integration package is sentence-transformers-haystack; its component imports now begin with haystack_integrations rather than haystack.components.

    Template inputs. PromptBuilder and ChatPromptBuilder require all template variables by default. An explicit subset such as required_variables=["name"] leaves other variables optional.

    Serialized pipelines. Stored component type paths must match relocated classes as well as the Python imports. Custom classes need a matching trusted-module allowance, such as allowed_modules=["myapp.components.*"] for classes inside that package.

    Experimental imports. Imports from haystack_experimental need migration to their core equivalents where available, since the experimental package is no longer installed automatically and is unmaintained.

    Related: How to run an async pipeline in Haystack
    Related: How to save and load a pipeline in Haystack

    Pipeline files can refer to executable Python classes. An allowlist is not permission to load untrusted files, and unsafe=True bypasses the loading protection. A migration does not require that bypass.

  27. Import the application's entry module from the project root, replacing application with its module name.
    $ python -c 'import application'

    No output means the module and its top-level imports load on Haystack 3. An ImportError names an import that still needs adapting.

  28. Run the application's test suite against its copied data, using its normal test command in place of pytest. Example output:
    $ python -m pytest -q
    ...                                                                      [100%]
    3 passed in 7.88s

    The tests must exercise the application's real pipeline entry points. Relevant comparisons include retrieved document IDs, expected answers or answer criteria, tool results, serialized pipeline loading, and any deliberately optional prompt inputs. An application without a test suite runs its normal entry point against the copied data instead, and its output is compared with the original application's output for the same input. Passing tests establish the candidate environment's behavior before traffic or writes move from the original application.

Share this guide