Skip to content

Handle OpenAI refusals and missing message content - #496

Open
malai001 wants to merge 4 commits into
google:mainfrom
malai001:fix/openai-refusal-handling
Open

Handle OpenAI refusals and missing message content#496
malai001 wants to merge 4 commits into
google:mainfrom
malai001:fix/openai-refusal-handling

Conversation

@malai001

@malai001 malai001 commented Jul 25, 2026

Copy link
Copy Markdown

Description

Fixes #491.

The realtime OpenAI provider previously returned
ScoredOutput(score=1.0, output=None) when message.content was
missing, causing refusal or content-filtered responses to be reported
as successful extractions.

This change aligns the realtime provider with the batch provider by:

  • Raising InferenceRuntimeError when message.content is None.
  • Surfacing the refusal reason when message.refusal is present.
  • Raising a descriptive error when neither content nor refusal is available.
  • Adding regression tests for successful responses, refusals, and missing-content responses.

How Has This Been Tested?

  • Added regression tests covering:
    • Successful completion
    • Refusal response (message.refusal)
    • Missing message.content
  • Ran:
python -m pytest tests/provider_schema_test.py

Result:

31 passed, 10 warnings

Checklist

  • I have read the contribution guidelines.
  • I have added or updated tests where appropriate.
  • I have verified that all tests pass locally.
  • My changes are limited to the scope of this fix.

@github-actions github-actions Bot added the size/S Pull request with 50-150 lines changed label Jul 25, 2026
@google-cla

google-cla Bot commented Jul 25, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@malai001
malai001 force-pushed the fix/openai-refusal-handling branch from 1e98e04 to f17989f Compare July 25, 2026 08:26
@github-actions

Copy link
Copy Markdown

⚠️ Branch Update Required

Your branch is 1 commits behind main. Please update your branch to ensure CI checks run with the latest code:

git fetch origin main
git merge origin/main
git push

Note: Enable "Allow edits by maintainers" to allow automatic updates.

@github-actions

Copy link
Copy Markdown

⚠️ Branch Update Required

Your branch is 1 commits behind main. Please update your branch to ensure CI checks run with the latest code:

git fetch origin main
git merge origin/main
git push

Note: Enable "Allow edits by maintainers" to allow automatic updates.

mittalpk added a commit to mittalpk/langextract that referenced this pull request Aug 15, 2026
…orting success

_process_single_prompt returned ScoredOutput(score=1.0, output=response.text)
unconditionally. google.genai's response.text returns None (not an exception)
when: the prompt was blocked before generation (prompt_feedback.block_reason
set, no candidates), a candidate stopped for a non-STOP finish_reason (SAFETY,
RECITATION, PROHIBITED_CONTENT, etc.) with no content, or the response
contains only non-text parts. Every one of these was silently reported as a
successful empty extraction, discarding the actual block/refusal reason.

This is the same class of bug already fixed for the OpenAI realtime provider
in google#491/PR google#496 -- that issue explicitly noted "The Gemini realtime path has
the same class of gap... worth folding the same guard in there too," but
PR google#496 only touched openai.py, leaving this side unaddressed. Verified
directly against the installed google-genai 2.11.0 SDK source
(GenerateContentResponse._get_text) that None is returned, not raised, in
all three cases.

Fix: check output_text is None after reading response.text, and raise
InferenceRuntimeError with a diagnostic built from whichever information is
available -- prompt_feedback.block_reason(+message) for prompt-level blocks,
candidates[0].finish_reason for candidate-level stops, or a generic message
otherwise. Also re-raises InferenceRuntimeError before the generic retry
handler, matching the OpenAI fix's intent: retrying a safety-blocked prompt
with identical content won't produce a different result.

Added 5 tests to GeminiRefusalHandlingTest in tests/provider_schema_test.py:
successful text, prompt-level block, candidate-level non-STOP finish reason,
no-diagnostic-available fallback, and a check that a refusal is not retried.
All 4 error-path tests confirmed to fail against the pre-fix code (git stash
isolation of just gemini.py). Full verification:
- tests/provider_schema_test.py: 32 passed (up from 27 baseline, exactly the
  5 new tests), same 1 pre-existing unrelated failure as clean upstream/main.
- tests -k gemini: 137 passed, 7 skipped (live API creds unavailable), 0
  failed.
- Full suite (-m "not live_api"): 673 passed (up from 668 baseline), same 39
  pre-existing unrelated failures (OpenAI kwargs passthrough) as clean
  upstream/main -- no regressions.
- tox -e format, tox -e lint-src, tox -e lint-tests (10.00/10): all clean.
@github-actions

Copy link
Copy Markdown

⚠️ Branch Update Required

Your branch is 1 commits behind main. Please update your branch to ensure CI checks run with the latest code:

git fetch origin main
git merge origin/main
git push

Note: Enable "Allow edits by maintainers" to allow automatic updates.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/S Pull request with 50-150 lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: OpenAI realtime path returns empty success (score=1.0, output=None) on a refusal / content-filtered response

1 participant