-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Consistently raise ContentFilterError when model response is empty because of content filter
#3634
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Consistently raise ContentFilterError when model response is empty because of content filter
#3634
Conversation
|
Is it possible to handle AWS Bedrock as well? Thanks. |
dsfaccini
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @AlanPonnachan thank you for the PR! I've requested a couple small changes, let me know if you have any questions
|
one more thing I missed, please include the name of the new exception in the fallback model docs
|
|
@dsfaccini Thank you for the review. I’ve made the requested changes. |
|
hey @AlanPonnachan thanks a lot for your work! It looks very good now, I requested a couple more changes but once that's done and coverage passes I think the PR will be ready for merge. |
|
@dsfaccini Thanks again for the review! I’ve applied the requested changes. Test coverage is now at 100%. |
DouweM
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@AlanPonnachan Thanks for working on this! My main concern is that this doesn't actually make it consistent for all models that respond with finish_reason=='content_filter', just for Anthropic/Google/OpenAI.
| chat.chat_completion.Choice( | ||
| finish_reason='content_filter', | ||
| index=0, | ||
| message=chat.ChatCompletionMessage(content='', role='assistant'), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need this? I'd want it to result in a ModelResponse with 0 parts, and this may result in a TextPart(content='')
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It may be cleaner to just build that ModelResponse directly...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a ModelResponse directly would be cleaner but _completions_create is strictly typed to return ChatCompletion (or AsyncStream).
I updated to constructing a ChatCompletion with content=None (not '') which will successfully results in a ModelResponse with 0 parts via _process_response.
|
@DouweM , I’ve made a few changes. Let me know your thoughts. |
ContentFilterError when model response is empty because of content filter
|
|
||
|
|
||
| class ContentFilterError(UnexpectedModelBehavior): | ||
| """Raised when content filtering is triggered by the model provider.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| """Raised when content filtering is triggered by the model provider.""" | |
| """Raised when content filtering is triggered by the model provider resulting in an empty response.""" |
|
|
||
|
|
||
| def _check_azure_content_filter(e: APIStatusError) -> bool: | ||
| """Check if the error is an Azure content filter error.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we do this only if self.system == 'azure'?
| # Check for content filter on empty response | ||
| if self.model_response.finish_reason == 'content_filter': | ||
| raise exceptions.ContentFilterError( | ||
| f'Content filter triggered for model {self.model_response.model_name}' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of naming the model (which we don't do anywhere else), I'd prefer to include model_response.provider_details['finish_reason'] if it exists, like we did in the Google exception
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Related to what I wrote in google.py about storing more of the actual error response context on provider_details, what do you think about including (JSONified) model_response on this exception, so that it can be accessed by the user (or in an observability platform)?
| ) | ||
|
|
||
|
|
||
| async def test_google_stream_empty_chunk( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need this?
| raw_finish_reason = candidate.finish_reason | ||
| if raw_finish_reason: # pragma: no branch | ||
| vendor_details = {'finish_reason': raw_finish_reason.value} | ||
| finish_reason = _FINISH_REASON_MAP.get(raw_finish_reason) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you able to test against Vertex AI?
It may be worth exposing these extra data points on ModelResponse.provider_details https://docs.cloud.google.com/vertex-ai/generative-ai/docs/multimodal/configure-safety-filters#gemini-api-in-vertex-ai
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar for the content_filter_result returned by Azure:
{
'message': "The response was filtered due to the prompt triggering Azure OpenAI's content management policy. Please modify your prompt and retry. To learn more about our content filtering policies please read our documentation: https://go.microsoft.com/fwlink/?linkid=2198766",
'type': None,
'param': 'prompt',
'code': 'content_filter',
'status': 400,
'innererror': {
'code': 'ResponsibleAIPolicyViolation',
'content_filter_result': {
'hate': {
'filtered': True,
'severity': 'high'
},
'jailbreak': {
'filtered': False,
'detected': False
},
'self_harm': {
'filtered': False,
'severity': 'safe'
},
'sexual': {
'filtered': False,
'severity': 'safe'
},
'violence': {
'filtered': False,
'severity': 'medium'
}
}
}
}
Unified Content Filtering Exception Handling
This PR closes #1035
Standardizing how content filtering events are handled across different model providers.
Previously, triggering a content filter resulted in inconsistent behaviors: generic
ModelHTTPError(Azure),UnexpectedModelBehavior(Google), or silent failures depending on the provider. This PR introduces a dedicated exception hierarchy to allow users to catch and handle prompt refusals and response interruptions programmatically and consistently.Key Changes:
ContentFilterError(base),PromptContentFilterError(for input rejections, e.g., Azure 400), andResponseContentFilterError(for output refusals).PromptContentFilterErrorfor Azure's specific 400 error body andResponseContentFilterErrorwhenfinish_reason='content_filter'._process_responseto raiseResponseContentFilterErrorinstead ofUnexpectedModelBehaviorwhen safety thresholds are triggered.ResponseContentFilterError.tests/models/.Example Usage: