mirror of
https://github.com/hwchase17/langchain.git
synced 2025-08-07 20:15:40 +00:00
core[patch]: support labeled json schema as tools (#18935)
This commit is contained in:
parent
950ab056eb
commit
19721246f5
@ -270,7 +270,8 @@ def convert_to_openai_function(
|
|||||||
Args:
|
Args:
|
||||||
function: Either a dictionary, a pydantic.BaseModel class, or a Python function.
|
function: Either a dictionary, a pydantic.BaseModel class, or a Python function.
|
||||||
If a dictionary is passed in, it is assumed to already be a valid OpenAI
|
If a dictionary is passed in, it is assumed to already be a valid OpenAI
|
||||||
function.
|
function or a JSON schema with top-level 'title' and 'description' keys
|
||||||
|
specified.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A dict version of the passed in function which is compatible with the
|
A dict version of the passed in function which is compatible with the
|
||||||
@ -278,8 +279,21 @@ def convert_to_openai_function(
|
|||||||
"""
|
"""
|
||||||
from langchain_core.tools import BaseTool
|
from langchain_core.tools import BaseTool
|
||||||
|
|
||||||
if isinstance(function, dict):
|
# already in OpenAI function format
|
||||||
|
if isinstance(function, dict) and all(
|
||||||
|
k in function for k in ("name", "description", "parameters")
|
||||||
|
):
|
||||||
return function
|
return function
|
||||||
|
# a JSON schema with title and description
|
||||||
|
elif isinstance(function, dict) and all(
|
||||||
|
k in function for k in ("title", "description", "properties")
|
||||||
|
):
|
||||||
|
function = function.copy()
|
||||||
|
return {
|
||||||
|
"name": function.pop("title"),
|
||||||
|
"description": function.pop("description"),
|
||||||
|
"parameters": function,
|
||||||
|
}
|
||||||
elif isinstance(function, type) and issubclass(function, BaseModel):
|
elif isinstance(function, type) and issubclass(function, BaseModel):
|
||||||
return cast(Dict, convert_pydantic_to_openai_function(function))
|
return cast(Dict, convert_pydantic_to_openai_function(function))
|
||||||
elif isinstance(function, BaseTool):
|
elif isinstance(function, BaseTool):
|
||||||
@ -288,8 +302,10 @@ def convert_to_openai_function(
|
|||||||
return convert_python_function_to_openai_function(function)
|
return convert_python_function_to_openai_function(function)
|
||||||
else:
|
else:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Unsupported function type {type(function)}. Functions must be passed in"
|
f"Unsupported function\n\n{function}\n\nFunctions must be passed in"
|
||||||
f" as Dict, pydantic.BaseModel, or Callable."
|
" as Dict, pydantic.BaseModel, or Callable. If they're a dict they must"
|
||||||
|
" either be in OpenAI function format or valid JSON schema with top-level"
|
||||||
|
" 'title' and 'description' keys."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -301,13 +317,14 @@ def convert_to_openai_tool(
|
|||||||
Args:
|
Args:
|
||||||
tool: Either a dictionary, a pydantic.BaseModel class, Python function, or
|
tool: Either a dictionary, a pydantic.BaseModel class, Python function, or
|
||||||
BaseTool. If a dictionary is passed in, it is assumed to already be a valid
|
BaseTool. If a dictionary is passed in, it is assumed to already be a valid
|
||||||
OpenAI tool or OpenAI function.
|
OpenAI tool, OpenAI function, or a JSON schema with top-level 'title' and
|
||||||
|
'description' keys specified.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A dict version of the passed in tool which is compatible with the
|
A dict version of the passed in tool which is compatible with the
|
||||||
OpenAI tool-calling API.
|
OpenAI tool-calling API.
|
||||||
"""
|
"""
|
||||||
if isinstance(tool, dict) and "type" in tool:
|
if isinstance(tool, dict) and tool.get("type") == "function" and "function" in tool:
|
||||||
return tool
|
return tool
|
||||||
function = convert_to_openai_function(tool)
|
function = convert_to_openai_function(tool)
|
||||||
return {"type": "function", "function": function}
|
return {"type": "function", "function": function}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from typing import Any, Callable, List, Literal, Optional, Type
|
from typing import Any, Callable, Dict, List, Literal, Optional, Type
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -49,8 +49,29 @@ def dummy_tool() -> BaseTool:
|
|||||||
return DummyFunction()
|
return DummyFunction()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def json_schema() -> Dict:
|
||||||
|
return {
|
||||||
|
"title": "dummy_function",
|
||||||
|
"description": "dummy function",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"arg1": {"description": "foo", "type": "integer"},
|
||||||
|
"arg2": {
|
||||||
|
"description": "one of 'bar', 'baz'",
|
||||||
|
"enum": ["bar", "baz"],
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": ["arg1", "arg2"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_convert_to_openai_function(
|
def test_convert_to_openai_function(
|
||||||
pydantic: Type[BaseModel], function: Callable, dummy_tool: BaseTool
|
pydantic: Type[BaseModel],
|
||||||
|
function: Callable,
|
||||||
|
dummy_tool: BaseTool,
|
||||||
|
json_schema: Dict,
|
||||||
) -> None:
|
) -> None:
|
||||||
expected = {
|
expected = {
|
||||||
"name": "dummy_function",
|
"name": "dummy_function",
|
||||||
@ -69,7 +90,7 @@ def test_convert_to_openai_function(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for fn in (pydantic, function, dummy_tool, expected):
|
for fn in (pydantic, function, dummy_tool, json_schema, expected):
|
||||||
actual = convert_to_openai_function(fn) # type: ignore
|
actual = convert_to_openai_function(fn) # type: ignore
|
||||||
assert actual == expected
|
assert actual == expected
|
||||||
|
|
||||||
|
@ -799,7 +799,8 @@ class ChatOpenAI(BaseChatModel):
|
|||||||
the model output will be a dict. With a Pydantic class the returned
|
the model output will be a dict. With a Pydantic class the returned
|
||||||
attributes will be validated, whereas with a dict they will not be. If
|
attributes will be validated, whereas with a dict they will not be. If
|
||||||
`method` is "function_calling" and `schema` is a dict, then the dict
|
`method` is "function_calling" and `schema` is a dict, then the dict
|
||||||
must match the OpenAI function-calling spec.
|
must match the OpenAI function-calling spec or be a valid JSON schema
|
||||||
|
with top level 'title' and 'description' keys specified.
|
||||||
method: The method for steering model generation, either "function_calling"
|
method: The method for steering model generation, either "function_calling"
|
||||||
or "json_mode". If "function_calling" then the schema will be converted
|
or "json_mode". If "function_calling" then the schema will be converted
|
||||||
to an OpenAI function and the returned model will make use of the
|
to an OpenAI function and the returned model will make use of the
|
||||||
|
Loading…
Reference in New Issue
Block a user