|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import unittest |
|
from typing import Optional, Union |
|
|
|
from transformers.utils import DocstringParsingException, TypeHintParsingException, get_json_schema |
|
|
|
|
|
class JsonSchemaGeneratorTest(unittest.TestCase): |
|
def test_simple_function(self): |
|
def fn(x: int): |
|
""" |
|
Test function |
|
|
|
Args: |
|
x: The input |
|
""" |
|
return x |
|
|
|
schema = get_json_schema(fn) |
|
expected_schema = { |
|
"name": "fn", |
|
"description": "Test function", |
|
"parameters": { |
|
"type": "object", |
|
"properties": {"x": {"type": "integer", "description": "The input"}}, |
|
"required": ["x"], |
|
}, |
|
} |
|
self.assertEqual(schema["function"], expected_schema) |
|
|
|
def test_no_arguments(self): |
|
def fn(): |
|
""" |
|
Test function |
|
""" |
|
return True |
|
|
|
schema = get_json_schema(fn) |
|
expected_schema = { |
|
"name": "fn", |
|
"description": "Test function", |
|
"parameters": {"type": "object", "properties": {}}, |
|
} |
|
self.assertEqual(schema["function"], expected_schema) |
|
|
|
def test_union(self): |
|
def fn(x: Union[int, float]): |
|
""" |
|
Test function |
|
|
|
Args: |
|
x: The input |
|
""" |
|
return x |
|
|
|
schema = get_json_schema(fn) |
|
expected_schema = { |
|
"name": "fn", |
|
"description": "Test function", |
|
"parameters": { |
|
"type": "object", |
|
"properties": {"x": {"type": ["integer", "number"], "description": "The input"}}, |
|
"required": ["x"], |
|
}, |
|
} |
|
self.assertEqual(schema["function"], expected_schema) |
|
|
|
def test_optional(self): |
|
def fn(x: Optional[int]): |
|
""" |
|
Test function |
|
|
|
Args: |
|
x: The input |
|
""" |
|
return x |
|
|
|
schema = get_json_schema(fn) |
|
expected_schema = { |
|
"name": "fn", |
|
"description": "Test function", |
|
"parameters": { |
|
"type": "object", |
|
"properties": {"x": {"type": "integer", "description": "The input", "nullable": True}}, |
|
"required": ["x"], |
|
}, |
|
} |
|
self.assertEqual(schema["function"], expected_schema) |
|
|
|
def test_default_arg(self): |
|
def fn(x: int = 42): |
|
""" |
|
Test function |
|
|
|
Args: |
|
x: The input |
|
""" |
|
return x |
|
|
|
schema = get_json_schema(fn) |
|
expected_schema = { |
|
"name": "fn", |
|
"description": "Test function", |
|
"parameters": {"type": "object", "properties": {"x": {"type": "integer", "description": "The input"}}}, |
|
} |
|
self.assertEqual(schema["function"], expected_schema) |
|
|
|
def test_nested_list(self): |
|
def fn(x: list[list[Union[str, int]]]): |
|
""" |
|
Test function |
|
|
|
Args: |
|
x: The input |
|
""" |
|
return x |
|
|
|
schema = get_json_schema(fn) |
|
expected_schema = { |
|
"name": "fn", |
|
"description": "Test function", |
|
"parameters": { |
|
"type": "object", |
|
"properties": { |
|
"x": { |
|
"type": "array", |
|
"items": {"type": "array", "items": {"type": ["integer", "string"]}}, |
|
"description": "The input", |
|
} |
|
}, |
|
"required": ["x"], |
|
}, |
|
} |
|
self.assertEqual(schema["function"], expected_schema) |
|
|
|
def test_multiple_arguments(self): |
|
def fn(x: int, y: str): |
|
""" |
|
Test function |
|
|
|
Args: |
|
x: The input |
|
y: Also the input |
|
""" |
|
return x |
|
|
|
schema = get_json_schema(fn) |
|
expected_schema = { |
|
"name": "fn", |
|
"description": "Test function", |
|
"parameters": { |
|
"type": "object", |
|
"properties": { |
|
"x": {"type": "integer", "description": "The input"}, |
|
"y": {"type": "string", "description": "Also the input"}, |
|
}, |
|
"required": ["x", "y"], |
|
}, |
|
} |
|
self.assertEqual(schema["function"], expected_schema) |
|
|
|
def test_multiple_complex_arguments(self): |
|
def fn(x: list[Union[int, float]], y: Optional[Union[int, str]] = None): |
|
""" |
|
Test function |
|
|
|
Args: |
|
x: The input |
|
y: Also the input |
|
""" |
|
return x |
|
|
|
schema = get_json_schema(fn) |
|
expected_schema = { |
|
"name": "fn", |
|
"description": "Test function", |
|
"parameters": { |
|
"type": "object", |
|
"properties": { |
|
"x": {"type": "array", "items": {"type": ["integer", "number"]}, "description": "The input"}, |
|
"y": { |
|
"type": ["integer", "string"], |
|
"nullable": True, |
|
"description": "Also the input", |
|
}, |
|
}, |
|
"required": ["x"], |
|
}, |
|
} |
|
self.assertEqual(schema["function"], expected_schema) |
|
|
|
def test_missing_docstring(self): |
|
def fn(x: int): |
|
return x |
|
|
|
with self.assertRaises(DocstringParsingException): |
|
get_json_schema(fn) |
|
|
|
def test_missing_param_docstring(self): |
|
def fn(x: int): |
|
""" |
|
Test function |
|
""" |
|
return x |
|
|
|
with self.assertRaises(DocstringParsingException): |
|
get_json_schema(fn) |
|
|
|
def test_missing_type_hint(self): |
|
def fn(x): |
|
""" |
|
Test function |
|
|
|
Args: |
|
x: The input |
|
""" |
|
return x |
|
|
|
with self.assertRaises(TypeHintParsingException): |
|
get_json_schema(fn) |
|
|
|
def test_return_value(self): |
|
def fn(x: int) -> int: |
|
""" |
|
Test function |
|
|
|
Args: |
|
x: The input |
|
""" |
|
return x |
|
|
|
schema = get_json_schema(fn) |
|
expected_schema = { |
|
"name": "fn", |
|
"description": "Test function", |
|
"parameters": { |
|
"type": "object", |
|
"properties": {"x": {"type": "integer", "description": "The input"}}, |
|
"required": ["x"], |
|
}, |
|
"return": {"type": "integer"}, |
|
} |
|
self.assertEqual(schema["function"], expected_schema) |
|
|
|
def test_return_value_docstring(self): |
|
def fn(x: int) -> int: |
|
""" |
|
Test function |
|
|
|
Args: |
|
x: The input |
|
|
|
|
|
Returns: |
|
The output |
|
""" |
|
return x |
|
|
|
schema = get_json_schema(fn) |
|
expected_schema = { |
|
"name": "fn", |
|
"description": "Test function", |
|
"parameters": { |
|
"type": "object", |
|
"properties": {"x": {"type": "integer", "description": "The input"}}, |
|
"required": ["x"], |
|
}, |
|
"return": {"type": "integer", "description": "The output"}, |
|
} |
|
self.assertEqual(schema["function"], expected_schema) |
|
|
|
def test_tuple(self): |
|
def fn(x: tuple[int, str]): |
|
""" |
|
Test function |
|
|
|
Args: |
|
x: The input |
|
|
|
|
|
Returns: |
|
The output |
|
""" |
|
return x |
|
|
|
schema = get_json_schema(fn) |
|
expected_schema = { |
|
"name": "fn", |
|
"description": "Test function", |
|
"parameters": { |
|
"type": "object", |
|
"properties": { |
|
"x": { |
|
"type": "array", |
|
"prefixItems": [{"type": "integer"}, {"type": "string"}], |
|
"description": "The input", |
|
} |
|
}, |
|
"required": ["x"], |
|
}, |
|
} |
|
self.assertEqual(schema["function"], expected_schema) |
|
|
|
def test_single_element_tuple_fails(self): |
|
def fn(x: tuple[int]): |
|
""" |
|
Test function |
|
|
|
Args: |
|
x: The input |
|
|
|
|
|
Returns: |
|
The output |
|
""" |
|
return x |
|
|
|
|
|
with self.assertRaises(TypeHintParsingException): |
|
get_json_schema(fn) |
|
|
|
def test_ellipsis_type_fails(self): |
|
def fn(x: tuple[int, ...]): |
|
""" |
|
Test function |
|
|
|
Args: |
|
x: The input |
|
|
|
|
|
Returns: |
|
The output |
|
""" |
|
return x |
|
|
|
|
|
with self.assertRaises(TypeHintParsingException): |
|
get_json_schema(fn) |
|
|
|
def test_enum_extraction(self): |
|
def fn(temperature_format: str): |
|
""" |
|
Test function |
|
|
|
Args: |
|
temperature_format: The temperature format to use (Choices: ["celsius", "fahrenheit"]) |
|
|
|
|
|
Returns: |
|
The temperature |
|
""" |
|
return -40.0 |
|
|
|
|
|
schema = get_json_schema(fn) |
|
expected_schema = { |
|
"name": "fn", |
|
"description": "Test function", |
|
"parameters": { |
|
"type": "object", |
|
"properties": { |
|
"temperature_format": { |
|
"type": "string", |
|
"enum": ["celsius", "fahrenheit"], |
|
"description": "The temperature format to use", |
|
} |
|
}, |
|
"required": ["temperature_format"], |
|
}, |
|
} |
|
|
|
self.assertEqual(schema["function"], expected_schema) |
|
|
|
def test_multiline_docstring_with_types(self): |
|
def fn(x: int, y: int): |
|
""" |
|
Test function |
|
|
|
Args: |
|
x: The first input |
|
|
|
y: The second input. This is a longer description |
|
that spans multiple lines with indentation and stuff. |
|
|
|
Returns: |
|
God knows what |
|
""" |
|
pass |
|
|
|
schema = get_json_schema(fn) |
|
expected_schema = { |
|
"name": "fn", |
|
"description": "Test function", |
|
"parameters": { |
|
"type": "object", |
|
"properties": { |
|
"x": {"type": "integer", "description": "The first input"}, |
|
"y": { |
|
"type": "integer", |
|
"description": "The second input. This is a longer description that spans multiple lines with indentation and stuff.", |
|
}, |
|
}, |
|
"required": ["x", "y"], |
|
}, |
|
} |
|
|
|
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") |
|
) -> tuple[int, str]: |
|
""" |
|
Test function with multiple args, and docstring args that we have to strip out. |
|
|
|
Args: |
|
x: The first input. It's got a big multiline |
|
description and also contains |
|
(choices: ["a", "b", "c"]) |
|
|
|
y: The second input. It's a big list with a single-line description. |
|
|
|
z: The third input. It's some kind of tuple with a default arg. |
|
|
|
Returns: |
|
The output. The return description is also a big multiline |
|
description that spans multiple lines. |
|
""" |
|
pass |
|
|
|
schema = get_json_schema(fn) |
|
expected_schema = { |
|
"name": "fn", |
|
"description": "Test function with multiple args, and docstring args that we have to strip out.", |
|
"parameters": { |
|
"type": "object", |
|
"properties": { |
|
"x": { |
|
"type": "string", |
|
"enum": ["a", "b", "c"], |
|
"description": "The first input. It's got a big multiline description and also contains", |
|
}, |
|
"y": { |
|
"type": "array", |
|
"items": {"type": ["integer", "string"]}, |
|
"nullable": True, |
|
"description": "The second input. It's a big list with a single-line description.", |
|
}, |
|
"z": { |
|
"type": "array", |
|
"prefixItems": [{"type": ["integer", "string"]}, {"type": "string"}], |
|
"description": "The third input. It's some kind of tuple with a default arg.", |
|
}, |
|
}, |
|
"required": ["x", "y"], |
|
}, |
|
"return": { |
|
"type": "array", |
|
"prefixItems": [{"type": "integer"}, {"type": "string"}], |
|
"description": "The output. The return description is also a big multiline\n description that spans multiple lines.", |
|
}, |
|
} |
|
self.assertEqual(schema["function"], expected_schema) |
|
|