Repository: deepset-ai/haystack

Issue:
Empty `documents` list causes `PromptNode` to fail
**Describe the bug**
If we specify `documents` as a prompt parameter in `PromptTemplate`, running `PromptNode` fails when it receives an empty list of documents.

**Error message**
```
Traceback (most recent call last):
  File "/Users/bogdan/Repositories/haystack/haystack/pipelines/base.py", line 552, in run
    node_output, stream_id = self._run_node(node_id, node_input)
  File "/Users/bogdan/Repositories/haystack/haystack/pipelines/base.py", line 467, in _run_node
    return self.graph.nodes[node_id]["component"]._dispatch_run(**node_input)
  File "/Users/bogdan/Repositories/haystack/haystack/nodes/base.py", line 201, in _dispatch_run
    return self._dispatch_run_general(self.run, **kwargs)
  File "/Users/bogdan/Repositories/haystack/haystack/nodes/base.py", line 245, in _dispatch_run_general
    output, stream = run_method(**run_inputs, **run_params)
  File "/Users/bogdan/Repositories/haystack/haystack/nodes/prompt/prompt_node.py", line 361, in run
    results = self(prompt_collector=prompt_collector, **invocation_context)
  File "/Users/bogdan/Repositories/haystack/haystack/nodes/prompt/prompt_node.py", line 145, in __call__
    return self.prompt(prompt_template, *args, **kwargs)
  File "/Users/bogdan/Repositories/haystack/haystack/nodes/prompt/prompt_node.py", line 168, in prompt
    for prompt in template_to_fill.fill(*args, **kwargs):
  File "/Users/bogdan/Repositories/haystack/haystack/nodes/prompt/prompt_template.py", line 320, in fill
    template_dict = self.prepare(*args, **kwargs)
  File "/Users/bogdan/Repositories/haystack/haystack/nodes/prompt/prompt_template.py", line 275, in prepare
    raise ValueError(
ValueError: Expected prompt parameters ['documents', 'query'] to be provided but got only ['query']. Make sure to provide all template parameters.

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/bogdan/Repositories/haystack/bogdan_scripts/prompt_empty_docs.py", line 23, in <module>
    p.run(query="Sample query")
  File "/Users/bogdan/Repositories/haystack/haystack/pipelines/base.py", line 559, in run
    raise Exception(
Exception: Exception while running node 'Prompt': Expected prompt parameters ['documents', 'query'] to be provided but got only ['query']. Make sure to provide all template parameters.
Enable debug logging to see the data that was passed when the pipeline failed.
```

**Expected behavior**
A clear and concise description of what you expected to happen.
We should run the prompt without raising an error, instead we could issue a warning message saying that no documents have been provided.

**Additional context**
Add any other context about the problem here, like document types / preprocessing steps / settings of reader etc.
Example cases where the retriever will return empty list of Documents are when the DocumentStore doesn't contain any Documents or when the BM25Retriever is used and no word in the query can be matched with any of the indexed Documents.

This came up in discussion #4823.

**To Reproduce**
Steps to reproduce the behavior
```python
from haystack.nodes import PromptTemplate, PromptNode, BM25Retriever
from haystack.document_stores import OpenSearchDocumentStore
from haystack import Pipeline, Document

# Empty DocumentStore and retriever that will return empty document list
document_store = OpenSearchDocumentStore(embedding_dim=384)
document_store.write_documents(documents=[Document("Sample doc")])
retriever = BM25Retriever(document_store=document_store)

lfqa_prompt = PromptTemplate(
    name="lfqa",
    prompt_text="""Synthesize a comprehensive answer from the following text for the given question. 
                             Provide a clear and concise response that summarizes the key points and information presented in the text. 
                             Your answer should be in your own words and be no longer than 50 words.
                             If answer is not in .text. say i dont know.
                             \n\n Related text: {join(documents)} \n\n Question: {query} \n\n Answer:""",
)
prompt_node = PromptNode(model_name_or_path="google/flan-t5-base", default_prompt_template=lfqa_prompt)

p = Pipeline()
p.add_node(component=retriever, name="Retriever", inputs=["Query"])
p.add_node(component=prompt_node, name="Prompt", inputs=["Retriever"])
p.run(query="non-matching query")
```




