20
20
BuiltinToolCallPart ,
21
21
BuiltinToolReturnPart ,
22
22
FileUrl ,
23
+ FinishReason ,
23
24
ModelMessage ,
24
25
ModelRequest ,
25
26
ModelResponse ,
54
55
ContentUnionDict ,
55
56
CountTokensConfigDict ,
56
57
ExecutableCodeDict ,
58
+ FinishReason as GoogleFinishReason ,
57
59
FunctionCallDict ,
58
60
FunctionCallingConfigDict ,
59
61
FunctionCallingConfigMode ,
99
101
See [the Gemini API docs](https://ai.google.dev/gemini-api/docs/models/gemini#model-variations) for a full list.
100
102
"""
101
103
104
+ _FINISH_REASON_MAP : dict [GoogleFinishReason , FinishReason | None ] = {
105
+ GoogleFinishReason .FINISH_REASON_UNSPECIFIED : None ,
106
+ GoogleFinishReason .STOP : 'stop' ,
107
+ GoogleFinishReason .MAX_TOKENS : 'length' ,
108
+ GoogleFinishReason .SAFETY : 'content_filter' ,
109
+ GoogleFinishReason .RECITATION : 'content_filter' ,
110
+ GoogleFinishReason .LANGUAGE : 'error' ,
111
+ GoogleFinishReason .OTHER : None ,
112
+ GoogleFinishReason .BLOCKLIST : 'content_filter' ,
113
+ GoogleFinishReason .PROHIBITED_CONTENT : 'content_filter' ,
114
+ GoogleFinishReason .SPII : 'content_filter' ,
115
+ GoogleFinishReason .MALFORMED_FUNCTION_CALL : 'error' ,
116
+ GoogleFinishReason .IMAGE_SAFETY : 'content_filter' ,
117
+ GoogleFinishReason .UNEXPECTED_TOOL_CALL : 'error' ,
118
+ }
119
+
102
120
103
121
class GoogleModelSettings (ModelSettings , total = False ):
104
122
"""Settings used for a Gemini model request."""
@@ -403,11 +421,14 @@ def _process_response(self, response: GenerateContentResponse) -> ModelResponse:
403
421
'Content field missing from Gemini response' , str (response )
404
422
) # pragma: no cover
405
423
parts = candidate .content .parts or []
406
- vendor_id = response .response_id or None
424
+
425
+ vendor_id = response .response_id
407
426
vendor_details : dict [str , Any ] | None = None
408
- finish_reason = candidate .finish_reason
409
- if finish_reason : # pragma: no branch
410
- vendor_details = {'finish_reason' : finish_reason .value }
427
+ finish_reason : FinishReason | None = None
428
+ if raw_finish_reason := candidate .finish_reason : # pragma: no branch
429
+ vendor_details = {'finish_reason' : raw_finish_reason .value }
430
+ finish_reason = _FINISH_REASON_MAP .get (raw_finish_reason )
431
+
411
432
usage = _metadata_as_usage (response )
412
433
return _process_response_from_parts (
413
434
parts ,
@@ -416,6 +437,7 @@ def _process_response(self, response: GenerateContentResponse) -> ModelResponse:
416
437
usage ,
417
438
vendor_id = vendor_id ,
418
439
vendor_details = vendor_details ,
440
+ finish_reason = finish_reason ,
419
441
)
420
442
421
443
async def _process_streamed_response (
@@ -550,6 +572,14 @@ async def _get_event_iterator(self) -> AsyncIterator[ModelResponseStreamEvent]:
550
572
551
573
assert chunk .candidates is not None
552
574
candidate = chunk .candidates [0 ]
575
+
576
+ if chunk .response_id : # pragma: no branch
577
+ self .provider_response_id = chunk .response_id
578
+
579
+ if raw_finish_reason := candidate .finish_reason :
580
+ self .provider_details = {'finish_reason' : raw_finish_reason .value }
581
+ self .finish_reason = _FINISH_REASON_MAP .get (raw_finish_reason )
582
+
553
583
if candidate .content is None or candidate .content .parts is None :
554
584
if candidate .finish_reason == 'STOP' : # pragma: no cover
555
585
# Normal completion - skip this chunk
@@ -632,6 +662,7 @@ def _process_response_from_parts(
632
662
usage : usage .RequestUsage ,
633
663
vendor_id : str | None ,
634
664
vendor_details : dict [str , Any ] | None = None ,
665
+ finish_reason : FinishReason | None = None ,
635
666
) -> ModelResponse :
636
667
items : list [ModelResponsePart ] = []
637
668
for part in parts :
@@ -672,6 +703,7 @@ def _process_response_from_parts(
672
703
provider_response_id = vendor_id ,
673
704
provider_details = vendor_details ,
674
705
provider_name = provider_name ,
706
+ finish_reason = finish_reason ,
675
707
)
676
708
677
709
0 commit comments