From 9ef32e9817eff419630641263340e5af46c24150 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 17 May 2026 17:34:35 -0500 Subject: [PATCH] fix: guard against IndexError/AttributeError on empty LLM choices (19 sites) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LLM APIs (OpenAI-compatible) can return HTTP 200 with an empty choices list on content filtering, provider errors, or quota limits — the SDK does NOT raise, causing IndexError. Gemini 2.5 Flash additionally returns choices[0].message=None on PROHIBITED_CONTENT (finish_reason: content_filter), causing AttributeError: 'NoneType' object has no attribute 'content'. Guards added to 17 files across inference/ and WebAgent/: inference/react_agent.py, inference/tool_visit.py, inference/file_tools/video_analysis.py, WebAgent/WebWeaver/{react_agent_outline_write,react_agent_search_id,tool/tool_retrieve}.py, WebAgent/AgentFold/infer.py, WebAgent/WebSailor/src/{react_agent,tool_visit,evaluate}.py, WebAgent/WebResummer/src/{react_agent,tool_visit}.py, WebAgent/WebWatcher/infer/scripts_eval/agent_eval.py, WebAgent/WebWatcher/.../qwen_agent/tools/private/visit.py, WebAgent/WebWalker/src/{rag_system,agent}.py, WebAgent/WebDancer/demos/tools/private/visit.py Found by pact (https://github.com/qizwiz/pact) static analysis, mode: llm_response_unguarded. --- WebAgent/AgentFold/infer.py | 2 ++ WebAgent/WebDancer/demos/tools/private/visit.py | 2 ++ WebAgent/WebResummer/src/react_agent.py | 2 ++ WebAgent/WebResummer/src/tool_visit.py | 2 ++ WebAgent/WebSailor/src/evaluate.py | 2 ++ WebAgent/WebSailor/src/react_agent.py | 2 ++ WebAgent/WebSailor/src/tool_visit.py | 2 ++ WebAgent/WebWalker/src/agent.py | 4 ++++ WebAgent/WebWalker/src/rag_system.py | 4 ++++ WebAgent/WebWatcher/infer/scripts_eval/agent_eval.py | 2 ++ .../qwen-agent-o1_search/qwen_agent/tools/private/visit.py | 2 ++ WebAgent/WebWeaver/react_agent_outline_write.py | 2 ++ WebAgent/WebWeaver/react_agent_search_id.py | 2 ++ WebAgent/WebWeaver/tool/tool_retrieve.py | 2 ++ inference/file_tools/video_analysis.py | 2 ++ inference/react_agent.py | 2 ++ inference/tool_visit.py | 2 ++ 17 files changed, 38 insertions(+) diff --git a/WebAgent/AgentFold/infer.py b/WebAgent/AgentFold/infer.py index c1a45429..c9d95511 100644 --- a/WebAgent/AgentFold/infer.py +++ b/WebAgent/AgentFold/infer.py @@ -112,6 +112,8 @@ def get_llm_response_nonstream(prompt, check_func_list=[]): presence_penalty=1.1 ) + if not response.choices: + raise ValueError("LLM returned empty choices") return response.choices[0].text def execute_tool(llm_generated_response): diff --git a/WebAgent/WebDancer/demos/tools/private/visit.py b/WebAgent/WebDancer/demos/tools/private/visit.py index b911cc60..1f4c272e 100644 --- a/WebAgent/WebDancer/demos/tools/private/visit.py +++ b/WebAgent/WebDancer/demos/tools/private/visit.py @@ -124,6 +124,8 @@ def llm(self, messages): messages=messages, response_format={"type": "json_object"}, ) + if not response.choices or response.choices[0].message is None: + raise ValueError("LLM returned empty or filtered response") return response.choices[0].message.content return "" diff --git a/WebAgent/WebResummer/src/react_agent.py b/WebAgent/WebResummer/src/react_agent.py index 5a63342b..01658284 100644 --- a/WebAgent/WebResummer/src/react_agent.py +++ b/WebAgent/WebResummer/src/react_agent.py @@ -59,6 +59,8 @@ def call_server(self, msgs, max_tries=10): temperature=self.llm_generate_cfg.get('temperature', 0.6), top_p=self.llm_generate_cfg.get('top_p', 0.95), ) + if not chat_response.choices or chat_response.choices[0].message is None: + raise ValueError("LLM returned empty or filtered response") content = chat_response.choices[0].message.content if content: return content diff --git a/WebAgent/WebResummer/src/tool_visit.py b/WebAgent/WebResummer/src/tool_visit.py index 50d68d10..afdeeff3 100644 --- a/WebAgent/WebResummer/src/tool_visit.py +++ b/WebAgent/WebResummer/src/tool_visit.py @@ -93,6 +93,8 @@ def call_server(self, msgs, max_retries=2): messages=msgs, temperature=0.7 ) + if not chat_response.choices or chat_response.choices[0].message is None: + raise ValueError("LLM returned empty or filtered response") content = chat_response.choices[0].message.content print(content) if content: diff --git a/WebAgent/WebSailor/src/evaluate.py b/WebAgent/WebSailor/src/evaluate.py index 6b3a22c2..ebb9621b 100644 --- a/WebAgent/WebSailor/src/evaluate.py +++ b/WebAgent/WebSailor/src/evaluate.py @@ -42,6 +42,8 @@ def call_llm_judge(item): model='qwen2.5-72b-instruct', messages=[{"role": "user", "content": prompt}], ) + if not chat_response.choices or chat_response.choices[0].message is None: + raise ValueError("LLM returned empty or filtered response") response = chat_response.choices[0].message.content if response: break diff --git a/WebAgent/WebSailor/src/react_agent.py b/WebAgent/WebSailor/src/react_agent.py index 9e5e1db2..603d6135 100644 --- a/WebAgent/WebSailor/src/react_agent.py +++ b/WebAgent/WebSailor/src/react_agent.py @@ -55,6 +55,8 @@ def call_server(self, msgs, max_tries=10): temperature=self.llm_generate_cfg.get('temperature', 0.6), top_p=self.llm_generate_cfg.get('top_p', 0.95), ) + if not chat_response.choices or chat_response.choices[0].message is None: + raise ValueError("LLM returned empty or filtered response") content = chat_response.choices[0].message.content if content: return content diff --git a/WebAgent/WebSailor/src/tool_visit.py b/WebAgent/WebSailor/src/tool_visit.py index ac8e5e61..cf7369ac 100644 --- a/WebAgent/WebSailor/src/tool_visit.py +++ b/WebAgent/WebSailor/src/tool_visit.py @@ -83,6 +83,8 @@ def call_server(self, msgs, max_tries=10): stop=["\n", ""], temperature=0.7 ) + if not chat_response.choices or chat_response.choices[0].message is None: + raise ValueError("LLM returned empty or filtered response") content = chat_response.choices[0].message.content if content: try: diff --git a/WebAgent/WebWalker/src/agent.py b/WebAgent/WebWalker/src/agent.py index c8e0421e..e49773e6 100644 --- a/WebAgent/WebWalker/src/agent.py +++ b/WebAgent/WebWalker/src/agent.py @@ -58,6 +58,8 @@ def observation_information_extraction(self, query, observation): response_format={"type": "json_object"}, messages=messages ) + if not response.choices or response.choices[0].message is None: + raise ValueError("LLM returned empty or filtered response") print(response.choices[0].message.content) # response_content = json.loads(response.choices[0].message.content) if "true" in response.choices[0].message.content: @@ -93,6 +95,8 @@ def critic_information(self, query, memory): response_format={"type": "json_object"}, messages=messages ) + if not response.choices or response.choices[0].message is None: + raise ValueError("LLM returned empty or filtered response") print(response.choices[0].message.content) if "true" in response.choices[0].message.content: try: diff --git a/WebAgent/WebWalker/src/rag_system.py b/WebAgent/WebWalker/src/rag_system.py index dd0e502a..69e0a382 100644 --- a/WebAgent/WebWalker/src/rag_system.py +++ b/WebAgent/WebWalker/src/rag_system.py @@ -161,6 +161,8 @@ def call(data): {"role": "user", "content": data["question"]} ] ) + if not completion.choices or completion.choices[0].message is None: + raise ValueError("LLM returned empty or filtered response") return completion.choices[0].message.content except Exception as e: print(f"Error: {e}") @@ -210,6 +212,8 @@ def chat(messages) -> Choice: "function": {"name": "$web_search"} }] ) + if not completion.choices: + raise ValueError("LLM returned empty choices") return completion.choices[0] except Exception as e: print(f"Error: {e}") diff --git a/WebAgent/WebWatcher/infer/scripts_eval/agent_eval.py b/WebAgent/WebWatcher/infer/scripts_eval/agent_eval.py index a94bf23c..ade41c2f 100644 --- a/WebAgent/WebWatcher/infer/scripts_eval/agent_eval.py +++ b/WebAgent/WebWatcher/infer/scripts_eval/agent_eval.py @@ -228,6 +228,8 @@ def run_main(self, sample): top_p=0.95, temperature=0.6 ) + if not response.choices or response.choices[0].message is None: + raise ValueError("LLM returned empty or filtered response") response_content = response.choices[0].message.content # break if response_content: diff --git a/WebAgent/WebWatcher/infer/vl_search_r1/qwen-agent-o1_search/qwen_agent/tools/private/visit.py b/WebAgent/WebWatcher/infer/vl_search_r1/qwen-agent-o1_search/qwen_agent/tools/private/visit.py index e94f49fb..142249f9 100644 --- a/WebAgent/WebWatcher/infer/vl_search_r1/qwen-agent-o1_search/qwen_agent/tools/private/visit.py +++ b/WebAgent/WebWatcher/infer/vl_search_r1/qwen-agent-o1_search/qwen_agent/tools/private/visit.py @@ -102,6 +102,8 @@ def call_server(self, msgs, max_tries=10): stop=["\n", ""], temperature=0.7 ) + if not chat_response.choices or chat_response.choices[0].message is None: + raise ValueError("LLM returned empty or filtered response") content = chat_response.choices[0].message.content if content: try: diff --git a/WebAgent/WebWeaver/react_agent_outline_write.py b/WebAgent/WebWeaver/react_agent_outline_write.py index 2e2e8d24..48994322 100644 --- a/WebAgent/WebWeaver/react_agent_outline_write.py +++ b/WebAgent/WebWeaver/react_agent_outline_write.py @@ -58,6 +58,8 @@ def call_server(self, msgs, max_tries=10): temperature=self.llm_generate_cfg.get('temperature', 0.6), top_p=self.llm_generate_cfg.get('top_p', 0.95), ) + if not chat_response.choices or chat_response.choices[0].message is None: + raise ValueError("LLM returned empty or filtered response") content = chat_response.choices[0].message.content if content: return content diff --git a/WebAgent/WebWeaver/react_agent_search_id.py b/WebAgent/WebWeaver/react_agent_search_id.py index fcefd657..4ad7ca09 100644 --- a/WebAgent/WebWeaver/react_agent_search_id.py +++ b/WebAgent/WebWeaver/react_agent_search_id.py @@ -64,6 +64,8 @@ def call_server(self, msgs, max_tries=10): temperature=self.llm_generate_cfg.get('temperature', 0.6), top_p=self.llm_generate_cfg.get('top_p', 0.95), ) + if not chat_response.choices or chat_response.choices[0].message is None: + raise ValueError("LLM returned empty or filtered response") content = chat_response.choices[0].message.content if content: return content diff --git a/WebAgent/WebWeaver/tool/tool_retrieve.py b/WebAgent/WebWeaver/tool/tool_retrieve.py index e024f961..ee527fc4 100644 --- a/WebAgent/WebWeaver/tool/tool_retrieve.py +++ b/WebAgent/WebWeaver/tool/tool_retrieve.py @@ -185,6 +185,8 @@ def call_server(self, msgs, max_tries=10): messages=msgs, temperature=0.7 ) + if not chat_response.choices or chat_response.choices[0].message is None: + raise ValueError("LLM returned empty or filtered response") content = chat_response.choices[0].message.content if content: try: diff --git a/inference/file_tools/video_analysis.py b/inference/file_tools/video_analysis.py index acc0a587..45352e7d 100644 --- a/inference/file_tools/video_analysis.py +++ b/inference/file_tools/video_analysis.py @@ -588,6 +588,8 @@ def _analyze_media(self, prompt: str, transcript: str, frames: List[str], is_aud messages=messages, temperature=0.3, ) + if not response.choices or response.choices[0].message is None: + raise ValueError("LLM returned empty or filtered response") return response.choices[0].message.content except Exception as e: logger.error(f"AI analysis failed: {str(e)}") diff --git a/inference/react_agent.py b/inference/react_agent.py index b9f769ee..ef4a7ed5 100644 --- a/inference/react_agent.py +++ b/inference/react_agent.py @@ -81,6 +81,8 @@ def call_server(self, msgs, planning_port, max_tries=10): max_tokens=10000, presence_penalty=self.llm_generate_cfg.get('presence_penalty', 1.1) ) + if not chat_response.choices or chat_response.choices[0].message is None: + raise ValueError("LLM returned empty or filtered response") content = chat_response.choices[0].message.content # OpenRouter provides API calling. If you want to use OpenRouter, you need to uncomment line 89 - 90. diff --git a/inference/tool_visit.py b/inference/tool_visit.py index 6a03bac1..b7d5ea17 100644 --- a/inference/tool_visit.py +++ b/inference/tool_visit.py @@ -111,6 +111,8 @@ def call_server(self, msgs, max_retries=2): messages=msgs, temperature=0.7 ) + if not chat_response.choices or chat_response.choices[0].message is None: + raise ValueError("LLM returned empty or filtered response") content = chat_response.choices[0].message.content if content: try: