Repository: deepset-ai/haystack
Pull request: feat: add `document_store` to all `BaseRetriever.retrieve()` and `BaseRetriever.retrieve_batch()` implementations
### Related Issues
- fixes #3348

### Proposed Changes:
- Makes the document store optional in all Retrievers' `__init__` methods
- Adds a `document_store` optional parameter to all the Retrievers' `retrieve` and `retrieve_batch` methods.

### How did you test it?
- Existing tests

### Notes for the reviewer
n/a

### Checklist
- [x] I have read the [contributors guidelines](https://github.com/deepset-ai/haystack/blob/main/CONTRIBUTING.md) and the [code of conduct](https://github.com/deepset-ai/haystack/blob/main/code_of_conduct.txt)
- [x] I have updated the related issue with new insights and changes
- [ ] I added tests that demonstrate the correct behavior of the change
- [x] I've used the [conventional commit convention](https://www.conventionalcommits.org/en/v1.0.0/) for my PR title
- [x] I documented my code
- [x] I ran [pre-commit hooks](https://github.com/deepset-ai/haystack/blob/main/CONTRIBUTING.md#installation) and fixed any issue


Problem statement:
Move `document_store` argument to `retrieve` method instead of the Retriever's constructor
**Is your feature request related to a problem? Please describe.**

Retrievers' constructors require the `document_store` parameter to be created. While this may be ok for sparse retrievers, DPR, for example, requires loading models. And then, when I want to use the same retriever, but with a different document store, I have to create a new instance of it making all the models load again, which doesn't make any sense. 

At the same time constructors mostly just emit some warnings connected with the document store, and no other significant operations are executed.

**Describe the solution you'd like**

The best and most intuitive would be to move the `document_store` argument to the methods, which are directly operating on it (`retrieve`, `retrieve_batch`, etc). This way model operates on the data and does not depend on them.

To preserve the backward compatibility `document_store` argument can stay in the `__init__`, but each method will have then the `document_store=None` argument so that we can fall back to the constructor's object if no other is passed.

**Describe alternatives you've considered**

Another way would be to add the method to substitute the `DocumentStore` object. It can be done by substituting the attribute value, but the self-preserving instinct would tell any programmer not to change any object's internal values.

**Additional Context**

I have asked about the intentions of putting the `document_store` argument in the `__init__` on Haystack's Discord. And @masci  advised me to address this idea here as an issue.

Hints from issue discussion:
Hi @Goader thank you for making this suggestion and for describing it in so much detail. 👍 We thought about improving the current coupling of retriever and document store in our team too. What do you think about the suggestion here @ZanSara @masci ?
@julian-risch I think the solution proposed here could be implemented right ahead: users would get immediate benefit but the overall logic would stay the same, without affecting the long-term plan of revisiting the retriever/docstore design
I agree that the current design doesn't make much sense and I'd really like to see this change... but I'm afraid it will break `Pipeline`. Unfortunately, the document store object right now is not passed along: `Pipeline` assumes that nodes that require access to the docstore will have their own internal way to access it, that's why Retrievers take it in their `__init__`. Making `retrieve()` take a `document_store` param would simply make Pipeline crash right now.

If we want to start from this angle, then we need also to make Pipeline aware that some nodes might require a document store and, if their `run` method asks for it, where to find it. It would mean that is no more Retrievers that takes a document store in their init, but `Pipeline`.

This is a _big_ shift unfortunately, but a very welcome one if you're up for the challenge. @masci do you have opinions here? Is it something we can target or does it sound too big? It is surely a delicate change. I'm of course open to other ideas if you have any. This is a tangled problem that can be approached from several angles right now.
Ok, after a review of the code I realized that this is doable if we don't want to affect Pipelines at all. I'll go ahead and give it a try to see if my idea is correct. I'll link a PR soon.

Commit message: revert accidental test changes

Patch to review:
diff --git a/haystack/json-schemas/haystack-pipeline-1.10.0rc0.schema.json b/haystack/json-schemas/haystack-pipeline-1.10.0rc0.schema.json
index cdf8de2789..60affda944 100644
--- a/haystack/json-schemas/haystack-pipeline-1.10.0rc0.schema.json
+++ b/haystack/json-schemas/haystack-pipeline-1.10.0rc0.schema.json
@@ -227,8 +227,16 @@
               "title": "Custom Query"
             },
             "document_store": {
-              "title": "Document Store",
-              "type": "string"
+              "anyOf": [
+                {
+                  "type": "string"
+                },
+                {
+                  "type": "null"
+                }
+              ],
+              "default": null,
+              "title": "Document Store"
             },
             "scale_score": {
               "default": true,
@@ -241,9 +249,6 @@
               "type": "integer"
             }
           },
-          "required": [
-            "document_store"
-          ],
           "title": "Parameters",
           "type": "object"
         },
@@ -510,8 +515,16 @@
               "title": "Devices"
             },
             "document_store": {
-              "title": "Document Store",
-              "type": "string"
+              "anyOf": [
+                {
+                  "type": "string"
+                },
+                {
+                  "type": "null"
+                }
+              ],
+              "default": null,
+              "title": "Document Store"
             },
             "embed_title": {
               "default": true,
@@ -615,9 +628,6 @@
               "type": "boolean"
             }
           },
-          "required": [
-            "document_store"
-          ],
           "title": "Parameters",
           "type": "object"
         },
@@ -1092,8 +1102,16 @@
               "title": "Custom Query"
             },
             "document_store": {
-              "title": "Document Store",
-              "type": "string"
+              "anyOf": [
+                {
+                  "type": "string"
+                },
+                {
+                  "type": "null"
+                }
+              ],
+              "default": null,
+              "title": "Document Store"
             },
             "top_k": {
               "default": 10,
@@ -1101,9 +1119,6 @@
               "type": "integer"
             }
           },
-          "required": [
-            "document_store"
-          ],
           "title": "Parameters",
           "type": "object"
         },
@@ -1149,8 +1164,16 @@
               "title": "Custom Query"
             },
             "document_store": {
-              "title": "Document Store",
-              "type": "string"
+              "anyOf": [
+                {
+                  "type": "string"
+                },
+                {
+                  "type": "null"
+                }
+              ],
+              "default": null,
+              "title": "Document Store"
             },
             "top_k": {
               "default": 10,
@@ -1158,9 +1181,6 @@
               "type": "integer"
             }
           },
-          "required": [
-            "document_store"
-          ],
           "title": "Parameters",
           "type": "object"
         },
@@ -1227,8 +1247,16 @@
               "title": "Devices"
             },
             "document_store": {
-              "title": "Document Store",
-              "type": "string"
+              "anyOf": [
+                {
+                  "type": "string"
+                },
+                {
+                  "type": "null"
+                }
+              ],
+              "default": null,
+              "title": "Document Store"
             },
             "emb_extraction_layer": {
               "default": -1,
@@ -1315,7 +1343,6 @@
             }
           },
           "required": [
-            "document_store",
             "embedding_model"
           ],
           "title": "Parameters",
@@ -1977,8 +2004,16 @@
               "title": "Custom Query"
             },
             "document_store": {
-              "title": "Document Store",
-              "type": "string"
+              "anyOf": [
+                {
+                  "type": "string"
+                },
+                {
+                  "type": "null"
+                }
+              ],
+              "default": null,
+              "title": "Document Store"
             },
             "scale_score": {
               "default": true,
@@ -1991,9 +2026,6 @@
               "type": "integer"
             }
           },
-          "required": [
-            "document_store"
-          ],
           "title": "Parameters",
           "type": "object"
         },
@@ -2825,8 +2857,16 @@
               "title": "Devices"
             },
             "document_store": {
-              "title": "Document Store",
-              "type": "string"
+              "anyOf": [
+                {
+                  "type": "string"
+                },
+                {
+                  "type": "null"
+                }
+              ],
+              "default": null,
+              "title": "Document Store"
             },
             "emb_extraction_layer": {
               "default": -1,
@@ -2912,7 +2952,6 @@
             }
           },
           "required": [
-            "document_store",
             "embedding_model"
           ],
           "title": "Parameters",
@@ -5121,8 +5160,16 @@
               "title": "Devices"
             },
             "document_store": {
-              "title": "Document Store",
-              "type": "string"
+              "anyOf": [
+                {
+                  "type": "string"
+                },
+                {
+                  "type": "null"
+                }
+              ],
+              "default": null,
+              "title": "Document Store"
             },
             "embed_meta_fields": {
               "default": [
@@ -5256,9 +5303,6 @@
               "type": "boolean"
             }
           },
-          "required": [
-            "document_store"
-          ],
           "title": "Parameters",
           "type": "object"
         },
@@ -5429,8 +5473,16 @@
               "title": "Auto Fit"
             },
             "document_store": {
-              "title": "Document Store",
-              "type": "string"
+              "anyOf": [
+                {
+                  "type": "string"
+                },
+                {
+                  "type": "null"
+                }
+              ],
+              "default": null,
+              "title": "Document Store"
             },
             "top_k": {
               "default": 10,
@@ -5438,9 +5490,6 @@
               "type": "integer"
             }
           },
