Skip to content

fix(provider): add None check for completion.choices to prevent TypeError#6316

Open
ccsang wants to merge 3 commits intoAstrBotDevs:devfrom
ccsang:fix/openai-choices-none-check
Open

fix(provider): add None check for completion.choices to prevent TypeError#6316
ccsang wants to merge 3 commits intoAstrBotDevs:devfrom
ccsang:fix/openai-choices-none-check

Conversation

@ccsang
Copy link
Copy Markdown
Contributor

@ccsang ccsang commented Mar 15, 2026

Fixes #6252

Problem

When using certain providers (e.g., OpenRouter), the API may return completion.choices as None instead of an empty list. This causes:

TypeError: object of type 'NoneType' has no len()

in three locations where len(completion.choices) is called without checking for None first.

Solution

Add None checks before len() calls in three locations:

  1. Streaming chunk handler (line 314): In the async chunk iteration loop
  2. _extract_reasoning_content (line 348): When extracting reasoning from sync completion
  3. _parse_openai_completion (line 471): When parsing non-streaming completion

Changes

# Before:
if len(completion.choices) == 0:

# After:
if completion.choices is None or len(completion.choices) == 0:

Testing

  • Prevents TypeError when choices is None
  • Preserves existing behavior when choices is empty list
  • Minimal change, no functional side effects

Summary by Sourcery

Bug Fixes:

  • Prevent TypeError by safely handling cases where completion or chunk choices are None before checking their length in streaming and non-streaming completion parsing paths.

…rror

Fixes AstrBotDevs#6252

When using certain providers (e.g., OpenRouter), the API may return
completion.choices as None instead of an empty list. This causes
TypeError: object of type 'NoneType' has no len() when calling
len(completion.choices).

This fix adds None checks before len() calls in three locations:
- Streaming chunk handler (line 314)
- _extract_reasoning_content method (line 348)
- _parse_openai_completion method (line 471)
@auto-assign auto-assign Bot requested review from LIghtJUNction and anka-afk March 15, 2026 05:48
@dosubot dosubot Bot added the size:XS This PR changes 0-9 lines, ignoring generated files. label Mar 15, 2026
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a TypeError that occurs when certain API providers (like OpenRouter) return completion.choices as None instead of an empty list. By introducing None checks before attempting to call len() on completion.choices in three critical locations, the change enhances the robustness of the system, preventing crashes and ensuring consistent behavior regardless of the API response format.

Highlights

  • Error Prevention: Added a None check for completion.choices in the streaming chunk handler (line 314) to prevent TypeError during async chunk iteration.
  • Error Prevention: Implemented a None check for completion.choices in _extract_reasoning_content (line 348) to avoid TypeError when extracting reasoning from sync completions.
  • Error Prevention: Included a None check for completion.choices in _parse_openai_completion (line 471) to prevent TypeError when parsing non-streaming completions.
Changelog
  • astrbot/core/provider/sources/openai_source.py
    • Modified conditional logic to include a None check for completion.choices before evaluating its length, specifically changing if len(completion.choices) == 0: to if completion.choices is None or len(completion.choices) == 0:.
Activity
  • No human activity has been recorded for this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • The completion.choices is None or len(completion.choices) == 0 pattern is now duplicated in three places; consider extracting a small helper (e.g., has_choices(completion)) or using a single idiom like if not completion.choices: to keep this logic consistent and easier to maintain.
  • For the streaming handler and _extract_reasoning_content, you silently skip when choices is None; consider logging or otherwise surfacing this case so that an upstream provider returning None instead of an empty list is more visible during debugging.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `completion.choices is None or len(completion.choices) == 0` pattern is now duplicated in three places; consider extracting a small helper (e.g., `has_choices(completion)`) or using a single idiom like `if not completion.choices:` to keep this logic consistent and easier to maintain.
- For the streaming handler and `_extract_reasoning_content`, you silently skip when `choices` is `None`; consider logging or otherwise surfacing this case so that an upstream provider returning `None` instead of an empty list is more visible during debugging.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request correctly addresses a TypeError that occurs when completion.choices is None by adding explicit None checks before accessing its length. The fix is applied in three different locations to prevent the crash. My review includes suggestions to make these checks more concise and Pythonic by using truthiness evaluation, which simplifies the code while maintaining the same logic.

except Exception as e:
logger.warning("Saving chunk state error: " + str(e))
if len(chunk.choices) == 0:
if chunk.choices is None or len(chunk.choices) == 0:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For better readability and to follow Pythonic conventions, you can simplify this check. Checking for the truthiness of chunk.choices handles both None and an empty list, making the code more concise.

Suggested change
if chunk.choices is None or len(chunk.choices) == 0:
if not chunk.choices:

"""Extract reasoning content from OpenAI ChatCompletion if available."""
reasoning_text = ""
if len(completion.choices) == 0:
if completion.choices is None or len(completion.choices) == 0:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To make the code more concise and Pythonic, you can simplify this condition. A truthiness check on completion.choices covers both None and an empty list.

Suggested change
if completion.choices is None or len(completion.choices) == 0:
if not completion.choices:

llm_response = LLMResponse("assistant")

if len(completion.choices) == 0:
if completion.choices is None or len(completion.choices) == 0:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This check can be simplified to be more Pythonic. A truthiness check on completion.choices will correctly handle both None and an empty list, which is a common idiom in Python.

Suggested change
if completion.choices is None or len(completion.choices) == 0:
if not completion.choices:

@dosubot dosubot Bot added the area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. label Mar 15, 2026
Address Sourcery AI review comment:
- Use 'if not completion.choices:' instead of explicit None check
- This handles both None and empty list cases concisely
- More Pythonic and easier to maintain
@ccsang ccsang changed the base branch from master to dev March 16, 2026 08:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. size:XS This PR changes 0-9 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]频繁报错 LLM 响应错误: All chat models failed: TypeError: object of type 'NoneType' has no len()

2 participants