fix(vertex_ai): preserve uppercase Gemini finish_reason so SAFETY maps to content_filter

Lowercasing hard-stop finish reasons (SAFETY, RECITATION, BLOCKLIST,
PROHIBITED_CONTENT, SPII) before handing them to StreamingChoices caused
map_finish_reason to miss the _FINISH_REASON_MAP lookup (which is keyed
on the uppercase Gemini values) and silently fall back to 'stop'. Pass
the raw uppercase finish_reason through so safety-blocked Agent Engine
responses surface as 'content_filter' as intended.
This commit is contained in:
Cursor Agent 2026-05-12 16:10:41 +00:00
parent 10d6dfe701
commit 91a8fd4b89
No known key found for this signature in database
2 changed files with 10 additions and 7 deletions

View file

@ -119,9 +119,11 @@ class VertexAgentEngineResponseIterator(BaseModelResponseIterator):
if raw_finish_reason and (
text is not None or raw_finish_reason in self._HARD_STOP_FINISH_REASONS
):
finish_reason = (
"stop" if raw_finish_reason == "STOP" else raw_finish_reason.lower()
)
# Pass the raw Gemini finish reason (uppercase) through so that
# downstream ``map_finish_reason`` can map hard-stop signals like
# ``SAFETY`` / ``RECITATION`` to ``content_filter`` instead of
# silently falling back to ``stop``.
finish_reason = raw_finish_reason
usage = None
usage_metadata = chunk.get("usage_metadata") or {}

View file

@ -216,10 +216,11 @@ class TestVertexAgentEngineChunkParser:
result = self._iterator().chunk_parser(chunk)
# StreamingChoices normalizes "safety" → OpenAI "stop"; the key
# behavior is that *some* terminal finish_reason is surfaced
# instead of being dropped (which is what happens for plain STOP).
assert result.choices[0].finish_reason is not None
# StreamingChoices normalizes Gemini "SAFETY" → OpenAI "content_filter"
# via map_finish_reason; the raw uppercase value must be passed through
# so that downstream consumers can distinguish a safety block from a
# plain stop.
assert result.choices[0].finish_reason == "content_filter"
def test_chunk_parser_surfaces_max_tokens_finish_reason_without_content(self):
chunk = {