-          "required": [
-            "document_store"
-          ],
           "title": "Parameters",
           "type": "object"
         },
diff --git a/haystack/json-schemas/haystack-pipeline-1.11.0rc0.schema.json b/haystack/json-schemas/haystack-pipeline-1.11.0rc0.schema.json
index 5411f0e63b..d116f249aa 100644
--- a/haystack/json-schemas/haystack-pipeline-1.11.0rc0.schema.json
+++ b/haystack/json-schemas/haystack-pipeline-1.11.0rc0.schema.json
@@ -227,8 +227,16 @@
               "title": "Custom Query"
             },
             "document_store": {
-              "title": "Document Store",
-              "type": "string"
+              "anyOf": [
+                {
+                  "type": "string"
+                },
+                {
+                  "type": "null"
+                }
+              ],
+              "default": null,
+              "title": "Document Store"
             },
             "scale_score": {
               "default": true,
@@ -241,9 +249,6 @@
               "type": "integer"
             }
           },
-          "required": [
-            "document_store"
-          ],
           "title": "Parameters",
           "type": "object"
         },
@@ -510,8 +515,16 @@
               "title": "Devices"
             },
             "document_store": {
-              "title": "Document Store",
-              "type": "string"
+              "anyOf": [
+                {
+                  "type": "string"
+                },
+                {
+                  "type": "null"
+                }
+              ],
+              "default": null,
+              "title": "Document Store"
             },
             "embed_title": {
               "default": true,
@@ -615,9 +628,6 @@
               "type": "boolean"
             }
           },
-          "required": [
-            "document_store"
-          ],
           "title": "Parameters",
           "type": "object"
         },
@@ -1092,8 +1102,16 @@
               "title": "Custom Query"
             },
             "document_store": {
-              "title": "Document Store",
-              "type": "string"
+              "anyOf": [
+                {
+                  "type": "string"
+                },
+                {
+                  "type": "null"
+                }
+              ],
+              "default": null,
+              "title": "Document Store"
             },
             "top_k": {
               "default": 10,
@@ -1101,9 +1119,6 @@
               "type": "integer"
             }
           },
-          "required": [
-            "document_store"
-          ],
           "title": "Parameters",
           "type": "object"
         },
@@ -1149,8 +1164,16 @@
               "title": "Custom Query"
             },
             "document_store": {
-              "title": "Document Store",
-              "type": "string"
+              "anyOf": [
+                {
+                  "type": "string"
+                },
+                {
+                  "type": "null"
+                }
+              ],
+              "default": null,
+              "title": "Document Store"
             },
             "top_k": {
               "default": 10,
@@ -1158,9 +1181,6 @@
               "type": "integer"
             }
           },
-          "required": [
-            "document_store"
-          ],
           "title": "Parameters",
           "type": "object"
         },
@@ -1227,8 +1247,16 @@
               "title": "Devices"
             },
             "document_store": {
-              "title": "Document Store",
-              "type": "string"
+              "anyOf": [
+                {
+                  "type": "string"
+                },
+                {
+                  "type": "null"
+                }
+              ],
+              "default": null,
+              "title": "Document Store"
             },
             "emb_extraction_layer": {
               "default": -1,
@@ -1315,7 +1343,6 @@
             }
           },
           "required": [
-            "document_store",
             "embedding_model"
           ],
           "title": "Parameters",
@@ -1977,8 +2004,16 @@
               "title": "Custom Query"
             },
             "document_store": {
-              "title": "Document Store",
-              "type": "string"
+              "anyOf": [
+                {
+                  "type": "string"
+                },
+                {
+                  "type": "null"
+                }
+              ],
+              "default": null,
+              "title": "Document Store"
             },
             "scale_score": {
               "default": true,
@@ -1991,9 +2026,6 @@
               "type": "integer"
             }
           },
-          "required": [
-            "document_store"
-          ],
           "title": "Parameters",
           "type": "object"
         },
@@ -2825,8 +2857,16 @@
               "title": "Devices"
             },
             "document_store": {
-              "title": "Document Store",
-              "type": "string"
+              "anyOf": [
+                {
+                  "type": "string"
+                },
+                {
+                  "type": "null"
+                }
+              ],
+              "default": null,
+              "title": "Document Store"
             },
             "emb_extraction_layer": {
               "default": -1,
@@ -2912,7 +2952,6 @@
             }
           },
           "required": [
-            "document_store",
             "embedding_model"
           ],
           "title": "Parameters",
@@ -5121,8 +5160,16 @@
               "title": "Devices"
             },
             "document_store": {
-              "title": "Document Store",
-              "type": "string"
+              "anyOf": [
+                {
+                  "type": "string"
+                },
+                {
+                  "type": "null"
+                }
+              ],
+              "default": null,
+              "title": "Document Store"
             },
             "embed_meta_fields": {
               "default": [
@@ -5256,9 +5303,6 @@
               "type": "boolean"
             }
           },
-          "required": [
-            "document_store"
-          ],
           "title": "Parameters",
           "type": "object"
         },
@@ -5429,8 +5473,16 @@
               "title": "Auto Fit"
             },
             "document_store": {
-              "title": "Document Store",
-              "type": "string"
+              "anyOf": [
+                {
+                  "type": "string"
+                },
+                {
+                  "type": "null"
+                }
+              ],
+              "default": null,
+              "title": "Document Store"
             },
             "top_k": {
               "default": 10,
@@ -5438,9 +5490,6 @@
               "type": "integer"
             }
           },
-          "required": [
-            "document_store"
-          ],
           "title": "Parameters",
           "type": "object"
         },
diff --git a/haystack/json-schemas/haystack-pipeline-main.schema.json b/haystack/json-schemas/haystack-pipeline-main.schema.json
index a286d64d16..ba3cbf2b63 100644
--- a/haystack/json-schemas/haystack-pipeline-main.schema.json
+++ b/haystack/json-schemas/haystack-pipeline-main.schema.json
@@ -227,8 +227,16 @@
               "title": "Custom Query"
             },
             "document_store": {
-              "title": "Document Store",
-              "type": "string"
+              "anyOf": [
+                {
+                  "type": "string"
+                },
+                {
+                  "type": "null"
+                }
+              ],
+              "default": null,
+              "title": "Document Store"
             },
             "scale_score": {
               "default": true,
@@ -241,9 +249,6 @@
               "type": "integer"
             }
           },
-          "required": [
-            "document_store"
-          ],
           "title": "Parameters",
           "type": "object"
         },
@@ -510,8 +515,16 @@
               "title": "Devices"
             },
             "document_store": {
-              "title": "Document Store",
-              "type": "string"
+              "anyOf": [
+                {
+                  "type": "string"
+                },
+                {
+                  "type": "null"
+                }
+              ],
+              "default": null,
+              "title": "Document Store"
             },
             "embed_title": {
               "default": true,
@@ -615,9 +628,6 @@
               "type": "boolean"
             }
           },
-          "required": [
-            "document_store"
-          ],
           "title": "Parameters",
           "type": "object"
         },
@@ -1092,8 +1102,16 @@
               "title": "Custom Query"
             },
             "document_store": {
-              "title": "Document Store",
-              "type": "string"
+              "anyOf": [
+                {
+                  "type": "string"
+                },
+                {
+                  "type": "null"
+                }
+              ],
+              "default": null,
+              "title": "Document Store"
             },
             "top_k": {
               "default": 10,
@@ -1101,9 +1119,6 @@
               "type": "integer"
             }
           },
-          "required": [
-            "document_store"
-          ],
           "title": "Parameters",
           "type": "object"
         },
@@ -1149,8 +1164,16 @@
               "title": "Custom Query"
             },
             "document_store": {
-              "title": "Document Store",
-              "type": "string"
+              "anyOf": [
+                {
+                  "type": "string"
+                },
+                {
+                  "type": "null"
+                }
+              ],
+              "default": null,
+              "title": "Document Store"
             },
             "top_k": {
               "default": 10,
@@ -1158,9 +1181,6 @@
               "type": "integer"
             }
           },
-          "required": [
-            "document_store"
-          ],
           "title": "Parameters",
           "type": "object"
         },
@@ -1227,8 +1247,16 @@
               "title": "Devices"
             },
             "document_store": {
-              "title": "Document Store",
-              "type": "string"
+              "anyOf": [
+                {
+                  "type": "string"
+                },
+                {
+                  "type": "null"
+                }
+              ],
+              "default": null,
+              "title": "Document Store"
             },
             "emb_extraction_layer": {
               "default": -1,
@@ -1315,7 +1343,6 @@
             }
           },
           "required": [
-            "document_store",
             "embedding_model"
           ],
           "title": "Parameters",
