fix: guard against empty choices and None message in LLM responses (#924)

The OpenAI-compatible API can return HTTP 200 with an empty `choices`
list or with `choices[0].message = None` (e.g. content-filtered
responses on Gemini, overwhelmed Ollama instances). Without a guard,
both sites raise an unhandled IndexError or AttributeError.

`_call_openai_compat` already documents this hazard ("Ollama can return
HTTP 200 with empty/null content") and has `_response_is_hollow` logic
downstream, but `_response_is_hollow` is unreachable when the choices
list itself is empty. The new guard closes that gap.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jonathan Hill
2026-05-18 12:04:45 +01:00
committed by GitHub
co-authored by Claude Sonnet 4.6
parent 596d800aa1
commit f5fea13dbc
+4
View File
@@ -345,6 +345,8 @@ def _call_openai_compat(
keep_alive = os.environ.get("GRAPHIFY_OLLAMA_KEEP_ALIVE", "30m")
kwargs["extra_body"] = {"options": {"num_ctx": num_ctx}, "keep_alive": keep_alive}
resp = client.chat.completions.create(**kwargs)
if not resp.choices or resp.choices[0].message is None:
raise ValueError("LLM returned empty or filtered response")
raw_content = resp.choices[0].message.content
result = _parse_llm_json(raw_content or "{}")
result["input_tokens"] = resp.usage.prompt_tokens if resp.usage else 0
@@ -1038,6 +1040,8 @@ def _call_llm(prompt: str, *, backend: str, max_tokens: int = 200) -> str:
if "moonshot" in cfg["base_url"]:
kwargs["extra_body"] = {"thinking": {"type": "disabled"}}
resp = client.chat.completions.create(**kwargs)
if not resp.choices or resp.choices[0].message is None:
raise ValueError("LLM returned empty or filtered response")
return resp.choices[0].message.content or ""