Repository: huggingface/transformers
Pull request #35776 (2025-01-19 17:21:28)

Tool calling: support more types
# What does this PR do?

Adds more supported types in chat templates.
Why not use an empty type for "any"?
=> Sometimes users want to specify a type hint like this:
```py
def get_weather(location: Any) -> None:
    return "Nice weather today."
```
=> In that case, I think it makes it more explicit to have the type hint being precised in the json schema as "any" rather than left empty as is currently the case.

I don't know if these changes are compatible with the current usage of chat template in transformers (in particular, maybe some models are trained with the convention to let the type for 'Any' blank), but they allow finer definition of inputs which is useful for using transformers chat templates in `smolagents`. Let me know if this is an appropriate change @Rocketknight1!

CISC: `any` is not part of the [JSON Schema specification](https://json-schema.org/understanding-json-schema/reference/type), so I think this should be left blank.

However setting NoneType to `null` is the right thing to do and seems to have been an omission.

@Rocketknight1 I noticed some of your chat templates are missing the handling of `null` as well, and as I've reused your `json_to_python_type` macro I added this in f.ex. MiniCPM-4B and Dria-Agent models' templates.
Rocketknight1: Seconding @CISC's comment, I don't think we can use `{"type": "any"}`, but the type for `None` is definitely appreciated!
aymeric-roucher: That's updated @Rocketknight1 ! Thanks to you and @CISC for your feedback! 🤗 

Patch:
diff --git a/src/transformers/utils/chat_template_utils.py b/src/transformers/utils/chat_template_utils.py
index be047f9f5c..a6dff34ebc 100644
--- a/src/transformers/utils/chat_template_utils.py
+++ b/src/transformers/utils/chat_template_utils.py
@@ -19,6 +19,7 @@ import types
 from contextlib import contextmanager
 from datetime import datetime
 from functools import lru_cache
+from types import NoneType
 from typing import Any, Callable, Dict, List, Optional, Tuple, Union, get_args, get_origin, get_type_hints
 
 from packaging import version
@@ -77,6 +78,7 @@ def _get_json_schema_type(param_type: str) -> Dict[str, str]:
         float: {"type": "number"},
         str: {"type": "string"},
         bool: {"type": "boolean"},
+        NoneType: {"type": "null"},
         Any: {},
     }
     if is_vision_available():


Test patch:
diff --git a/tests/utils/test_chat_template_utils.py b/tests/utils/test_chat_template_utils.py
index 1816ddd951..730f64859d 100644
--- a/tests/utils/test_chat_template_utils.py
+++ b/tests/utils/test_chat_template_utils.py
@@ -419,6 +419,31 @@ class JsonSchemaGeneratorTest(unittest.TestCase):
 
         self.assertEqual(schema["function"], expected_schema)
 
+    def test_return_none(self):
+        def fn(x: int) -> None:
+            """
+            Test function
+
+            Args:
+                x: The first input
+            """
+            pass
+
+        schema = get_json_schema(fn)
+        expected_schema = {
+            "name": "fn",
+            "description": "Test function",
+            "parameters": {
+                "type": "object",
+                "properties": {
+                    "x": {"type": "integer", "description": "The first input"},
+                },
+                "required": ["x"],
+            },
+            "return": {"type": "null"},
+        }
+        self.assertEqual(schema["function"], expected_schema)
+
     def test_everything_all_at_once(self):
         def fn(
             x: str, y: Optional[List[Union[str, int]]], z: Tuple[Union[str, int], str] = (42, "hello")