Skip to content

fix: guard against empty choices and null message in Azure LLM response#305

Open
qizwiz wants to merge 1 commit into
PySpur-Dev:mainfrom
qizwiz:fix/llm-response-unguarded-crash
Open

fix: guard against empty choices and null message in Azure LLM response#305
qizwiz wants to merge 1 commit into
PySpur-Dev:mainfrom
qizwiz:fix/llm-response-unguarded-crash

Conversation

@qizwiz
Copy link
Copy Markdown

@qizwiz qizwiz commented May 18, 2026

Summary

In backend/pyspur/nodes/llm/_utils.py, the Azure path inside completion_with_backoff() accesses response.choices[0].message.content without first verifying that choices is non-empty and message is not None:

response = await acompletion(**azure_kwargs, drop_params=True)
return response.choices[0].message.content  # ← unguarded

Two crash vectors:

  • IndexError — API returns an empty choices list (network hiccup, quota exhaustion, provider-side filtering)
  • AttributeErrorchoices[0].message is None (documented Gemini PROHIBITED_CONTENT behaviour: returns HTTP 200 with a null message object rather than an error status)

Both exceptions bypass the existing except Exception handler and propagate unhandled to the tenacity retry wrapper, which may exhaust retries and surface a confusing traceback to the caller.

Change

# Before
return response.choices[0].message.content

# After
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

The ValueError is caught by the inner except Exception block (logging.error + re-raise), then by the tenacity retry policy — matching existing behaviour for other transient errors.

References

In completion_with_backoff(), the Azure path returns
response.choices[0].message.content without first verifying that
choices is non-empty and message is not None.

- IndexError: raised if the API returns an empty choices list
- AttributeError: raised if choices[0].message is None (documented
  Gemini PROHIBITED_CONTENT behaviour that returns HTTP 200 with a
  null message object)

Both exceptions bypass the surrounding try/except and propagate
unhandled to callers.

Add the standard guard before the content access.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant