Skip to content

Commit bce5801

Browse files
Python: fix(google_ai): report more Gemini finish reasons as content_filter (#14632)
### Motivation and Context `finish_reason_from_google_ai_to_semantic_kernel` only mapped `SAFETY` to `FinishReason.CONTENT_FILTER`. Gemini also ends a response for `RECITATION`, `BLOCKLIST`, `PROHIBITED_CONTENT` and `SPII` (and the image variants available across the supported google-genai range), and for those the message came back with `finish_reason=None`, indistinguishable from an unrecognised value or an unfinished response. ### Description - `SAFETY`, `RECITATION`, `BLOCKLIST`, `PROHIBITED_CONTENT`, `SPII`, `IMAGE_SAFETY` and `IMAGE_PROHIBITED_CONTENT` map to `FinishReason.CONTENT_FILTER`. - `STOP` and `MAX_TOKENS` are unchanged; other reasons (`OTHER`, `MALFORMED_FUNCTION_CALL`, ...) still return `None`. - A parametrized unit test covers the new reasons; its 6 cases fail on `main` and pass with the change. `tests/unit/connectors/ai/google` passes (158). - The deprecated `vertex_ai` connector is left as is. ### Contribution Checklist - [x] The code builds clean without any errors or warnings - [x] The PR follows the SK Contribution Guidelines - [x] All unit tests pass, and I have added new tests where possible - [x] I didn't break anyone 😄 This change was made with Claude Code; the tests were run against `main` and the branch.
1 parent 5377800 commit bce5801

2 files changed

Lines changed: 29 additions & 1 deletion

File tree

‎python/semantic_kernel/connectors/ai/google/google_ai/services/utils.py‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,18 @@
3131
logger: logging.Logger = logging.getLogger(__name__)
3232

3333

34+
# Finish reasons where Gemini stopped the response for the content, not for length or a natural end.
35+
_GOOGLE_AI_CONTENT_FILTER_REASONS = frozenset({
36+
FinishReason.SAFETY,
37+
FinishReason.RECITATION,
38+
FinishReason.BLOCKLIST,
39+
FinishReason.PROHIBITED_CONTENT,
40+
FinishReason.SPII,
41+
FinishReason.IMAGE_SAFETY,
42+
FinishReason.IMAGE_PROHIBITED_CONTENT,
43+
})
44+
45+
3446
def finish_reason_from_google_ai_to_semantic_kernel(
3547
finish_reason: FinishReason | None,
3648
) -> SemanticKernelFinishReason | None:
@@ -47,7 +59,7 @@ def finish_reason_from_google_ai_to_semantic_kernel(
4759
if finish_reason == FinishReason.MAX_TOKENS:
4860
return SemanticKernelFinishReason.LENGTH
4961

50-
if finish_reason == FinishReason.SAFETY:
62+
if finish_reason in _GOOGLE_AI_CONTENT_FILTER_REASONS:
5163
return SemanticKernelFinishReason.CONTENT_FILTER
5264

5365
return None

‎python/tests/unit/connectors/ai/google/google_ai/services/test_google_ai_utils.py‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,22 @@ def test_finish_reason_from_google_ai_to_semantic_kernel():
3333
assert finish_reason_from_google_ai_to_semantic_kernel(None) is None
3434

3535

36+
@pytest.mark.parametrize(
37+
"reason",
38+
[
39+
FinishReason.RECITATION,
40+
FinishReason.BLOCKLIST,
41+
FinishReason.PROHIBITED_CONTENT,
42+
FinishReason.SPII,
43+
FinishReason.IMAGE_SAFETY,
44+
FinishReason.IMAGE_PROHIBITED_CONTENT,
45+
],
46+
)
47+
def test_content_filter_finish_reasons_from_google_ai(reason):
48+
"""Responses Gemini stopped for their content are reported as content filtered."""
49+
assert finish_reason_from_google_ai_to_semantic_kernel(reason) == SemanticKernelFinishReason.CONTENT_FILTER
50+
51+
3652
def test_format_user_message():
3753
"""Test format_user_message."""
3854
user_message = ChatMessageContent(role=AuthorRole.USER, content="User message")

0 commit comments

Comments
 (0)