@@ -1977,8 +2004,16 @@
               "title": "Custom Query"
             },
             "document_store": {
-              "title": "Document Store",
-              "type": "string"
+              "anyOf": [
+                {
+                  "type": "string"
+                },
+                {
+                  "type": "null"
+                }
+              ],
+              "default": null,
+              "title": "Document Store"
             },
             "scale_score": {
               "default": true,
@@ -1991,9 +2026,6 @@
               "type": "integer"
             }
           },
-          "required": [
-            "document_store"
-          ],
           "title": "Parameters",
           "type": "object"
         },
@@ -2825,8 +2857,16 @@
               "title": "Devices"
             },
             "document_store": {
-              "title": "Document Store",
-              "type": "string"
+              "anyOf": [
+                {
+                  "type": "string"
+                },
+                {
+                  "type": "null"
+                }
+              ],
+              "default": null,
+              "title": "Document Store"
             },
             "emb_extraction_layer": {
               "default": -1,
@@ -2912,7 +2952,6 @@
             }
           },
           "required": [
-            "document_store",
             "embedding_model"
           ],
           "title": "Parameters",
@@ -5121,8 +5160,16 @@
               "title": "Devices"
             },
             "document_store": {
-              "title": "Document Store",
-              "type": "string"
+              "anyOf": [
+                {
+                  "type": "string"
+                },
+                {
+                  "type": "null"
+                }
+              ],
+              "default": null,
+              "title": "Document Store"
             },
             "embed_meta_fields": {
               "default": [
@@ -5256,9 +5303,6 @@
               "type": "boolean"
             }
           },
-          "required": [
-            "document_store"
-          ],
           "title": "Parameters",
           "type": "object"
         },
@@ -5429,8 +5473,16 @@
               "title": "Auto Fit"
             },
             "document_store": {
-              "title": "Document Store",
-              "type": "string"
+              "anyOf": [
+                {
+                  "type": "string"
+                },
+                {
+                  "type": "null"
+                }
+              ],
+              "default": null,
+              "title": "Document Store"
             },
             "top_k": {
               "default": 10,
@@ -5438,9 +5490,6 @@
               "type": "integer"
             }
           },
-          "required": [
-            "document_store"
-          ],
           "title": "Parameters",
           "type": "object"
         },
diff --git a/haystack/nodes/retriever/_embedding_encoder.py b/haystack/nodes/retriever/_embedding_encoder.py
index 95078debff..423a3eb103 100644
--- a/haystack/nodes/retriever/_embedding_encoder.py
+++ b/haystack/nodes/retriever/_embedding_encoder.py
@@ -105,21 +105,6 @@ def __init__(self, retriever: "EmbeddingRetriever"):
             num_processes=0,
             use_auth_token=retriever.use_auth_token,
         )
-        # Check that document_store has the right similarity function
-        similarity = retriever.document_store.similarity
-        # If we are using a sentence transformer model
-        if "sentence" in retriever.embedding_model.lower() and similarity != "cosine":
-            logger.warning(
-                f"You seem to be using a Sentence Transformer with the {similarity} function. "
-                f"We recommend using cosine instead. "
-                f"This can be set when initializing the DocumentStore"
-            )
-        elif "dpr" in retriever.embedding_model.lower() and similarity != "dot_product":
-            logger.warning(
-                f"You seem to be using a DPR model with the {similarity} function. "
-                f"We recommend using dot_product instead. "
-                f"This can be set when initializing the DocumentStore"
-            )
 
     def embed(self, texts: Union[List[List[str]], List[str], str]) -> np.ndarray:
         # TODO: FARM's `sample_to_features_text` need to fix following warning -
@@ -182,13 +167,6 @@ def __init__(self, retriever: "EmbeddingRetriever"):
         self.batch_size = retriever.batch_size
         self.embedding_model.max_seq_length = retriever.max_seq_len
         self.show_progress_bar = retriever.progress_bar
-        document_store = retriever.document_store
-        if document_store.similarity != "cosine":
-            logger.warning(
-                f"You are using a Sentence Transformer with the {document_store.similarity} function. "
-                f"We recommend using cosine instead. "
-                f"This can be set when initializing the DocumentStore"
-            )
 
     def embed(self, texts: Union[List[List[str]], List[str], str]) -> np.ndarray:
         # texts can be a list of strings or a list of [title, text]
diff --git a/haystack/nodes/retriever/base.py b/haystack/nodes/retriever/base.py
index 4d144a2878..0924d345ac 100644
--- a/haystack/nodes/retriever/base.py
+++ b/haystack/nodes/retriever/base.py
@@ -51,7 +51,7 @@ class BaseRetriever(BaseComponent):
     Base class for regular retrievers.
     """
 
-    document_store: BaseDocumentStore
+    document_store: Optional[BaseDocumentStore]
     outgoing_edges = 1
     query_count = 0
     index_count = 0
@@ -68,6 +68,7 @@ def retrieve(
         index: str = None,
         headers: Optional[Dict[str, str]] = None,
         scale_score: bool = None,
+        document_store: Optional[BaseDocumentStore] = None,
     ) -> List[Document]:
         """
         Scan through documents in DocumentStore and return a small number documents
