Skip to content

Commit

Permalink
core[patch]: support labeled json schema as tools (langchain-ai#18935)
Browse files Browse the repository at this point in the history
  • Loading branch information
baskaryan authored and Dave Bechberger committed Mar 29, 2024
1 parent 00647ca commit 1d8f130
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 10 deletions.
29 changes: 23 additions & 6 deletions libs/core/langchain_core/utils/function_calling.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,16 +270,30 @@ def convert_to_openai_function(
Args:
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
function.
function or a JSON schema with top-level 'title' and 'description' keys
specified.
Returns:
A dict version of the passed in function which is compatible with the
OpenAI function-calling API.
"""
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
# 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):
return cast(Dict, convert_pydantic_to_openai_function(function))
elif isinstance(function, BaseTool):
Expand All @@ -288,8 +302,10 @@ def convert_to_openai_function(
return convert_python_function_to_openai_function(function)
else:
raise ValueError(
f"Unsupported function type {type(function)}. Functions must be passed in"
f" as Dict, pydantic.BaseModel, or Callable."
f"Unsupported function\n\n{function}\n\nFunctions must be passed in"
" 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."
)


Expand All @@ -301,13 +317,14 @@ def convert_to_openai_tool(
Args:
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
OpenAI tool or OpenAI function.
OpenAI tool, OpenAI function, or a JSON schema with top-level 'title' and
'description' keys specified.
Returns:
A dict version of the passed in tool which is compatible with the
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
function = convert_to_openai_function(tool)
return {"type": "function", "function": function}
27 changes: 24 additions & 3 deletions libs/core/tests/unit_tests/utils/test_function_calling.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -49,8 +49,29 @@ def _run(self, *args: Any, **kwargs: Any) -> Any:
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(
pydantic: Type[BaseModel], function: Callable, dummy_tool: BaseTool
pydantic: Type[BaseModel],
function: Callable,
dummy_tool: BaseTool,
json_schema: Dict,
) -> None:
expected = {
"name": "dummy_function",
Expand All @@ -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
assert actual == expected

Expand Down
3 changes: 2 additions & 1 deletion libs/partners/openai/langchain_openai/chat_models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -799,7 +799,8 @@ def with_structured_output(
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
`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"
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
Expand Down

0 comments on commit 1d8f130

Please sign in to comment.