first vommit
This commit is contained in:
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."""
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -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'
|
||||
+72
@@ -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'))])
|
||||
]
|
||||
+398
@@ -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'),
|
||||
},
|
||||
),
|
||||
),
|
||||
]
|
||||
),
|
||||
]
|
||||
Reference in new issue
Block a user