Content-Length: 532977 | pFad | https://github.com/googleapis/python-aiplatform/commit/e82264d273e35d3b305d434181badfb63a37c79c

21 feat: GenAI - Improved the exception messages when candidates, parts … · googleapis/python-aiplatform@e82264d · GitHub
Skip to content

Commit e82264d

Browse files
Ark-kuncopybara-github
authored andcommitted
feat: GenAI - Improved the exception messages when candidates, parts or text are not available
Improved cases: * Response has no Candidates * Response Candidate Content has no Parts * Response Candidate Content Part has no text PiperOrigin-RevId: 628236405
1 parent 3d22a18 commit e82264d

File tree

2 files changed

+97
-5
lines changed

2 files changed

+97
-5
lines changed

tests/unit/vertexai/test_generative_models.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,58 @@ def test_generate_content_streaming(self, generative_models: generative_models):
497497
for chunk in stream:
498498
assert chunk.text
499499

500+
@mock.patch.object(
501+
target=prediction_service.PredictionServiceClient,
502+
attribute="generate_content",
503+
new=mock_generate_content,
504+
)
505+
@pytest.mark.parametrize(
506+
"generative_models",
507+
[generative_models, preview_generative_models],
508+
)
509+
def test_generate_content_response_accessor_errors(
510+
self, generative_models: generative_models
511+
):
512+
"""Checks that the exception text contains response information."""
513+
model = generative_models.GenerativeModel("gemini-pro")
514+
515+
# Case when response has no candidates
516+
response1 = model.generate_content("Please block with block_reason=OTHER")
517+
518+
assert response1.prompt_feedback.block_reason.name == "OTHER"
519+
520+
with pytest.raises(ValueError) as e:
521+
_ = response1.text
522+
assert e.match("no candidates")
523+
assert e.match("prompt_feedback")
524+
525+
# Case when response candidate content has no parts
526+
response2 = model.generate_content("Please fail!")
527+
528+
with pytest.raises(ValueError) as e:
529+
_ = response2.text
530+
assert e.match("no parts")
531+
assert e.match("finish_reason")
532+
533+
with pytest.raises(ValueError) as e:
534+
_ = response2.candidates[0].text
535+
assert e.match("no parts")
536+
assert e.match("finish_reason")
537+
538+
# Case when response candidate content part has no text
539+
weather_tool = generative_models.Tool(
540+
function_declarations=[
541+
generative_models.FunctionDeclaration.from_func(get_current_weather)
542+
],
543+
)
544+
response3 = model.generate_content(
545+
"What's the weather like in Boston?", tools=[weather_tool]
546+
)
547+
with pytest.raises(ValueError) as e:
548+
print(response3.text)
549+
assert e.match("no text")
550+
assert e.match("function_call")
551+
500552
@mock.patch.object(
501553
target=prediction_service.PredictionServiceClient,
502554
attribute="generate_content",

vertexai/generative_models/_generative_models.py

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import copy
1919
import io
20+
import json
2021
import pathlib
2122
from typing import (
2223
Any,
@@ -1658,8 +1659,23 @@ def text(self) -> str:
16581659
" Use `response.candidate[i].text` to get text of a particular candidate."
16591660
)
16601661
if not self.candidates:
1661-
raise ValueError("Response has no candidates (and no text).")
1662-
return self.candidates[0].text
1662+
raise ValueError(
1663+
"Response has no candidates (and thus no text)."
1664+
" The response is likely blocked by the safety filters.\n"
1665+
"Response:\n"
1666+
+ _dict_to_pretty_string(self.to_dict())
1667+
)
1668+
try:
1669+
return self.candidates[0].text
1670+
except (ValueError, AttributeError) as e:
1671+
# Enrich the error message with the whole Response.
1672+
# The Candidate object does not have full information.
1673+
raise ValueError(
1674+
"Cannot get the response text.\n"
1675+
f"{e}\n"
1676+
"Response:\n"
1677+
+ _dict_to_pretty_string(self.to_dict())
1678+
) from e
16631679

16641680
@property
16651681
def prompt_feedback(
@@ -1728,7 +1744,17 @@ def citation_metadata(self) -> gapic_content_types.CitationMetadata:
17281744
# GenerationPart properties
17291745
@property
17301746
def text(self) -> str:
1731-
return self.content.text
1747+
try:
1748+
return self.content.text
1749+
except (ValueError, AttributeError) as e:
1750+
# Enrich the error message with the whole Candidate.
1751+
# The Content object does not have full information.
1752+
raise ValueError(
1753+
"Cannot get the Candidate text.\n"
1754+
f"{e}\n"
1755+
"Candidate:\n"
1756+
+ _dict_to_pretty_string(self.to_dict())
1757+
) from e
17321758

17331759
@property
17341760
def function_calls(self) -> Sequence[gapic_tool_types.FunctionCall]:
@@ -1799,7 +1825,12 @@ def text(self) -> str:
17991825
if len(self.parts) > 1:
18001826
raise ValueError("Multiple content parts are not supported.")
18011827
if not self.parts:
1802-
raise AttributeError("Content has no parts.")
1828+
raise ValueError(
1829+
"Response candidate content has no parts (and thus no text)."
1830+
" The candidate is likely blocked by the safety filters.\n"
1831+
"Content:\n"
1832+
+ _dict_to_pretty_string(self.to_dict())
1833+
)
18031834
return self.parts[0].text
18041835

18051836

@@ -1886,7 +1917,11 @@ def to_dict(self) -> Dict[str, Any]:
18861917
@property
18871918
def text(self) -> str:
18881919
if "text" not in self._raw_part:
1889-
raise AttributeError("Part has no text.")
1920+
raise AttributeError(
1921+
"Response candidate content part has no text.\n"
1922+
"Part:\n"
1923+
+ _dict_to_pretty_string(self.to_dict())
1924+
)
18901925
return self._raw_part.text
18911926

18921927
@property
@@ -2188,6 +2223,11 @@ def _append_gapic_part(
21882223
base_part._pb = copy.deepcopy(new_part._pb)
21892224

21902225

2226+
def _dict_to_pretty_string(d: dict) -> str:
2227+
"""Format dict as a pretty-printed JSON string."""
2228+
return json.dumps(d, indent=2)
2229+
2230+
21912231
_FORMAT_TO_MIME_TYPE = {
21922232
"png": "image/png",
21932233
"jpeg": "image/jpeg",

0 commit comments

Comments
 (0)








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: https://github.com/googleapis/python-aiplatform/commit/e82264d273e35d3b305d434181badfb63a37c79c

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy