From 4822beb2980ec3bea1ab0280ba3e47ab1b40cd45 Mon Sep 17 00:00:00 2001 From: hmn falahi <46359682+hmnfalahi@users.noreply.github.com> Date: Mon, 29 Apr 2024 19:16:26 +0330 Subject: [PATCH] Ignore self/cls from required args of class functions in convert_to_openai_tool (#20691) Removed redundant self/cls from required args of class functions in _get_python_function_required_args: ```python class MemberTool: def search_member( self, keyword: str, *args, **kwargs, ): """Search on members with any keyword like first_name, last_name, email Args: keyword: Any keyword of member """ headers = dict(authorization=kwargs['token']) members = [] try: members = request_( method='SEARCH', url=f'{service_url}/apiv1/members', headers=headers, json=dict(query=keyword), ) except Exception as e: logger.info(e.__doc__) return members convert_to_openai_tool(MemberTool.search_member) ``` expected result: ``` {'type': 'function', 'function': {'name': 'search_member', 'description': 'Search on members with any keyword like first_name, last_name, username, email', 'parameters': {'type': 'object', 'properties': {'keyword': {'type': 'string', 'description': 'Any keyword of member'}}, 'required': ['keyword']}}} ``` #20685 --------- Co-authored-by: Bagatur <22008038+baskaryan@users.noreply.github.com> Co-authored-by: Bagatur --- .../langchain_core/utils/function_calling.py | 8 +++-- .../unit_tests/utils/test_function_calling.py | 33 ++++++++++++++++++- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/libs/core/langchain_core/utils/function_calling.py b/libs/core/langchain_core/utils/function_calling.py index 860259a93e..b2b380e201 100644 --- a/libs/core/langchain_core/utils/function_calling.py +++ b/libs/core/langchain_core/utils/function_calling.py @@ -4,6 +4,7 @@ from __future__ import annotations import inspect import uuid +from types import FunctionType, MethodType from typing import ( TYPE_CHECKING, Any, @@ -200,8 +201,11 @@ def _get_python_function_required_args(function: Callable) -> List[str]: required = spec.args[: -len(spec.defaults)] if spec.defaults else spec.args required += [k for k in spec.kwonlyargs if k not in (spec.kwonlydefaults or {})] - is_class = type(function) is type - if is_class and required[0] == "self": + is_function_type = isinstance(function, FunctionType) + is_method_type = isinstance(function, MethodType) + if is_function_type and required[0] == "self": + required = required[1:] + elif is_method_type and required[0] == "cls": required = required[1:] return required 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 00328bcf29..1f818fe6ba 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,29 @@ def json_schema() -> Dict: } +class Dummy: + def dummy_function(self, arg1: int, arg2: Literal["bar", "baz"]) -> None: + """dummy function + + Args: + arg1: foo + arg2: one of 'bar', 'baz' + """ + pass + + +class DummyWithClassMethod: + @classmethod + def dummy_function(cls, arg1: int, arg2: Literal["bar", "baz"]) -> None: + """dummy function + + Args: + arg1: foo + arg2: one of 'bar', 'baz' + """ + pass + + def test_convert_to_openai_function( pydantic: Type[BaseModel], function: Callable, @@ -94,7 +117,15 @@ def test_convert_to_openai_function( }, } - for fn in (pydantic, function, dummy_tool, json_schema, expected): + for fn in ( + pydantic, + function, + dummy_tool, + json_schema, + expected, + Dummy.dummy_function, + DummyWithClassMethod.dummy_function, + ): actual = convert_to_openai_function(fn) # type: ignore assert actual == expected