Skip to content

Commit 122cdc8

Browse files
MarkDaoustcopybara-github
authored andcommitted
feat: support new UsageMetadata fields
PiperOrigin-RevId: 738783088
1 parent efbcb52 commit 122cdc8

File tree

4 files changed

+136
-22
lines changed

4 files changed

+136
-22
lines changed

google/genai/tests/chats/test_send_message.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -615,7 +615,8 @@ async def test_async_stream_config_override(client):
615615
):
616616
request_config_text += chunk.text
617617
default_config_text = ''
618-
async for chunk in await chat.send_message_stream('tell me a story in 100 words'):
618+
619+
async for chunk in await chat.send_message_stream('tell me family friendly story in 100 words'):
619620
default_config_text += chunk.text
620621

621622
assert json.loads(request_config_text)

google/genai/tests/models/test_generate_content.py

+46-3
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@
1313
# limitations under the License.
1414
#
1515

16+
import base64
1617
import enum
18+
import os
1719

20+
import PIL.Image
1821
from pydantic import BaseModel, ValidationError, Field
1922
from typing import Literal, List, Optional, Union
2023
from datetime import datetime
@@ -28,6 +31,13 @@
2831
from .. import pytest_helper
2932
from enum import Enum
3033

34+
IMAGE_PNG_FILE_PATH = os.path.abspath(
35+
os.path.join(os.path.dirname(__file__), '../data/google.png')
36+
)
37+
image_png = PIL.Image.open(IMAGE_PNG_FILE_PATH)
38+
39+
with open(IMAGE_PNG_FILE_PATH, 'rb') as image_file:
40+
image_bytes = image_file.read()
3141

3242
safety_settings_with_method = [
3343
{
@@ -1668,7 +1678,12 @@ def test_catch_stack_trace_in_error_handling(client):
16681678
# }]
16691679
# }
16701680
# }
1671-
assert e.details == {'code': 400, 'message': '', 'status': 'UNKNOWN'}
1681+
if 'error' in e.details:
1682+
details = e.details['error']
1683+
else:
1684+
details = e.details
1685+
assert details['code'] == 400
1686+
assert details['status'] == 'INVALID_ARGUMENT'
16721687

16731688

16741689
def test_multiple_strings(client):
@@ -1715,8 +1730,8 @@ class SummaryResponses(BaseModel):
17151730

17161731
assert 'Shakespeare' in response.text
17171732
assert 'Hemingway' in response.text
1718-
assert 'Shakespeare' == response.parsed[0].person
1719-
assert 'Hemingway' == response.parsed[1].person
1733+
assert 'Shakespeare' in response.parsed[0].person
1734+
assert 'Hemingway' in response.parsed[1].person
17201735

17211736

17221737
def test_multiple_function_calls(client):
@@ -1780,3 +1795,31 @@ def test_multiple_function_calls(client):
17801795
assert 'sunny' in response.text
17811796
assert '100 degrees' in response.text
17821797
assert '$100' in response.text
1798+
1799+
def test_usage_metadata_part_types(client):
1800+
contents = [
1801+
'Hello world.',
1802+
types.Part.from_bytes(
1803+
data=image_bytes,
1804+
mime_type='image/png',
1805+
),
1806+
]
1807+
1808+
response = client.models.generate_content(
1809+
model='gemini-1.5-flash', contents=contents
1810+
)
1811+
usage_metadata = response.usage_metadata
1812+
1813+
assert usage_metadata.candidates_token_count
1814+
assert usage_metadata.candidates_tokens_details
1815+
modalities = sorted(
1816+
[d.modality.name for d in usage_metadata.candidates_tokens_details]
1817+
)
1818+
assert modalities == ['TEXT']
1819+
1820+
assert usage_metadata.prompt_token_count
1821+
assert usage_metadata.prompt_tokens_details
1822+
modalities = sorted(
1823+
[d.modality.name for d in usage_metadata.prompt_tokens_details]
1824+
)
1825+
assert modalities == ['IMAGE', 'TEXT']

google/genai/tests/models/test_generate_content_tools.py

+10-7
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import typing
1919
import pydantic
2020
import pytest
21+
2122
from ... import _transformers as t
2223
from ... import errors
2324
from ... import types
@@ -676,6 +677,7 @@ def describe_cities(
676677
country: str,
677678
cities: typing.Optional[list[str]] = None,
678679
) -> str:
680+
"Given a country and an optional list of cities, describe the cities."
679681
if cities is None:
680682
return 'There are no cities to describe.'
681683
else:
@@ -715,7 +717,7 @@ def test_empty_tools(client):
715717

716718
def test_with_1_empty_tool(client):
717719
# Bad request for empty tool.
718-
with pytest_helper.exception_if_vertex(client, errors.ClientError):
720+
with pytest.raises(errors.ClientError):
719721
client.models.generate_content(
720722
model='gemini-1.5-flash',
721723
contents='What is the price of GOOG?.',
@@ -824,17 +826,16 @@ def divide_integers(a: int, b: int) -> int:
824826

825827
@pytest.mark.asyncio
826828
async def test_automatic_function_calling_async_with_exception(client):
827-
def divide_integers(a: int, b: int) -> int:
829+
def mystery_function(a: int, b: int) -> int:
828830
return a // b
829831

830832
response = await client.aio.models.generate_content(
831833
model='gemini-1.5-flash',
832834
contents='what is the result of 1000/0?',
833835
config={'tools': [divide_integers]},
834836
)
835-
836-
assert 'undefined' in response.text
837-
837+
assert response.automatic_function_calling_history
838+
assert response.automatic_function_calling_history[-1].parts[0].function_response.response['error']
838839

839840
@pytest.mark.asyncio
840841
async def test_automatic_function_calling_async_float_without_decimal(client):
@@ -1058,8 +1059,10 @@ def test_code_execution_tool(client):
10581059
),
10591060
)
10601061

1061-
assert 'def is_prime' in response.executable_code
1062-
assert 'primes=' in response.code_execution_result
1062+
assert response.executable_code
1063+
assert (
1064+
'prime' in response.code_execution_result.lower() or
1065+
'5117' in response.code_execution_result)
10631066

10641067

10651068
def test_afc_logs_to_logger_instance(client, caplog):

google/genai/types.py

+78-11
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,15 @@ class BlockedReason(_common.CaseInSensitiveEnum):
185185
PROHIBITED_CONTENT = 'PROHIBITED_CONTENT'
186186

187187

188+
class Modality(_common.CaseInSensitiveEnum):
189+
"""Server content modalities."""
190+
191+
MODALITY_UNSPECIFIED = 'MODALITY_UNSPECIFIED'
192+
TEXT = 'TEXT'
193+
IMAGE = 'IMAGE'
194+
AUDIO = 'AUDIO'
195+
196+
188197
class DeploymentResourcesType(_common.CaseInSensitiveEnum):
189198
""""""
190199

@@ -334,15 +343,6 @@ class FileSource(_common.CaseInSensitiveEnum):
334343
GENERATED = 'GENERATED'
335344

336345

337-
class Modality(_common.CaseInSensitiveEnum):
338-
"""Server content modalities."""
339-
340-
MODALITY_UNSPECIFIED = 'MODALITY_UNSPECIFIED'
341-
TEXT = 'TEXT'
342-
IMAGE = 'IMAGE'
343-
AUDIO = 'AUDIO'
344-
345-
346346
class VideoMetadata(_common.BaseModel):
347347
"""Metadata describes the input video content."""
348348

@@ -2840,40 +2840,107 @@ class GenerateContentResponsePromptFeedbackDict(TypedDict, total=False):
28402840
]
28412841

28422842

2843+
class ModalityTokenCount(_common.BaseModel):
2844+
"""Represents token counting info for a single modality."""
2845+
2846+
modality: Optional[Modality] = Field(
2847+
default=None,
2848+
description="""The modality associated with this token count.""",
2849+
)
2850+
token_count: Optional[int] = Field(
2851+
default=None, description="""Number of tokens."""
2852+
)
2853+
2854+
2855+
class ModalityTokenCountDict(TypedDict, total=False):
2856+
"""Represents token counting info for a single modality."""
2857+
2858+
modality: Optional[Modality]
2859+
"""The modality associated with this token count."""
2860+
2861+
token_count: Optional[int]
2862+
"""Number of tokens."""
2863+
2864+
2865+
ModalityTokenCountOrDict = Union[ModalityTokenCount, ModalityTokenCountDict]
2866+
2867+
28432868
class GenerateContentResponseUsageMetadata(_common.BaseModel):
28442869
"""Usage metadata about response(s)."""
28452870

2871+
cache_tokens_details: Optional[list[ModalityTokenCount]] = Field(
2872+
default=None,
2873+
description="""Output only. List of modalities of the cached content in the request input.""",
2874+
)
28462875
cached_content_token_count: Optional[int] = Field(
28472876
default=None,
28482877
description="""Output only. Number of tokens in the cached part in the input (the cached content).""",
28492878
)
28502879
candidates_token_count: Optional[int] = Field(
28512880
default=None, description="""Number of tokens in the response(s)."""
28522881
)
2882+
candidates_tokens_details: Optional[list[ModalityTokenCount]] = Field(
2883+
default=None,
2884+
description="""Output only. List of modalities that were returned in the response.""",
2885+
)
28532886
prompt_token_count: Optional[int] = Field(
28542887
default=None,
28552888
description="""Number of tokens in the request. When `cached_content` is set, this is still the total effective prompt size meaning this includes the number of tokens in the cached content.""",
28562889
)
2890+
prompt_tokens_details: Optional[list[ModalityTokenCount]] = Field(
2891+
default=None,
2892+
description="""Output only. List of modalities that were processed in the request input.""",
2893+
)
2894+
thoughts_token_count: Optional[int] = Field(
2895+
default=None,
2896+
description="""Output only. Number of tokens present in thoughts output.""",
2897+
)
2898+
tool_use_prompt_token_count: Optional[int] = Field(
2899+
default=None,
2900+
description="""Output only. Number of tokens present in tool-use prompt(s).""",
2901+
)
2902+
tool_use_prompt_tokens_details: Optional[list[ModalityTokenCount]] = Field(
2903+
default=None,
2904+
description="""Output only. List of modalities that were processed for tool-use request inputs.""",
2905+
)
28572906
total_token_count: Optional[int] = Field(
28582907
default=None,
2859-
description="""Total token count for prompt and response candidates.""",
2908+
description="""Total token count for prompt, response candidates, and tool-use prompts (if present).""",
28602909
)
28612910

28622911

28632912
class GenerateContentResponseUsageMetadataDict(TypedDict, total=False):
28642913
"""Usage metadata about response(s)."""
28652914

2915+
cache_tokens_details: Optional[list[ModalityTokenCountDict]]
2916+
"""Output only. List of modalities of the cached content in the request input."""
2917+
28662918
cached_content_token_count: Optional[int]
28672919
"""Output only. Number of tokens in the cached part in the input (the cached content)."""
28682920

28692921
candidates_token_count: Optional[int]
28702922
"""Number of tokens in the response(s)."""
28712923

2924+
candidates_tokens_details: Optional[list[ModalityTokenCountDict]]
2925+
"""Output only. List of modalities that were returned in the response."""
2926+
28722927
prompt_token_count: Optional[int]
28732928
"""Number of tokens in the request. When `cached_content` is set, this is still the total effective prompt size meaning this includes the number of tokens in the cached content."""
28742929

2930+
prompt_tokens_details: Optional[list[ModalityTokenCountDict]]
2931+
"""Output only. List of modalities that were processed in the request input."""
2932+
2933+
thoughts_token_count: Optional[int]
2934+
"""Output only. Number of tokens present in thoughts output."""
2935+
2936+
tool_use_prompt_token_count: Optional[int]
2937+
"""Output only. Number of tokens present in tool-use prompt(s)."""
2938+
2939+
tool_use_prompt_tokens_details: Optional[list[ModalityTokenCountDict]]
2940+
"""Output only. List of modalities that were processed for tool-use request inputs."""
2941+
28752942
total_token_count: Optional[int]
2876-
"""Total token count for prompt and response candidates."""
2943+
"""Total token count for prompt, response candidates, and tool-use prompts (if present)."""
28772944

28782945

28792946
GenerateContentResponseUsageMetadataOrDict = Union[

0 commit comments

Comments
 (0)