first vommit

This commit is contained in:
Till-Immer committed 2026-07-04 14:31:50 +02:00
commit a04e68bc95
4926 files changed
+764440

No files matched your search

@@ -0,0 +1,17 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Tests for the Google GenAI SDK's _transformers module."""
@@ -0,0 +1,84 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Tests t_bytes methods in the _transformers module."""
import os
import PIL.Image
import pytest
from ... import _transformers as t
from ... import types
def test_blob_dict():
blob = t.t_blob({
'data': bytes([0, 0, 0, 0, 0, 0]),
'mime_type': 'audio/pcm',
})
assert blob.data == bytes([0, 0, 0, 0, 0, 0])
assert blob.mime_type == 'audio/pcm'
def test_blob():
blob = t.t_blob(
types.Blob(data=bytes([0, 0, 0, 0, 0, 0]), mime_type='audio/pcm')
)
assert blob.data == bytes([0, 0, 0, 0, 0, 0])
assert blob.mime_type == 'audio/pcm'
def test_image(image_jpeg):
blob = t.t_blob(image_jpeg)
assert blob.data[6:10] == b'JFIF'
assert blob.mime_type == 'image/jpeg'
round_trip_image = blob.as_image()._pil_image
assert round_trip_image is not None
assert round_trip_image.size == image_jpeg.size
assert round_trip_image.mode == image_jpeg.mode
assert round_trip_image.format == image_jpeg.format
def test_not_image():
blob = types.Blob(data=bytes([0, 0, 0, 0, 0, 0]), mime_type='audio/pcm')
assert blob.as_image() is None
def test_part_image(image_jpeg):
part = t.t_part(image_jpeg)
assert part.inline_data.data[6:10] == b'JFIF'
assert part.inline_data.mime_type == 'image/jpeg'
round_trip_image = part.as_image()._pil_image
assert round_trip_image is not None
assert round_trip_image.size == image_jpeg.size
assert round_trip_image.mode == image_jpeg.mode
assert round_trip_image.format == image_jpeg.format
def test_part_not_image():
part = t.t_part('hello world')
assert part.as_image() is None
def test_pil_to_blob_with_memory_pil_image():
img = PIL.Image.new('RGB', (1, 1), color='red')
blob = t.pil_to_blob(img)
assert blob.mime_type == 'image/png'
assert blob.data and len(blob.data) == 69
assert blob.data[0:4] == b'\x89PNG'
@@ -0,0 +1,15 @@
"""Tests t_bytes methods in the _transformers module."""
import base64
from ... import _base_transformers as t
_RAW_BYTES = (
b'\xfb\xf6\x9bq\xd7\x9f\x82\x18\xa3\x92Y\xa7\xa2\x9a\xab\xb2\xdb\xaf\xc3\x1c\xb3\x00\x10\x83\x10Q\x87'
b' \x92\x8b0\xd3\x8fA\x14\x93QU\x97a\x9d5\xdb~9\xeb\xbf='
)
def test_t_bytes():
assert t.t_bytes(_RAW_BYTES) == base64.b64encode(_RAW_BYTES).decode('ascii')
assert t.t_bytes('string') == 'string'
@@ -0,0 +1,72 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Tests for live.py."""
from ... import types
from ... import _transformers as t
def test_function_response_dict():
input = {
'name': 'get_current_weather',
'response': {'temperature': 14.5, 'unit': 'C'},
'id': 'some-id',
}
function_responses = t.t_function_responses(input)
assert len(function_responses) == 1
assert function_responses[0].name == 'get_current_weather'
assert function_responses[0].response['temperature'] == 14.5
assert function_responses[0].response['unit'] == 'C'
def test_send_function_response():
input = types.FunctionResponse(
name='get_current_weather',
response={'temperature': 14.5, 'unit': 'C'},
id='some-id',
)
function_responses = t.t_function_responses(input)
assert len(function_responses) == 1
assert function_responses[0].name == 'get_current_weather'
assert function_responses[0].response['temperature'] == 14.5
assert function_responses[0].response['unit'] == 'C'
def test_send_function_response_list():
input1 = {
'name': 'get_current_weather',
'response': {'temperature': 14.5, 'unit': 'C'},
'id': '1',
}
input2 = {
'name': 'get_current_weather',
'response': {'temperature': 99.9, 'unit': 'C'},
'id': '2',
}
function_responses = t.t_function_responses([input1, input2])
assert len(function_responses) == 2
assert function_responses[0].name == 'get_current_weather'
assert function_responses[0].response['temperature'] == 14.5
assert function_responses[0].response['unit'] == 'C'
assert function_responses[1].name == 'get_current_weather'
assert function_responses[1].response['temperature'] == 99.9
assert function_responses[1].response['unit'] == 'C'
@@ -0,0 +1,695 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Tests schema processing methods in the _transformers module."""
import copy
from typing import Union
import pydantic
import pytest
from ... import _transformers
from ... import client as google_genai_client_module
from ... import types
class CurrencyInfo(pydantic.BaseModel):
name: str
code: str
symbol: str
currency_info_fields = CurrencyInfo.model_fields
class CountryInfo(pydantic.BaseModel):
name: str
population: int
capital: str
continent: str
gdp: int
official_language: str
total_area_sq_mi: int
country_info_fields = CountryInfo.model_fields
class CountryInfoWithCurrency(pydantic.BaseModel):
name: str
population: int
capital: str
continent: str
gdp: int
official_language: str
total_area_sq_mi: int
currency: CurrencyInfo
nested_country_info_fields = CountryInfoWithCurrency.model_fields
class CountryInfoWithNullFields(pydantic.BaseModel):
name: str
population: Union[int, None] = None
class CountryInfoWithDefaultValue(pydantic.BaseModel):
name: str
population: int = 0
class CountryInfoWithAnyOf(pydantic.BaseModel):
name: str
restaurants_per_capita: Union[int, float]
@pytest.fixture
def client(use_vertex):
if use_vertex:
yield google_genai_client_module.Client(
vertexai=use_vertex, project='test-project', location='test-location'
)
else:
yield google_genai_client_module.Client(
vertexai=use_vertex, api_key='test-api-key'
)
@pytest.mark.parametrize('use_vertex', [True, False])
def test_build_schema_for_list_of_pydantic_schema(client):
"""Tests _build_schema() when list[pydantic.BaseModel] is provided to response_schema."""
list_schema = _transformers.t_schema(client, CountryInfo).model_dump()
assert isinstance(list_schema, dict)
for field_name in list_schema['properties']:
assert 'title' in list_schema['properties'][field_name]
assert 'type' in list_schema['properties'][field_name]
assert field_name in country_info_fields
field_type_str = country_info_fields[field_name].annotation.__name__
assert (
list_schema['properties'][field_name]['type']
.lower()
.startswith(field_type_str.lower())
)
assert 'required' in list_schema
assert list_schema['required'] == list(country_info_fields.keys())
@pytest.mark.parametrize('use_vertex', [True, False])
def test_build_schema_for_list_of_nested_pydantic_schema(client):
"""Tests _build_schema() when list[pydantic.BaseModel] is provided to response_schema and the pydantic.BaseModel has nested pydantic fields."""
list_schema = _transformers.t_schema(
client, CountryInfoWithCurrency
).model_dump()
assert isinstance(list_schema, dict)
for field_name in list_schema['properties']:
assert 'title' in list_schema['properties'][field_name]
assert 'type' in list_schema['properties'][field_name]
assert field_name in nested_country_info_fields
# Tested nested schema was created
assert 'properties' in list_schema['properties']['currency']
for field_name in list_schema['properties']['currency']['properties']:
assert field_name in currency_info_fields
@pytest.mark.parametrize('use_vertex', [True, False])
def test_t_schema_for_pydantic_schema(client):
"""Tests t_schema when pydantic.BaseModel is passed to response_schema."""
transformed_schema = _transformers.t_schema(client, CountryInfo)
assert isinstance(transformed_schema, types.Schema)
for schema_property in transformed_schema.properties:
assert schema_property in country_info_fields
assert isinstance(
transformed_schema.properties[schema_property], types.Schema
)
@pytest.mark.parametrize('use_vertex', [True, False])
def test_t_schema_for_list_of_pydantic_schema(client):
"""Tests t_schema when list[pydantic.BaseModel] is passed to response_schema."""
transformed_schema = _transformers.t_schema(client, list[CountryInfo])
assert isinstance(transformed_schema, types.Schema)
assert isinstance(transformed_schema.items, types.Schema)
for schema_property in transformed_schema.items.properties:
assert schema_property in country_info_fields
assert isinstance(
transformed_schema.items.properties[schema_property], types.Schema
)
@pytest.mark.parametrize('use_vertex', [True, False])
def test_t_schema_for_null_fields(client):
"""Tests t_schema when null fields are present."""
transformed_schema = _transformers.t_schema(client, CountryInfoWithNullFields)
assert isinstance(transformed_schema, types.Schema)
assert transformed_schema.properties['population'].nullable
#@pytest.mark.parametrize('use_vertex', [True, False])
def test_schema_with_no_null_fields_is_unchanged():
"""Tests handle_null_fields() doesn't change anything when no null fields are present."""
test_properties = {
'name': {'title': 'Name', 'type': 'string'},
'total_area_sq_mi': {
'anyOf': [{'type': 'integer'}, {'type': 'float'}],
'default': 'null',
'title': 'Total Area Sq Mi',
},
}
for _, schema in test_properties.items():
schema_before = copy.deepcopy(schema)
_transformers.handle_null_fields(schema)
assert schema_before == schema
@pytest.mark.parametrize('use_vertex', [True, False])
def test_schema_with_default_value(client):
transformed_schema = _transformers.t_schema(
client._api_client, CountryInfoWithDefaultValue
)
expected_schema = types.Schema(
properties={
'name': types.Schema(
type='STRING',
title='Name',
),
'population': types.Schema(
type='INTEGER',
default=0,
title='Population',
),
},
type='OBJECT',
required=['name'],
title='CountryInfoWithDefaultValue',
property_ordering=['name', 'population'],
)
assert transformed_schema == expected_schema
@pytest.mark.parametrize('use_vertex', [True, False])
def test_schema_with_any_of(client):
transformed_schema = _transformers.t_schema(client, CountryInfoWithAnyOf)
expected_schema = types.Schema(
properties={
'name': types.Schema(
type='STRING',
title='Name',
),
'restaurants_per_capita': types.Schema(
any_of=[
types.Schema(type='INTEGER'),
types.Schema(type='NUMBER'),
],
title='Restaurants Per Capita',
),
},
type='OBJECT',
required=['name', 'restaurants_per_capita'],
title='CountryInfoWithAnyOf',
property_ordering=['name', 'restaurants_per_capita'],
)
assert transformed_schema == expected_schema
@pytest.mark.parametrize('use_vertex', [True, False])
def test_complex_dict_schema_with_anyof_is_unchanged(client):
"""When a dict schema is passed to process_schema, the only change should be camel-casing anyOf."""
if client.vertexai:
dict_schema = {
'type': 'OBJECT',
'title': 'Fruit Basket',
'description': 'A structured representation of a fruit basket',
'required': ['fruit'],
'properties': {
'fruit': {
'type': 'ARRAY',
'description': 'An ordered list of the fruit in the basket',
'items': {
'description': 'A piece of fruit',
'anyOf': [
{
'title': 'Apple',
'description': 'Describes an apple',
'type': 'OBJECT',
'properties': {
'type': {
'type': 'STRING',
'description': "Always 'apple'",
},
'color': {
'type': 'STRING',
'description': (
"The color of the apple (e.g., 'red')"
),
},
},
'propertyOrdering': ['type', 'color'],
'required': ['type', 'color'],
},
{
'title': 'Orange',
'description': 'Describes an orange',
'type': 'OBJECT',
'properties': {
'type': {
'type': 'STRING',
'description': "Always 'orange'",
},
'size': {
'type': 'STRING',
'description': (
'The size of the orange (e.g.,'
" 'medium')"
),
},
},
'propertyOrdering': ['type', 'size'],
'required': ['type', 'size'],
},
],
},
}
},
}
schema_before = copy.deepcopy(dict_schema)
_transformers.process_schema(dict_schema, client)
assert schema_before == dict_schema
@pytest.mark.parametrize('use_vertex', [True, False])
def test_process_schema_converts_const_to_enum(client):
"""The 'const' field should be converted to a singleton 'enum'."""
schema = {
'type': 'STRING',
'const': 'FOO',
}
expected_schema = {
'type': 'STRING',
'enum': ['FOO'],
}
_transformers.process_schema(schema, client)
assert schema == expected_schema
@pytest.mark.parametrize('use_vertex', [True, False])
def test_process_schema_forbids_non_string_const(client):
"""The 'const' field only works for strings."""
schema = {
'type': 'INTEGER',
'const': 123,
}
with pytest.raises(ValueError, match='.*Literal values must be strings.*'):
_transformers.process_schema(schema, client)
@pytest.mark.parametrize(
'use_vertex,order_properties',
[(False, False), (False, True), (True, False), (True, True)],
)
def test_process_schema_order_properties_propagates_into_defs(
client, order_properties
):
"""The `order_properties` setting should apply to '$defs'."""
schema = {
'$ref': '#/$defs/Foo',
'$defs': {
'Foo': {
'type': 'OBJECT',
'properties': {
'foo': {'type': 'STRING'},
'bar': {'type': 'STRING'},
},
},
},
}
schema_without_property_ordering = {
'type': 'OBJECT',
'properties': {
'foo': {'type': 'STRING'},
'bar': {'type': 'STRING'},
},
}
schema_with_property_ordering = {
'type': 'OBJECT',
'properties': {
'foo': {'type': 'STRING'},
'bar': {'type': 'STRING'},
},
'property_ordering': ['foo', 'bar'],
}
_transformers.process_schema(
schema, client, order_properties=order_properties
)
if order_properties:
assert schema == schema_with_property_ordering
else:
assert schema == schema_without_property_ordering
@pytest.mark.parametrize(
'use_vertex,order_properties',
[(False, False), (False, True), (True, False), (True, True)],
)
def test_process_schema_order_properties_propagates_into_items(
client, order_properties
):
"""The `order_properties` setting should apply to 'items'."""
schema = {
'type': 'ARRAY',
'items': {
'type': 'OBJECT',
'properties': {
'foo': {'type': 'STRING'},
'bar': {'type': 'STRING'},
},
},
}
schema_without_property_ordering = copy.deepcopy(schema)
schema_with_property_ordering = {
'type': 'ARRAY',
'items': {
'type': 'OBJECT',
'properties': {
'foo': {'type': 'STRING'},
'bar': {'type': 'STRING'},
},
'property_ordering': ['foo', 'bar'],
},
}
_transformers.process_schema(
schema, client, order_properties=order_properties
)
if order_properties:
assert schema == schema_with_property_ordering
else:
assert schema == schema_without_property_ordering
@pytest.mark.parametrize(
'use_vertex,order_properties',
[(False, False), (False, True), (True, False), (True, True)],
)
def test_process_schema_order_properties_propagates_into_prefix_items(
client, order_properties
):
"""The `order_properties` setting should apply to 'prefixItems'."""
schema = {
'type': 'ARRAY',
'prefixItems': [
{
'type': 'OBJECT',
'properties': {
'foo': {'type': 'STRING'},
'bar': {'type': 'STRING'},
},
},
],
}
schema_without_property_ordering = copy.deepcopy(schema)
schema_with_property_ordering = {
'type': 'ARRAY',
'prefixItems': [
{
'type': 'OBJECT',
'properties': {
'foo': {'type': 'STRING'},
'bar': {'type': 'STRING'},
},
'property_ordering': ['foo', 'bar'],
},
],
}
_transformers.process_schema(
schema, client, order_properties=order_properties
)
if order_properties:
assert schema == schema_with_property_ordering
else:
assert schema == schema_without_property_ordering
@pytest.mark.parametrize(
'use_vertex,order_properties',
[(False, False), (False, True), (True, False), (True, True)],
)
def test_process_schema_order_properties_propagates_into_properties(
client, order_properties
):
"""The `order_properties` setting should apply to 'properties'."""
schema = {
'type': 'OBJECT',
'properties': {
'xyz': {
'type': 'OBJECT',
'properties': {
'foo': {'type': 'STRING'},
'bar': {'type': 'STRING'},
},
},
'abc': {'type': 'STRING'},
},
}
schema_without_property_ordering = copy.deepcopy(schema)
schema_with_property_ordering = {
'type': 'OBJECT',
'properties': {
'xyz': {
'type': 'OBJECT',
'properties': {
'foo': {'type': 'STRING'},
'bar': {'type': 'STRING'},
},
'property_ordering': ['foo', 'bar'],
},
'abc': {'type': 'STRING'},
},
'property_ordering': ['xyz', 'abc'],
}
_transformers.process_schema(
schema, client, order_properties=order_properties
)
if order_properties:
assert schema == schema_with_property_ordering
else:
assert schema == schema_without_property_ordering
@pytest.mark.parametrize(
'use_vertex,order_properties',
[(False, False), (False, True), (True, False), (True, True)],
)
def test_process_schema_order_properties_propagates_into_additional_properties(
client, order_properties
):
"""The `order_properties` setting should apply to 'additionalProperties'."""
schema = {
'type': 'OBJECT',
'additionalProperties': {
'type': 'OBJECT',
'properties': {
'foo': {'type': 'STRING'},
'bar': {'type': 'STRING'},
},
},
}
schema_without_property_ordering = copy.deepcopy(schema)
schema_with_property_ordering = {
'type': 'OBJECT',
'additionalProperties': {
'type': 'OBJECT',
'properties': {
'foo': {'type': 'STRING'},
'bar': {'type': 'STRING'},
},
'property_ordering': ['foo', 'bar'],
},
}
if client.vertexai:
_transformers.process_schema(
schema, client, order_properties=order_properties
)
if order_properties:
assert schema == schema_with_property_ordering
else:
assert schema == schema_without_property_ordering
else:
with pytest.raises(ValueError) as e:
_transformers.process_schema(
schema, client, order_properties=order_properties
)
assert 'additionalProperties is only supported in Gemini Enterprise Agent Platform mode' in str(e)
@pytest.mark.parametrize(
'use_vertex,order_properties',
[(False, False), (False, True), (True, False), (True, True)],
)
def test_process_schema_order_properties_propagates_into_any_of(
client, order_properties
):
"""The `order_properties` setting should apply to 'anyOf'."""
schema = {
'anyOf': [
{
'type': 'OBJECT',
'properties': {
'foo': {'type': 'STRING'},
'bar': {'type': 'STRING'},
},
},
{'type': 'STRING'},
]
}
schema_without_property_ordering = copy.deepcopy(schema)
schema_with_property_ordering = {
'anyOf': [
{
'type': 'OBJECT',
'properties': {
'foo': {'type': 'STRING'},
'bar': {'type': 'STRING'},
},
'property_ordering': ['foo', 'bar'],
},
{'type': 'STRING'},
]
}
_transformers.process_schema(
schema, client, order_properties=order_properties
)
if order_properties:
assert schema == schema_with_property_ordering
else:
assert schema == schema_without_property_ordering
@pytest.mark.parametrize('use_vertex', [True, False])
def test_process_schema_with_cycle(client):
schema = {
'type': 'OBJECT',
'properties': {
'recursive': {'$ref': '#/$defs/RecursiveObject'},
},
'$defs': {
'RecursiveObject': {
'type': 'OBJECT',
'properties': {
'self': {'$ref': '#/$defs/RecursiveObject'},
}
}
}
}
_transformers.process_schema(schema, client)
expected = {
'type': 'OBJECT',
'properties': {
'recursive': {
'type': 'OBJECT',
'properties': {
'self': {}
}
}
}
}
assert schema == expected
@pytest.mark.parametrize('use_vertex', [True, False])
def test_t_schema_does_not_change_property_ordering_if_set(client):
"""Tests t_schema doesn't overwrite the property_ordering field if already set."""
schema = CountryInfo.model_json_schema()
custom_property_ordering = ['code', 'symbol', 'name']
schema['property_ordering'] = custom_property_ordering.copy()
transformed_schema = _transformers.t_schema(client, schema)
assert transformed_schema.property_ordering == custom_property_ordering
@pytest.mark.parametrize('use_vertex', [True, False])
def test_t_schema_sets_property_ordering_for_json_schema(client):
"""Tests t_schema sets the property_ordering field for json schemas."""
schema = CountryInfo.model_json_schema()
transformed_schema = _transformers.t_schema(client, schema)
assert transformed_schema.property_ordering == [
'name',
'population',
'capital',
'continent',
'gdp',
'official_language',
'total_area_sq_mi',
]
@pytest.mark.parametrize('use_vertex', [True, False])
def test_t_schema_sets_property_ordering_for_schema_type(client):
"""Tests t_schema sets the property_ordering field for Schema types."""
schema = types.Schema(
properties={
'name': types.Schema(
type='STRING',
title='Name',
),
'population': types.Schema(
type='INTEGER',
default=0,
title='Population',
),
},
type='OBJECT',
required=['name'],
title='CountryInfoWithDefaultValue',
)
transformed_schema = _transformers.t_schema(client, schema)
assert transformed_schema.property_ordering == ['name', 'population']
@@ -0,0 +1,333 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an 'AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Tests for t_batch."""
import pytest
from ... import _transformers as t
from ... import client as google_genai_client_module
from ... import types
@pytest.fixture
def vertex_client():
yield google_genai_client_module.Client(
vertexai=True, project='test-project', location='test-location'
)
@pytest.fixture
def mldev_client():
yield google_genai_client_module.Client(
vertexai=False, api_key='test-api-key'
)
@pytest.mark.usefixtures('vertex_client', 'mldev_client')
class TestBatchJobSource:
# Test cases for src as str
@pytest.mark.parametrize(
'src_str, expected_gcs_uri, expected_bigquery_uri, expected_file_name,'
' expected_format',
[
(
'gs://bucket/path/to/data.jsonl',
['gs://bucket/path/to/data.jsonl'],
None,
None,
'jsonl',
),
(
'bq://project.dataset.table',
None,
'bq://project.dataset.table',
None,
'bigquery',
),
('files/data.csv', None, None, 'files/data.csv', None),
],
)
def test_batch_job_source_str(
self,
mldev_client,
src_str,
expected_gcs_uri,
expected_bigquery_uri,
expected_file_name,
expected_format,
):
result = t.t_batch_job_source(mldev_client, src_str)
expected = types.BatchJobSource(
gcs_uri=expected_gcs_uri,
bigquery_uri=expected_bigquery_uri,
file_name=expected_file_name,
format=expected_format,
)
assert result == expected
def test_batch_job_source_str_unsupported(self, mldev_client):
with pytest.raises(ValueError, match='Unsupported source'):
t.t_batch_job_source(mldev_client, 'http://example.com/data')
with pytest.raises(ValueError, match='Unsupported source'):
t.t_batch_job_source(mldev_client, 'invalid-path')
# Test cases for src as list
def test_batch_job_source_list_empty(self, mldev_client):
result = t.t_batch_job_source(mldev_client, [])
expected = types.BatchJobSource(inlined_requests=[])
assert result == expected
def test_batch_job_source_list_with_items(self, mldev_client):
inlined_data = [
types.InlinedRequest(
contents=[types.Content(parts=[types.Part(text='item1')])]
),
types.InlinedRequest(
contents=[types.Content(parts=[types.Part(text='item2')])]
),
]
result = t.t_batch_job_source(mldev_client, inlined_data)
expected = types.BatchJobSource(inlined_requests=inlined_data)
assert result == expected
# Test cases for src as dict
def test_batch_job_source_dict(self, vertex_client, mldev_client):
src_dict = {'gcs_uri': ['gs://test/file.jsonl'], 'format': 'jsonl'}
result = t.t_batch_job_source(vertex_client, src_dict)
expected = types.BatchJobSource(
gcs_uri=['gs://test/file.jsonl'], format='jsonl'
)
assert result == expected
src_dict_inlined = {
'inlined_requests': [{
'contents': [{
'parts': [{
'text': 'Hello!',
}],
'role': 'user',
}],
}]
}
result_inlined = t.t_batch_job_source(mldev_client, src_dict_inlined)
expected_inlined = types.BatchJobSource(
inlined_requests=[{
'contents': [{
'parts': [{
'text': 'Hello!',
}],
'role': 'user',
}],
}]
)
assert result_inlined == expected_inlined
# Test cases for src as types.BatchJobSource (mocked MockBatchJobSource)
def test_batch_job_source_mldev_valid_file_name(self, mldev_client):
src_obj = types.BatchJobSource(file_name='files/my_data.csv')
result = t.t_batch_job_source(mldev_client, src_obj)
assert result is src_obj
def test_batch_job_source_mldev_invalid_both_set(self, mldev_client):
src_obj = types.BatchJobSource(
inlined_requests=[types.InlinedRequest()],
file_name='files/data.csv',
)
with pytest.raises(
ValueError,
match='`inlined_requests`, `file_name`,',
):
t.t_batch_job_source(mldev_client, src_obj)
def test_batch_job_source_mldev_invalid_neither_set(self, mldev_client):
src_obj = types.BatchJobSource(gcs_uri=['gs://temp'])
with pytest.raises(
ValueError,
match='`inlined_requests`, `file_name`,',
):
t.t_batch_job_source(mldev_client, src_obj)
# client.vertexai = True (Vertex AI)
def test_batch_job_source_vertexai_valid_gcs(self, vertex_client):
src_obj = types.BatchJobSource(gcs_uri=['gs://vertex-bucket/data.jsonl'])
result = t.t_batch_job_source(vertex_client, src_obj)
assert result is src_obj
def test_batch_job_source_vertexai_valid_bigquery(self, vertex_client):
src_obj = types.BatchJobSource(bigquery_uri='bq://project.dataset.table')
result = t.t_batch_job_source(vertex_client, src_obj)
assert result is src_obj
def test_batch_job_source_vertexai_valid_all(self, vertex_client):
src_obj = types.BatchJobSource(
gcs_uri=['gs://vertex-bucket/data.jsonl'],
bigquery_uri='bq://project.dataset.table',
vertex_dataset_name='projects/123/locations/us-central1/datasets/456',
)
with pytest.raises(
ValueError,
match=(
'Exactly one of `gcs_uri` or `bigquery_uri`, or'
' `vertex_dataset_name` must be set, other sources are not'
' supported in Gemini Enterprise Agent Platform.'
),
):
t.t_batch_job_source(vertex_client, src_obj)
def test_batch_job_source_vertexai_valid_gcs_and_bigquery(
self, vertex_client
):
src_obj = types.BatchJobSource(
gcs_uri=['gs://vertex-bucket/data.jsonl'],
bigquery_uri='bq://project.dataset.table',
)
with pytest.raises(
ValueError,
match=(
'Exactly one of `gcs_uri` or `bigquery_uri`, or'
' `vertex_dataset_name` must be set, other sources are not'
' supported in Gemini Enterprise Agent Platform.'
),
):
t.t_batch_job_source(vertex_client, src_obj)
def test_batch_job_source_vertexai_valid_bigquery_and_vertex_dataset(
self, vertex_client
):
src_obj = types.BatchJobSource(
bigquery_uri='bq://project.dataset.table',
vertex_dataset_name='projects/123/locations/us-central1/datasets/456',
)
with pytest.raises(
ValueError,
match=(
'Exactly one of `gcs_uri` or `bigquery_uri`, or'
' `vertex_dataset_name` must be set, other sources are not'
' supported in Gemini Enterprise Agent Platform.'
),
):
t.t_batch_job_source(vertex_client, src_obj)
def test_batch_job_source_vertexai_invalid_neither_set(self, vertex_client):
src_obj = types.BatchJobSource(file_name='files/data.csv')
with pytest.raises(
ValueError,
match=(
'Exactly one of `gcs_uri` or `bigquery_uri`, or'
' `vertex_dataset_name` must be set, other sources are not'
' supported in Gemini Enterprise Agent Platform.'
),
):
t.t_batch_job_source(vertex_client, src_obj)
class TestBatchJobDestination:
@pytest.mark.parametrize(
'dest_str, expected_gcs_uri, expected_bigquery_uri, expected_format',
[
(
'gs://bucket/path/to/output',
'gs://bucket/path/to/output',
None,
'jsonl',
),
(
'bq://project.dataset.output_table',
None,
'bq://project.dataset.output_table',
'bigquery',
),
],
)
def test_valid_destinations(
self, dest_str, expected_gcs_uri, expected_bigquery_uri, expected_format
):
result = t.t_batch_job_destination(dest_str)
expected = types.BatchJobDestination(
gcs_uri=expected_gcs_uri,
bigquery_uri=expected_bigquery_uri,
format=expected_format,
)
assert result == expected
def test_unsupported_destination(self):
with pytest.raises(ValueError, match='Unsupported destination'):
t.t_batch_job_destination('local/path/output.jsonl')
with pytest.raises(ValueError, match='Unsupported destination'):
t.t_batch_job_destination('http://some.url')
class TestBatchJobName:
def test_mldev_valid_name(self, mldev_client):
name = 'batches/my-job-123'
assert t.t_batch_job_name(mldev_client, name) == 'my-job-123'
@pytest.mark.parametrize(
'invalid_name',
[
'my-job-123',
'batches/my-job/suffix',
'batches/',
'batches/my-job-123/',
],
)
def test_mldev_invalid_name(self, mldev_client, invalid_name):
with pytest.raises(ValueError, match='Invalid batch job name'):
t.t_batch_job_name(mldev_client, invalid_name)
def test_vertexai_valid_full_path(self, vertex_client):
name = 'projects/123/locations/us-central1/batchPredictionJobs/my-vertex-job-456'
assert t.t_batch_job_name(vertex_client, name) == 'my-vertex-job-456'
def test_vertexai_valid_digit_name(self, vertex_client):
name = '9876543210'
assert t.t_batch_job_name(vertex_client, name) == '9876543210'
@pytest.mark.parametrize(
'invalid_name',
[
'my-vertex-job-456',
'projects/123/locations/us-central1/job/my-vertex-job-456',
'projects/123/locations/us-central1/batchPredictionJobs/',
'not-a-number-or-path',
'batchPredictionJobs/my-job',
],
)
def test_vertexai_invalid_name(self, vertex_client, invalid_name):
with pytest.raises(ValueError, match='Invalid batch job name'):
t.t_batch_job_name(vertex_client, invalid_name)
class TestJobState:
@pytest.mark.parametrize(
'input_state, expected_output_state',
[
('BATCH_STATE_UNSPECIFIED', 'JOB_STATE_UNSPECIFIED'),
('BATCH_STATE_PENDING', 'JOB_STATE_PENDING'),
('BATCH_STATE_SUCCEEDED', 'JOB_STATE_SUCCEEDED'),
('BATCH_STATE_FAILED', 'JOB_STATE_FAILED'),
('BATCH_STATE_CANCELLED', 'JOB_STATE_CANCELLED'),
('BATCH_STATE_EXPIRED', 'JOB_STATE_EXPIRED'),
('BATCH_STATE_RUNNING', 'JOB_STATE_RUNNING'),
('BATCH_STATE_FOOBAR', 'BATCH_STATE_FOOBAR'),
],
)
def test_job_state_mapping(self, input_state, expected_output_state):
assert t.t_job_state(input_state) == expected_output_state
@@ -0,0 +1,160 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Tests for t_content."""
import pytest
import pydantic
from ... import _transformers as t
from ... import types
def test_none():
with pytest.raises(ValueError):
t.t_content( None)
def test_content():
assert t.t_content(
types.Content(parts=[types.Part(text='test')])
) == types.Content(parts=[types.Part(text='test')])
def test_content_dict():
assert t.t_content(
{'role': 'user', 'parts': [{'text': 'test'}]}
) == types.Content(parts=[types.Part(text='test')], role='user')
def test_content_dict_invalid():
with pytest.raises(pydantic.ValidationError):
t.t_content({'invalid_key': 'test'})
def test_text_part_dict():
assert t.t_content({'text': 'test'}) == types.UserContent(
parts=[types.Part(text='test')]
)
def test_function_call_part_dict():
assert t.t_content(
{'function_call': {'name': 'test_func', 'args': {'arg1': 'value1'}}}
) == (
types.ModelContent(
parts=[
types.Part(
function_call=types.FunctionCall(
name='test_func', args={'arg1': 'value1'}
)
)
]
)
)
def test_text_part():
assert t.t_content(types.Part(text='test')) == types.UserContent(
parts=[types.Part(text='test')]
)
def test_function_call_part():
assert t.t_content(
types.Part(
function_call=types.FunctionCall(
name='test_func', args={'arg1': 'value1'}
)
),
) == (
types.ModelContent(
parts=[
types.Part(
function_call=types.FunctionCall(
name='test_func', args={'arg1': 'value1'}
)
)
]
)
)
def test_string():
assert t.t_content('test') == types.UserContent(
parts=[types.Part(text='test')]
)
def test_file():
assert t.t_content(
types.File(uri='gs://test', mime_type='image/png')
) == types.UserContent(
parts=[
types.Part(
file_data=types.FileData(
file_uri='gs://test', mime_type='image/png'
)
)
]
)
def test_file_no_uri():
with pytest.raises(ValueError):
t.t_content(types.File(mime_type='image/png'))
def test_file_no_mime_type():
with pytest.raises(ValueError):
t.t_content(types.File(uri='gs://test'))
def test_file_dict():
assert t.t_content({'file_uri': 'gs://test', 'mime_type': 'image/png'}) == types.UserContent(
parts=[
types.Part(
file_data=types.FileData(
file_uri='gs://test', mime_type='image/png'
)
)
]
)
def test_int():
try:
t.t_content(1)
except ValueError as e:
assert 'Unsupported content part type: <class \'int\'>' in str(e)
@pytest.mark.parametrize(
'contents',
[
types.Content(parts=[types.Part(inline_data=types.Blob(data=b'test'))]),
{'parts': [{'inline_data': {'data': b'test'}}]},
[
types.Content(
parts=[types.Part(inline_data=types.Blob(data=b'test'))]
)
],
[{'parts': [{'inline_data': {'data': b'test'}}]}],
],
)
def test_t_contents_strict_content(contents):
result = t.t_contents_strict(contents)
assert result == [
types.Content(parts=[types.Part(inline_data=types.Blob(data=b'test'))])
]
@@ -0,0 +1,398 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Tests for t_contents."""
import pytest
import pydantic
from ... import _transformers as t
from ... import types
def test_none():
with pytest.raises(ValueError):
t.t_contents(None)
def test_empty_list():
with pytest.raises(ValueError):
t.t_contents([])
def test_content():
assert t.t_contents(
[types.Content(parts=[types.Part(text='test')])]
) == [types.Content(parts=[types.Part(text='test')])]
def test_content_dict():
assert t.t_contents(
[{'role': 'user', 'parts': [{'text': 'test'}]}]
) == [types.Content(parts=[types.Part(text='test')], role='user')]
def test_empty_dict():
assert t.t_contents({}) == [types.Content()]
def test_invalid_dict():
with pytest.raises(pydantic.ValidationError):
t.t_contents({'invalid_key': 'test'})
def test_text_part():
assert t.t_contents([types.Part(text='test')]) == [
types.UserContent(parts=[types.Part(text='test')])
]
def test_function_call_part():
function_call = types.FunctionCall(name='test_func', args={'arg1': 'value1'})
assert t.t_contents(
[types.Part(function_call=function_call)]
) == [
types.ModelContent(
parts=[
types.Part(function_call=function_call)
]
)
]
def test_text_part_dict():
assert t.t_contents([{'text': 'test'}]) == [
types.UserContent(parts=[types.Part(text='test')])
]
def test_function_call_part_dict():
assert t.t_contents(
[{'function_call': {'name': 'test_func', 'args': {'arg1': 'value1'}}}]
) == [
types.ModelContent(
parts=[
types.Part(
function_call=types.FunctionCall(
name='test_func', args={'arg1': 'value1'}
)
)
]
)
]
def test_empty_string():
assert t.t_contents('') == [
types.UserContent(parts=[types.Part(text='')])
]
def test_string():
assert t.t_contents('test') == [
types.UserContent(parts=[types.Part(text='test')])
]
def test_file():
assert t.t_contents(
types.File(uri='gs://test', mime_type='image/png')
) == [
types.UserContent(
parts=[
types.Part(
file_data=types.FileData(
file_uri='gs://test', mime_type='image/png'
)
)
]
)
]
def test_file_dict():
assert t.t_contents({'file_uri': 'gs://test', 'mime_type': 'image/png'}) == [
types.UserContent(
parts=[
types.Part(
file_data=types.FileData(
file_uri='gs://test', mime_type='image/png'
)
)
]
)
]
def test_file_dict_list():
assert t.t_contents([{'file_uri': 'gs://test', 'mime_type': 'image/png'}]) == [
types.UserContent(
parts=[
types.Part(
file_data=types.FileData(
file_uri='gs://test', mime_type='image/png'
)
)
]
)
]
def test_file_no_uri():
with pytest.raises(ValueError):
t.t_contents(types.File(mime_type='image/png'))
def test_file_no_mime_type():
with pytest.raises(ValueError):
t.t_contents(types.File(uri='gs://test'))
def test_string_list():
assert t.t_contents(['test1', 'test2']) == [
types.UserContent(parts=[
types.Part(text='test1'),
types.Part(text='test2'),
])
]
def test_file_list():
assert t.t_contents(
[
types.File(uri='gs://test1', mime_type='image/png'),
types.File(uri='gs://test2', mime_type='image/png'),
],
) == [
types.UserContent(
parts=[
types.Part(
file_data=types.FileData(
file_uri='gs://test1', mime_type='image/png'
)
),
types.Part(
file_data=types.FileData(
file_uri='gs://test2', mime_type='image/png'
)
),
]
)
]
def test_string_file_list():
assert t.t_contents(
['test1', types.File(uri='gs://test2', mime_type='image/png')],
) == [
types.UserContent(
parts=[
types.Part(text='test1'),
types.Part(
file_data=types.FileData(
file_uri='gs://test2', mime_type='image/png'
)
),
]
)
]
def test_function_call_list():
function_call1 = types.FunctionCall(
name='test_func1', args={'arg1': 'value1'}
)
function_call2 = types.FunctionCall(
name='test_func2', args={'arg2': 'value2'}
)
assert t.t_contents(
[
types.Part(function_call=function_call1),
types.Part(function_call=function_call2),
],
) == [
types.ModelContent(
parts=[
types.Part(function_call=function_call1),
types.Part(function_call=function_call2),
]
)
]
def test_function_call_function_response_list():
question1 = 'question1'
question2 = 'question2'
function_call1 = types.FunctionCall(
name='test_func1', args={'arg1': 'value1'}
)
function_call2 = types.FunctionCall(
name='test_func2', args={'arg2': 'value2'}
)
function_response1 = types.FunctionResponse(
name='test_func1',
response={
'answer': 'answer1',
},
)
function_response2 = types.FunctionResponse(
name='test_func2',
response={
'answer': 'answer2',
},
)
assert t.t_contents(
[
question1,
question2,
types.Part(function_call=function_call1),
{'function_call': function_call2},
{'function_response': function_response1},
types.Part(function_response=function_response2),
],
) == [
types.UserContent(
parts=[
types.Part(text=question1),
types.Part(text=question2),
]
),
types.ModelContent(
parts=[
types.Part(function_call=function_call1),
types.Part(function_call=function_call2),
]
),
types.UserContent(
parts=[
types.Part(function_response=function_response1),
types.Part(function_response=function_response2),
]
),
]
def test_content_list():
assert t.t_contents(
[
types.Content(parts=[types.Part(text='test1')]),
types.Content(parts=[types.Part(text='test2')]),
],
) == [
types.Content(parts=[types.Part(text='test1')]),
types.Content(parts=[types.Part(text='test2')]),
]
def test_content_text_part_list():
assert t.t_contents(
[
types.Part(text='test1'),
types.Part(text='test2'),
types.Content(parts=[types.Part(text='test3')]),
types.Part(text='test4'),
],
) == [
types.UserContent(
parts=[
types.Part(text='test1'),
types.Part(text='test2'),
]
),
types.Content(parts=[types.Part(text='test3')]),
types.UserContent(parts=[types.Part(text='test4')]),
]
def test_list_of_text_part_list():
contents = [
'question1',
{'function_call': {'name': 'test_func1', 'args': {'arg1': 'value1'}}},
types.Part(
function_response=types.FunctionResponse(
name='test_func1',
response={
'answer': 'answer1',
},
)
),
['context2_1', 'context2_2', types.Part(text='context2_3')],
'question2',
types.Part(
function_call={'name': 'test_func2', 'args': {'arg2': 'value2'}}
),
{
'function_response': types.FunctionResponse(
name='test_func2',
response={
'answer': 'answer2',
},
)
},
]
assert t.t_contents(contents) == [
types.UserContent(parts='question1'),
types.ModelContent(
parts=[
types.Part(
function_call=types.FunctionCall(
name='test_func1', args={'arg1': 'value1'}
)
)
]
),
types.UserContent(
parts=[
types.Part(
function_response=types.FunctionResponse(
name='test_func1',
response={
'answer': 'answer1',
},
)
)
]
),
types.UserContent(
parts=[
types.Part(text='context2_1'),
types.Part(text='context2_2'),
types.Part(text='context2_3'),
]
),
types.UserContent(parts=[types.Part(text='question2')]),
types.ModelContent(
parts=[
types.Part(
function_call=types.FunctionCall(
name='test_func2', args={'arg2': 'value2'}
)
)
]
),
types.UserContent(
parts=[
types.Part(
function_response=types.FunctionResponse(
name='test_func2',
response={
'answer': 'answer2',
},
)
)
]
),
]
@@ -0,0 +1,85 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Tests for t_part."""
import pytest
import pydantic
from ... import _transformers as t
from ... import types
def test_none():
with pytest.raises(ValueError):
t.t_part(None)
def test_empty_string():
assert t.t_part('') == types.Part(text='')
def test_string():
assert t.t_part('test') == types.Part(text='test')
def test_file():
assert t.t_part(
types.File(uri='gs://test', mime_type='image/png')
) == types.Part(
file_data=types.FileData(file_uri='gs://test', mime_type='image/png')
)
def test_file_dict():
assert t.t_part(
{'file_uri': 'gs://test', 'mime_type': 'image/png'}
) == types.Part(
file_data=types.FileData(file_uri='gs://test', mime_type='image/png')
)
def test_file_no_uri():
with pytest.raises(ValueError):
t.t_part(types.File(mime_type='image/png'))
def test_file_no_mime_type():
with pytest.raises(ValueError):
t.t_part(types.File(uri='gs://test'))
def test_empty_dict():
assert t.t_part({}) == types.Part()
def test_dict():
assert t.t_part({'text': 'test'}) == types.Part(text='test')
def test_invalid_dict():
with pytest.raises(pydantic.ValidationError):
t.t_part({'invalid_key': 'test'})
def test_part():
assert t.t_part(types.Part(text='test')) == types.Part(text='test')
def test_int():
try:
t.t_part(1)
except ValueError as e:
assert 'Unsupported content part type: <class \'int\'>' in str(e)
@@ -0,0 +1,87 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Tests for t_parts."""
import pytest
import pydantic
from ... import _transformers as t
from ... import types
def test_none():
with pytest.raises(ValueError):
t.t_parts(None)
def test_empty_list():
with pytest.raises(ValueError):
t.t_parts([])
def test_list():
assert t.t_parts(['test1', 'test2']) == [
types.Part(text='test1'),
types.Part(text='test2'),
]
def test_empty_dict():
assert t.t_parts({}) == [types.Part()]
def test_dict():
assert t.t_parts({'text': 'test'}) == [types.Part(text='test')]
def test_invalid_dict():
with pytest.raises(pydantic.ValidationError):
t.t_parts({'invalid_key': 'test'})
def test_string():
assert t.t_parts('test') == [types.Part(text='test')]
def test_file():
assert t.t_parts(
types.File(uri='gs://test', mime_type='image/png')
) == [
types.Part(
file_data=types.FileData(file_uri='gs://test', mime_type='image/png')
)
]
def test_file_no_uri():
with pytest.raises(ValueError):
t.t_parts(types.File(mime_type='image/png'))
def test_file_no_mime_type():
with pytest.raises(ValueError):
t.t_parts(types.File(uri='gs://test'))
def test_part():
assert t.t_parts(types.Part(text='test')) == [types.Part(text='test')]
def test_int():
try:
t.t_parts(1)
except ValueError as e:
assert 'Unsupported content part type: <class \'int\'>' in str(e)
@@ -0,0 +1,157 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Tests for t_tool."""
import typing
from typing import Any
import pytest
from ... import _transformers as t
from ... import client as google_genai_client_module
from ... import types
_is_mcp_imported = False
if typing.TYPE_CHECKING:
from mcp import types as mcp_types
_is_mcp_imported = True
else:
try:
from mcp import types as mcp_types
_is_mcp_imported = True
except ImportError:
_is_mcp_imported = False
@pytest.fixture
def client(use_vertex):
if use_vertex:
yield google_genai_client_module.Client(
vertexai=use_vertex, project='test-project', location='test-location'
)
else:
yield google_genai_client_module.Client(
vertexai=use_vertex, api_key='test-api-key'
)
@pytest.mark.usefixtures('client')
def test_none(client):
assert t.t_tool(client, None) == None
@pytest.mark.usefixtures('client')
def test_function(client):
def test_func(arg1: str, arg2: int):
pass
assert t.t_tool(client, test_func) == types.Tool(
function_declarations=[
types.FunctionDeclaration(
name='test_func',
parameters=types.Schema(
type='OBJECT',
properties={
'arg1': types.Schema(type='STRING'),
'arg2': types.Schema(type='INTEGER'),
},
required=['arg1', 'arg2'],
),
)
]
)
@pytest.mark.usefixtures('client')
def test_dictionary(client):
assert t.t_tool(
client,
{
'function_declarations': [{
'name': 'tool',
'description': 'tool-description',
'parameters': {'type': 'OBJECT', 'properties': {}},
}]
},
) == types.Tool(
function_declarations=[
types.FunctionDeclaration(
name='tool',
description='tool-description',
parameters=types.Schema(
type='OBJECT',
properties={},
),
)
]
)
@pytest.mark.usefixtures('client')
def test_mcp_tool(client):
if not _is_mcp_imported:
return
mcp_tool = mcp_types.Tool(
name='tool',
description='tool-description',
inputSchema={
'type': 'object',
'properties': {
'key1': {
'type': 'string',
},
'key2': {
'type': 'number',
},
},
},
)
assert t.t_tool(client, mcp_tool) == types.Tool(
function_declarations=[
types.FunctionDeclaration(
name='tool',
description='tool-description',
parameters=types.Schema(
type='OBJECT',
properties={
'key1': types.Schema(type='STRING'),
'key2': types.Schema(type='NUMBER'),
},
),
)
]
)
@pytest.mark.usefixtures('client')
def test_tool(client):
tool = types.Tool(
function_declarations=[
types.FunctionDeclaration(
name='tool',
description='tool-description',
parameters=types.Schema(
type='OBJECT',
properties={
'key1': types.Schema(type='STRING'),
'key2': types.Schema(type='NUMBER'),
},
),
)
]
)
assert t.t_tool(client, tool) == tool
@@ -0,0 +1,195 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Tests for t_tools."""
import typing
from typing import Any
import pytest
from ... import _transformers as t
from ... import client as google_genai_client_module
from ... import types
_is_mcp_imported = False
if typing.TYPE_CHECKING:
from mcp import ClientSession as McpClientSession
from mcp import types as mcp_types
_is_mcp_imported = True
else:
try:
from mcp import ClientSession as McpClientSession
from mcp import types as mcp_types
_is_mcp_imported = True
except ImportError:
_is_mcp_imported = False
@pytest.fixture
def client(use_vertex):
if use_vertex:
yield google_genai_client_module.Client(
vertexai=use_vertex, project='test-project', location='test-location'
)
else:
yield google_genai_client_module.Client(
vertexai=use_vertex, api_key='test-api-key'
)
@pytest.mark.usefixtures('client')
def test_empty(client):
assert t.t_tools(client, []) == []
@pytest.mark.usefixtures('client')
def test_function(client):
def test_func(arg1: str, arg2: int):
pass
assert t.t_tools(client, [test_func]) == [
types.Tool(
function_declarations=[
types.FunctionDeclaration(
name='test_func',
parameters=types.Schema(
type='OBJECT',
properties={
'arg1': types.Schema(type='STRING'),
'arg2': types.Schema(type='INTEGER'),
},
required=['arg1', 'arg2'],
),
)
]
)
]
@pytest.mark.usefixtures('client')
def test_tool(client):
tool = types.Tool(
function_declarations=[
types.FunctionDeclaration(
name='tool',
description='tool-description',
parameters=types.Schema(
type='OBJECT',
properties={
'key1': types.Schema(type='STRING'),
'key2': types.Schema(type='NUMBER'),
},
),
)
]
)
assert t.t_tools(client, [tool]) == [tool]
@pytest.mark.usefixtures('client')
def test_mcp_tool(client):
if not _is_mcp_imported:
return
mcp_tool = mcp_types.Tool(
name='tool',
description='tool-description',
inputSchema={
'type': 'object',
'properties': {
'key1': {
'type': 'string',
},
'key2': {
'type': 'number',
},
},
},
)
assert t.t_tools(client, [mcp_tool]) == [
types.Tool(
function_declarations=[
types.FunctionDeclaration(
name='tool',
description='tool-description',
parameters=types.Schema(
type='OBJECT',
properties={
'key1': types.Schema(type='STRING'),
'key2': types.Schema(type='NUMBER'),
},
),
)
]
)
]
@pytest.mark.usefixtures('client')
def test_multiple_tools(client):
if not _is_mcp_imported:
return
mcp_tool1 = mcp_types.Tool(
name='tool1',
description='tool1-description',
inputSchema={
'type': 'object',
'properties': {
'key1': {
'type': 'string',
},
},
},
)
mcp_tool2 = mcp_types.Tool(
name='tool2',
description='tool2-description',
inputSchema={
'type': 'object',
'properties': {
'key1': {
'type': 'number',
},
},
},
)
assert t.t_tools(client, [mcp_tool1, mcp_tool2]) == [
types.Tool(
function_declarations=[
types.FunctionDeclaration(
name='tool1',
description='tool1-description',
parameters=types.Schema(
type='OBJECT',
properties={
'key1': types.Schema(type='STRING'),
},
),
),
types.FunctionDeclaration(
name='tool2',
description='tool2-description',
parameters=types.Schema(
type='OBJECT',
properties={
'key1': types.Schema(type='NUMBER'),
},
),
),
]
),
]