Repository: deepset-ai/haystack

Issue:
REST API for Query breaks for document retrieval Pipelines
In the current implementation, we expect `answers` to be present in the [QueryResponse](https://github.com/deepset-ai/haystack/blob/ea5aab23ec5bbc88e51e00cca239a8feb88aaab3/rest_api/schema.py#L44) model. 

This breaks the validation of document search Pipelines when no answer is present. 



Discussion:
In addition, let's make sure that the use case of DocumentRetrieval pipelines as mentioned in #1618 is properly documented.

Resolving patch:
diff --git a/rest_api/controller/search.py b/rest_api/controller/search.py
--- a/rest_api/controller/search.py
+++ b/rest_api/controller/search.py
@@ -1,3 +1,5 @@
+from typing import Dict, Any
+
 import logging
 import time
 import json
@@ -65,7 +67,7 @@ def query(request: QueryRequest):
         return result
 
 
-def _process_request(pipeline, request) -> QueryResponse:
+def _process_request(pipeline, request) -> Dict[str, Any]:
     start_time = time.time()
 
     params = request.params or {}
@@ -75,22 +77,26 @@ def _process_request(pipeline, request) -> QueryResponse:
         params["filters"] = _format_filters(params["filters"])
 
     # format targeted node filters (e.g. "params": {"Retriever": {"filters": {"value"}}})
-    for key, value in params.items():
+    for key in params.keys():
         if "filters" in params[key].keys():
             params[key]["filters"] = _format_filters(params[key]["filters"])
 
     result = pipeline.run(query=request.query, params=params, debug=request.debug)
 
+    # Ensure answers and documents exist, even if they're empty lists
+    if not "documents" in result:
+        result["documents"] = []
+    if not "answers" in result:
+        result["answers"] = []
+
     # if any of the documents contains an embedding as an ndarray the latter needs to be converted to list of float
-    for document in result["documents"] or []:
+    for document in result["documents"]:
         if isinstance(document.embedding, ndarray):
             document.embedding = document.embedding.tolist()
 
-    end_time = time.time()
     logger.info(
-        json.dumps({"request": request, "response": result, "time": f"{(end_time - start_time):.2f}"}, default=str)
+        json.dumps({"request": request, "response": result, "time": f"{(time.time() - start_time):.2f}"}, default=str)
     )
-
     return result
 
 
diff --git a/rest_api/schema.py b/rest_api/schema.py
--- a/rest_api/schema.py
+++ b/rest_api/schema.py
@@ -46,6 +46,6 @@ class LabelSerialized(Label):
 
 class QueryResponse(BaseModel):
     query: str
-    answers: List[AnswerSerialized]
-    documents: Optional[List[DocumentSerialized]]
+    answers: List[AnswerSerialized] = []
+    documents: List[DocumentSerialized] = []
     debug: Optional[Dict] = Field(None, alias="_debug")


Test patch:
diff --git a/rest_api/test/test_rest_api.py b/rest_api/test/test_rest_api.py
--- a/rest_api/test/test_rest_api.py
+++ b/rest_api/test/test_rest_api.py
@@ -215,6 +215,23 @@ def test_query_with_invalid_filter(populated_client: TestClient):
     assert len(response_json["answers"]) == 0
 
 
+def test_query_with_no_documents_and_no_answers():
+    os.environ["PIPELINE_YAML_PATH"] = str(
+        (Path(__file__).parent / "samples" / "pipeline" / "test_pipeline.yaml").absolute()
+    )
+    os.environ["INDEXING_PIPELINE_NAME"] = "indexing_text_pipeline"
+    client = TestClient(app)
+
+    # Clean up to make sure the docstore is empty
+    client.post(url="/documents/delete_by_filters", data='{"filters": {}}')
+    query = {"query": "Who made the PDF specification?"}
+    response = client.post(url="/query", json=query)
+    assert 200 == response.status_code
+    response_json = response.json()
+    assert response_json["documents"] == []
+    assert response_json["answers"] == []
+
+
 def test_write_feedback(populated_client: TestClient):
     response = populated_client.post(url="/feedback", json=FEEDBACK)
     assert 200 == response.status_code