From 0275f9bce3f1c591871b1c03a9af219f00d46421 Mon Sep 17 00:00:00 2001 From: kamilbenkirane Date: Sat, 18 Apr 2026 19:21:05 +0200 Subject: [PATCH] fix(google): send tool schemas via parametersJsonSchema (#262) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The legacy functionDeclarations[].parameters field restricts enum to TYPE_STRING properties, so any integer Choice (e.g. veo-3 duration [4, 6, 8], gemini-embedding dimensions [768, 1536, 3072]) produced a 400 at tool-registration time. Gemini's November 2025 update added parametersJsonSchema, which accepts raw JSON Schema — integer enums, $defs, $ref, anyOf, additionalProperties — with no sanitization. Switch ToolsMapper._map_user_tool to emit parametersJsonSchema and drop the now-unused _remove_titles helper (title is natively supported by the JSON Schema field). Closes #262. --- .../google/generate_content/parameters.py | 22 +------ tests/unit_tests/test_google_tools_mapper.py | 65 +++++++++++++++++++ 2 files changed, 66 insertions(+), 21 deletions(-) create mode 100644 tests/unit_tests/test_google_tools_mapper.py 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"