mirror of https://github.com/hwchase17/langchain
core[patch], community[patch], openai[patch]: consolidate openai tool… (#16485)
… converters One way to convert anything to an OAI function: convert_to_openai_function One way to convert anything to an OAI tool: convert_to_openai_tool Corresponding bind functions on OAI models: bind_functions, bind_toolspull/16588/head
parent
148347e858
commit
ef42d9d559
File diff suppressed because one or more lines are too long
@ -0,0 +1,492 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "dae8d4ed-9150-45da-b494-7717ab0a2960",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Function calling\n",
|
||||
"\n",
|
||||
"Certain chat models, like [OpenAI's](https://platform.openai.com/docs/guides/function-calling), have a function-calling API that lets you describe functions and their arguments, and have the model return a JSON object with a function to invoke and the inputs to that function. Function-calling is extremely useful for building [tool-using chains and agents](/docs/use_cases/tool_use/), and for getting structured outputs from models more generally.\n",
|
||||
"\n",
|
||||
"LangChain comes with a number of utilities to make function-calling easy. Namely, it comes with\n",
|
||||
"\n",
|
||||
"* simple syntax for binding functions to models\n",
|
||||
"* converters for formatting various types of objects to the expected function schemas\n",
|
||||
"* output parsers for extracting the function invocations from API responses\n",
|
||||
"\n",
|
||||
"We'll focus here on the first two bullets. To see how output parsing works as well check out the [OpenAI Tools output parsers](/docs/modules/model_io/output_parsers/types/openai_tools)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a177c64b-7c99-495c-b362-5ed3b40aa26a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Defining functions\n",
|
||||
"\n",
|
||||
"We'll focus on the [OpenAI function format](https://platform.openai.com/docs/api-reference/chat/create#chat-create-tools) here since as of this writing that is the main model provider that supports function calling. LangChain has a built-in converter that can turn Python functions, Pydantic classes, and LangChain Tools into the OpenAI function format:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "f6d1dc0c-6170-4977-809f-365099f628ea",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n",
|
||||
"\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m23.2.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m23.3.2\u001b[0m\n",
|
||||
"\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n",
|
||||
"Note: you may need to restart the kernel to use updated packages.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"%pip install -qU langchain-core langchain-openai"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6bd290bd-7621-466b-a73e-fc8480f879ec",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Python function"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "41ebab5c-0e9f-4b49-86ee-9290ced2fe96",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{\n",
|
||||
" \"type\": \"function\",\n",
|
||||
" \"function\": {\n",
|
||||
" \"name\": \"multiply\",\n",
|
||||
" \"description\": \"Multiply two integers together.\",\n",
|
||||
" \"parameters\": {\n",
|
||||
" \"type\": \"object\",\n",
|
||||
" \"properties\": {\n",
|
||||
" \"a\": {\n",
|
||||
" \"type\": \"integer\",\n",
|
||||
" \"description\": \"First integer\"\n",
|
||||
" },\n",
|
||||
" \"b\": {\n",
|
||||
" \"type\": \"integer\",\n",
|
||||
" \"description\": \"Second integer\"\n",
|
||||
" }\n",
|
||||
" },\n",
|
||||
" \"required\": [\n",
|
||||
" \"a\",\n",
|
||||
" \"b\"\n",
|
||||
" ]\n",
|
||||
" }\n",
|
||||
" }\n",
|
||||
"}\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import json\n",
|
||||
"\n",
|
||||
"from langchain_core.utils.function_calling import convert_to_openai_tool\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def multiply(a: int, b: int) -> int:\n",
|
||||
" \"\"\"Multiply two integers together.\n",
|
||||
"\n",
|
||||
" Args:\n",
|
||||
" a: First integer\n",
|
||||
" b: Second integer\n",
|
||||
" \"\"\"\n",
|
||||
" return a * b\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"print(json.dumps(convert_to_openai_tool(multiply), indent=2))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ecf22577-38ab-48f1-ba0b-371aaba1bacc",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Pydantic class"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "ecc8ffd4-aed3-4f47-892d-1896cc1ca4dc",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{\n",
|
||||
" \"type\": \"function\",\n",
|
||||
" \"function\": {\n",
|
||||
" \"name\": \"multiply\",\n",
|
||||
" \"description\": \"Multiply two integers together.\",\n",
|
||||
" \"parameters\": {\n",
|
||||
" \"type\": \"object\",\n",
|
||||
" \"properties\": {\n",
|
||||
" \"a\": {\n",
|
||||
" \"description\": \"First integer\",\n",
|
||||
" \"type\": \"integer\"\n",
|
||||
" },\n",
|
||||
" \"b\": {\n",
|
||||
" \"description\": \"Second integer\",\n",
|
||||
" \"type\": \"integer\"\n",
|
||||
" }\n",
|
||||
" },\n",
|
||||
" \"required\": [\n",
|
||||
" \"a\",\n",
|
||||
" \"b\"\n",
|
||||
" ]\n",
|
||||
" }\n",
|
||||
" }\n",
|
||||
"}\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from langchain_core.pydantic_v1 import BaseModel, Field\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class multiply(BaseModel):\n",
|
||||
" \"\"\"Multiply two integers together.\"\"\"\n",
|
||||
"\n",
|
||||
" a: int = Field(..., description=\"First integer\")\n",
|
||||
" b: int = Field(..., description=\"Second integer\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"print(json.dumps(convert_to_openai_tool(multiply), indent=2))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b83d5a88-50ed-4ae4-85cf-8b895617496f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### LangChain Tool"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "696c7dd6-660c-4797-909f-bf878b3acf93",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{\n",
|
||||
" \"type\": \"function\",\n",
|
||||
" \"function\": {\n",
|
||||
" \"name\": \"multiply\",\n",
|
||||
" \"description\": \"Multiply two integers together.\",\n",
|
||||
" \"parameters\": {\n",
|
||||
" \"type\": \"object\",\n",
|
||||
" \"properties\": {\n",
|
||||
" \"a\": {\n",
|
||||
" \"description\": \"First integer\",\n",
|
||||
" \"type\": \"integer\"\n",
|
||||
" },\n",
|
||||
" \"b\": {\n",
|
||||
" \"description\": \"Second integer\",\n",
|
||||
" \"type\": \"integer\"\n",
|
||||
" }\n",
|
||||
" },\n",
|
||||
" \"required\": [\n",
|
||||
" \"a\",\n",
|
||||
" \"b\"\n",
|
||||
" ]\n",
|
||||
" }\n",
|
||||
" }\n",
|
||||
"}\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from typing import Any, Type\n",
|
||||
"\n",
|
||||
"from langchain_core.tools import BaseTool\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class MultiplySchema(BaseModel):\n",
|
||||
" \"\"\"Multiply tool schema.\"\"\"\n",
|
||||
"\n",
|
||||
" a: int = Field(..., description=\"First integer\")\n",
|
||||
" b: int = Field(..., description=\"Second integer\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class Multiply(BaseTool):\n",
|
||||
" args_schema: Type[BaseModel] = MultiplySchema\n",
|
||||
" name: str = \"multiply\"\n",
|
||||
" description: str = \"Multiply two integers together.\"\n",
|
||||
"\n",
|
||||
" def _run(self, a: int, b: int, **kwargs: Any) -> Any:\n",
|
||||
" return a * b\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Note: we're passing in a Multiply object not the class itself.\n",
|
||||
"print(json.dumps(convert_to_openai_tool(Multiply()), indent=2))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "04bda177-202f-4811-bb74-f3fa7094a15b",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Binding functions\n",
|
||||
"\n",
|
||||
"Now that we've defined a function, we'll want to pass it in to our model."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "a5aa93a7-6859-43e8-be85-619d975b908c",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_JvOu9oUwMrQHiDekZTbpNCHY', 'function': {'arguments': '{\\n \"a\": 5,\\n \"b\": 3\\n}', 'name': 'multiply'}, 'type': 'function'}]})"
|
||||
]
|
||||
},
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"\n",
|
||||
"llm = ChatOpenAI(model=\"gpt-3.5-turbo\")\n",
|
||||
"llm.invoke(\"what's 5 times three\", tools=[convert_to_openai_tool(multiply)])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "dd0e7365-32d0-46a3-b8f2-caf27d5d9262",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"And if we wanted this function to be passed in every time we call the tool, we could bind it to the tool:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"id": "87165d64-31a7-4332-965e-18fa939fda50",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_cwRoTnD1ux1SnWXLrTj2KlWH', 'function': {'arguments': '{\\n \"a\": 5,\\n \"b\": 3\\n}', 'name': 'multiply'}, 'type': 'function'}]})"
|
||||
]
|
||||
},
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"llm_with_tool = llm.bind(tools=[convert_to_openai_tool(multiply)])\n",
|
||||
"llm_with_tool.invoke(\"what's 5 times three\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "21b4d000-3828-4e32-9226-55119f47ee67",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We can also enforce that a tool is called using the [tool_choice](https://platform.openai.com/docs/api-reference/chat/create#chat-create-tools) parameter."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"id": "2daa354c-cc85-4a60-a9b2-b681ec22ca33",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_sWjLyioSZAtYMQRLMTzncz1v', 'function': {'arguments': '{\\n \"a\": 5,\\n \"b\": 4\\n}', 'name': 'multiply'}, 'type': 'function'}]})"
|
||||
]
|
||||
},
|
||||
"execution_count": 10,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"llm_with_tool = llm.bind(\n",
|
||||
" tools=[convert_to_openai_tool(multiply)],\n",
|
||||
" tool_choice={\"type\": \"function\", \"function\": {\"name\": \"multiply\"}},\n",
|
||||
")\n",
|
||||
"llm_with_tool.invoke(\n",
|
||||
" \"don't answer my question. no do answer my question. no don't. what's five times four\"\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ce013d11-49ea-4de9-8bbc-bc9ae203002c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"The [ChatOpenAI](https://api.python.langchain.com/en/latest/chat_models/langchain_openai.chat_models.base.ChatOpenAI.html#langchain_openai.chat_models.base.ChatOpenAI) class even comes with a `bind_tools` helper function that handles converting function-like objects to the OpenAI format and binding them for you:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"id": "842c9914-ac28-428f-9fcc-556177e8e715",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_LCdBa4cbhMJPRdtkhDzpRh7x', 'function': {'arguments': '{\\n \"a\": 5,\\n \"b\": 3\\n}', 'name': 'multiply'}, 'type': 'function'}]})"
|
||||
]
|
||||
},
|
||||
"execution_count": 11,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"llm_with_tool = llm.bind_tools([multiply], tool_choice=\"multiply\")\n",
|
||||
"llm_with_tool.invoke(\"what's 5 times three\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7d6e22d8-9f33-4845-9364-0d276df35ff5",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Legacy args `functions` and `function_call`\n",
|
||||
"\n",
|
||||
"Until Fall of 2023 the OpenAI API expected arguments `functions` and `funtion_call` instead of `tools` and `tool_choice`, and they had a slightly different format than `tools` and `tool_choice`. LangChain maintains utilities for using the old API if you need to use that as well:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"id": "a317f71e-177e-404b-b09c-8fb365a4d8a2",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'name': 'multiply',\n",
|
||||
" 'description': 'Multiply two integers together.',\n",
|
||||
" 'parameters': {'type': 'object',\n",
|
||||
" 'properties': {'a': {'description': 'First integer', 'type': 'integer'},\n",
|
||||
" 'b': {'description': 'Second integer', 'type': 'integer'}},\n",
|
||||
" 'required': ['a', 'b']}}"
|
||||
]
|
||||
},
|
||||
"execution_count": 13,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from langchain_core.utils.function_calling import convert_to_openai_function\n",
|
||||
"\n",
|
||||
"convert_to_openai_function(multiply)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 14,
|
||||
"id": "dd124259-75e2-4704-9f57-824d3e463bfa",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"a\": 3,\\n \"b\": 1000000\\n}', 'name': 'multiply'}})"
|
||||
]
|
||||
},
|
||||
"execution_count": 14,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"llm_with_functions = llm.bind(\n",
|
||||
" functions=[convert_to_openai_function(multiply)], function_call={\"name\": \"multiply\"}\n",
|
||||
")\n",
|
||||
"llm_with_functions.invoke(\"what's 3 times a million\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"id": "d9a90af9-1c81-4ace-b155-1589f7308a1c",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"a\": 3,\\n \"b\": 1000000\\n}', 'name': 'multiply'}})"
|
||||
]
|
||||
},
|
||||
"execution_count": 15,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"llm_with_functions = llm.bind_functions([multiply], function_call=\"multiply\")\n",
|
||||
"llm_with_functions.invoke(\"what's 3 times a million\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7779808d-d75c-4d76-890d-ba8c6c571514",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Next steps\n",
|
||||
"\n",
|
||||
"* **Output parsing**: See [OpenAI Tools output parsers](/docs/modules/model_io/output_parsers/types/openai_tools) and [OpenAI Functions output parsers](/docs/modules/model_io/output_parsers/types/openai_functions) to learn about extracting the function calling API responses into various formats.\n",
|
||||
"* **Tool use**: See how to construct chains and agents that actually call the invoked tools in [these guides](/docs/use_cases/tool_use/)."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "poetry-venv",
|
||||
"language": "python",
|
||||
"name": "poetry-venv"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.9.1"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
@ -0,0 +1,385 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "bcbe5c87",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# OpenAI Tools\n",
|
||||
"\n",
|
||||
"These output parsers extract tool calls from OpenAI's function calling API responses. This means they are only usable with models that support function calling, and specifically the latest `tools` and `tool_choice` parameters. We recommend familiarizing yourself with [function calling](/docs/modules/model_io/chat/function_calling) before reading this gu\n",
|
||||
"\n",
|
||||
"There are a few different variants of output parsers:\n",
|
||||
"\n",
|
||||
"- [JsonOutputToolsParser](https://api.python.langchain.com/en/latest/output_parsers/langchain.output_parsers.openai_tools.JsonOutputToolsParser.html#langchain.output_parsers.openai_tools.JsonOutputToolsParser): Returns the arguments of the function call as JSON\n",
|
||||
"- [JsonOutputKeyToolsParser](https://api.python.langchain.com/en/latest/output_parsers/langchain.output_parsers.openai_tools.JsonOutputKeyToolsParser.html#langchain.output_parsers.openai_tools.JsonOutputKeyToolsParser): Returns the value of specific key in the function call as JSON\n",
|
||||
"- [PydanticToolsParser](https://api.python.langchain.com/en/latest/output_parsers/langchain.output_parsers.openai_tools.PydanticToolsParser.html#langchain.output_parsers.openai_tools.PydanticToolsParser): Returns the arguments of the function call as a Pydantic Model"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "aac4262b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.prompts import ChatPromptTemplate\n",
|
||||
"from langchain_core.pydantic_v1 import BaseModel, Field, validator\n",
|
||||
"from langchain_openai import ChatOpenAI"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "52cb351d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class Joke(BaseModel):\n",
|
||||
" \"\"\"Joke to tell user.\"\"\"\n",
|
||||
"\n",
|
||||
" setup: str = Field(description=\"question to set up a joke\")\n",
|
||||
" punchline: str = Field(description=\"answer to resolve the joke\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "2c3259c4",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"model = ChatOpenAI(model=\"gpt-3.5-turbo\", temperature=0).bind_tools([Joke])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "75c33a76-ead8-43aa-ba18-c1822c38cfa9",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[{'type': 'function',\n",
|
||||
" 'function': {'name': 'Joke',\n",
|
||||
" 'description': 'Joke to tell user.',\n",
|
||||
" 'parameters': {'type': 'object',\n",
|
||||
" 'properties': {'setup': {'description': 'question to set up a joke',\n",
|
||||
" 'type': 'string'},\n",
|
||||
" 'punchline': {'description': 'answer to resolve the joke',\n",
|
||||
" 'type': 'string'}},\n",
|
||||
" 'required': ['setup', 'punchline']}}}]"
|
||||
]
|
||||
},
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"model.kwargs[\"tools\"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "d3e9007c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"prompt = ChatPromptTemplate.from_messages(\n",
|
||||
" [(\"system\", \"You are helpful assistant\"), (\"user\", \"{input}\")]\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "87680951",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## JsonOutputToolsParser"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "cb065bdd",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain.output_parsers.openai_tools import JsonOutputToolsParser"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "6ff758c8",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"parser = JsonOutputToolsParser()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"id": "27a3acd1",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"chain = prompt | model | parser"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"id": "59b59179",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[{'type': 'Joke',\n",
|
||||
" 'args': {'setup': \"Why don't scientists trust atoms?\",\n",
|
||||
" 'punchline': 'Because they make up everything!'}}]"
|
||||
]
|
||||
},
|
||||
"execution_count": 10,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"chain.invoke({\"input\": \"tell me a joke\"})"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "0f093b2b-ffd1-47b7-9221-b4265ae52701",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"To include the tool call id we can specify `return_id=True`:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"id": "d43fd620-dcdc-4ad0-a3a9-e7d2d71d6e68",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[{'type': 'Joke',\n",
|
||||
" 'args': {'setup': \"Why don't scientists trust atoms?\",\n",
|
||||
" 'punchline': 'Because they make up everything!'},\n",
|
||||
" 'id': 'call_Isuoh0RTeQzzOKGg5QlQ7UqI'}]"
|
||||
]
|
||||
},
|
||||
"execution_count": 13,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"parser = JsonOutputToolsParser(return_id=True)\n",
|
||||
"chain = prompt | model | parser\n",
|
||||
"chain.invoke({\"input\": \"tell me a joke\"})"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7ca55ac9",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## JsonOutputKeyToolsParser\n",
|
||||
"\n",
|
||||
"This merely extracts a single key from the returned response. This is useful for when you are passing in a single tool and just want it's arguments."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 14,
|
||||
"id": "f8bc404e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import List\n",
|
||||
"\n",
|
||||
"from langchain.output_parsers.openai_tools import JsonOutputKeyToolsParser"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 19,
|
||||
"id": "c91c5949",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"parser = JsonOutputKeyToolsParser(key_name=\"Joke\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 20,
|
||||
"id": "b4583baf",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"chain = prompt | model | parser"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 23,
|
||||
"id": "e8b766ff",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[{'setup': \"Why don't scientists trust atoms?\",\n",
|
||||
" 'punchline': 'Because they make up everything!'}]"
|
||||
]
|
||||
},
|
||||
"execution_count": 23,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"chain.invoke({\"input\": \"tell me a joke\"})"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "fc5695c5-451f-482f-bde6-462d85f1a93e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Certain models can return multiple tool invocations each call, so by default the output is a list. If we just want to return the first tool invocation, we can specify `return_single=True`"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 24,
|
||||
"id": "b1f3097a-5040-435e-9e26-bbdf9506aead",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'setup': \"Why don't scientists trust atoms?\",\n",
|
||||
" 'punchline': 'Because they make up everything!'}"
|
||||
]
|
||||
},
|
||||
"execution_count": 24,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"parser = JsonOutputKeyToolsParser(key_name=\"Joke\", return_single=True)\n",
|
||||
"chain = prompt | model | parser\n",
|
||||
"chain.invoke({\"input\": \"tell me a joke\"})"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "941a3d4e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## PydanticToolsParser\n",
|
||||
"\n",
|
||||
"This builds on top of `JsonOutputToolsParser` but passes the results to a Pydantic Model. This allows for further validation should you choose."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 25,
|
||||
"id": "f51823fe",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain.output_parsers.openai_tools import PydanticToolsParser"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 28,
|
||||
"id": "3c6a5e4d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class Joke(BaseModel):\n",
|
||||
" \"\"\"Joke to tell user.\"\"\"\n",
|
||||
"\n",
|
||||
" setup: str = Field(description=\"question to set up a joke\")\n",
|
||||
" punchline: str = Field(description=\"answer to resolve the joke\")\n",
|
||||
"\n",
|
||||
" # You can add custom validation logic easily with Pydantic.\n",
|
||||
" @validator(\"setup\")\n",
|
||||
" def question_ends_with_question_mark(cls, field):\n",
|
||||
" if field[-1] != \"?\":\n",
|
||||
" raise ValueError(\"Badly formed question!\")\n",
|
||||
" return field\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"parser = PydanticToolsParser(tools=[Joke])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 30,
|
||||
"id": "d2bbd54f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"model = ChatOpenAI(model=\"gpt-3.5-turbo\", temperature=0).bind_tools([Joke])\n",
|
||||
"chain = prompt | model | parser"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 31,
|
||||
"id": "db1a06e8",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[Joke(setup=\"Why don't scientists trust atoms?\", punchline='Because they make up everything!')]"
|
||||
]
|
||||
},
|
||||
"execution_count": 31,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"chain.invoke({\"input\": \"tell me a joke\"})"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.9.1"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
@ -1,38 +1,6 @@
|
||||
from langchain_core.tools import BaseTool
|
||||
|
||||
from langchain_community.utils.openai_functions import (
|
||||
FunctionDescription,
|
||||
ToolDescription,
|
||||
convert_pydantic_to_openai_function,
|
||||
from langchain_core.utils.function_calling import (
|
||||
format_tool_to_openai_function,
|
||||
format_tool_to_openai_tool,
|
||||
)
|
||||
|
||||
|
||||
def format_tool_to_openai_function(tool: BaseTool) -> FunctionDescription:
|
||||
"""Format tool into the OpenAI function API."""
|
||||
if tool.args_schema:
|
||||
return convert_pydantic_to_openai_function(
|
||||
tool.args_schema, name=tool.name, description=tool.description
|
||||
)
|
||||
else:
|
||||
return {
|
||||
"name": tool.name,
|
||||
"description": tool.description,
|
||||
"parameters": {
|
||||
# This is a hack to get around the fact that some tools
|
||||
# do not expose an args_schema, and expect an argument
|
||||
# which is a string.
|
||||
# And Open AI does not support an array type for the
|
||||
# parameters.
|
||||
"properties": {
|
||||
"__arg1": {"title": "__arg1", "type": "string"},
|
||||
},
|
||||
"required": ["__arg1"],
|
||||
"type": "object",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def format_tool_to_openai_tool(tool: BaseTool) -> ToolDescription:
|
||||
"""Format tool into the OpenAI function API."""
|
||||
function = format_tool_to_openai_function(tool)
|
||||
return {"type": "function", "function": function}
|
||||
__all__ = ["format_tool_to_openai_function", "format_tool_to_openai_tool"]
|
||||
|
@ -1,44 +1,6 @@
|
||||
"""Different methods for rendering Tools to be passed to LLMs.
|
||||
|
||||
Depending on the LLM you are using and the prompting strategy you are using,
|
||||
you may want Tools to be rendered in a different way.
|
||||
This module contains various ways to render tools.
|
||||
"""
|
||||
from langchain_core.tools import BaseTool
|
||||
|
||||
from langchain_community.utils.openai_functions import (
|
||||
FunctionDescription,
|
||||
ToolDescription,
|
||||
convert_pydantic_to_openai_function,
|
||||
from langchain_core.utils.function_calling import (
|
||||
format_tool_to_openai_function,
|
||||
format_tool_to_openai_tool,
|
||||
)
|
||||
|
||||
|
||||
def format_tool_to_openai_function(tool: BaseTool) -> FunctionDescription:
|
||||
"""Format tool into the OpenAI function API."""
|
||||
if tool.args_schema:
|
||||
return convert_pydantic_to_openai_function(
|
||||
tool.args_schema, name=tool.name, description=tool.description
|
||||
)
|
||||
else:
|
||||
return {
|
||||
"name": tool.name,
|
||||
"description": tool.description,
|
||||
"parameters": {
|
||||
# This is a hack to get around the fact that some tools
|
||||
# do not expose an args_schema, and expect an argument
|
||||
# which is a string.
|
||||
# And Open AI does not support an array type for the
|
||||
# parameters.
|
||||
"properties": {
|
||||
"__arg1": {"title": "__arg1", "type": "string"},
|
||||
},
|
||||
"required": ["__arg1"],
|
||||
"type": "object",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def format_tool_to_openai_tool(tool: BaseTool) -> ToolDescription:
|
||||
"""Format tool into the OpenAI function API."""
|
||||
function = format_tool_to_openai_function(tool)
|
||||
return {"type": "function", "function": function}
|
||||
__all__ = ["format_tool_to_openai_function", "format_tool_to_openai_tool"]
|
||||
|
@ -0,0 +1,74 @@
|
||||
from typing import Any, Callable, Literal, Type
|
||||
|
||||
import pytest
|
||||
|
||||
from langchain_core.pydantic_v1 import BaseModel, Field
|
||||
from langchain_core.tools import BaseTool
|
||||
from langchain_core.utils.function_calling import convert_to_openai_function
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def pydantic() -> Type[BaseModel]:
|
||||
class dummy_function(BaseModel):
|
||||
"""dummy function"""
|
||||
|
||||
arg1: int = Field(..., description="foo")
|
||||
arg2: Literal["bar", "baz"] = Field(..., description="one of 'bar', 'baz'")
|
||||
|
||||
return dummy_function
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def function() -> Callable:
|
||||
def dummy_function(arg1: int, arg2: Literal["bar", "baz"]) -> None:
|
||||
"""dummy function
|
||||
|
||||
Args:
|
||||
arg1: foo
|
||||
arg2: one of 'bar', 'baz'
|
||||
"""
|
||||
pass
|
||||
|
||||
return dummy_function
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def tool() -> BaseTool:
|
||||
class Schema(BaseModel):
|
||||
arg1: int = Field(..., description="foo")
|
||||
arg2: Literal["bar", "baz"] = Field(..., description="one of 'bar', 'baz'")
|
||||
|
||||
class DummyFunction(BaseTool):
|
||||
args_schema: Type[BaseModel] = Schema
|
||||
name: str = "dummy_function"
|
||||
description: str = "dummy function"
|
||||
|
||||
def _run(self, *args: Any, **kwargs: Any) -> Any:
|
||||
pass
|
||||
|
||||
return DummyFunction()
|
||||
|
||||
|
||||
def test_convert_to_openai_function(
|
||||
pydantic: Type[BaseModel], function: Callable, tool: BaseTool
|
||||
) -> None:
|
||||
expected = {
|
||||
"name": "dummy_function",
|
||||
"description": "dummy function",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"arg1": {"description": "foo", "type": "integer"},
|
||||
"arg2": {
|
||||
"description": "one of 'bar', 'baz'",
|
||||
"enum": ["bar", "baz"],
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"required": ["arg1", "arg2"],
|
||||
},
|
||||
}
|
||||
|
||||
for fn in (pydantic, function, tool, expected):
|
||||
actual = convert_to_openai_function(fn) # type: ignore
|
||||
assert actual == expected
|
@ -1,4 +1,4 @@
|
||||
from langchain_community.tools.convert_to_openai import format_tool_to_openai_function
|
||||
from langchain_core.utils.function_calling import format_tool_to_openai_function
|
||||
|
||||
# For backwards compatibility
|
||||
__all__ = ["format_tool_to_openai_function"]
|
||||
|
Loading…
Reference in New Issue