From 4bb391fd4e94a968d26f80d2faeb163e266112aa Mon Sep 17 00:00:00 2001 From: ccurme Date: Fri, 3 Jan 2025 14:29:01 -0500 Subject: [PATCH] core[patch]: remove deprecated functions from tool binding hotpath (#29015) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (Inspired by https://github.com/langchain-ai/langchain/issues/26918) We rely on some deprecated public functions in the hot path for tool binding (`convert_pydantic_to_openai_function`, `convert_python_function_to_openai_function`, and `format_tool_to_openai_function`). My understanding is that what is deprecated is not the functionality they implement, but use of them in the public API -- we expect to continue to rely on them. Here we update these functions to be private and not deprecated. We keep the public, deprecated functions as simple wrappers that can be safely deleted. The `@deprecated` wrapper adds considerable latency due to its use of the `inspect` module. This update speeds up `bind_tools` by a factor of ~100x: Before: ![Screenshot 2025-01-03 at 11 22 55 AM](https://github.com/user-attachments/assets/94b1c433-ce12-406f-b64c-ca7103badfe0) After: ![Screenshot 2025-01-03 at 11 23 41 AM](https://github.com/user-attachments/assets/02d0deab-82e4-45ca-8cc7-a20b91a5b5db) --------- Co-authored-by: Erick Friis --- .../langchain_core/utils/function_calling.py | 58 ++++++++++--------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/libs/core/langchain_core/utils/function_calling.py b/libs/core/langchain_core/utils/function_calling.py index e56e7dd49ba..dd3468d59e5 100644 --- a/libs/core/langchain_core/utils/function_calling.py +++ b/libs/core/langchain_core/utils/function_calling.py @@ -75,12 +75,7 @@ def _rm_titles(kv: dict, prev_key: str = "") -> dict: return new_kv -@deprecated( - "0.1.16", - alternative="langchain_core.utils.function_calling.convert_to_openai_function()", - removal="1.0", -) -def convert_pydantic_to_openai_function( +def _convert_pydantic_to_openai_function( model: type, *, name: Optional[str] = None, @@ -121,6 +116,13 @@ def convert_pydantic_to_openai_function( } +convert_pydantic_to_openai_function = deprecated( + "0.1.16", + alternative="langchain_core.utils.function_calling.convert_to_openai_function()", + removal="1.0", +)(_convert_pydantic_to_openai_function) + + @deprecated( "0.1.16", alternative="langchain_core.utils.function_calling.convert_to_openai_tool()", @@ -144,7 +146,7 @@ def convert_pydantic_to_openai_tool( Returns: The tool description. """ - function = convert_pydantic_to_openai_function( + function = _convert_pydantic_to_openai_function( model, name=name, description=description ) return {"type": "function", "function": function} @@ -155,12 +157,7 @@ def _get_python_function_name(function: Callable) -> str: return function.__name__ -@deprecated( - "0.1.16", - alternative="langchain_core.utils.function_calling.convert_to_openai_function()", - removal="1.0", -) -def convert_python_function_to_openai_function( +def _convert_python_function_to_openai_function( function: Callable, ) -> FunctionDescription: """Convert a Python function to an OpenAI function-calling API compatible dict. @@ -186,13 +183,20 @@ def convert_python_function_to_openai_function( error_on_invalid_docstring=False, include_injected=False, ) - return convert_pydantic_to_openai_function( + return _convert_pydantic_to_openai_function( model, name=func_name, description=model.__doc__, ) +convert_python_function_to_openai_function = deprecated( + "0.1.16", + alternative="langchain_core.utils.function_calling.convert_to_openai_function()", + removal="1.0", +)(_convert_python_function_to_openai_function) + + def _convert_typed_dict_to_openai_function(typed_dict: type) -> FunctionDescription: visited: dict = {} from pydantic.v1 import BaseModel @@ -201,7 +205,7 @@ def _convert_typed_dict_to_openai_function(typed_dict: type) -> FunctionDescript type[BaseModel], _convert_any_typed_dicts_to_pydantic(typed_dict, visited=visited), ) - return convert_pydantic_to_openai_function(model) # type: ignore + return _convert_pydantic_to_openai_function(model) # type: ignore _MAX_TYPED_DICT_RECURSION = 25 @@ -272,12 +276,7 @@ def _convert_any_typed_dicts_to_pydantic( return type_ -@deprecated( - "0.1.16", - alternative="langchain_core.utils.function_calling.convert_to_openai_function()", - removal="1.0", -) -def format_tool_to_openai_function(tool: BaseTool) -> FunctionDescription: +def _format_tool_to_openai_function(tool: BaseTool) -> FunctionDescription: """Format tool into the OpenAI function API. Args: @@ -290,7 +289,7 @@ def format_tool_to_openai_function(tool: BaseTool) -> FunctionDescription: is_simple_oai_tool = isinstance(tool, simple.Tool) and not tool.args_schema if tool.tool_call_schema and not is_simple_oai_tool: - return convert_pydantic_to_openai_function( + return _convert_pydantic_to_openai_function( tool.tool_call_schema, name=tool.name, description=tool.description ) else: @@ -312,6 +311,13 @@ def format_tool_to_openai_function(tool: BaseTool) -> FunctionDescription: } +format_tool_to_openai_function = deprecated( + "0.1.16", + alternative="langchain_core.utils.function_calling.convert_to_openai_function()", + removal="1.0", +)(_format_tool_to_openai_function) + + @deprecated( "0.1.16", alternative="langchain_core.utils.function_calling.convert_to_openai_tool()", @@ -326,7 +332,7 @@ def format_tool_to_openai_tool(tool: BaseTool) -> ToolDescription: Returns: The tool description. """ - function = format_tool_to_openai_function(tool) + function = _format_tool_to_openai_function(tool) return {"type": "function", "function": function} @@ -408,15 +414,15 @@ def convert_to_openai_function( if function_copy and "properties" in function_copy: oai_function["parameters"] = function_copy elif isinstance(function, type) and is_basemodel_subclass(function): - oai_function = cast(dict, convert_pydantic_to_openai_function(function)) + oai_function = cast(dict, _convert_pydantic_to_openai_function(function)) elif is_typeddict(function): oai_function = cast( dict, _convert_typed_dict_to_openai_function(cast(type, function)) ) elif isinstance(function, BaseTool): - oai_function = cast(dict, format_tool_to_openai_function(function)) + oai_function = cast(dict, _format_tool_to_openai_function(function)) elif callable(function): - oai_function = cast(dict, convert_python_function_to_openai_function(function)) + oai_function = cast(dict, _convert_python_function_to_openai_function(function)) else: msg = ( f"Unsupported function\n\n{function}\n\nFunctions must be passed in"