Discussion:

Resolving patch:
diff --git a/haystack/nodes/prompt/prompt_template.py b/haystack/nodes/prompt/prompt_template.py
--- a/haystack/nodes/prompt/prompt_template.py
+++ b/haystack/nodes/prompt/prompt_template.py
@@ -482,6 +482,13 @@ def prepare(self, *args, **kwargs) -> Dict[str, Any]:
                 if param in kwargs:
                     params_dict[param] = kwargs[param]
 
+        if "documents" in self.prompt_params and "documents" not in params_dict:
+            params_dict["documents"] = []
+            logger.warning(
+                "Expected prompt parameter 'documents' to be provided but it is missing. "
+                "Continuing with an empty list of documents."
+            )
+
         if not set(self.prompt_params).issubset(params_dict.keys()):
             available_params = {*params_dict.keys(), *kwargs.keys()}
             provided = set(self.prompt_params).intersection(available_params)


Test patch:
diff --git a/test/prompt/test_prompt_node.py b/test/prompt/test_prompt_node.py
--- a/test/prompt/test_prompt_node.py
+++ b/test/prompt/test_prompt_node.py
@@ -1009,3 +1009,23 @@ def test_chatgpt_direct_prompting_w_messages(chatgpt_prompt_model):
 
     result = pn(messages)
     assert len(result) == 1 and all(w in result[0].casefold() for w in ["arlington", "texas"])
+
+
+@pytest.mark.unit
+@patch("haystack.nodes.prompt.prompt_node.PromptModel")
+def test_prompt_node_warns_about_missing_documents(mock_model, caplog):
+    lfqa_prompt = PromptTemplate(
+        prompt="""Synthesize a comprehensive answer from the following text for the given question.
+        Provide a clear and concise response that summarizes the key points and information presented in the text.
+        Your answer should be in your own words and be no longer than 50 words.
+        If answer is not in .text. say i dont know.
+        \n\n Related text: {join(documents)} \n\n Question: {query} \n\n Answer:"""
+    )
+    prompt_node = PromptNode(default_prompt_template=lfqa_prompt)
+
+    with caplog.at_level(logging.WARNING):
+        results, _ = prompt_node.run(query="non-matching query")
+        assert (
+            "Expected prompt parameter 'documents' to be provided but it is missing. "
+            "Continuing with an empty list of documents." in caplog.text
+        )
diff --git a/test/prompt/test_prompt_template.py b/test/prompt/test_prompt_template.py
--- a/test/prompt/test_prompt_template.py
+++ b/test/prompt/test_prompt_template.py
@@ -211,6 +211,19 @@ def test_prompt_template_deserialization(mock_prompt_model):
     assert isinstance(loaded_generator.default_prompt_template.output_parser, AnswerParser)
 
 
+@pytest.mark.unit
+def test_prompt_template_fills_in_missing_documents():
+    lfqa_prompt = PromptTemplate(
+        prompt="""Synthesize a comprehensive answer from the following text for the given question.
+        Provide a clear and concise response that summarizes the key points and information presented in the text.
+        Your answer should be in your own words and be no longer than 50 words.
+        If answer is not in .text. say i dont know.
+        \n\n Related text: {join(documents)} \n\n Question: {query} \n\n Answer:"""
+    )
+    prepared_prompt = next(lfqa_prompt.fill(query="What is the meaning of life?"))  # no documents provided but expected
+    assert "Related text:  \n\n Question: What is the meaning of life?" in prepared_prompt
+
+
 class TestPromptTemplateSyntax:
     @pytest.mark.unit
     @pytest.mark.parametrize(