Skip to content

Commit 8ad9495

Browse files
sararobcopybara-github
authored andcommitted
fix: remove logging when response.text is not called directly (fixes #455)
PiperOrigin-RevId: 737992402
1 parent 2ac5129 commit 8ad9495

File tree

2 files changed

+63
-12
lines changed

2 files changed

+63
-12
lines changed

google/genai/tests/models/test_generate_content.py

+22
Original file line numberDiff line numberDiff line change
@@ -1780,3 +1780,25 @@ def test_multiple_function_calls(client):
17801780
assert 'sunny' in response.text
17811781
assert '100 degrees' in response.text
17821782
assert '$100' in response.text
1783+
1784+
1785+
def test_response_parsed_does_not_log_warning(client, caplog):
1786+
caplog.set_level(logging.DEBUG, logger='google_genai')
1787+
1788+
class SongLyric(BaseModel):
1789+
song_name: str
1790+
lyric: str
1791+
artist: str
1792+
1793+
response = client.models.generate_content(
1794+
model='gemini-1.5-flash',
1795+
contents='Can you give me 2 Taylor Swift song lyrics?',
1796+
config=types.GenerateContentConfig(
1797+
response_mime_type='application/json',
1798+
response_schema=SongLyric,
1799+
candidate_count=2,
1800+
)
1801+
)
1802+
assert response.parsed
1803+
assert len(response.candidates) == 2
1804+
assert 'returning text from the first candidate' not in caplog.text

google/genai/types.py

+41-12
Original file line numberDiff line numberDiff line change
@@ -2917,6 +2917,35 @@ class GenerateContentResponse(_common.BaseModel):
29172917
description="""Parsed response if response_schema is provided. Not available for streaming.""",
29182918
)
29192919

2920+
@property
2921+
def _text(self) -> Optional[str]:
2922+
"""Returns the concatenation of all text parts in the response.
2923+
2924+
This is an internal method that doesn't include logging when called.
2925+
"""
2926+
if (
2927+
not self.candidates
2928+
or not self.candidates[0].content
2929+
or not self.candidates[0].content.parts
2930+
):
2931+
return None
2932+
text = ''
2933+
any_text_part_text = False
2934+
non_text_parts = []
2935+
for part in self.candidates[0].content.parts:
2936+
for field_name, field_value in part.model_dump(
2937+
exclude={'text', 'thought'}
2938+
).items():
2939+
if field_value is not None:
2940+
non_text_parts.append(field_name)
2941+
if isinstance(part.text, str):
2942+
if isinstance(part.thought, bool) and part.thought:
2943+
continue
2944+
any_text_part_text = True
2945+
text += part.text
2946+
# part.text == '' is different from part.text is None
2947+
return text if any_text_part_text else None
2948+
29202949
@property
29212950
def text(self) -> Optional[str]:
29222951
"""Returns the concatenation of all text parts in the response."""
@@ -3037,16 +3066,16 @@ def _from_response(
30373066
):
30383067
# Pydantic schema.
30393068
try:
3040-
if result.text is not None:
3041-
result.parsed = response_schema.model_validate_json(result.text)
3069+
if result._text is not None:
3070+
result.parsed = response_schema.model_validate_json(result._text)
30423071
# may not be a valid json per stream response
30433072
except pydantic.ValidationError:
30443073
pass
30453074
except json.decoder.JSONDecodeError:
30463075
pass
3047-
elif isinstance(response_schema, EnumMeta) and result.text is not None:
3076+
elif isinstance(response_schema, EnumMeta) and result._text is not None:
30483077
# Enum with "application/json" returns response in double quotes.
3049-
enum_value = result.text.replace('"', '')
3078+
enum_value = result._text.replace('"', '')
30503079
try:
30513080
result.parsed = response_schema(enum_value)
30523081
if (
@@ -3064,8 +3093,8 @@ class Placeholder(pydantic.BaseModel):
30643093
placeholder: response_schema # type: ignore[valid-type]
30653094

30663095
try:
3067-
if result.text is not None:
3068-
parsed = {'placeholder': json.loads(result.text)}
3096+
if result._text is not None:
3097+
parsed = {'placeholder': json.loads(result._text)}
30693098
placeholder = Placeholder.model_validate(parsed)
30703099
result.parsed = placeholder.placeholder
30713100
except json.decoder.JSONDecodeError:
@@ -3080,8 +3109,8 @@ class Placeholder(pydantic.BaseModel):
30803109
# want the result converted to. So just return json.
30813110
# JSON schema.
30823111
try:
3083-
if result.text is not None:
3084-
result.parsed = json.loads(result.text)
3112+
if result._text is not None:
3113+
result.parsed = json.loads(result._text)
30853114
# may not be a valid json per stream response
30863115
except json.decoder.JSONDecodeError:
30873116
pass
@@ -3091,12 +3120,12 @@ class Placeholder(pydantic.BaseModel):
30913120
for union_type in union_types:
30923121
if issubclass(union_type, pydantic.BaseModel):
30933122
try:
3094-
if result.text is not None:
3123+
if result._text is not None:
30953124

30963125
class Placeholder(pydantic.BaseModel): # type: ignore[no-redef]
30973126
placeholder: response_schema # type: ignore[valid-type]
30983127

3099-
parsed = {'placeholder': json.loads(result.text)}
3128+
parsed = {'placeholder': json.loads(result._text)}
31003129
placeholder = Placeholder.model_validate(parsed)
31013130
result.parsed = placeholder.placeholder
31023131
except json.decoder.JSONDecodeError:
@@ -3105,8 +3134,8 @@ class Placeholder(pydantic.BaseModel): # type: ignore[no-redef]
31053134
pass
31063135
else:
31073136
try:
3108-
if result.text is not None:
3109-
result.parsed = json.loads(result.text)
3137+
if result._text is not None:
3138+
result.parsed = json.loads(result._text)
31103139
# may not be a valid json per stream response
31113140
except json.decoder.JSONDecodeError:
31123141
pass

0 commit comments

Comments
 (0)