mirror of
https://github.com/hwchase17/langchain.git
synced 2025-09-29 15:28:54 +00:00
976 lines
31 KiB
Plaintext
976 lines
31 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "5436020b",
|
|
"metadata": {},
|
|
"source": [
|
|
"# How to create tools\n",
|
|
"\n",
|
|
"When constructing an agent, you will need to provide it with a list of `Tool`s that it can use. Besides the actual function that is called, the Tool consists of several components:\n",
|
|
"\n",
|
|
"| Attribute | Type | Description |\n",
|
|
"|---------------|---------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n",
|
|
"| name | str | Must be unique within a set of tools provided to an LLM or agent. |\n",
|
|
"| description | str | Describes what the tool does. Used as context by the LLM or agent. |\n",
|
|
"| args_schema | pydantic.BaseModel | Optional but recommended, and required if using callback handlers. It can be used to provide more information (e.g., few-shot examples) or validation for expected parameters. |\n",
|
|
"| return_direct | boolean | Only relevant for agents. When True, after invoking the given tool, the agent will stop and return the result direcly to the user. |\n",
|
|
"\n",
|
|
"LangChain supports the creation of tools from:\n",
|
|
"\n",
|
|
"1. Functions;\n",
|
|
"2. LangChain [Runnables](/docs/concepts#runnable-interface);\n",
|
|
"3. By sub-classing from [BaseTool](https://python.langchain.com/v0.2/api_reference/core/tools/langchain_core.tools.BaseTool.html) -- This is the most flexible method, it provides the largest degree of control, at the expense of more effort and code.\n",
|
|
"\n",
|
|
"Creating tools from functions may be sufficient for most use cases, and can be done via a simple [@tool decorator](https://python.langchain.com/v0.2/api_reference/core/tools/langchain_core.tools.tool.html#langchain_core.tools.tool). If more configuration is needed-- e.g., specification of both sync and async implementations-- one can also use the [StructuredTool.from_function](https://python.langchain.com/v0.2/api_reference/core/tools/langchain_core.tools.StructuredTool.html#langchain_core.tools.StructuredTool.from_function) class method.\n",
|
|
"\n",
|
|
"In this guide we provide an overview of these methods.\n",
|
|
"\n",
|
|
":::{.callout-tip}\n",
|
|
"\n",
|
|
"Models will perform better if the tools have well chosen names, descriptions and JSON schemas.\n",
|
|
":::"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "c7326b23",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Creating tools from functions\n",
|
|
"\n",
|
|
"### @tool decorator\n",
|
|
"\n",
|
|
"This `@tool` decorator is the simplest way to define a custom tool. The decorator uses the function name as the tool name by default, but this can be overridden by passing a string as the first argument. Additionally, the decorator will use the function's docstring as the tool's description - so a docstring MUST be provided. "
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"id": "cc7005cd-072f-4d37-8453-6297468e5192",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"multiply\n",
|
|
"Multiply two numbers.\n",
|
|
"{'a': {'title': 'A', 'type': 'integer'}, 'b': {'title': 'B', 'type': 'integer'}}\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"from langchain_core.tools import tool\n",
|
|
"\n",
|
|
"\n",
|
|
"@tool\n",
|
|
"def multiply(a: int, b: int) -> int:\n",
|
|
" \"\"\"Multiply two numbers.\"\"\"\n",
|
|
" return a * b\n",
|
|
"\n",
|
|
"\n",
|
|
"# Let's inspect some of the attributes associated with the tool.\n",
|
|
"print(multiply.name)\n",
|
|
"print(multiply.description)\n",
|
|
"print(multiply.args)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "96698b67-993a-4c97-b867-333132e1eb14",
|
|
"metadata": {},
|
|
"source": [
|
|
"Or create an **async** implementation, like this:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"id": "0c0991db-b997-4611-be37-4346e660506b",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from langchain_core.tools import tool\n",
|
|
"\n",
|
|
"\n",
|
|
"@tool\n",
|
|
"async def amultiply(a: int, b: int) -> int:\n",
|
|
" \"\"\"Multiply two numbers.\"\"\"\n",
|
|
" return a * b"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "8f0edc51-c586-414c-8941-c8abe779943f",
|
|
"metadata": {},
|
|
"source": [
|
|
"Note that `@tool` supports parsing of annotations, nested schemas, and other features:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 3,
|
|
"id": "5626423f-053e-4a66-adca-1d794d835397",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"{'title': 'multiply_by_maxSchema',\n",
|
|
" 'description': 'Multiply a by the maximum of b.',\n",
|
|
" 'type': 'object',\n",
|
|
" 'properties': {'a': {'title': 'A',\n",
|
|
" 'description': 'scale factor',\n",
|
|
" 'type': 'string'},\n",
|
|
" 'b': {'title': 'B',\n",
|
|
" 'description': 'list of ints over which to take maximum',\n",
|
|
" 'type': 'array',\n",
|
|
" 'items': {'type': 'integer'}}},\n",
|
|
" 'required': ['a', 'b']}"
|
|
]
|
|
},
|
|
"execution_count": 3,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"from typing import Annotated, List\n",
|
|
"\n",
|
|
"\n",
|
|
"@tool\n",
|
|
"def multiply_by_max(\n",
|
|
" a: Annotated[str, \"scale factor\"],\n",
|
|
" b: Annotated[List[int], \"list of ints over which to take maximum\"],\n",
|
|
") -> int:\n",
|
|
" \"\"\"Multiply a by the maximum of b.\"\"\"\n",
|
|
" return a * max(b)\n",
|
|
"\n",
|
|
"\n",
|
|
"multiply_by_max.args_schema.schema()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "98d6eee9",
|
|
"metadata": {},
|
|
"source": [
|
|
"You can also customize the tool name and JSON args by passing them into the tool decorator."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"id": "9216d03a-f6ea-4216-b7e1-0661823a4c0b",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"multiplication-tool\n",
|
|
"Multiply two numbers.\n",
|
|
"{'a': {'title': 'A', 'description': 'first number', 'type': 'integer'}, 'b': {'title': 'B', 'description': 'second number', 'type': 'integer'}}\n",
|
|
"True\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"from pydantic import BaseModel, Field\n",
|
|
"\n",
|
|
"\n",
|
|
"class CalculatorInput(BaseModel):\n",
|
|
" a: int = Field(description=\"first number\")\n",
|
|
" b: int = Field(description=\"second number\")\n",
|
|
"\n",
|
|
"\n",
|
|
"@tool(\"multiplication-tool\", args_schema=CalculatorInput, return_direct=True)\n",
|
|
"def multiply(a: int, b: int) -> int:\n",
|
|
" \"\"\"Multiply two numbers.\"\"\"\n",
|
|
" return a * b\n",
|
|
"\n",
|
|
"\n",
|
|
"# Let's inspect some of the attributes associated with the tool.\n",
|
|
"print(multiply.name)\n",
|
|
"print(multiply.description)\n",
|
|
"print(multiply.args)\n",
|
|
"print(multiply.return_direct)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "33a9e94d-0b60-48f3-a4c2-247dce096e66",
|
|
"metadata": {},
|
|
"source": [
|
|
"#### Docstring parsing"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "6d0cb586-93d4-4ff1-9779-71df7853cb68",
|
|
"metadata": {},
|
|
"source": [
|
|
"`@tool` can optionally parse [Google Style docstrings](https://google.github.io/styleguide/pyguide.html#383-functions-and-methods) and associate the docstring components (such as arg descriptions) to the relevant parts of the tool schema. To toggle this behavior, specify `parse_docstring`:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 5,
|
|
"id": "336f5538-956e-47d5-9bde-b732559f9e61",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"{'title': 'fooSchema',\n",
|
|
" 'description': 'The foo.',\n",
|
|
" 'type': 'object',\n",
|
|
" 'properties': {'bar': {'title': 'Bar',\n",
|
|
" 'description': 'The bar.',\n",
|
|
" 'type': 'string'},\n",
|
|
" 'baz': {'title': 'Baz', 'description': 'The baz.', 'type': 'integer'}},\n",
|
|
" 'required': ['bar', 'baz']}"
|
|
]
|
|
},
|
|
"execution_count": 5,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"@tool(parse_docstring=True)\n",
|
|
"def foo(bar: str, baz: int) -> str:\n",
|
|
" \"\"\"The foo.\n",
|
|
"\n",
|
|
" Args:\n",
|
|
" bar: The bar.\n",
|
|
" baz: The baz.\n",
|
|
" \"\"\"\n",
|
|
" return bar\n",
|
|
"\n",
|
|
"\n",
|
|
"foo.args_schema.schema()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "f18a2503-5393-421b-99fa-4a01dd824d0e",
|
|
"metadata": {},
|
|
"source": [
|
|
":::{.callout-caution}\n",
|
|
"By default, `@tool(parse_docstring=True)` will raise `ValueError` if the docstring does not parse correctly. See [API Reference](https://python.langchain.com/v0.2/api_reference/core/tools/langchain_core.tools.tool.html) for detail and examples.\n",
|
|
":::"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "b63fcc3b",
|
|
"metadata": {},
|
|
"source": [
|
|
"### StructuredTool\n",
|
|
"\n",
|
|
"The `StructuredTool.from_function` class method provides a bit more configurability than the `@tool` decorator, without requiring much additional code."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 6,
|
|
"id": "564fbe6f-11df-402d-b135-ef6ff25e1e63",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"6\n",
|
|
"10\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"from langchain_core.tools import StructuredTool\n",
|
|
"\n",
|
|
"\n",
|
|
"def multiply(a: int, b: int) -> int:\n",
|
|
" \"\"\"Multiply two numbers.\"\"\"\n",
|
|
" return a * b\n",
|
|
"\n",
|
|
"\n",
|
|
"async def amultiply(a: int, b: int) -> int:\n",
|
|
" \"\"\"Multiply two numbers.\"\"\"\n",
|
|
" return a * b\n",
|
|
"\n",
|
|
"\n",
|
|
"calculator = StructuredTool.from_function(func=multiply, coroutine=amultiply)\n",
|
|
"\n",
|
|
"print(calculator.invoke({\"a\": 2, \"b\": 3}))\n",
|
|
"print(await calculator.ainvoke({\"a\": 2, \"b\": 5}))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "26b3712a-b38d-4582-b6e6-bc7cfb1d6680",
|
|
"metadata": {},
|
|
"source": [
|
|
"To configure it:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 7,
|
|
"id": "6bc055d4-1fbe-4db5-8881-9c382eba6b1b",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"6\n",
|
|
"Calculator\n",
|
|
"multiply numbers\n",
|
|
"{'a': {'title': 'A', 'description': 'first number', 'type': 'integer'}, 'b': {'title': 'B', 'description': 'second number', 'type': 'integer'}}\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"class CalculatorInput(BaseModel):\n",
|
|
" a: int = Field(description=\"first number\")\n",
|
|
" b: int = Field(description=\"second number\")\n",
|
|
"\n",
|
|
"\n",
|
|
"def multiply(a: int, b: int) -> int:\n",
|
|
" \"\"\"Multiply two numbers.\"\"\"\n",
|
|
" return a * b\n",
|
|
"\n",
|
|
"\n",
|
|
"calculator = StructuredTool.from_function(\n",
|
|
" func=multiply,\n",
|
|
" name=\"Calculator\",\n",
|
|
" description=\"multiply numbers\",\n",
|
|
" args_schema=CalculatorInput,\n",
|
|
" return_direct=True,\n",
|
|
" # coroutine= ... <- you can specify an async method if desired as well\n",
|
|
")\n",
|
|
"\n",
|
|
"print(calculator.invoke({\"a\": 2, \"b\": 3}))\n",
|
|
"print(calculator.name)\n",
|
|
"print(calculator.description)\n",
|
|
"print(calculator.args)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "5517995d-54e3-449b-8fdb-03561f5e4647",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Creating tools from Runnables\n",
|
|
"\n",
|
|
"LangChain [Runnables](/docs/concepts#runnable-interface) that accept string or `dict` input can be converted to tools using the [as_tool](https://python.langchain.com/v0.2/api_reference/core/runnables/langchain_core.runnables.base.Runnable.html#langchain_core.runnables.base.Runnable.as_tool) method, which allows for the specification of names, descriptions, and additional schema information for arguments.\n",
|
|
"\n",
|
|
"Example usage:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 9,
|
|
"id": "8ef593c5-cf72-4c10-bfc9-7d21874a0c24",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"{'answer_style': {'title': 'Answer Style', 'type': 'string'}}"
|
|
]
|
|
},
|
|
"execution_count": 9,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"from langchain_core.language_models import GenericFakeChatModel\n",
|
|
"from langchain_core.output_parsers import StrOutputParser\n",
|
|
"from langchain_core.prompts import ChatPromptTemplate\n",
|
|
"\n",
|
|
"prompt = ChatPromptTemplate.from_messages(\n",
|
|
" [(\"human\", \"Hello. Please respond in the style of {answer_style}.\")]\n",
|
|
")\n",
|
|
"\n",
|
|
"# Placeholder LLM\n",
|
|
"llm = GenericFakeChatModel(messages=iter([\"hello matey\"]))\n",
|
|
"\n",
|
|
"chain = prompt | llm | StrOutputParser()\n",
|
|
"\n",
|
|
"as_tool = chain.as_tool(\n",
|
|
" name=\"Style responder\", description=\"Description of when to use tool.\"\n",
|
|
")\n",
|
|
"as_tool.args"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "0521b787-a146-45a6-8ace-ae1ac4669dd7",
|
|
"metadata": {},
|
|
"source": [
|
|
"See [this guide](/docs/how_to/convert_runnable_to_tool) for more detail."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "b840074b-9c10-4ca0-aed8-626c52b2398f",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Subclass BaseTool\n",
|
|
"\n",
|
|
"You can define a custom tool by sub-classing from `BaseTool`. This provides maximal control over the tool definition, but requires writing more code."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 10,
|
|
"id": "1dad8f8e",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from typing import Optional, Type\n",
|
|
"\n",
|
|
"from langchain_core.callbacks import (\n",
|
|
" AsyncCallbackManagerForToolRun,\n",
|
|
" CallbackManagerForToolRun,\n",
|
|
")\n",
|
|
"from langchain_core.tools import BaseTool\n",
|
|
"from pydantic import BaseModel\n",
|
|
"\n",
|
|
"\n",
|
|
"class CalculatorInput(BaseModel):\n",
|
|
" a: int = Field(description=\"first number\")\n",
|
|
" b: int = Field(description=\"second number\")\n",
|
|
"\n",
|
|
"\n",
|
|
"class CustomCalculatorTool(BaseTool):\n",
|
|
" name = \"Calculator\"\n",
|
|
" description = \"useful for when you need to answer questions about math\"\n",
|
|
" args_schema: Type[BaseModel] = CalculatorInput\n",
|
|
" return_direct: bool = True\n",
|
|
"\n",
|
|
" def _run(\n",
|
|
" self, a: int, b: int, run_manager: Optional[CallbackManagerForToolRun] = None\n",
|
|
" ) -> str:\n",
|
|
" \"\"\"Use the tool.\"\"\"\n",
|
|
" return a * b\n",
|
|
"\n",
|
|
" async def _arun(\n",
|
|
" self,\n",
|
|
" a: int,\n",
|
|
" b: int,\n",
|
|
" run_manager: Optional[AsyncCallbackManagerForToolRun] = None,\n",
|
|
" ) -> str:\n",
|
|
" \"\"\"Use the tool asynchronously.\"\"\"\n",
|
|
" # If the calculation is cheap, you can just delegate to the sync implementation\n",
|
|
" # as shown below.\n",
|
|
" # If the sync calculation is expensive, you should delete the entire _arun method.\n",
|
|
" # LangChain will automatically provide a better implementation that will\n",
|
|
" # kick off the task in a thread to make sure it doesn't block other async code.\n",
|
|
" return self._run(a, b, run_manager=run_manager.get_sync())"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 11,
|
|
"id": "bb551c33",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Calculator\n",
|
|
"useful for when you need to answer questions about math\n",
|
|
"{'a': {'title': 'A', 'description': 'first number', 'type': 'integer'}, 'b': {'title': 'B', 'description': 'second number', 'type': 'integer'}}\n",
|
|
"True\n",
|
|
"6\n",
|
|
"6\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"multiply = CustomCalculatorTool()\n",
|
|
"print(multiply.name)\n",
|
|
"print(multiply.description)\n",
|
|
"print(multiply.args)\n",
|
|
"print(multiply.return_direct)\n",
|
|
"\n",
|
|
"print(multiply.invoke({\"a\": 2, \"b\": 3}))\n",
|
|
"print(await multiply.ainvoke({\"a\": 2, \"b\": 3}))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "97aba6cc-4bdf-4fab-aff3-d89e7d9c3a09",
|
|
"metadata": {},
|
|
"source": [
|
|
"## How to create async tools\n",
|
|
"\n",
|
|
"LangChain Tools implement the [Runnable interface 🏃](https://python.langchain.com/v0.2/api_reference/core/runnables/langchain_core.runnables.base.Runnable.html).\n",
|
|
"\n",
|
|
"All Runnables expose the `invoke` and `ainvoke` methods (as well as other methods like `batch`, `abatch`, `astream` etc).\n",
|
|
"\n",
|
|
"So even if you only provide an `sync` implementation of a tool, you could still use the `ainvoke` interface, but there\n",
|
|
"are some important things to know:\n",
|
|
"\n",
|
|
"* LangChain's by default provides an async implementation that assumes that the function is expensive to compute, so it'll delegate execution to another thread.\n",
|
|
"* If you're working in an async codebase, you should create async tools rather than sync tools, to avoid incuring a small overhead due to that thread.\n",
|
|
"* If you need both sync and async implementations, use `StructuredTool.from_function` or sub-class from `BaseTool`.\n",
|
|
"* If implementing both sync and async, and the sync code is fast to run, override the default LangChain async implementation and simply call the sync code.\n",
|
|
"* You CANNOT and SHOULD NOT use the sync `invoke` with an `async` tool."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 12,
|
|
"id": "6615cb77-fd4c-4676-8965-f92cc71d4944",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"6\n",
|
|
"10\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"from langchain_core.tools import StructuredTool\n",
|
|
"\n",
|
|
"\n",
|
|
"def multiply(a: int, b: int) -> int:\n",
|
|
" \"\"\"Multiply two numbers.\"\"\"\n",
|
|
" return a * b\n",
|
|
"\n",
|
|
"\n",
|
|
"calculator = StructuredTool.from_function(func=multiply)\n",
|
|
"\n",
|
|
"print(calculator.invoke({\"a\": 2, \"b\": 3}))\n",
|
|
"print(\n",
|
|
" await calculator.ainvoke({\"a\": 2, \"b\": 5})\n",
|
|
") # Uses default LangChain async implementation incurs small overhead"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 13,
|
|
"id": "bb2af583-eadd-41f4-a645-bf8748bd3dcd",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"6\n",
|
|
"10\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"from langchain_core.tools import StructuredTool\n",
|
|
"\n",
|
|
"\n",
|
|
"def multiply(a: int, b: int) -> int:\n",
|
|
" \"\"\"Multiply two numbers.\"\"\"\n",
|
|
" return a * b\n",
|
|
"\n",
|
|
"\n",
|
|
"async def amultiply(a: int, b: int) -> int:\n",
|
|
" \"\"\"Multiply two numbers.\"\"\"\n",
|
|
" return a * b\n",
|
|
"\n",
|
|
"\n",
|
|
"calculator = StructuredTool.from_function(func=multiply, coroutine=amultiply)\n",
|
|
"\n",
|
|
"print(calculator.invoke({\"a\": 2, \"b\": 3}))\n",
|
|
"print(\n",
|
|
" await calculator.ainvoke({\"a\": 2, \"b\": 5})\n",
|
|
") # Uses use provided amultiply without additional overhead"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "c80ffdaa-e4ba-4a70-8500-32bf4f60cc1a",
|
|
"metadata": {},
|
|
"source": [
|
|
"You should not and cannot use `.invoke` when providing only an async definition."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 14,
|
|
"id": "4ad0932c-8610-4278-8c57-f9218f654c8a",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Raised not implemented error. You should not be doing this.\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"@tool\n",
|
|
"async def multiply(a: int, b: int) -> int:\n",
|
|
" \"\"\"Multiply two numbers.\"\"\"\n",
|
|
" return a * b\n",
|
|
"\n",
|
|
"\n",
|
|
"try:\n",
|
|
" multiply.invoke({\"a\": 2, \"b\": 3})\n",
|
|
"except NotImplementedError:\n",
|
|
" print(\"Raised not implemented error. You should not be doing this.\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "f9c746a7-88d7-4afb-bcb8-0e98b891e8b6",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Handling Tool Errors \n",
|
|
"\n",
|
|
"If you're using tools with agents, you will likely need an error handling strategy, so the agent can recover from the error and continue execution.\n",
|
|
"\n",
|
|
"A simple strategy is to throw a `ToolException` from inside the tool and specify an error handler using `handle_tool_error`. \n",
|
|
"\n",
|
|
"When the error handler is specified, the exception will be caught and the error handler will decide which output to return from the tool.\n",
|
|
"\n",
|
|
"You can set `handle_tool_error` to `True`, a string value, or a function. If it's a function, the function should take a `ToolException` as a parameter and return a value.\n",
|
|
"\n",
|
|
"Please note that only raising a `ToolException` won't be effective. You need to first set the `handle_tool_error` of the tool because its default value is `False`."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 15,
|
|
"id": "7094c0e8-6192-4870-a942-aad5b5ae48fd",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from langchain_core.tools import ToolException\n",
|
|
"\n",
|
|
"\n",
|
|
"def get_weather(city: str) -> int:\n",
|
|
" \"\"\"Get weather for the given city.\"\"\"\n",
|
|
" raise ToolException(f\"Error: There is no city by the name of {city}.\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "9d93b217-1d44-4d31-8956-db9ea680ff4f",
|
|
"metadata": {},
|
|
"source": [
|
|
"Here's an example with the default `handle_tool_error=True` behavior."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 16,
|
|
"id": "b4d22022-b105-4ccc-a15b-412cb9ea3097",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"'Error: There is no city by the name of foobar.'"
|
|
]
|
|
},
|
|
"execution_count": 16,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"get_weather_tool = StructuredTool.from_function(\n",
|
|
" func=get_weather,\n",
|
|
" handle_tool_error=True,\n",
|
|
")\n",
|
|
"\n",
|
|
"get_weather_tool.invoke({\"city\": \"foobar\"})"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "f91d6dc0-3271-4adc-a155-21f2e62ffa56",
|
|
"metadata": {},
|
|
"source": [
|
|
"We can set `handle_tool_error` to a string that will always be returned."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 17,
|
|
"id": "3fad1728-d367-4e1b-9b54-3172981271cf",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"\"There is no such city, but it's probably above 0K there!\""
|
|
]
|
|
},
|
|
"execution_count": 17,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"get_weather_tool = StructuredTool.from_function(\n",
|
|
" func=get_weather,\n",
|
|
" handle_tool_error=\"There is no such city, but it's probably above 0K there!\",\n",
|
|
")\n",
|
|
"\n",
|
|
"get_weather_tool.invoke({\"city\": \"foobar\"})"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "b0a640c1-e08f-4413-83b6-f599f304935f",
|
|
"metadata": {},
|
|
"source": [
|
|
"Handling the error using a function:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 18,
|
|
"id": "ebfe7c1f-318d-4e58-99e1-f31e69473c46",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"'The following errors occurred during tool execution: `Error: There is no city by the name of foobar.`'"
|
|
]
|
|
},
|
|
"execution_count": 18,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"def _handle_error(error: ToolException) -> str:\n",
|
|
" return f\"The following errors occurred during tool execution: `{error.args[0]}`\"\n",
|
|
"\n",
|
|
"\n",
|
|
"get_weather_tool = StructuredTool.from_function(\n",
|
|
" func=get_weather,\n",
|
|
" handle_tool_error=_handle_error,\n",
|
|
")\n",
|
|
"\n",
|
|
"get_weather_tool.invoke({\"city\": \"foobar\"})"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "1a8d8383-11b3-445e-956f-df4e96995e00",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Returning artifacts of Tool execution\n",
|
|
"\n",
|
|
"Sometimes there are artifacts of a tool's execution that we want to make accessible to downstream components in our chain or agent, but that we don't want to expose to the model itself. For example if a tool returns custom objects like Documents, we may want to pass some view or metadata about this output to the model without passing the raw output to the model. At the same time, we may want to be able to access this full output elsewhere, for example in downstream tools.\n",
|
|
"\n",
|
|
"The Tool and [ToolMessage](https://python.langchain.com/v0.2/api_reference/core/messages/langchain_core.messages.tool.ToolMessage.html) interfaces make it possible to distinguish between the parts of the tool output meant for the model (this is the ToolMessage.content) and those parts which are meant for use outside the model (ToolMessage.artifact).\n",
|
|
"\n",
|
|
":::info Requires ``langchain-core >= 0.2.19``\n",
|
|
"\n",
|
|
"This functionality was added in ``langchain-core == 0.2.19``. Please make sure your package is up to date.\n",
|
|
"\n",
|
|
":::\n",
|
|
"\n",
|
|
"If we want our tool to distinguish between message content and other artifacts, we need to specify `response_format=\"content_and_artifact\"` when defining our tool and make sure that we return a tuple of (content, artifact):"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"id": "14905425-0334-43a0-9de9-5bcf622ede0e",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import random\n",
|
|
"from typing import List, Tuple\n",
|
|
"\n",
|
|
"from langchain_core.tools import tool\n",
|
|
"\n",
|
|
"\n",
|
|
"@tool(response_format=\"content_and_artifact\")\n",
|
|
"def generate_random_ints(min: int, max: int, size: int) -> Tuple[str, List[int]]:\n",
|
|
" \"\"\"Generate size random ints in the range [min, max].\"\"\"\n",
|
|
" array = [random.randint(min, max) for _ in range(size)]\n",
|
|
" content = f\"Successfully generated array of {size} random ints in [{min}, {max}].\"\n",
|
|
" return content, array"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "49f057a6-8938-43ea-8faf-ae41e797ceb8",
|
|
"metadata": {},
|
|
"source": [
|
|
"If we invoke our tool directly with the tool arguments, we'll get back just the content part of the output:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 9,
|
|
"id": "0f2e1528-404b-46e6-b87c-f0957c4b9217",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"'Successfully generated array of 10 random ints in [0, 9].'"
|
|
]
|
|
},
|
|
"execution_count": 9,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"generate_random_ints.invoke({\"min\": 0, \"max\": 9, \"size\": 10})"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "1e62ebba-1737-4b97-b61a-7313ade4e8c2",
|
|
"metadata": {},
|
|
"source": [
|
|
"If we invoke our tool with a ToolCall (like the ones generated by tool-calling models), we'll get back a ToolMessage that contains both the content and artifact generated by the Tool:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 3,
|
|
"id": "cc197777-26eb-46b3-a83b-c2ce116c6311",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"ToolMessage(content='Successfully generated array of 10 random ints in [0, 9].', name='generate_random_ints', tool_call_id='123', artifact=[1, 4, 2, 5, 3, 9, 0, 4, 7, 7])"
|
|
]
|
|
},
|
|
"execution_count": 3,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"generate_random_ints.invoke(\n",
|
|
" {\n",
|
|
" \"name\": \"generate_random_ints\",\n",
|
|
" \"args\": {\"min\": 0, \"max\": 9, \"size\": 10},\n",
|
|
" \"id\": \"123\", # required\n",
|
|
" \"type\": \"tool_call\", # required\n",
|
|
" }\n",
|
|
")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "dfdc1040-bf25-4790-b4c3-59452db84e11",
|
|
"metadata": {},
|
|
"source": [
|
|
"We can do the same when subclassing BaseTool:"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 6,
|
|
"id": "fe1a09d1-378b-4b91-bb5e-0697c3d7eb92",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from langchain_core.tools import BaseTool\n",
|
|
"\n",
|
|
"\n",
|
|
"class GenerateRandomFloats(BaseTool):\n",
|
|
" name: str = \"generate_random_floats\"\n",
|
|
" description: str = \"Generate size random floats in the range [min, max].\"\n",
|
|
" response_format: str = \"content_and_artifact\"\n",
|
|
"\n",
|
|
" ndigits: int = 2\n",
|
|
"\n",
|
|
" def _run(self, min: float, max: float, size: int) -> Tuple[str, List[float]]:\n",
|
|
" range_ = max - min\n",
|
|
" array = [\n",
|
|
" round(min + (range_ * random.random()), ndigits=self.ndigits)\n",
|
|
" for _ in range(size)\n",
|
|
" ]\n",
|
|
" content = f\"Generated {size} floats in [{min}, {max}], rounded to {self.ndigits} decimals.\"\n",
|
|
" return content, array\n",
|
|
"\n",
|
|
" # Optionally define an equivalent async method\n",
|
|
"\n",
|
|
" # async def _arun(self, min: float, max: float, size: int) -> Tuple[str, List[float]]:\n",
|
|
" # ..."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 8,
|
|
"id": "8c3d16f6-1c4a-48ab-b05a-38547c592e79",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"ToolMessage(content='Generated 3 floats in [0.1, 3.3333], rounded to 4 decimals.', name='generate_random_floats', tool_call_id='123', artifact=[1.4277, 0.7578, 2.4871])"
|
|
]
|
|
},
|
|
"execution_count": 8,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"rand_gen = GenerateRandomFloats(ndigits=4)\n",
|
|
"\n",
|
|
"rand_gen.invoke(\n",
|
|
" {\n",
|
|
" \"name\": \"generate_random_floats\",\n",
|
|
" \"args\": {\"min\": 0.1, \"max\": 3.3333, \"size\": 3},\n",
|
|
" \"id\": \"123\",\n",
|
|
" \"type\": \"tool_call\",\n",
|
|
" }\n",
|
|
")"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "poetry-venv-311",
|
|
"language": "python",
|
|
"name": "poetry-venv-311"
|
|
},
|
|
"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.11.9"
|
|
},
|
|
"vscode": {
|
|
"interpreter": {
|
|
"hash": "e90c8aa204a57276aa905271aff2d11799d0acb3547adabc5892e639a5e45e34"
|
|
}
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 5
|
|
}
|