Skip to content

fix(gemini,groq): include finish reason in unhandled finish reason exceptions#994

Open
datashaman wants to merge 1 commit intoprism-php:mainfrom
datashaman:fix/gemini-enriched-finish-reason-exception
Open

fix(gemini,groq): include finish reason in unhandled finish reason exceptions#994
datashaman wants to merge 1 commit intoprism-php:mainfrom
datashaman:fix/gemini-enriched-finish-reason-exception

Conversation

@datashaman
Copy link
Copy Markdown

@datashaman datashaman commented Mar 29, 2026

Description

Enriches the default PrismException in Gemini\Handlers\Text, Gemini\Handlers\Structured, and Groq\Handlers\Text to include both the mapped FinishReason enum value and the raw provider finish reason string — matching the approach already used in OpenAI handlers (PR #941).

Previously threw a generic "Gemini: unhandled finish reason" with no indication of what the actual reason was (e.g. SAFETY, LANGUAGE, etc.). Now throws 'Gemini: unhandled finish reason "content-filter" (raw: SAFETY)'.

Relates to #205.

Changes:

  • src/Providers/Gemini/Handlers/Text.php — enriched default exception with sprintf
  • src/Providers/Gemini/Handlers/Structured.php — same
  • src/Providers/Groq/Handlers/Text.php — same
  • Added Pest tests for Gemini Text and Structured content filter exceptions
  • Added test fixtures simulating SAFETY finish reason responses

Breaking Changes

None — only changes exception message strings in error paths.

…eason exceptions

The default branch in the finish reason match threw a generic "unhandled finish reason" message without including what the actual reason was, making debugging difficult. Now includes both the mapped FinishReason enum value and the raw provider finish reason string.

Affects Gemini Text, Gemini Structured, and Groq Text handlers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@datashaman
Copy link
Copy Markdown
Author

Additional context: unhandled finish reasons

While investigating this fix, I catalogued the complete set of finish reasons each provider can return vs what's currently handled.

Gemini

Gemini's FinishReasonMap currently maps 10 values, but Google's API defines at least 20+. These newer values are not in the map (they fall through to default => FinishReason::Other, then hit the exception this PR improves):

Value Notes
IMAGE_SAFETY Image generation safety violation
IMAGE_PROHIBITED_CONTENT Image generation prohibited content
IMAGE_RECITATION Image generation recitation
IMAGE_OTHER Image generation misc
NO_IMAGE Expected image but none produced
UNEXPECTED_TOOL_CALL Tool call generated but no tools enabled
TOO_MANY_TOOL_CALLS Too many consecutive tool calls (Google AI only)
MISSING_THOUGHT_SIGNATURE Thought signature missing (Google AI only)
MALFORMED_RESPONSE Malformed response (Google AI only)
MODEL_ARMOR Blocked by Model Armor (Vertex AI only)

Additionally, 8 values that are in the FinishReasonMap (mapping to ContentFilter or Other) still hit the default exception branch in the handler because only Stop, Length, and ToolCalls are matched:

SAFETY, RECITATION, BLOCKLIST, PROHIBITED_CONTENT, SPII, MALFORMED_FUNCTION_CALL, LANGUAGE, OTHER, FINISH_REASON_UNSPECIFIED

Groq

Groq only has 4 finish reasons (stop, length, tool_calls, function_call). The first three are handled. function_call (deprecated) is not in the FinishReasonMap.


These are potential follow-up items — this PR just ensures the exception messages are useful for debugging when any of them are encountered.

Sources: Google AI REST v1, Vertex AI REST v1, Groq API reference

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