livekit--agents
188 行
5.7 KiB
Python
188 行
5.7 KiB
Python
import datetime
|
|
|
|
import pytest
|
|
from google.genai import types
|
|
from pydantic import BaseModel, Field
|
|
|
|
from livekit.plugins.google import utils
|
|
|
|
pytestmark = [pytest.mark.unit, pytest.mark.concurrent]
|
|
|
|
# Gemini Schema Tests
|
|
|
|
|
|
# Test for inlining $ref definitions
|
|
async def test_json_def_replaced():
|
|
class Location(BaseModel):
|
|
lat: float
|
|
lng: float = 1.1
|
|
|
|
class Locations(BaseModel):
|
|
locations: list[Location]
|
|
|
|
json_schema = Locations.model_json_schema()
|
|
# Original schema with $defs as produced by Pydantic.
|
|
expected_schema = {
|
|
"$defs": {
|
|
"Location": {
|
|
"properties": {
|
|
"lat": {"title": "Lat", "type": "number"},
|
|
"lng": {"default": 1.1, "title": "Lng", "type": "number"},
|
|
},
|
|
"required": ["lat"],
|
|
"title": "Location",
|
|
"type": "object",
|
|
}
|
|
},
|
|
"properties": {
|
|
"locations": {
|
|
"items": {"$ref": "#/$defs/Location"},
|
|
"title": "Locations",
|
|
"type": "array",
|
|
}
|
|
},
|
|
"required": ["locations"],
|
|
"title": "Locations",
|
|
"type": "object",
|
|
}
|
|
assert json_schema == expected_schema
|
|
|
|
gemini_schema = utils._GeminiJsonSchema(json_schema).simplify()
|
|
|
|
expected_gemini_schema = {
|
|
"properties": {
|
|
"locations": {
|
|
"items": {
|
|
"properties": {
|
|
"lat": {"type": types.Type.NUMBER},
|
|
"lng": {"type": types.Type.NUMBER},
|
|
},
|
|
"required": ["lat"],
|
|
"type": types.Type.OBJECT,
|
|
},
|
|
"type": types.Type.ARRAY,
|
|
}
|
|
},
|
|
"required": ["locations"],
|
|
"type": types.Type.OBJECT,
|
|
}
|
|
assert gemini_schema == expected_gemini_schema
|
|
|
|
|
|
# Test for handling anyOf (optional field)
|
|
async def test_json_def_replaced_any_of():
|
|
class Location(BaseModel):
|
|
lat: float
|
|
lng: float
|
|
|
|
class Locations(BaseModel):
|
|
op_location: Location | None = None
|
|
|
|
json_schema = Locations.model_json_schema()
|
|
|
|
gemini_schema = utils._GeminiJsonSchema(json_schema).simplify()
|
|
|
|
# The anyOf containing the Location ref and {"type": "null"} is merged,
|
|
# so op_location becomes the inlined Location with "nullable": True.
|
|
expected_gemini_schema = {
|
|
"properties": {
|
|
"op_location": {
|
|
"properties": {
|
|
"lat": {"type": types.Type.NUMBER},
|
|
"lng": {"type": types.Type.NUMBER},
|
|
},
|
|
"required": ["lat", "lng"],
|
|
"type": types.Type.OBJECT,
|
|
"nullable": True,
|
|
}
|
|
},
|
|
"type": types.Type.OBJECT,
|
|
}
|
|
assert gemini_schema == expected_gemini_schema
|
|
|
|
|
|
# Test for recursive $ref – should raise ValueError
|
|
async def test_json_def_recursive():
|
|
class Location(BaseModel):
|
|
lat: float
|
|
lng: float
|
|
nested_locations: list["Location"]
|
|
|
|
Location.model_rebuild()
|
|
json_schema = Location.model_json_schema()
|
|
expected_schema = {
|
|
"$defs": {
|
|
"Location": {
|
|
"properties": {
|
|
"lat": {"title": "Lat", "type": "number"},
|
|
"lng": {"title": "Lng", "type": "number"},
|
|
"nested_locations": {
|
|
"items": {"$ref": "#/$defs/Location"},
|
|
"title": "Nested Locations",
|
|
"type": "array",
|
|
},
|
|
},
|
|
"required": ["lat", "lng", "nested_locations"],
|
|
"title": "Location",
|
|
"type": "object",
|
|
}
|
|
},
|
|
"$ref": "#/$defs/Location",
|
|
}
|
|
assert json_schema == expected_schema
|
|
|
|
with pytest.raises(
|
|
ValueError,
|
|
match=r"Recursive `\$ref`s in JSON Schema are not supported by Gemini",
|
|
):
|
|
utils._GeminiJsonSchema(json_schema).simplify()
|
|
|
|
|
|
# Test for preserving format, and description on string fields
|
|
async def test_json_def_date():
|
|
class FormattedStringFields(BaseModel):
|
|
d: datetime.date
|
|
dt: datetime.datetime
|
|
t: datetime.time = Field(description="")
|
|
td: datetime.timedelta = Field(description="my timedelta")
|
|
|
|
json_schema = FormattedStringFields.model_json_schema()
|
|
expected_schema = {
|
|
"properties": {
|
|
"d": {"format": "date", "title": "D", "type": "string"},
|
|
"dt": {"format": "date-time", "title": "Dt", "type": "string"},
|
|
"t": {"format": "time", "title": "T", "type": "string", "description": ""},
|
|
"td": {
|
|
"format": "duration",
|
|
"title": "Td",
|
|
"type": "string",
|
|
"description": "my timedelta",
|
|
},
|
|
},
|
|
"required": ["d", "dt", "t", "td"],
|
|
"title": "FormattedStringFields",
|
|
"type": "object",
|
|
}
|
|
assert json_schema == expected_schema
|
|
|
|
gemini_schema = utils._GeminiJsonSchema(json_schema).simplify()
|
|
expected_gemini_schema = {
|
|
"properties": {
|
|
"d": {"format": "date", "type": types.Type.STRING},
|
|
"dt": {"format": "date-time", "type": types.Type.STRING},
|
|
"t": {
|
|
"format": "time",
|
|
"type": types.Type.STRING,
|
|
"description": "",
|
|
},
|
|
"td": {
|
|
"format": "duration",
|
|
"type": types.Type.STRING,
|
|
"description": "my timedelta",
|
|
},
|
|
},
|
|
"required": ["d", "dt", "t", "td"],
|
|
"type": types.Type.OBJECT,
|
|
}
|
|
assert gemini_schema == expected_gemini_schema
|