From 6d5a2467ccf966518ff801c1df1f5fa79f77285b Mon Sep 17 00:00:00 2001 From: chiruu12 <103719146+chiruu12@users.noreply.github.com> Date: Wed, 19 Aug 2026 02:07:38 +0530 Subject: [PATCH] fix(providers): handle an empty choices list in litellm/vllm --- .../litellm_provider/litellm_provider.py | 2 +- keep/providers/vllm_provider/vllm_provider.py | 2 +- .../test_litellm_response_parsing.py | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/keep/providers/litellm_provider/litellm_provider.py b/keep/providers/litellm_provider/litellm_provider.py index 935332f58e..cc15039c7c 100644 --- a/keep/providers/litellm_provider/litellm_provider.py +++ b/keep/providers/litellm_provider/litellm_provider.py @@ -115,7 +115,7 @@ def _query( # Extract the generated text from the response try: generated_text = result["choices"][0]["message"]["content"] - except KeyError: + except (KeyError, IndexError): generated_text = "" # Reasoning models return content=None when the whole max_tokens diff --git a/keep/providers/vllm_provider/vllm_provider.py b/keep/providers/vllm_provider/vllm_provider.py index a33096119f..dd56d53340 100644 --- a/keep/providers/vllm_provider/vllm_provider.py +++ b/keep/providers/vllm_provider/vllm_provider.py @@ -100,7 +100,7 @@ def _query( # Adjust this based on your vLLM API response structure try: generated_text = result["choices"][0]['text'] - except KeyError: + except (KeyError, IndexError): generated_text = "" # Try to parse as JSON if it's meant to be structured diff --git a/tests/providers/litellm_provider/test_litellm_response_parsing.py b/tests/providers/litellm_provider/test_litellm_response_parsing.py index c1f9ac30fb..ba2fa12b42 100644 --- a/tests/providers/litellm_provider/test_litellm_response_parsing.py +++ b/tests/providers/litellm_provider/test_litellm_response_parsing.py @@ -121,3 +121,22 @@ def test_missing_usage_does_not_break_the_response(): assert result["prompt_tokens"] is None assert result["cost"] is None assert result["model"] == "local-model" + + +def _empty_choices_response(): + """Some OpenAI-compatible backends (e.g. Azure content filtering) return + HTTP 200 with an empty ``choices`` list. ``raise_for_status`` passes, so the + provider still has to read ``choices[0]``.""" + response = MagicMock() + response.raise_for_status = MagicMock() + response.json = MagicMock(return_value={"choices": []}) + return response + + +def test_empty_choices_does_not_crash(): + # choices[0] on an empty list raises IndexError, which the old + # ``except KeyError`` did not catch, so the step died with an opaque + # traceback instead of an empty response. + provider = _build_provider() + with patch("requests.post", return_value=_empty_choices_response()): + assert provider._query(prompt="hi")["response"] == ""