diff --git a/libs/core/langchain_core/utils/function_calling.py b/libs/core/langchain_core/utils/function_calling.py index c5c4d82461d..67ac7ebd7f9 100644 --- a/libs/core/langchain_core/utils/function_calling.py +++ b/libs/core/langchain_core/utils/function_calling.py @@ -280,7 +280,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 ba5ba4c9fa6..9570c52d4c2 100644 --- a/libs/core/tests/unit_tests/utils/test_function_calling.py +++ b/libs/core/tests/unit_tests/utils/test_function_calling.py @@ -37,7 +37,7 @@ except ImportError: from langchain_core.messages import AIMessage, HumanMessage, ToolMessage from langchain_core.pydantic_v1 import BaseModel, Field 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, @@ -111,6 +111,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): @@ -233,6 +247,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, @@ -263,6 +278,7 @@ def test_convert_to_openai_function( for fn in ( pydantic, function, + dummy_structured_tool, dummy_tool, json_schema, expected, @@ -295,6 +311,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: