@@ -412,14 +412,22 @@ def parts(self):
412412 """
413413 candidates = self .candidates
414414 if not candidates :
415- raise ValueError (
415+ msg = (
416416 "Invalid operation: The `response.parts` quick accessor requires a single candidate, "
417- "but none were returned. Please check the `response.prompt_feedback` to determine if the prompt was blocked ."
417+ "but but `response.candidates` is empty ."
418418 )
419+ if self .prompt_feedback :
420+ raise ValueError (
421+ msg + "\n This appears to be caused by a blocked prompt, "
422+ f"see `response.prompt_feedback`: { self .prompt_feedback } "
423+ )
424+ else :
425+ raise ValueError (msg )
426+
419427 if len (candidates ) > 1 :
420428 raise ValueError (
421- "Invalid operation: The `response.parts` quick accessor requires a single candidate. "
422- "For multiple candidates, please use `result.candidates[index].text`."
429+ "Invalid operation: The `response.parts` quick accessor retrieves the parts for a single candidate. "
430+ "This response contains multiple candidates, please use `result.candidates[index].text`."
423431 )
424432 parts = candidates [0 ].content .parts
425433 return parts
@@ -433,10 +441,53 @@ def text(self):
433441 """
434442 parts = self .parts
435443 if not parts :
436- raise ValueError (
437- "Invalid operation: The `response.text` quick accessor requires the response to contain a valid `Part`, "
438- "but none were returned. Please check the `candidate.safety_ratings` to determine if the response was blocked."
444+ candidate = self .candidates [0 ]
445+
446+ fr = candidate .finish_reason
447+ FinishReason = protos .Candidate .FinishReason
448+
449+ msg = (
450+ "Invalid operation: The `response.text` quick accessor requires the response to contain a valid "
451+ "`Part`, but none were returned. The candidate's "
452+ f"[finish_reason](https://ai.google.dev/api/generate-content#finishreason) is { fr } ."
439453 )
454+ if candidate .finish_message :
455+ msg += 'The `finish_message` is "{candidate.finish_message}".'
456+
457+ if fr is FinishReason .FINISH_REASON_UNSPECIFIED :
458+ raise ValueError (msg )
459+ elif fr is FinishReason .STOP :
460+ raise ValueError (msg )
461+ elif fr is FinishReason .MAX_TOKENS :
462+ raise ValueError (msg )
463+ elif fr is FinishReason .SAFETY :
464+ raise ValueError (
465+ msg + f" The candidate's safety_ratings are: { candidate .safety_ratings } ." ,
466+ candidate .safety_ratings ,
467+ )
468+ elif fr is FinishReason .RECITATION :
469+ raise ValueError (
470+ msg + " Meaning that the model was reciting from copyrighted material."
471+ )
472+ elif fr is FinishReason .LANGUAGE :
473+ raise ValueError (msg + " Meaning the response was using an unsupported language." )
474+ elif fr is FinishReason .OTHER :
475+ raise ValueError (msg )
476+ elif fr is FinishReason .BLOCKLIST :
477+ raise ValueError (msg )
478+ elif fr is FinishReason .PROHIBITED_CONTENT :
479+ raise ValueError (msg )
480+ elif fr is FinishReason .SPII :
481+ raise ValueError (msg + " SPII - Sensitive Personally Identifiable Information." )
482+ elif fr is FinishReason .MALFORMED_FUNCTION_CALL :
483+ raise ValueError (
484+ msg + " Meaning that model generated a `FunctionCall` that was invalid. "
485+ "Setting the "
486+ "[Function calling mode](https://ai.google.dev/gemini-api/docs/function-calling#function_calling_mode) "
487+ "to `ANY` can fix this because it enables constrained decoding."
488+ )
489+ else :
490+ raise ValueError (msg )
440491
441492 texts = []
442493 for part in parts :
0 commit comments