core[patch]: fix regression in convert_to_openai_tool with instances of Tool (#26327)

```python
from langchain_core.tools import Tool
from langchain_core.utils.function_calling import convert_to_openai_tool

def my_function(x: int) -> int:
    return x + 2

tool = Tool(
    name="tool_name",
    func=my_function,
    description="test description",
)
convert_to_openai_tool(tool)
```

Current:
```
{'type': 'function',
 'function': {'name': 'tool_name',
  'description': 'test description',
  'parameters': {'type': 'object',
   'properties': {'args': {'type': 'array', 'items': {}},
    'config': {'type': 'object',
     'properties': {'tags': {'type': 'array', 'items': {'type': 'string'}},
      'metadata': {'type': 'object'},
      'callbacks': {'anyOf': [{'type': 'array', 'items': {}}, {}]},
      'run_name': {'type': 'string'},
      'max_concurrency': {'type': 'integer'},
      'recursion_limit': {'type': 'integer'},
      'configurable': {'type': 'object'},
      'run_id': {'type': 'string', 'format': 'uuid'}}},
    'kwargs': {'type': 'object'}},
   'required': ['config']}}}
```

Here:
```
{'type': 'function',
 'function': {'name': 'tool_name',
  'description': 'test description',
  'parameters': {'properties': {'__arg1': {'title': '__arg1',
     'type': 'string'}},
   'required': ['__arg1'],
   'type': 'object'}}}
```
This commit is contained in:
ccurme 2024-09-11 15:51:10 -04:00 committed by GitHub
parent 7feae62ad7
commit 398718e1cb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 42 additions and 2 deletions

View File

@ -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
)

View File

@ -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: