core[patch]: convert_to_openai_tool Anthropic support (#27591)

This commit is contained in:
Bagatur 2024-10-23 12:27:06 -07:00 committed by GitHub
parent 217de4e6a6
commit 968dccee04
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 56 additions and 22 deletions

View File

@ -883,8 +883,6 @@ def convert_to_openai_messages(
) -> Union[dict, list[dict]]: ) -> Union[dict, list[dict]]:
"""Convert LangChain messages into OpenAI message dicts. """Convert LangChain messages into OpenAI message dicts.
.. versionadded:: 0.3.11
Args: Args:
messages: Message-like object or iterable of objects whose contents are messages: Message-like object or iterable of objects whose contents are
in OpenAI, Anthropic, Bedrock Converse, or VertexAI formats. in OpenAI, Anthropic, Bedrock Converse, or VertexAI formats.
@ -937,6 +935,8 @@ def convert_to_openai_messages(
# {'role': 'assistant', 'content': 'thats nice'} # {'role': 'assistant', 'content': 'thats nice'}
# ] # ]
.. versionadded:: 0.3.11
""" # noqa: E501 """ # noqa: E501
if text_format not in ("string", "block"): if text_format not in ("string", "block"):
err = f"Unrecognized {text_format=}, expected one of 'string' or 'block'." err = f"Unrecognized {text_format=}, expected one of 'string' or 'block'."

View File

@ -336,30 +336,32 @@ def convert_to_openai_function(
strict: Optional[bool] = None, strict: Optional[bool] = None,
) -> dict[str, Any]: ) -> dict[str, Any]:
"""Convert a raw function/class to an OpenAI function. """Convert a raw function/class to an OpenAI function.
.. versionchanged:: 0.2.29
``strict`` arg added.
Args: Args:
function: function:
A dictionary, Pydantic BaseModel class, TypedDict class, a LangChain A dictionary, Pydantic BaseModel class, TypedDict class, a LangChain
Tool object, or a Python function. If a dictionary is passed in, it is Tool object, or a Python function. If a dictionary is passed in, it is
assumed to already be a valid OpenAI function or a JSON schema with assumed to already be a valid OpenAI function, a JSON schema with
top-level 'title' and 'description' keys specified. top-level 'title' and 'description' keys specified, or an Anthropic format
tool.
strict: strict:
If True, model output is guaranteed to exactly match the JSON Schema If True, model output is guaranteed to exactly match the JSON Schema
provided in the function definition. If None, ``strict`` argument will not provided in the function definition. If None, ``strict`` argument will not
be included in function definition. be included in function definition.
.. versionadded:: 0.2.29
Returns: Returns:
A dict version of the passed in function which is compatible with the OpenAI A dict version of the passed in function which is compatible with the OpenAI
function-calling API. function-calling API.
Raises: Raises:
ValueError: If function is not in a supported format. ValueError: If function is not in a supported format.
.. versionchanged:: 0.2.29
``strict`` arg added.
.. versionchanged:: 0.3.13
Support for Anthropic format tools added.
""" """
from langchain_core.tools import BaseTool from langchain_core.tools import BaseTool
@ -378,6 +380,15 @@ def convert_to_openai_function(
"description": function.pop("description"), "description": function.pop("description"),
"parameters": function, "parameters": function,
} }
# an Anthropic format tool
elif isinstance(function, dict) and all(
k in function for k in ("name", "description", "input_schema")
):
oai_function = {
"name": function["name"],
"description": function["description"],
"parameters": function["input_schema"],
}
elif isinstance(function, type) and is_basemodel_subclass(function): elif isinstance(function, type) and is_basemodel_subclass(function):
oai_function = cast(dict, convert_pydantic_to_openai_function(function)) oai_function = cast(dict, convert_pydantic_to_openai_function(function))
elif is_typeddict(function): elif is_typeddict(function):
@ -414,34 +425,35 @@ def convert_to_openai_tool(
*, *,
strict: Optional[bool] = None, strict: Optional[bool] = None,
) -> dict[str, Any]: ) -> dict[str, Any]:
"""Convert a raw function/class to an OpenAI tool. """Convert a tool-like object to an OpenAI tool schema.
.. versionchanged:: 0.2.29
``strict`` arg added.
Args: Args:
tool: tool:
Either a dictionary, a pydantic.BaseModel class, Python function, or 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, OpenAI function, or a JSON schema with top-level 'title' and OpenAI tool, OpenAI function, a JSON schema with top-level 'title' and
'description' keys specified. 'description' keys specified, or an Anthropic format tool.
strict: strict:
If True, model output is guaranteed to exactly match the JSON Schema If True, model output is guaranteed to exactly match the JSON Schema
provided in the function definition. If None, ``strict`` argument will not provided in the function definition. If None, ``strict`` argument will not
be included in tool definition. be included in tool definition.
.. versionadded:: 0.2.29
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.
.. versionchanged:: 0.2.29
``strict`` arg added.
.. versionchanged:: 0.3.13
Support for Anthropic format tools added.
""" """
if isinstance(tool, dict) and tool.get("type") == "function" and "function" in tool: if isinstance(tool, dict) and tool.get("type") == "function" and "function" in tool:
return tool return tool
oai_function = convert_to_openai_function(tool, strict=strict) oai_function = convert_to_openai_function(tool, strict=strict)
oai_tool: dict[str, Any] = {"type": "function", "function": oai_function} return {"type": "function", "function": oai_function}
return oai_tool
def tool_example_to_messages( def tool_example_to_messages(

View File

@ -210,6 +210,26 @@ def json_schema() -> dict:
} }
@pytest.fixture()
def anthropic_tool() -> dict:
return {
"name": "dummy_function",
"description": "dummy function",
"input_schema": {
"type": "object",
"properties": {
"arg1": {"description": "foo", "type": "integer"},
"arg2": {
"description": "one of 'bar', 'baz'",
"enum": ["bar", "baz"],
"type": "string",
},
},
"required": ["arg1", "arg2"],
},
}
class Dummy: class Dummy:
def dummy_function(self, arg1: int, arg2: Literal["bar", "baz"]) -> None: def dummy_function(self, arg1: int, arg2: Literal["bar", "baz"]) -> None:
"""dummy function """dummy function
@ -237,6 +257,7 @@ def test_convert_to_openai_function(
dummy_structured_tool: StructuredTool, dummy_structured_tool: StructuredTool,
dummy_tool: BaseTool, dummy_tool: BaseTool,
json_schema: dict, json_schema: dict,
anthropic_tool: dict,
annotated_function: Callable, annotated_function: Callable,
dummy_pydantic: type[BaseModel], dummy_pydantic: type[BaseModel],
runnable: Runnable, runnable: Runnable,
@ -268,6 +289,7 @@ def test_convert_to_openai_function(
dummy_structured_tool, dummy_structured_tool,
dummy_tool, dummy_tool,
json_schema, json_schema,
anthropic_tool,
expected, expected,
Dummy.dummy_function, Dummy.dummy_function,
DummyWithClassMethod.dummy_function, DummyWithClassMethod.dummy_function,