@@ -81,6 +82,7 @@ def retrieve(
         :param scale_score: Whether to scale the similarity score to the unit interval (range of [0,1]).
                             If true (default) similarity scores (e.g. cosine or dot_product) which naturally have a different value range will be scaled to a range of [0,1], where 1 means extremely relevant.
                             Otherwise raw similarity scores (e.g. cosine or dot_product) will be used.
+        :param document_store: the docstore to use for retrieval. If `None`, the one given in the __init__ is used instead.
         """
         pass
 
@@ -94,6 +96,7 @@ def retrieve_batch(
         headers: Optional[Dict[str, str]] = None,
         batch_size: Optional[int] = None,
         scale_score: bool = None,
+        document_store: Optional[BaseDocumentStore] = None,
     ) -> List[List[Document]]:
         pass
 
@@ -121,6 +124,7 @@ def eval(
         open_domain: bool = False,
         return_preds: bool = False,
         headers: Optional[Dict[str, str]] = None,
+        document_store: Optional[BaseDocumentStore] = None,
     ) -> dict:
         """
         Performs evaluation on the Retriever.
@@ -155,7 +159,13 @@ def eval(
 
         timed_retrieve = self.timing(self.retrieve, "retrieve_time")
 
-        labels: List[MultiLabel] = self.document_store.get_all_labels_aggregated(
+        if document_store is None:
+            document_store = self.document_store
+            if document_store is None:
+                raise ValueError(
+                    "This Retriever was not initialized with a Document Store. Provide one to the eval() method."
+                )
+        labels: List[MultiLabel] = document_store.get_all_labels_aggregated(
             index=label_index,
             filters=filters,
             open_domain=open_domain,
diff --git a/haystack/nodes/retriever/dense.py b/haystack/nodes/retriever/dense.py
index 09e4f32fb4..ab1b89b376 100644
--- a/haystack/nodes/retriever/dense.py
+++ b/haystack/nodes/retriever/dense.py
@@ -86,7 +86,7 @@ class DensePassageRetriever(DenseRetriever):
 
     def __init__(
         self,
-        document_store: BaseDocumentStore,
+        document_store: Optional[BaseDocumentStore] = None,
         query_embedding_model: Union[Path, str] = "facebook/dpr-question_encoder-single-nq-base",
         passage_embedding_model: Union[Path, str] = "facebook/dpr-ctx_encoder-single-nq-base",
         model_version: Optional[str] = None,
@@ -167,7 +167,7 @@ def __init__(
         self.devices, _ = initialize_device_settings(devices=devices, use_cuda=use_gpu, multi_gpu=True)
 
         if batch_size < len(self.devices):
-            logger.warning("Batch size is less than the number of devices.All gpus will not be utilized.")
+            logger.warning("Batch size is less than the number of devices. All gpus will not be utilized.")
 
         self.document_store = document_store
         self.batch_size = batch_size
@@ -176,13 +176,7 @@ def __init__(
         self.scale_score = scale_score
         self.use_auth_token = use_auth_token
 
-        if document_store is None:
-            logger.warning(
-                "DensePassageRetriever initialized without a document store. "
-                "This is fine if you are performing DPR training. "
-                "Otherwise, please provide a document store in the constructor."
-            )
-        elif document_store.similarity != "dot_product":
+        if document_store and document_store.similarity != "dot_product":
             logger.warning(
                 f"You are using a Dense Passage Retriever model with the {document_store.similarity} function. "
                 "We recommend you use dot_product instead. "
@@ -252,6 +246,7 @@ def retrieve(
         index: str = None,
         headers: Optional[Dict[str, str]] = None,
         scale_score: bool = None,
+        document_store: Optional[BaseDocumentStore] = None,
     ) -> List[Document]:
         """
         Scan through documents in DocumentStore and return a small number documents
@@ -326,18 +321,22 @@ def retrieve(
         :param scale_score: Whether to scale the similarity score to the unit interval (range of [0,1]).
                                            If true similarity scores (e.g. cosine or dot_product) which naturally have a different value range will be scaled to a range of [0,1], where 1 means extremely relevant.
                                            Otherwise raw similarity scores (e.g. cosine or dot_product) will be used.
+        :param document_store: the docstore to use for retrieval. If `None`, the one given in the `__init__` is used instead.
         """
+        if document_store is None:
+            document_store = self.document_store
+            if document_store is None:
+                raise ValueError(
+                    "This Retriever was not initialized with a Document Store. Provide one to the retrieve() method."
+                )
         if top_k is None:
             top_k = self.top_k
-        if not self.document_store:
-            logger.error("Cannot perform retrieve() since DensePassageRetriever initialized with document_store=None")
-            return []
         if index is None:
-            index = self.document_store.index
+            index = document_store.index
         if scale_score is None:
             scale_score = self.scale_score
         query_emb = self.embed_queries(queries=[query])
-        documents = self.document_store.query_by_embedding(
+        documents = document_store.query_by_embedding(
             query_emb=query_emb[0], top_k=top_k, filters=filters, index=index, headers=headers, scale_score=scale_score
         )
         return documents
@@ -356,6 +355,7 @@ def retrieve_batch(
         headers: Optional[Dict[str, str]] = None,
         batch_size: Optional[int] = None,
         scale_score: bool = None,
+        document_store: Optional[BaseDocumentStore] = None,
     ) -> List[List[Document]]:
         """
         Scan through documents in DocumentStore and return a small number documents
@@ -436,7 +436,14 @@ def retrieve_batch(
                             If true similarity scores (e.g. cosine or dot_product) which naturally have a different
                             value range will be scaled to a range of [0,1], where 1 means extremely relevant.
                             Otherwise raw similarity scores (e.g. cosine or dot_product) will be used.
+        :param document_store: the docstore to use for retrieval. If `None`, the one given in the `__init__` is used instead.
         """
+        if document_store is None:
+            document_store = self.document_store
+            if document_store is None:
+                raise ValueError(
+                    "This Retriever was not initialized with a Document Store. Provide one to the retrieve() method."
+                )
 
         if top_k is None:
             top_k = self.top_k
@@ -454,14 +461,9 @@ def retrieve_batch(
             filters = [filters] * len(queries) if filters is not None else [{}] * len(queries)
 
         if index is None:
-            index = self.document_store.index
+            index = document_store.index
         if scale_score is None:
             scale_score = self.scale_score
-        if not self.document_store:
-            logger.error(
-                "Cannot perform retrieve_batch() since DensePassageRetriever initialized with document_store=None"
-            )
-            return [[] * len(queries)]  # type: ignore
 
         documents = []
         query_embs: List[np.ndarray] = []
@@ -470,7 +472,7 @@ def retrieve_batch(
         for query_emb, cur_filters in tqdm(
             zip(query_embs, filters), total=len(query_embs), disable=not self.progress_bar, desc="Querying"
         ):
-            cur_docs = self.document_store.query_by_embedding(
+            cur_docs = document_store.query_by_embedding(
                 query_emb=query_emb,
                 top_k=top_k,
                 filters=cur_filters,
@@ -804,7 +806,7 @@ class TableTextRetriever(DenseRetriever):
 
     def __init__(
         self,
-        document_store: BaseDocumentStore,
+        document_store: Optional[BaseDocumentStore] = None,
         query_embedding_model: Union[Path, str] = "deepset/bert-small-mm_retrieval-question_encoder",
         passage_embedding_model: Union[Path, str] = "deepset/bert-small-mm_retrieval-passage_encoder",
         table_embedding_model: Union[Path, str] = "deepset/bert-small-mm_retrieval-table_encoder",
@@ -884,19 +886,6 @@ def __init__(
         self.embed_meta_fields = embed_meta_fields
         self.scale_score = scale_score
 
-        if document_store is None:
-            logger.warning(
-                "DensePassageRetriever initialized without a document store. "
-                "This is fine if you are performing DPR training. "
-                "Otherwise, please provide a document store in the constructor."
-            )
-        elif document_store.similarity != "dot_product":
-            logger.warning(
-                f"You are using a Dense Passage Retriever model with the {document_store.similarity} function. "
-                "We recommend you use dot_product instead. "
-                "This can be set when initializing the DocumentStore"
-            )
-
         query_tokenizer_class = DPRQuestionEncoderTokenizerFast if use_fast else DPRQuestionEncoderTokenizer
         passage_tokenizer_class = DPRContextEncoderTokenizerFast if use_fast else DPRContextEncoderTokenizer
         table_tokenizer_class = DPRContextEncoderTokenizerFast if use_fast else DPRContextEncoderTokenizer
@@ -976,18 +965,22 @@ def retrieve(
         index: str = None,
         headers: Optional[Dict[str, str]] = None,
         scale_score: bool = None,
+        document_store: Optional[BaseDocumentStore] = None,
     ) -> List[Document]:
         if top_k is None:
             top_k = self.top_k
-        if not self.document_store:
-            logger.error("Cannot perform retrieve() since TableTextRetriever initialized with document_store=None")
-            return []
+        if document_store is None:
+            document_store = self.document_store
+            if document_store is None:
+                raise ValueError(
+                    "This Retriever was not initialized with a Document Store. Provide one to the retrieve() method."
+                )
         if index is None:
-            index = self.document_store.index
+            index = document_store.index
         if scale_score is None:
             scale_score = self.scale_score
         query_emb = self.embed_queries(queries=[query])
-        documents = self.document_store.query_by_embedding(
+        documents = document_store.query_by_embedding(
             query_emb=query_emb[0], top_k=top_k, filters=filters, index=index, headers=headers, scale_score=scale_score
         )
         return documents
@@ -1006,6 +999,7 @@ def retrieve_batch(
         headers: Optional[Dict[str, str]] = None,
         batch_size: Optional[int] = None,
         scale_score: bool = None,
+        document_store: Optional[BaseDocumentStore] = None,
     ) -> List[List[Document]]:
         """
         Scan through documents in DocumentStore and return a small number documents
@@ -1086,7 +1080,14 @@ def retrieve_batch(
                             If true similarity scores (e.g. cosine or dot_product) which naturally have a different
                             value range will be scaled to a range of [0,1], where 1 means extremely relevant.
                             Otherwise raw similarity scores (e.g. cosine or dot_product) will be used.
+        :param document_store: the docstore to use for retrieval. If `None`, the one given in the `__init__` is used instead.
         """
+        if document_store is None:
+            document_store = self.document_store
+            if document_store is None:
+                raise ValueError(
+                    "This Retriever was not initialized with a Document Store. Provide one to the retrieve_batch() method."
+                )
 
         if top_k is None:
             top_k = self.top_k
@@ -1104,14 +1105,9 @@ def retrieve_batch(
             filters = [filters] * len(queries) if filters is not None else [{}] * len(queries)
 
         if index is None:
-            index = self.document_store.index
+            index = document_store.index
         if scale_score is None:
             scale_score = self.scale_score
-        if not self.document_store:
-            logger.error(
-                "Cannot perform retrieve_batch() since TableTextRetriever initialized with document_store=None"
-            )
-            return [[] * len(queries)]  # type: ignore
 
         documents = []
         query_embs: List[np.ndarray] = []
@@ -1120,7 +1116,7 @@ def retrieve_batch(
         for query_emb, cur_filters in tqdm(
             zip(query_embs, filters), total=len(query_embs), disable=not self.progress_bar, desc="Querying"
         ):
-            cur_docs = self.document_store.query_by_embedding(
+            cur_docs = document_store.query_by_embedding(
                 query_emb=query_emb,
                 top_k=top_k,
                 filters=cur_filters,
@@ -1480,8 +1476,8 @@ def load(
 class EmbeddingRetriever(DenseRetriever):
     def __init__(
         self,
-        document_store: BaseDocumentStore,
         embedding_model: str,
+        document_store: Optional[BaseDocumentStore] = None,
         model_version: Optional[str] = None,
         use_gpu: bool = True,
         batch_size: int = 32,
@@ -1590,7 +1586,7 @@ def __init__(
                 f"'model_format' parameter at all."
             )
 
-        self.embedding_encoder = _EMBEDDING_ENCODERS[self.model_format](self)
+        self.embedding_encoder = _EMBEDDING_ENCODERS[self.model_format](retriever=self)
         self.embed_meta_fields = embed_meta_fields
 
     def retrieve(
@@ -1601,6 +1597,7 @@ def retrieve(
         index: str = None,
         headers: Optional[Dict[str, str]] = None,
         scale_score: bool = None,
+        document_store: Optional[BaseDocumentStore] = None,
     ) -> List[Document]:
         """
         Scan through documents in DocumentStore and return a small number documents
@@ -1675,15 +1672,22 @@ def retrieve(
         :param scale_score: Whether to scale the similarity score to the unit interval (range of [0,1]).
                                            If true similarity scores (e.g. cosine or dot_product) which naturally have a different value range will be scaled to a range of [0,1], where 1 means extremely relevant.
                                            Otherwise raw similarity scores (e.g. cosine or dot_product) will be used.
+        :param document_store: the docstore to use for retrieval. If `None`, the one given in the `__init__` is used instead.
         """
+        if document_store is None:
+            document_store = self.document_store
+            if document_store is None:
+                raise ValueError(
+                    "This Retriever was not initialized with a Document Store. Provide one to the retrieve() method."
+                )
         if top_k is None:
             top_k = self.top_k
         if index is None:
-            index = self.document_store.index
+            index = document_store.index
         if scale_score is None:
             scale_score = self.scale_score
         query_emb = self.embed_queries(queries=[query])
-        documents = self.document_store.query_by_embedding(
+        documents = document_store.query_by_embedding(
             query_emb=query_emb[0], filters=filters, top_k=top_k, index=index, headers=headers, scale_score=scale_score
         )
         return documents
@@ -1702,6 +1706,7 @@ def retrieve_batch(
         headers: Optional[Dict[str, str]] = None,
         batch_size: Optional[int] = None,
         scale_score: bool = None,
+        document_store: Optional[BaseDocumentStore] = None,
     ) -> List[List[Document]]:
         """
         Scan through documents in DocumentStore and return a small number documents
@@ -1782,8 +1787,14 @@ def retrieve_batch(
                             If true similarity scores (e.g. cosine or dot_product) which naturally have a different
                             value range will be scaled to a range of [0,1], where 1 means extremely relevant.
                             Otherwise raw similarity scores (e.g. cosine or dot_product) will be used.
+        :param document_store: the docstore to use for retrieval. If `None`, the one given in the `__init__` is used instead.
         """
-
+        if document_store is None:
+            document_store = self.document_store
+            if document_store is None:
+                raise ValueError(
+                    "This Retriever was not initialized with a Document Store. Provide one to the retrieve_batch() method."
+                )
         if top_k is None:
             top_k = self.top_k
 
@@ -1800,14 +1811,9 @@ def retrieve_batch(
             filters = [filters] * len(queries) if filters is not None else [{}] * len(queries)
 
         if index is None:
-            index = self.document_store.index
+            index = document_store.index
         if scale_score is None:
             scale_score = self.scale_score
-        if not self.document_store:
-            logger.error(
-                "Cannot perform retrieve_batch() since EmbeddingRetriever initialized with document_store=None"
-            )
-            return [[] * len(queries)]  # type: ignore
 
         documents = []
         query_embs: List[np.ndarray] = []
@@ -1816,7 +1822,7 @@ def retrieve_batch(
         for query_emb, cur_filters in tqdm(
             zip(query_embs, filters), total=len(query_embs), disable=not self.progress_bar, desc="Querying"
         ):
-            cur_docs = self.document_store.query_by_embedding(
+            cur_docs = document_store.query_by_embedding(
                 query_emb=query_emb,
                 top_k=top_k,
                 filters=cur_filters,
@@ -1966,8 +1972,8 @@ class MultihopEmbeddingRetriever(EmbeddingRetriever):
 
     def __init__(
         self,
-        document_store: BaseDocumentStore,
         embedding_model: str,
+        document_store: Optional[BaseDocumentStore] = None,
         model_version: Optional[str] = None,
         num_iterations: int = 2,
         use_gpu: bool = True,
@@ -2031,21 +2037,21 @@ def __init__(
                                   (topic, entities etc.).
         """
         super().__init__(
-            document_store,
-            embedding_model,
-            model_version,
-            use_gpu,
-            batch_size,
-            max_seq_len,
-            model_format,
-            pooling_strategy,
-            emb_extraction_layer,
-            top_k,
-            progress_bar,
-            devices,
-            use_auth_token,
-            scale_score,
-            embed_meta_fields,
+            embedding_model=embedding_model,
+            document_store=document_store,
+            model_version=model_version,
+            use_gpu=use_gpu,
+            batch_size=batch_size,
+            max_seq_len=max_seq_len,
+            model_format=model_format,
+            pooling_strategy=pooling_strategy,
+            emb_extraction_layer=emb_extraction_layer,
+            top_k=top_k,
+            progress_bar=progress_bar,
+            devices=devices,
+            use_auth_token=use_auth_token,
+            scale_score=scale_score,
+            embed_meta_fields=embed_meta_fields,
         )
         self.num_iterations = num_iterations
 
@@ -2060,6 +2066,7 @@ def retrieve(
         index: str = None,
         headers: Optional[Dict[str, str]] = None,
         scale_score: bool = None,
+        document_store: Optional[BaseDocumentStore] = None,
     ) -> List[Document]:
         """
         Scan through documents in DocumentStore and return a small number documents
@@ -2134,6 +2141,7 @@ def retrieve(
         :param scale_score: Whether to scale the similarity score to the unit interval (range of [0,1]).
                                            If true similarity scores (e.g. cosine or dot_product) which naturally have a different value range will be scaled to a range of [0,1], where 1 means extremely relevant.
                                            Otherwise raw similarity scores (e.g. cosine or dot_product) will be used.
+        :param document_store: the docstore to use for retrieval. If `None`, the one given in the `__init__` is used instead.
         """
         return self.retrieve_batch(
             queries=[query],
@@ -2159,6 +2167,7 @@ def retrieve_batch(
         headers: Optional[Dict[str, str]] = None,
         batch_size: Optional[int] = None,
         scale_score: bool = None,
+        document_store: Optional[BaseDocumentStore] = None,
     ) -> List[List[Document]]:
         """
         Scan through documents in DocumentStore and return a small number documents
@@ -2240,7 +2249,14 @@ def retrieve_batch(
                             If true similarity scores (e.g. cosine or dot_product) which naturally have a different
                             value range will be scaled to a range of [0,1], where 1 means extremely relevant.
                             Otherwise raw similarity scores (e.g. cosine or dot_product) will be used.
+        :param document_store: the docstore to use for retrieval. If `None`, the one given in the `__init__` is used instead.
         """
+        if document_store is None:
+            document_store = self.document_store
+            if document_store is None:
+                raise ValueError(
+                    "This Retriever was not initialized with a Document Store. Provide one to the retrieve() method."
+                )
 
         if top_k is None:
             top_k = self.top_k
@@ -2258,15 +2274,9 @@ def retrieve_batch(
             filters = [filters] * len(queries) if filters is not None else [{}] * len(queries)
 
         if index is None:
-            index = self.document_store.index
+            index = document_store.index
         if scale_score is None:
             scale_score = self.scale_score
-        if not self.document_store:
-            logger.error(
-                "Cannot perform retrieve_batch() since MultihopEmbeddingRetriever initialized with document_store=None"
-            )
-            result: List[List[Document]] = [[] * len(queries)]
-            return result
 
         documents = []
         batches = self._get_batches(queries=queries, batch_size=batch_size)
@@ -2279,7 +2289,7 @@ def retrieve_batch(
                 texts = [self._merge_query_and_context(q, c) for q, c in zip(batch, context_docs)]
                 query_embs = self.embed_queries(texts)
                 for idx, emb in enumerate(query_embs):
-                    cur_docs = self.document_store.query_by_embedding(
+                    cur_docs = document_store.query_by_embedding(
                         query_emb=emb,
                         top_k=top_k,
                         filters=cur_filters,
diff --git a/haystack/nodes/retriever/multimodal/retriever.py b/haystack/nodes/retriever/multimodal/retriever.py
index 0a27e667a7..3a6891fb12 100644
--- a/haystack/nodes/retriever/multimodal/retriever.py
+++ b/haystack/nodes/retriever/multimodal/retriever.py
@@ -117,6 +117,7 @@ def retrieve(  # type: ignore
         index: str = None,
         headers: Optional[Dict[str, str]] = None,
         scale_score: bool = None,
+        document_store: Optional[BaseDocumentStore] = None,
     ) -> List[Document]:
         """
         Scan through documents in DocumentStore and return a small number of documents that are most relevant to the
@@ -144,6 +145,7 @@ def retrieve(  # type: ignore
             headers=headers,
             batch_size=1,
             scale_score=scale_score,
+            document_store=document_store,
         )[0]
 
     def retrieve_batch(  # type: ignore
@@ -156,6 +158,7 @@ def retrieve_batch(  # type: ignore
         headers: Optional[Dict[str, str]] = None,
         batch_size: Optional[int] = None,
         scale_score: bool = None,
+        document_store: Optional[BaseDocumentStore] = None,
     ) -> List[List[Document]]:
         """
         Scan through documents in DocumentStore and return a small number of documents that are most relevant to the
@@ -189,7 +192,12 @@ def retrieve_batch(  # type: ignore
             filters_list = filters
 
         top_k = top_k or self.top_k
-        index = index or self.document_store.index
+        document_store = document_store or self.document_store
+        if not document_store:
+            raise ValueError(
+                "This Retriever was not initialized with a Document Store. Provide one to the retrieve() or retrieve_batch() method."
+            )
+        index = index or document_store.index
         scale_score = scale_score or self.scale_score
 
         # Embed the queries - we need them into Document format to leverage MultiModalEmbedder.embed()
@@ -199,7 +207,7 @@ def retrieve_batch(  # type: ignore
         # Query documents by embedding (the actual retrieval step)
         documents = []
         for query_embedding, query_filters in zip(query_embeddings, filters_list):
-            docs = self.document_store.query_by_embedding(
+            docs = document_store.query_by_embedding(
                 query_emb=query_embedding,
                 top_k=top_k,
                 filters=query_filters,
diff --git a/haystack/nodes/retriever/sparse.py b/haystack/nodes/retriever/sparse.py
index aa7fd27cb2..d6cb1f92d5 100644
--- a/haystack/nodes/retriever/sparse.py
+++ b/haystack/nodes/retriever/sparse.py
@@ -1,4 +1,4 @@
-from typing import Dict, List, Optional, Union, Tuple
+from typing import Dict, List, Optional, Union
 
 import logging
 from collections import OrderedDict, namedtuple
@@ -10,6 +10,7 @@
 from haystack.document_stores.base import BaseDocumentStore
 from haystack.document_stores import KeywordDocumentStore
 from haystack.nodes.retriever import BaseRetriever
+from haystack.errors import DocumentStoreError
 
 
 logger = logging.getLogger(__name__)
@@ -18,14 +19,15 @@
 class BM25Retriever(BaseRetriever):
     def __init__(
         self,
-        document_store: KeywordDocumentStore,
+        document_store: Optional[KeywordDocumentStore] = None,
         top_k: int = 10,
         all_terms_must_match: bool = False,
         custom_query: Optional[str] = None,
         scale_score: bool = True,
     ):
         """
-        :param document_store: an instance of one of the following DocumentStores to retrieve from: ElasticsearchDocumentStore, OpenSearchDocumentStore and OpenDistroElasticsearchDocumentStore
+        :param document_store: an instance of one of the following DocumentStores to retrieve from: ElasticsearchDocumentStore, OpenSearchDocumentStore and OpenDistroElasticsearchDocumentStore.
+            If None, a document store must be passed to the retrieve method for this Retriever to work.
         :param all_terms_must_match: Whether all terms of the query must match the document.
                                      If true all query terms must be present in a document in order to be retrieved (i.e the AND operator is being used implicitly between query terms: "cozy fish restaurant" -> "cozy AND fish AND restaurant").
                                      Otherwise at least one query term must be present in a document in order to be retrieved (i.e the OR operator is being used implicitly between query terms: "cozy fish restaurant" -> "cozy OR fish OR restaurant").
@@ -100,9 +102,10 @@ def __init__(
         :param scale_score: Whether to scale the similarity score to the unit interval (range of [0,1]).
                             If true (default) similarity scores (e.g. cosine or dot_product) which naturally have a different value range will be scaled to a range of [0,1], where 1 means extremely relevant.
                             Otherwise raw similarity scores (e.g. cosine or dot_product) will be used.
+
         """
         super().__init__()
-        self.document_store: KeywordDocumentStore = document_store
+        self.document_store: Optional[KeywordDocumentStore] = document_store
         self.top_k = top_k
         self.custom_query = custom_query
         self.all_terms_must_match = all_terms_must_match
@@ -116,6 +119,7 @@ def retrieve(
         index: str = None,
         headers: Optional[Dict[str, str]] = None,
         scale_score: bool = None,
+        document_store: Optional[BaseDocumentStore] = None,
     ) -> List[Document]:
         """
         Scan through documents in DocumentStore and return a small number documents
@@ -192,16 +196,25 @@ def retrieve(
         :param scale_score: Whether to scale the similarity score to the unit interval (range of [0,1]).
                                            If true similarity scores (e.g. cosine or dot_product) which naturally have a different value range will be scaled to a range of [0,1], where 1 means extremely relevant.
                                            Otherwise raw similarity scores (e.g. cosine or dot_product) will be used.
+        :param document_store: the docstore to use for retrieval. If `None`, the one given in the `__init__` is used instead.
         """
+        if document_store is None:
+            document_store = self.document_store
+            if document_store is None:
+                raise ValueError(
+                    "This Retriever was not initialized with a Document Store. Provide one to the retrieve() method."
+                )
+        if not isinstance(document_store, KeywordDocumentStore):
+            raise ValueError("document_store must be a subclass of KeywordDocumentStore.")
+
         if top_k is None:
             top_k = self.top_k
         if index is None:
-            index = self.document_store.index
-
+            index = document_store.index
         if scale_score is None:
             scale_score = self.scale_score
 
-        documents = self.document_store.query(
+        documents = document_store.query(
             query=query,
             filters=filters,
             top_k=top_k,
@@ -227,6 +240,7 @@ def retrieve_batch(
         headers: Optional[Dict[str, str]] = None,
         batch_size: Optional[int] = None,
         scale_score: bool = None,
+        document_store: Optional[BaseDocumentStore] = None,
     ) -> List[List[Document]]:
         """
         Scan through documents in DocumentStore and return a small number documents
@@ -307,16 +321,25 @@ def retrieve_batch(
                             If true similarity scores (e.g. cosine or dot_product) which naturally have a different
                             value range will be scaled to a range of [0,1], where 1 means extremely relevant.
                             Otherwise raw similarity scores (e.g. cosine or dot_product) will be used.
+        :param document_store: the docstore to use for retrieval. If `None`, the one given in the `__init__` is used instead.
         """
+        if document_store is None:
+            document_store = self.document_store
+            if document_store is None:
+                raise ValueError(
+                    "This Retriever was not initialized with a Document Store. Provide one to the retrieve() method."
+                )
+        if not isinstance(document_store, KeywordDocumentStore):
+            raise ValueError("document_store must be a subclass of KeywordDocumentStore.")
 
         if top_k is None:
             top_k = self.top_k
         if index is None:
-            index = self.document_store.index
+            index = document_store.index
         if scale_score is None:
             scale_score = self.scale_score
 
-        documents = self.document_store.query_batch(
+        documents = document_store.query_batch(
             queries=queries,
             filters=filters,
             top_k=top_k,
@@ -332,7 +355,7 @@ def retrieve_batch(
 class ElasticsearchRetriever(BM25Retriever):
     def __init__(
         self,
-        document_store: KeywordDocumentStore,
+        document_store: Optional[KeywordDocumentStore] = None,
         top_k: int = 10,
         all_terms_must_match: bool = False,
         custom_query: Optional[str] = None,
@@ -355,6 +378,7 @@ def retrieve(
         index: str = None,
         headers: Optional[Dict[str, str]] = None,
         scale_score: bool = None,
+        document_store: Optional[BaseDocumentStore] = None,
     ) -> List[Document]:
         """
         Scan through documents in DocumentStore and return a small number documents
@@ -369,17 +393,24 @@ def retrieve(
         :param scale_score: Whether to scale the similarity score to the unit interval (range of [0,1]).
                                            If true similarity scores (e.g. cosine or dot_product) which naturally have a different value range will be scaled to a range of [0,1], where 1 means extremely relevant.
                                            Otherwise raw similarity scores (e.g. cosine or dot_product) will be used.
+        :param document_store: the docstore to use for retrieval. If `None`, the one given in the `__init__` is used instead.
         """
+        if document_store is None:
+            document_store = self.document_store
+            if document_store is None:
+                raise ValueError(
+                    "This Retriever was not initialized with a Document Store. Provide one to the retrieve() method."
+                )
         if index is None:
-            index = self.document_store.index
-        documents = self.document_store.get_all_documents(filters=filters, index=index, headers=headers)
+            index = document_store.index
+        documents = document_store.get_all_documents(filters=filters, index=index, headers=headers)
         return documents
 
 
 class ElasticsearchFilterOnlyRetriever(FilterRetriever):
     def __init__(
         self,
-        document_store: KeywordDocumentStore,
+        document_store: Optional[KeywordDocumentStore] = None,
         top_k: int = 10,
         all_terms_must_match: bool = False,
         custom_query: Optional[str] = None,
@@ -402,7 +433,7 @@ class TfidfRetriever(BaseRetriever):
     It uses sklearn's TfidfVectorizer to compute a tf-idf matrix.
     """
 
-    def __init__(self, document_store: BaseDocumentStore, top_k: int = 10, auto_fit=True):
+    def __init__(self, document_store: Optional[BaseDocumentStore] = None, top_k: int = 10, auto_fit=True):
         """
         :param document_store: an instance of a DocumentStore to retrieve documents from.
         :param top_k: How many documents to return per query.
@@ -413,20 +444,18 @@ def __init__(self, document_store: BaseDocumentStore, top_k: int = 10, auto_fit=
         self.vectorizer = TfidfVectorizer(
             lowercase=True, stop_words=None, token_pattern=r"(?u)\b\w\w+\b", ngram_range=(1, 1)
         )
-
         self.document_store = document_store
-        self.paragraphs = self._get_all_paragraphs()
-        self.df = None
         self.top_k = top_k
         self.auto_fit = auto_fit
         self.document_count = 0
-        self.fit()
+        if document_store and document_store.get_document_count():
+            self.fit(document_store=document_store)
 
-    def _get_all_paragraphs(self) -> List[Paragraph]:
+    def _get_all_paragraphs(self, document_store: BaseDocumentStore) -> List[Paragraph]:
         """
         Split the list of documents in paragraphs
         """
-        documents = self.document_store.get_all_documents()
+        documents = document_store.get_all_documents()
 
         paragraphs = []
         p_id = 0
@@ -445,17 +474,14 @@ def _calc_scores(self, queries: Union[str, List[str]]) -> List[Dict[int, float]]
         if isinstance(queries, str):
             queries = [queries]
         question_vector = self.vectorizer.transform(queries)
-
-        scores = self.tfidf_matrix.dot(question_vector.T).toarray()
-        # Create one OrderedDict per query
-        idx_scores: List[List[Tuple[int, float]]] = [[] for query in scores[0]]
-        for idx_doc, cur_doc_scores in enumerate(scores):
-            for idx_query, score in enumerate(cur_doc_scores):
-                idx_scores[idx_query].append((idx_doc, score))
-
+        doc_scores_per_query = self.tfidf_matrix.dot(question_vector.T).T.toarray()
+        doc_scores_per_query = [
+            [(doc_idx, doc_score) for doc_idx, doc_score in enumerate(doc_scores)]
+            for doc_scores in doc_scores_per_query
+        ]
         indices_and_scores: List[Dict] = [
             OrderedDict(sorted(query_idx_scores, key=lambda tup: tup[1], reverse=True))
-            for query_idx_scores in idx_scores
+            for query_idx_scores in doc_scores_per_query
         ]
         return indices_and_scores
 
@@ -472,6 +498,7 @@ def retrieve(
         index: str = None,
         headers: Optional[Dict[str, str]] = None,
         scale_score: bool = None,
+        document_store: Optional[BaseDocumentStore] = None,
     ) -> List[Document]:
         """
         Scan through documents in DocumentStore and return a small number documents
@@ -484,16 +511,27 @@ def retrieve(
         :param scale_score: Whether to scale the similarity score to the unit interval (range of [0,1]).
                                            If true similarity scores (e.g. cosine or dot_product) which naturally have a different value range will be scaled to a range of [0,1], where 1 means extremely relevant.
                                            Otherwise raw similarity scores (e.g. cosine or dot_product) will be used.
+
+        :param document_store: the docstore to use for retrieval. If `None`, the one given in the `__init__` is used instead.
         """
+        if document_store is None:
+            document_store = self.document_store
+            if document_store is None:
+                raise ValueError(
+                    "This Retriever was not initialized with a Document Store. Provide one to the retrieve() method."
+                )
+        else:
+            self.fit(document_store=document_store)
+
         if self.auto_fit:
-            if self.document_store.get_document_count(headers=headers) != self.document_count:
+            if document_store.get_document_count(headers=headers) != self.document_count:
                 # run fit() to update self.df, self.tfidf_matrix and self.document_count
                 logger.warning(
                     "Indexed documents have been updated and fit() method needs to be run before retrieval. Running it now."
                 )
-                self.fit()
+                self.fit(document_store=document_store)
         if self.df is None:
-            raise Exception(
+            raise DocumentStoreError(
                 "Retrieval requires dataframe df and tf-idf matrix but fit() did not calculate them probably due to an empty document store."
             )
 
@@ -541,6 +579,7 @@ def retrieve_batch(
         headers: Optional[Dict[str, str]] = None,
         batch_size: Optional[int] = None,
         scale_score: bool = None,
+        document_store: Optional[BaseDocumentStore] = None,
     ) -> List[List[Document]]:
         """
         Scan through documents in DocumentStore and return a small number documents
@@ -557,17 +596,26 @@ def retrieve_batch(
                             If true similarity scores (e.g. cosine or dot_product) which naturally have a different
                             value range will be scaled to a range of [0,1], where 1 means extremely relevant.
                             Otherwise raw similarity scores (e.g. cosine or dot_product) will be used.
+        :param document_store: the docstore to use for retrieval. If `None`, the one given in the `__init__` is used instead.
         """
+        if document_store is None:
+            document_store = self.document_store
+            if document_store is None:
+                raise ValueError(
+                    "This Retriever was not initialized with a Document Store. Provide one to the retrieve() method."
+                )
+        else:
+            self.fit(document_store=document_store)
 
         if self.auto_fit:
-            if self.document_store.get_document_count(headers=headers) != self.document_count:
+            if document_store.get_document_count(headers=headers) != self.document_count:
                 # run fit() to update self.df, self.tfidf_matrix and self.document_count
                 logger.warning(
                     "Indexed documents have been updated and fit() method needs to be run before retrieval. Running it now."
                 )
-                self.fit()
+                self.fit(document_store=document_store)
         if self.df is None:
-            raise Exception(
+            raise DocumentStoreError(
                 "Retrieval requires dataframe df and tf-idf matrix but fit() did not calculate them probably due to an empty document store."
             )
 
@@ -605,17 +653,19 @@ def retrieve_batch(
 
         return all_documents
 
-    def fit(self):
+    def fit(self, document_store: BaseDocumentStore):
         """
         Performing training on this class according to the TF-IDF algorithm.
         """
-        if not self.paragraphs or len(self.paragraphs) == 0:
-            self.paragraphs = self._get_all_paragraphs()
-            if not self.paragraphs or len(self.paragraphs) == 0:
-                logger.warning("Fit method called with empty document store")
-                return
+        if document_store is None:
+            raise ValueError(
+                "This Retriever was not initialized with a Document Store. Provide one to the fit() method."
+            )
+        paragraphs = self._get_all_paragraphs(document_store=document_store)
+        if not paragraphs or len(paragraphs) == 0:
+            raise DocumentStoreError("Fit method called with empty document store")
 
-        self.df = pd.DataFrame.from_dict(self.paragraphs)
+        self.df = pd.DataFrame.from_dict(paragraphs)
         self.df["content"] = self.df["content"].apply(lambda x: " ".join(x))  # pylint: disable=unnecessary-lambda
         self.tfidf_matrix = self.vectorizer.fit_transform(self.df["content"])
-        self.document_count = self.document_store.get_document_count()
+        self.document_count = document_store.get_document_count()
diff --git a/test/conftest.py b/test/conftest.py
index 29d89324f5..1851043e12 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -802,7 +802,6 @@ def get_retriever(retriever_type, document_store):
         )
     elif retriever_type == "tfidf":
         retriever = TfidfRetriever(document_store=document_store)
-        retriever.fit()
     elif retriever_type == "embedding":
         retriever = EmbeddingRetriever(
             document_store=document_store, embedding_model="deepset/sentence_bert", use_gpu=False
diff --git a/test/nodes/test_question_generator.py b/test/nodes/test_question_generator.py
index a0a1367f47..8270518fba 100644
--- a/test/nodes/test_question_generator.py
+++ b/test/nodes/test_question_generator.py
@@ -127,7 +127,6 @@ def test_qa_generator_non_default_params(split_length, num_queries_per_doc):
 @pytest.mark.parametrize("retriever,document_store", [("tfidf", "memory")], indirect=True)
 def test_rqg_pipeline(question_generator, retriever):
     retriever.document_store.write_documents([document])
-    retriever.fit()
     p = RetrieverQuestionGenerationPipeline(retriever, question_generator)
     result = p.run(query)
     keys = list(result)


Code review comments:
diff_hunk: @@ -155,7 +159,13 @@ def eval(
 
         timed_retrieve = self.timing(self.retrieve, "retrieve_time")
 
-        labels: List[MultiLabel] = self.document_store.get_all_labels_aggregated(
+        if document_store is None:
+            document_store = self.document_store
+            if document_store is None:
+                raise ValueError(
+                    "This Retriever was not initialized with a Document Store. Provide one to the eval() method."
+                )
path: haystack/nodes/retriever/base.py
text: @author:
```suggestion
        document_store = document_store or self.document_store
        if document_store is None:
            raise ValueError(
                "This Retriever was not initialized with a Document Store. Provide one to the eval() method."
            )
```

diff_hunk: @@ -1086,7 +1080,14 @@ def retrieve_batch(
                             If true similarity scores (e.g. cosine or dot_product) which naturally have a different
                             value range will be scaled to a range of [0,1], where 1 means extremely relevant.
                             Otherwise raw similarity scores (e.g. cosine or dot_product) will be used.
+        :param document_store: the docstore to use for retrieval. If `None`, the one given in the `__init__` is used instead.
         """
+        if document_store is None:
+            document_store = self.document_store
+            if document_store is None:
+                raise ValueError(
+                    "This Retriever was not initialized with a Document Store. Provide one to the retrieve_batch() method."
+                )
path: haystack/nodes/retriever/dense.py
text: @author:
```suggestion
        document_store = document_store or self.document_store
        if document_store is None:
            raise ValueError(
                "This Retriever was not initialized with a Document Store. Provide one to the retrieve_batch() method."
            )
```

diff_hunk: @@ -1675,15 +1672,22 @@ def retrieve(
         :param scale_score: Whether to scale the similarity score to the unit interval (range of [0,1]).
                                            If true similarity scores (e.g. cosine or dot_product) which naturally have a different value range will be scaled to a range of [0,1], where 1 means extremely relevant.
                                            Otherwise raw similarity scores (e.g. cosine or dot_product) will be used.
+        :param document_store: the docstore to use for retrieval. If `None`, the one given in the `__init__` is used instead.
         """
+        if document_store is None:
+            document_store = self.document_store
+            if document_store is None:
+                raise ValueError(
+                    "This Retriever was not initialized with a Document Store. Provide one to the retrieve() method."
+                )
path: haystack/nodes/retriever/dense.py
text: @author:
```suggestion
        document_store = document_store or self.document_store
        if document_store is None:
            raise ValueError(
                "This Retriever was not initialized with a Document Store. Provide one to the retrieve() method."
            )
```

diff_hunk: @@ -1782,8 +1787,14 @@ def retrieve_batch(
                             If true similarity scores (e.g. cosine or dot_product) which naturally have a different
                             value range will be scaled to a range of [0,1], where 1 means extremely relevant.
                             Otherwise raw similarity scores (e.g. cosine or dot_product) will be used.
+        :param document_store: the docstore to use for retrieval. If `None`, the one given in the `__init__` is used instead.
         """
-
+        if document_store is None:
+            document_store = self.document_store
+            if document_store is None:
+                raise ValueError(
+                    "This Retriever was not initialized with a Document Store. Provide one to the retrieve_batch() method."
+                )
path: haystack/nodes/retriever/dense.py
text: @author:
```suggestion
        document_store = document_store or self.document_store
        if document_store is None:
            raise ValueError(
                "This Retriever was not initialized with a Document Store. Provide one to the retrieve_batch() method."
            )
```

diff_hunk: @@ -2240,7 +2249,14 @@ def retrieve_batch(
                             If true similarity scores (e.g. cosine or dot_product) which naturally have a different
                             value range will be scaled to a range of [0,1], where 1 means extremely relevant.
                             Otherwise raw similarity scores (e.g. cosine or dot_product) will be used.
+        :param document_store: the docstore to use for retrieval. If `None`, the one given in the `__init__` is used instead.
         """
+        if document_store is None:
+            document_store = self.document_store
+            if document_store is None:
+                raise ValueError(
+                    "This Retriever was not initialized with a Document Store. Provide one to the retrieve() method."
+                )
path: haystack/nodes/retriever/dense.py
text: @author:
```suggestion
        document_store = document_store or self.document_store
        if document_store is None:
            raise ValueError(
                "This Retriever was not initialized with a Document Store. Provide one to the retrieve_batch() method."
            )
```

diff_hunk: @@ -192,16 +196,25 @@ def retrieve(
         :param scale_score: Whether to scale the similarity score to the unit interval (range of [0,1]).
                                            If true similarity scores (e.g. cosine or dot_product) which naturally have a different value range will be scaled to a range of [0,1], where 1 means extremely relevant.
                                            Otherwise raw similarity scores (e.g. cosine or dot_product) will be used.
+        :param document_store: the docstore to use for retrieval. If `None`, the one given in the `__init__` is used instead.
         """
+        if document_store is None:
+            document_store = self.document_store
+            if document_store is None:
+                raise ValueError(
+                    "This Retriever was not initialized with a Document Store. Provide one to the retrieve() method."
+                )
path: haystack/nodes/retriever/sparse.py
text: @author:
```suggestion
        document_store = document_store or self.document_store
        if document_store is None:
            raise ValueError(
                "This Retriever was not initialized with a Document Store. Provide one to the retrieve() method."
            )
```

diff_hunk: @@ -307,16 +321,25 @@ def retrieve_batch(
                             If true similarity scores (e.g. cosine or dot_product) which naturally have a different
                             value range will be scaled to a range of [0,1], where 1 means extremely relevant.
                             Otherwise raw similarity scores (e.g. cosine or dot_product) will be used.
+        :param document_store: the docstore to use for retrieval. If `None`, the one given in the `__init__` is used instead.
         """
+        if document_store is None:
+            document_store = self.document_store
+            if document_store is None:
+                raise ValueError(
+                    "This Retriever was not initialized with a Document Store. Provide one to the retrieve() method."
+                )
path: haystack/nodes/retriever/sparse.py
text: @author:
```suggestion
        document_store = document_store or self.document_store
        if document_store is None:
            raise ValueError(
                "This Retriever was not initialized with a Document Store. Provide one to the retrieve_batch() method."
            )
```

diff_hunk: @@ -369,17 +393,24 @@ def retrieve(
         :param scale_score: Whether to scale the similarity score to the unit interval (range of [0,1]).
                                            If true similarity scores (e.g. cosine or dot_product) which naturally have a different value range will be scaled to a range of [0,1], where 1 means extremely relevant.
                                            Otherwise raw similarity scores (e.g. cosine or dot_product) will be used.
+        :param document_store: the docstore to use for retrieval. If `None`, the one given in the `__init__` is used instead.
         """
+        if document_store is None:
+            document_store = self.document_store
+            if document_store is None:
+                raise ValueError(
+                    "This Retriever was not initialized with a Document Store. Provide one to the retrieve() method."
+                )
path: haystack/nodes/retriever/sparse.py
text: @author:
```suggestion
        document_store = document_store or self.document_store
        if document_store is None:
            raise ValueError(
                "This Retriever was not initialized with a Document Store. Provide one to the retrieve() method."
            )
```

diff_hunk: @@ -326,18 +321,22 @@ def retrieve(
         :param scale_score: Whether to scale the similarity score to the unit interval (range of [0,1]).
                                            If true similarity scores (e.g. cosine or dot_product) which naturally have a different value range will be scaled to a range of [0,1], where 1 means extremely relevant.
                                            Otherwise raw similarity scores (e.g. cosine or dot_product) will be used.
+        :param document_store: the docstore to use for retrieval. If `None`, the one given in the `__init__` is used instead.
         """
+        if document_store is None:
+            document_store = self.document_store
+            if document_store is None:
+                raise ValueError(
+                    "This Retriever was not initialized with a Document Store. Provide one to the retrieve() method."
+                )
path: haystack/nodes/retriever/dense.py
text: @author:
```suggestion
        document_store = document_store or self.document_store
        if document_store is None:
            raise ValueError(
                "This Retriever was not initialized with a Document Store. Provide one to the retrieve() method."
            )
```

diff_hunk: @@ -155,7 +159,13 @@ def eval(
 
         timed_retrieve = self.timing(self.retrieve, "retrieve_time")
 
-        labels: List[MultiLabel] = self.document_store.get_all_labels_aggregated(
+        if document_store is None:
path: haystack/nodes/retriever/base.py
text: @user1:
Why not use a shortcut here? `document_store = document_store or self.document_store` then if `document_store` is None raise error

diff_hunk: @@ -436,7 +436,14 @@ def retrieve_batch(
                             If true similarity scores (e.g. cosine or dot_product) which naturally have a different
                             value range will be scaled to a range of [0,1], where 1 means extremely relevant.
                             Otherwise raw similarity scores (e.g. cosine or dot_product) will be used.
+        :param document_store: the docstore to use for retrieval. If `None`, the one given in the `__init__` is used instead.
         """
+        if document_store is None:
+            document_store = self.document_store
+            if document_store is None:
+                raise ValueError(
+                    "This Retriever was not initialized with a Document Store. Provide one to the retrieve() method."
+                )
path: haystack/nodes/retriever/dense.py
text: @author:
```suggestion
        document_store = document_store or self.document_store
        if document_store is None:
            raise ValueError(
                "This Retriever was not initialized with a Document Store. Provide one to the retrieve() method."
            )
```

diff_hunk: @@ -976,18 +965,22 @@ def retrieve(
         index: str = None,
         headers: Optional[Dict[str, str]] = None,
         scale_score: bool = None,
+        document_store: Optional[BaseDocumentStore] = None,
     ) -> List[Document]:
         if top_k is None:
             top_k = self.top_k
-        if not self.document_store:
-            logger.error("Cannot perform retrieve() since TableTextRetriever initialized with document_store=None")
-            return []
+        if document_store is None:
+            document_store = self.document_store
+            if document_store is None:
+                raise ValueError(
+                    "This Retriever was not initialized with a Document Store. Provide one to the retrieve() method."
+                )
path: haystack/nodes/retriever/dense.py
text: @author:
```suggestion
        document_store = document_store or self.document_store
        if document_store is None:
            raise ValueError(
                "This Retriever was not initialized with a Document Store. Provide one to the retrieve() method."
            )
```