fix(openai): raise on empty chat completion instead of clean end_turn - #208
Open
gadievron wants to merge 1 commit into
Open
fix(openai): raise on empty chat completion instead of clean end_turn#208gadievron wants to merge 1 commit into
gadievron wants to merge 1 commit into
Conversation
The Chat Completions path guarded an empty `choices` array but not an empty message: a choice whose `message.content` is None/empty with no `tool_calls` returned a clean `end_turn` carrying no content. For a security tool an empty end_turn reads as a passing verdict, so a blank completion became a silent false-negative. Add the no-usable-content guard the three sibling paths already have -- the Responses path, the Anthropic adapter, and the Gemini adapter all raise `LLMResponseError` on empty content. A tool-use-only response stays valid (content_blocks is non-empty); refusal/content_filter still raises first as the more specific signal. Regression test: a choice with content=None and no tool_calls now raises `LLMResponseError` (was a clean end_turn). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
gadievron
requested review from
dgeyshis,
shahar-davidson and
sounil
as code owners
August 2, 2026 10:41
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Root cause
The OpenAI Chat Completions translation (
_response_to_unifiedinutilities/llm/providers/openai.py) guarded an emptychoicesarray but not an empty message. A choice whosemessage.contentisNone/empty with notool_callsfell through to a cleanend_turncarrying zero content blocks. For a security tool, an emptyend_turnis read as a clean, passing verdict — so a blank completion became a silent false-negative.This is the same silent-empty-completion shape #207 closed across the other paths, but the chat path never had the guard (it predates #207, which never touched
openai.py). The three sibling paths already raise on empty content:openai.py_responses_to_unified"no usable content" guardcandidatesguardFix
Add the missing no-usable-content guard to the chat path, mirroring the siblings: after the
content_filterrefusal check (the more specific signal, raised first),if not content_blocks: raise LLMResponseError(...). A tool-use-only response stays valid becausecontent_blocksis non-empty.Reproduction
A
ChatCompletionwith one choice,message.content=None,tool_calls=None,finish_reason="stop".Regression test
tests/test_llm_openai_adapter.py::test_empty_content_raises_llm_response_error— sibling of the existingtest_empty_choices_raises_llm_response_error.Compatibility
No API/signature change. Behavior change is narrow: a chat response with neither text nor tool calls now raises
LLMResponseErrorinstead of returning an emptyend_turn. Tool-use-only and normal text responses are unaffected; refusal/content_filterstill raises first.Surfaced by a post-merge collision review of #206/#207 (this gap is pre-existing, not introduced by either).