Skip to content

fix(openai-completions): keep a refusal instead of returning an empty turn - #484

Open
pcbeingused333 wants to merge 1 commit into
posit-dev:mainfrom
pcbeingused333:fix/completions-refusal
Open

pcbeingused333 wants to merge 1 commit into
posit-dev:mainfrom
pcbeingused333:fix/completions-refusal

Conversation

@pcbeingused333

Copy link
Copy Markdown
Contributor

A refused Chat Completions request (for example a refused structured output) ends with finish_reason: "stop", content: null and the text in message.refusal, or in delta.refusal when streamed. OpenAICompletionsProvider ignored the field, so the turn came back empty and was reported as "success": chat.chat(...) returned an empty string, a streamed call yielded nothing, and with a data_model the failure showed up as a missing JSON payload instead of a refusal.

The provider now yields streamed delta.refusal text, keeps the refusal as the turn's text when the message has no content, and reports finish_reason == "content_filter" for it, which makes the existing "filtered by the provider's content moderation policy" warning (or error, with a data_model) apply. Responses with content are unchanged, and so is a refusal-free stop. Databricks, Cloudflare, Portkey, Perplexity, Hugging Face and the other providers built on this class inherit the change.

Tests (tests/test_provider_openai_completions.py): a refused ChatCompletion gives the refusal text and content_filter; the same with a data_model raises the content-moderation error; and streamed chunks with delta.refusal fragments are yielded as text, merged, and end in a turn with the full text and content_filter. All three fail on main. The existing tests in the file still pass; the failures in tests/test_chat.py in my environment come from missing optional dependencies and are the same on main. No request was made to a live server.

This change was made with Claude Code; the tests above were run before opening the PR.

🤖 Generated with Claude Code

… turn

A refused Chat Completions request ends with finish_reason "stop", no content
and the text in message.refusal (delta.refusal when streamed). The provider
ignored the field, so the turn was empty and reported as a success. Yield the
streamed refusal, keep it as the turn's text, and report content_filter so the
usual "filtered by the provider" warning (or error, with a data model) applies.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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