From c074922876bb588824787628c405940de39e9bfa Mon Sep 17 00:00:00 2001 From: ccurme Date: Wed, 11 Sep 2024 16:00:16 -0400 Subject: [PATCH] core[patch]: fix regression in convert_to_openai_tool with instances of Tool (v0.3rc) (#26349) Cherry-pick https://github.com/langchain-ai/langchain/pull/26327 from master. --- .../langchain_core/utils/function_calling.py | 5 ++- .../unit_tests/utils/test_function_calling.py | 39 ++++++++++++++++++- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/libs/core/langchain_core/utils/function_calling.py b/libs/core/langchain_core/utils/function_calling.py index 1d57541f63a..b883b5cc838 100644 --- a/libs/core/langchain_core/utils/function_calling.py +++ b/libs/core/langchain_core/utils/function_calling.py @@ -288,7 +288,10 @@ def format_tool_to_openai_function(tool: BaseTool) -> FunctionDescription: Returns: The function description. """ - if tool.tool_call_schema: + from langchain_core.tools import simple + + 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( tool.tool_call_schema, name=tool.name, description=tool.description ) 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 6c8eb773db1..a2ef09eb231 100644 --- a/libs/core/tests/unit_tests/utils/test_function_calling.py +++ b/libs/core/tests/unit_tests/utils/test_function_calling.py @@ -38,7 +38,7 @@ from pydantic import BaseModel, Field from langchain_core.messages import AIMessage, HumanMessage, ToolMessage from langchain_core.runnables import Runnable, RunnableLambda -from langchain_core.tools import BaseTool, tool +from langchain_core.tools import BaseTool, StructuredTool, Tool, tool from langchain_core.utils.function_calling import ( _convert_typed_dict_to_openai_function, convert_to_openai_function, @@ -112,6 +112,20 @@ def dummy_tool() -> BaseTool: return DummyFunction() +@pytest.fixture() +def dummy_structured_tool() -> StructuredTool: + class Schema(BaseModel): + arg1: int = Field(..., description="foo") + arg2: Literal["bar", "baz"] = Field(..., description="one of 'bar', 'baz'") + + return StructuredTool.from_function( + lambda x: None, + name="dummy_function", + description="dummy function", + args_schema=Schema, + ) + + @pytest.fixture() def dummy_pydantic() -> Type[BaseModel]: class dummy_function(BaseModel): @@ -234,6 +248,7 @@ class DummyWithClassMethod: def test_convert_to_openai_function( pydantic: Type[BaseModel], function: Callable, + dummy_structured_tool: StructuredTool, dummy_tool: BaseTool, json_schema: Dict, Annotated_function: Callable, @@ -264,6 +279,7 @@ def test_convert_to_openai_function( for fn in ( pydantic, function, + dummy_structured_tool, dummy_tool, json_schema, expected, @@ -296,6 +312,27 @@ def test_convert_to_openai_function( runnable_expected["parameters"] = parameters assert actual == runnable_expected + # Test simple Tool + def my_function(input_string: str) -> str: + pass + + tool = Tool( + name="dummy_function", + func=my_function, + description="test description", + ) + actual = convert_to_openai_function(tool) + expected = { + "name": "dummy_function", + "description": "test description", + "parameters": { + "properties": {"__arg1": {"title": "__arg1", "type": "string"}}, + "required": ["__arg1"], + "type": "object", + }, + } + assert actual == expected + @pytest.mark.xfail(reason="Direct pydantic v2 models not yet supported") def test_convert_to_openai_function_nested_v2() -> None: