From e6952b4ba1fb7e30ec7184551f87b3b628020bf7 Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Wed, 27 Mar 2024 16:35:02 +0100 Subject: [PATCH] fix: relax top-level title/description requirement for converting JSON schema to OpenAI function --- .../langchain_core/utils/function_calling.py | 13 +++---- .../unit_tests/utils/test_function_calling.py | 35 +++++++++++++++++++ 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/libs/core/langchain_core/utils/function_calling.py b/libs/core/langchain_core/utils/function_calling.py index a406b87097e..385bedd0752 100644 --- a/libs/core/langchain_core/utils/function_calling.py +++ b/libs/core/langchain_core/utils/function_calling.py @@ -295,14 +295,12 @@ def convert_to_openai_function( k in function for k in ("name", "description", "parameters") ): return function - # a JSON schema with title and description - elif isinstance(function, dict) and all( - k in function for k in ("title", "description", "properties") - ): + # a JSON schema + elif isinstance(function, dict) and "properties" in function: function = function.copy() return { - "name": function.pop("title"), - "description": function.pop("description"), + "name": function.pop("title", "extract"), + "description": function.pop("description", ""), "parameters": function, } elif isinstance(function, type) and issubclass(function, BaseModel): @@ -315,8 +313,7 @@ def convert_to_openai_function( raise ValueError( f"Unsupported function\n\n{function}\n\nFunctions must be passed in" " as Dict, pydantic.BaseModel, or Callable. If they're a dict they must" - " either be in OpenAI function format or valid JSON schema with top-level" - " 'title' and 'description' keys." + " either be in OpenAI function format or valid JSON schema" ) diff --git a/libs/core/tests/unit_tests/utils/test_function_calling.py b/libs/core/tests/unit_tests/utils/test_function_calling.py index 00328bcf29b..cbae44c5e09 100644 --- a/libs/core/tests/unit_tests/utils/test_function_calling.py +++ b/libs/core/tests/unit_tests/utils/test_function_calling.py @@ -71,6 +71,21 @@ def json_schema() -> Dict: } +@pytest.fixture() +def json_schema_lax() -> Dict: + return { + "type": "object", + "properties": { + "arg1": {"description": "foo", "type": "integer"}, + "arg2": { + "description": "one of 'bar', 'baz'", + "enum": ["bar", "baz"], + "type": "string", + }, + }, + } + + def test_convert_to_openai_function( pydantic: Type[BaseModel], function: Callable, @@ -99,6 +114,26 @@ def test_convert_to_openai_function( assert actual == expected +def test_convert_lax_jsonschema_to_openai_function(json_schema_lax: Dict) -> None: + expected = { + "name": "extract", + "description": "", + "parameters": { + "type": "object", + "properties": { + "arg1": {"description": "foo", "type": "integer"}, + "arg2": { + "description": "one of 'bar', 'baz'", + "enum": ["bar", "baz"], + "type": "string", + }, + }, + }, + } + + assert convert_to_openai_function(json_schema_lax) == expected + + @pytest.mark.xfail(reason="Pydantic converts Optional[str] to str in .schema()") def test_function_optional_param() -> None: @tool