From 46ef407dab273465fedf2849777f66370a9eac6f Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 17 May 2026 13:51:07 -0500 Subject: [PATCH 1/2] fix: guard against IndexError on empty LLM choices/content list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three call sites access choices[0] or content[0] without checking whether the API returned any results — content-policy filters, token limits, and rate-limit fallbacks can all return an empty list. - api_provider.py: early return when res.choices is empty - classify/label.py: skip iteration when completion.choices is empty - classify/label.py: skip Anthropic iteration when response.content is empty - criteria_labeling.py: skip iteration when completion.choices is empty --- fastchat/serve/api_provider.py | 2 ++ fastchat/serve/monitor/classify/label.py | 4 ++++ fastchat/serve/monitor/criteria_labeling.py | 2 ++ 3 files changed, 8 insertions(+) diff --git a/fastchat/serve/api_provider.py b/fastchat/serve/api_provider.py index 2e967e3ef..817275321 100644 --- a/fastchat/serve/api_provider.py +++ b/fastchat/serve/api_provider.py @@ -348,6 +348,8 @@ def openai_api_stream_iter( max_tokens=max_new_tokens, stream=False, ) + if not res.choices: + return text = res.choices[0].message.content pos = 0 while pos < len(text): diff --git a/fastchat/serve/monitor/classify/label.py b/fastchat/serve/monitor/classify/label.py index fe928a9ba..44b7c1ea4 100644 --- a/fastchat/serve/monitor/classify/label.py +++ b/fastchat/serve/monitor/classify/label.py @@ -64,6 +64,8 @@ def chat_completion_openai(model, messages, temperature, max_tokens, api_dict=No max_tokens=max_tokens, # extra_body={"guided_choice": GUIDED_CHOICES} if GUIDED_CHOICES else None, ) + if not completion.choices: + break output = completion.choices[0].message.content # print(output) break @@ -114,6 +116,8 @@ def chat_completion_anthropic(model, messages, temperature, max_tokens, api_dict temperature=temperature, system=sys_msg, ) + if not response.content: + break output = response.content[0].text break except anthropic.APIError as e: diff --git a/fastchat/serve/monitor/criteria_labeling.py b/fastchat/serve/monitor/criteria_labeling.py index b08b030d4..bb601bda3 100644 --- a/fastchat/serve/monitor/criteria_labeling.py +++ b/fastchat/serve/monitor/criteria_labeling.py @@ -89,6 +89,8 @@ def chat_completion_openai(model, messages, temperature, max_tokens, api_dict=No max_tokens=max_tokens, # extra_body={"guided_choice": GUIDED_CHOICES} if GUIDED_CHOICES else None, ) + if not completion.choices: + break output = completion.choices[0].message.content break except openai.RateLimitError as e: From cab948d902e4a88b060def44e6c264435cd866dc Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 17 May 2026 17:00:50 -0500 Subject: [PATCH 2/2] fix: extend guards to cover choices[0].message is None (Gemini content-filter) Gemini 2.5 Flash returns HTTP 200 with choices[0].message=None when content is filtered (finish_reason: PROHIBITED_CONTENT), causing AttributeError even when choices is non-empty. Extends all three guards from `if not choices:` to `if not choices or choices[0].message is None:`. --- fastchat/serve/api_provider.py | 2 +- fastchat/serve/monitor/classify/label.py | 2 +- fastchat/serve/monitor/criteria_labeling.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fastchat/serve/api_provider.py b/fastchat/serve/api_provider.py index 817275321..dc15c82a6 100644 --- a/fastchat/serve/api_provider.py +++ b/fastchat/serve/api_provider.py @@ -348,7 +348,7 @@ def openai_api_stream_iter( max_tokens=max_new_tokens, stream=False, ) - if not res.choices: + if not res.choices or res.choices[0].message is None: return text = res.choices[0].message.content pos = 0 diff --git a/fastchat/serve/monitor/classify/label.py b/fastchat/serve/monitor/classify/label.py index 44b7c1ea4..925167f81 100644 --- a/fastchat/serve/monitor/classify/label.py +++ b/fastchat/serve/monitor/classify/label.py @@ -64,7 +64,7 @@ def chat_completion_openai(model, messages, temperature, max_tokens, api_dict=No max_tokens=max_tokens, # extra_body={"guided_choice": GUIDED_CHOICES} if GUIDED_CHOICES else None, ) - if not completion.choices: + if not completion.choices or completion.choices[0].message is None: break output = completion.choices[0].message.content # print(output) diff --git a/fastchat/serve/monitor/criteria_labeling.py b/fastchat/serve/monitor/criteria_labeling.py index bb601bda3..75010a563 100644 --- a/fastchat/serve/monitor/criteria_labeling.py +++ b/fastchat/serve/monitor/criteria_labeling.py @@ -89,7 +89,7 @@ def chat_completion_openai(model, messages, temperature, max_tokens, api_dict=No max_tokens=max_tokens, # extra_body={"guided_choice": GUIDED_CHOICES} if GUIDED_CHOICES else None, ) - if not completion.choices: + if not completion.choices or completion.choices[0].message is None: break output = completion.choices[0].message.content break