diff --git a/src/celeste/providers/google/generate_content/parameters.py b/src/celeste/providers/google/generate_content/parameters.py index 056b821..95653d0 100644 --- a/src/celeste/providers/google/generate_content/parameters.py +++ b/src/celeste/providers/google/generate_content/parameters.py @@ -210,8 +210,6 @@ def _map_user_tool(tool: dict[str, Any]) -> dict[str, Any]: params = tool.get("parameters", {}) if isinstance(params, type) and issubclass(params, BaseModel): schema = params.model_json_schema() - # Remove unsupported 'title' fields - schema = ToolsMapper._remove_titles(schema) else: schema = params @@ -219,25 +217,7 @@ def _map_user_tool(tool: dict[str, Any]) -> dict[str, Any]: if "description" in tool: result["description"] = tool["description"] if schema: - result["parameters"] = schema - return result - - @staticmethod - def _remove_titles(schema: dict[str, Any]) -> dict[str, Any]: - """Remove unsupported 'title' fields from schema for Google.""" - result: dict[str, Any] = {} - for key, value in schema.items(): - if key == "title": - continue - if isinstance(value, dict): - result[key] = ToolsMapper._remove_titles(value) - elif isinstance(value, list): - result[key] = [ - ToolsMapper._remove_titles(item) if isinstance(item, dict) else item - for item in value - ] - else: - result[key] = value + result["parametersJsonSchema"] = schema return result diff --git a/tests/unit_tests/test_google_tools_mapper.py b/tests/unit_tests/test_google_tools_mapper.py new file mode 100644 index 0000000..dc4e081 --- /dev/null +++ b/tests/unit_tests/test_google_tools_mapper.py @@ -0,0 +1,65 @@ +"""Regression coverage for Gemini ToolsMapper — emits `parametersJsonSchema`.""" + +from __future__ import annotations + +from celeste.providers.google.generate_content.parameters import ToolsMapper + + +def _map(tool: dict) -> dict: + return ToolsMapper._map_user_tool(tool) + + +def test_maps_to_parameters_json_schema_not_legacy_parameters() -> None: + fn = _map( + { + "name": "example", + "description": "example tool", + "parameters": { + "type": "object", + "properties": {"prompt": {"type": "string"}}, + "required": ["prompt"], + }, + }, + ) + assert "parametersJsonSchema" in fn + assert "parameters" not in fn + assert fn["parametersJsonSchema"]["properties"]["prompt"]["type"] == "string" + + +def test_preserves_integer_enum_on_non_string_type() -> None: + fn = _map( + { + "name": "example", + "parameters": { + "type": "object", + "properties": { + "duration": {"type": "integer", "enum": [4, 6, 8]}, + }, + }, + }, + ) + duration = fn["parametersJsonSchema"]["properties"]["duration"] + assert duration["type"] == "integer" + assert duration["enum"] == [4, 6, 8] + + +def test_preserves_title_fields() -> None: + fn = _map( + { + "name": "example", + "parameters": { + "title": "ExampleInput", + "type": "object", + "properties": {"x": {"type": "string", "title": "X"}}, + }, + }, + ) + assert fn["parametersJsonSchema"]["title"] == "ExampleInput" + assert fn["parametersJsonSchema"]["properties"]["x"]["title"] == "X" + + +def test_tool_with_no_parameters_emits_no_schema_field() -> None: + fn = _map({"name": "example", "description": "no-args"}) + assert "parameters" not in fn + assert "parametersJsonSchema" not in fn + assert fn["description"] == "no-args"