Repository: deepset-ai/haystack

Issue:
`OpenAIAnswerGenerator` does not verify the request was successful
**Describe the bug**
- In case of a failed response, `OpenAIAnswerGenerator` crashes with a `KeyError`
- Related to #2605

**Expected behavior**
- `OpenAIAnswerGenerator` should return a clear explanation of the issue, for example the status code and the body of the response.

**To Reproduce**
- Just try to run `OpenAIAnswerGenerator` with a typo in the key.

**Error message**

From `FAILED test/nodes/test_generator.py::test_openai_answer_generator`
```
=================================== FAILURES ===================================
_________________________ test_openai_answer_generator _________________________
openai_generator = <haystack.nodes.answer_generator.openai.OpenAIAnswerGenerator object at 0x7f91aceff510>
docs = [<Document: {'content': 'My name is Paul and I live in New York', 'content_type': 'text', 'score': None, 'meta': {'met...me5', 'date_field': '2019-01-01', 'numeric_field': 0.0}, 'embedding': None, 'id': '598dc9f8cc824fe847ce4c9161f94eb9'}>]
    @pytest.mark.integration
    def test_openai_answer_generator(openai_generator, docs):
        if "OPENAI_API_KEY" in os.environ:
>           prediction = openai_generator.predict(query="Who lives in Berlin?", documents=docs, top_k=1)
test/nodes/test_generator.py:133: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
self = <haystack.nodes.answer_generator.openai.OpenAIAnswerGenerator object at 0x7f91aceff510>
query = 'Who lives in Berlin?'
documents = [<Document: {'content': 'My name is Paul and I live in New York', 'content_type': 'text', 'score': None, 'meta': {'met...me5', 'date_field': '2019-01-01', 'numeric_field': 0.0}, 'embedding': None, 'id': '598dc9f8cc824fe847ce4c9161f94eb9'}>]
top_k = 1
    def predict(self, query: str, documents: List[Document], top_k: Optional[int] = None):
        """
        Use loaded QA model to generate answers for a query based on the supplied list of Documents.
        Returns dictionaries containing answers.
        Be aware that OpenAI doesn't return scores for those answers.
        Example:
         ```python
            |{
            |    'query': 'Who is the father of Arya Stark?',
            |    'answers':[Answer(
            |                 'answer': 'Eddard,',
            |                 'score': None,
            |                 ),...
            |              ]
            |}
         ```
        :param query: Query string
        :param documents: List of Document in which to search for the answer
        :param top_k: The maximum number of answers to return
        :return: Dict containing query and answers
        """
        if top_k is None:
            top_k = self.top_k
        # convert input to OpenAI format
        prompt, input_docs = self._build_prompt(query=query, documents=documents)
        # get answers from OpenAI API
        url = "https://api.openai.com/v1/completions"
        payload = {
            "model": self.model,
            "prompt": prompt,
            "max_tokens": self.max_tokens,
            "stop": self.stop_words,
            "n": top_k,
            "temperature": self.temperature,
            "presence_penalty": self.presence_penalty,
            "frequency_penalty": self.frequency_penalty,
        }
        headers = {"Authorization": f"***", "Content-Type": "application/json"}
        response = requests.request("POST", url, headers=headers, data=json.dumps(payload))
        res = json.loads(response.text)
>       generated_answers = [ans["text"] for ans in res["choices"]]
E       KeyError: 'choices'
haystack/nodes/answer_generator/openai.py:139: KeyError
```

**To Fix**:
Simply add validation on `response`:
```python
headers = {"Authorization": f"***", "Content-Type": "application/json"}
response = requests.request("POST", url, headers=headers, data=json.dumps(payload))
res = json.loads(response.text)
generated_answers = [ans["text"] for ans in res["choices"]]
```


Discussion:

Resolving patch:
diff --git a/haystack/errors.py b/haystack/errors.py
--- a/haystack/errors.py
+++ b/haystack/errors.py
@@ -88,3 +88,10 @@ class AudioNodeError(NodeError):
 
     def __init__(self, message: Optional[str] = None):
         super().__init__(message=message)
+
+
+class OpenAIError(NodeError):
+    """Exception for issues that occur in the OpenAI Answer Generator node"""
+
+    def __init__(self, message: Optional[str] = None):
+        super().__init__(message=message)
diff --git a/haystack/nodes/answer_generator/openai.py b/haystack/nodes/answer_generator/openai.py
--- a/haystack/nodes/answer_generator/openai.py
+++ b/haystack/nodes/answer_generator/openai.py
@@ -7,6 +7,7 @@
 
 from haystack.nodes.answer_generator import BaseGenerator
 from haystack import Document
+from haystack.errors import OpenAIError
 
 
 logger = logging.getLogger(__name__)
@@ -136,12 +137,18 @@ def predict(self, query: str, documents: List[Document], top_k: Optional[int] =
 
         headers = {"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"}
         response = requests.request("POST", url, headers=headers, data=json.dumps(payload))
-
         res = json.loads(response.text)
+
+        if response.status_code != 200 or "choices" not in res:
+            raise OpenAIError(
+                f"OpenAI returned an error.\n"
+                f"Status code: {response.status_code}\n"
+                f"Response body: {response.text}"
+            )
+
         generated_answers = [ans["text"] for ans in res["choices"]]
         answers = self._create_answers(generated_answers, input_docs)
         result = {"query": query, "answers": answers}
-
         return result
 
     def _build_prompt(self, query: str, documents: List[Document]) -> Tuple[str, List[Document]]:


Test patch: