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.
This commit is contained in:
ccurme
2024-09-11 16:00:16 -04:00
committed by GitHub
parent bbb7c267e5
commit c074922876
2 changed files with 42 additions and 2 deletions

View File

@@ -288,7 +288,10 @@ def format_tool_to_openai_function(tool: BaseTool) -> FunctionDescription:
Returns: Returns:
The function description. 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( return convert_pydantic_to_openai_function(
tool.tool_call_schema, name=tool.name, description=tool.description tool.tool_call_schema, name=tool.name, description=tool.description
) )

View File

@@ -38,7 +38,7 @@ from pydantic import BaseModel, Field
from langchain_core.messages import AIMessage, HumanMessage, ToolMessage from langchain_core.messages import AIMessage, HumanMessage, ToolMessage
from langchain_core.runnables import Runnable, RunnableLambda 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 ( from langchain_core.utils.function_calling import (
_convert_typed_dict_to_openai_function, _convert_typed_dict_to_openai_function,
convert_to_openai_function, convert_to_openai_function,
@@ -112,6 +112,20 @@ def dummy_tool() -> BaseTool:
return DummyFunction() 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() @pytest.fixture()
def dummy_pydantic() -> Type[BaseModel]: def dummy_pydantic() -> Type[BaseModel]:
class dummy_function(BaseModel): class dummy_function(BaseModel):
@@ -234,6 +248,7 @@ class DummyWithClassMethod:
def test_convert_to_openai_function( def test_convert_to_openai_function(
pydantic: Type[BaseModel], pydantic: Type[BaseModel],
function: Callable, function: Callable,
dummy_structured_tool: StructuredTool,
dummy_tool: BaseTool, dummy_tool: BaseTool,
json_schema: Dict, json_schema: Dict,
Annotated_function: Callable, Annotated_function: Callable,
@@ -264,6 +279,7 @@ def test_convert_to_openai_function(
for fn in ( for fn in (
pydantic, pydantic,
function, function,
dummy_structured_tool,
dummy_tool, dummy_tool,
json_schema, json_schema,
expected, expected,
@@ -296,6 +312,27 @@ def test_convert_to_openai_function(
runnable_expected["parameters"] = parameters runnable_expected["parameters"] = parameters
assert actual == runnable_expected 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") @pytest.mark.xfail(reason="Direct pydantic v2 models not yet supported")
def test_convert_to_openai_function_nested_v2() -> None: def test_convert_to_openai_function_nested_v2() -> None: