如何创建工具
在构建代理时,您需要为其提供可以使用的 工具(Tools) 列表。除了调用的实际函数外,该工具还由几个组件组成:
| 属性 | 类型 | 描述 |
|---|---|---|
| name | str | Must be unique within a set of tools provided to an LLM or agent. |
| description | str | Describes what the tool does. Used as context by the LLM or agent. |
| 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. |
| 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. |
LangChain 支持从以下位置创建工具:
对于大多数用例来说,从函数创建工具可能就足够了,并且可以通过简单的 @tool 装饰器来完成。如果需要更多配置 - 例如,规范 sync 和 async implementations - 也可以使用 StructuredTool.from_function 类方法。
在本指南中,我们概述了这些方法。
如果工具具有精心选择的名称、描述和 JSON 模式,则模型的性能会更好。
从函数创建工具
@tool 装饰器
这@tooldecorator 是定义自定义工具的最简单方法。默认情况下,装饰器使用函数名称作为工具名称,但可以通过传递字符串作为第一个参数来覆盖它。此外,装饰器将使用函数的文档字符串作为工具的描述 - 因此必须提供文档字符串。
from langchain_core.tools import tool
@tool
def multiply(a: int, b: int) -> int:
"""Multiply two numbers."""
return a * b
# Let's inspect some of the attributes associated with the tool.
print(multiply.name)
print(multiply.description)
print(multiply.args)
multiply
Multiply two numbers.
{'a': {'title': 'A', 'type': 'integer'}, 'b': {'title': 'B', 'type': 'integer'}}
或者创建一个异步实现,如下所示:
from langchain_core.tools import tool
@tool
async def amultiply(a: int, b: int) -> int:
"""Multiply two numbers."""
return a * b
请注意,@tool支持解析注释、嵌套架构和其他功能:
from typing import Annotated, List
@tool
def multiply_by_max(
a: Annotated[int, "scale factor"],
b: Annotated[List[int], "list of ints over which to take maximum"],
) -> int:
"""Multiply a by the maximum of b."""
return a * max(b)
print(multiply_by_max.args_schema.model_json_schema())
{'description': 'Multiply a by the maximum of b.',
'properties': {'a': {'description': 'scale factor',
'title': 'A',
'type': 'string'},
'b': {'description': 'list of ints over which to take maximum',
'items': {'type': 'integer'},
'title': 'B',
'type': 'array'}},
'required': ['a', 'b'],
'title': 'multiply_by_maxSchema',
'type': 'object'}
您还可以通过将工具名称和 JSON args 传递到工具装饰器来自定义它们。
from pydantic import BaseModel, Field
class CalculatorInput(BaseModel):
a: int = Field(description="first number")
b: int = Field(description="second number")
@tool("multiplication-tool", args_schema=CalculatorInput, return_direct=True)
def multiply(a: int, b: int) -> int:
"""Multiply two numbers."""
return a * b
# Let's inspect some of the attributes associated with the tool.
print(multiply.name)
print(multiply.description)
print(multiply.args)
print(multiply.return_direct)
multiplication-tool
Multiply two numbers.
{'a': {'description': 'first number', 'title': 'A', 'type': 'integer'}, 'b': {'description': 'second number', 'title': 'B', 'type': 'integer'}}
True
文档字符串解析
@tool可以选择解析 Google Style 文档字符串,并将文档字符串组件(例如 arg descriptions)关联到工具架构的相关部分。要切换此行为,请指定parse_docstring:
@tool(parse_docstring=True)
def foo(bar: str, baz: int) -> str:
"""The foo.
Args:
bar: The bar.
baz: The baz.
"""
return bar
print(foo.args_schema.model_json_schema())
{'description': 'The foo.',
'properties': {'bar': {'description': 'The bar.',
'title': 'Bar',
'type': 'string'},
'baz': {'description': 'The baz.', 'title': 'Baz', 'type': 'integer'}},
'required': ['bar', 'baz'],
'title': 'fooSchema',
'type': 'object'}
默认情况下,@tool(parse_docstring=True)会加注ValueError如果 DocString 没有正确解析。有关详细信息和示例,请参阅 API 参考。
结构化工具
这StructuredTool.from_functionclass 方法提供了比@tooldecorator 来执行,而不需要太多额外的代码。
from langchain_core.tools import StructuredTool
def multiply(a: int, b: int) -> int:
"""Multiply two numbers."""
return a * b
async def amultiply(a: int, b: int) -> int:
"""Multiply two numbers."""
return a * b
calculator = StructuredTool.from_function(func=multiply, coroutine=amultiply)
print(calculator.invoke({"a": 2, "b": 3}))
print(await calculator.ainvoke({"a": 2, "b": 5}))
6
10
要配置它:
class CalculatorInput(BaseModel):
a: int = Field(description="first number")
b: int = Field(description="second number")
def multiply(a: int, b: int) -> int:
"""Multiply two numbers."""
return a * b
calculator = StructuredTool.from_function(
func=multiply,
name="Calculator",
description="multiply numbers",
args_schema=CalculatorInput,
return_direct=True,
# coroutine= ... <- you can specify an async method if desired as well
)
print(calculator.invoke({"a": 2, "b": 3}))
print(calculator.name)
print(calculator.description)
print(calculator.args)
6
Calculator
multiply numbers
{'a': {'description': 'first number', 'title': 'A', 'type': 'integer'}, 'b': {'description': 'second number', 'title': 'B', 'type': 'integer'}}
从 Runnables 创建工具
接受 string 或dict可以使用 as_tool 方法将 input 转换为工具,该方法允许为参数指定名称、描述和其他架构信息。
用法示例:
from langchain_core.language_models import GenericFakeChatModel
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages(
[("human", "Hello. Please respond in the style of {answer_style}.")]
)
# Placeholder LLM
llm = GenericFakeChatModel(messages=iter(["hello matey"]))
chain = prompt | llm | StrOutputParser()
as_tool = chain.as_tool(
name="Style responder", description="Description of when to use tool."
)
as_tool.args
/var/folders/4j/2rz3865x6qg07tx43146py8h0000gn/T/ipykernel_95770/2548361071.py:14: LangChainBetaWarning: This API is in beta and may change in the future.
as_tool = chain.as_tool(
{'answer_style': {'title': 'Answer Style', 'type': 'string'}}
有关更多详细信息,请参阅本指南。
子类 BaseTool
您可以通过从BaseTool.这提供了对工具定义的最大控制,但需要编写更多代码。
from typing import Optional
from langchain_core.callbacks import (
AsyncCallbackManagerForToolRun,
CallbackManagerForToolRun,
)
from langchain_core.tools import BaseTool
from langchain_core.tools.base import ArgsSchema
from pydantic import BaseModel, Field
class CalculatorInput(BaseModel):
a: int = Field(description="first number")
b: int = Field(description="second number")
# Note: It's important that every field has type hints. BaseTool is a
# Pydantic class and not having type hints can lead to unexpected behavior.
class CustomCalculatorTool(BaseTool):
name: str = "Calculator"
description: str = "useful for when you need to answer questions about math"
args_schema: Optional[ArgsSchema] = CalculatorInput
return_direct: bool = True
def _run(
self, a: int, b: int, run_manager: Optional[CallbackManagerForToolRun] = None
) -> int:
"""Use the tool."""
return a * b
async def _arun(
self,
a: int,
b: int,
run_manager: Optional[AsyncCallbackManagerForToolRun] = None,
) -> int:
"""Use the tool asynchronously."""
# If the calculation is cheap, you can just delegate to the sync implementation
# as shown below.
# If the sync calculation is expensive, you should delete the entire _arun method.
# LangChain will automatically provide a better implementation that will
# kick off the task in a thread to make sure it doesn't block other async code.
return self._run(a, b, run_manager=run_manager.get_sync())
multiply = CustomCalculatorTool()
print(multiply.name)
print(multiply.description)
print(multiply.args)
print(multiply.return_direct)
print(multiply.invoke({"a": 2, "b": 3}))
print(await multiply.ainvoke({"a": 2, "b": 3}))
Calculator
useful for when you need to answer questions about math
{'a': {'description': 'first number', 'title': 'A', 'type': 'integer'}, 'b': {'description': 'second number', 'title': 'B', 'type': 'integer'}}
True
6
6
如何创建异步工具
LangChain 工具实现 Runnable 接口 🏃 。
所有 Runnables 都公开了invoke和ainvoke方法(以及其他方法,如batch,abatch,astream等)。
因此,即使您只提供sync实现工具时,您仍然可以使用ainvoke接口,但有
以下是一些需要了解的重要事项:
- LangChain 默认提供了一个异步实现,该实现假设该函数的计算成本很高,因此它会将执行委托给另一个线程。
- 如果你在异步代码库中工作,你应该创建异步工具而不是同步工具,以避免由于该线程而产生少量开销。
- 如果你同时需要 sync 和 async 实现,请使用
StructuredTool.from_function或 sub-class fromBaseTool. - 如果同时实现 sync 和 async,并且 sync 代码可以快速运行,请覆盖默认的 LangChain 异步实现,只需调用 sync 代码即可。
- 您不能也不应该使用同步
invoke替换为async工具。
from langchain_core.tools import StructuredTool
def multiply(a: int, b: int) -> int:
"""Multiply two numbers."""
return a * b
calculator = StructuredTool.from_function(func=multiply)
print(calculator.invoke({"a": 2, "b": 3}))
print(
await calculator.ainvoke({"a": 2, "b": 5})
) # Uses default LangChain async implementation incurs small overhead
6
10
from langchain_core.tools import StructuredTool
def multiply(a: int, b: int) -> int:
"""Multiply two numbers."""
return a * b
async def amultiply(a: int, b: int) -> int:
"""Multiply two numbers."""
return a * b
calculator = StructuredTool.from_function(func=multiply, coroutine=amultiply)
print(calculator.invoke({"a": 2, "b": 3}))
print(
await calculator.ainvoke({"a": 2, "b": 5})
) # Uses use provided amultiply without additional overhead
6
10
您不应该也不能使用.invoke当仅提供异步定义时。
@tool
async def multiply(a: int, b: int) -> int:
"""Multiply two numbers."""
return a * b
try:
multiply.invoke({"a": 2, "b": 3})
except NotImplementedError:
print("Raised not implemented error. You should not be doing this.")
Raised not implemented error. You should not be doing this.
处理工具错误
如果您将工具与代理一起使用,则可能需要错误处理策略,以便代理可以从错误中恢复并继续执行。
一个简单的策略是抛出一个ToolException并从工具内部使用handle_tool_error.
指定错误处理程序后,将捕获异常,错误处理程序将决定从工具返回哪个输出。
您可以设置handle_tool_error自True、字符串值或函数。如果它是一个函数,则该函数应该采用ToolException作为参数并返回一个值。
请注意,只有引发ToolException不会有效。您需要先设置handle_tool_error中,因为其默认值为False.
from langchain_core.tools import ToolException
def get_weather(city: str) -> int:
"""Get weather for the given city."""
raise ToolException(f"Error: There is no city by the name of {city}.")
下面是一个默认handle_tool_error=True行为。
get_weather_tool = StructuredTool.from_function(
func=get_weather,
handle_tool_error=True,
)
get_weather_tool.invoke({"city": "foobar"})
'Error: There is no city by the name of foobar.'
我们可以设置handle_tool_error转换为始终返回的字符串。
get_weather_tool = StructuredTool.from_function(
func=get_weather,
handle_tool_error="There is no such city, but it's probably above 0K there!",
)
get_weather_tool.invoke({"city": "foobar"})
"There is no such city, but it's probably above 0K there!"
使用函数处理错误:
def _handle_error(error: ToolException) -> str:
return f"The following errors occurred during tool execution: `{error.args[0]}`"
get_weather_tool = StructuredTool.from_function(
func=get_weather,
handle_tool_error=_handle_error,
)
get_weather_tool.invoke({"city": "foobar"})
'The following errors occurred during tool execution: `Error: There is no city by the name of foobar.`'
返回 Tool 执行的工件
有时,我们希望让 chain 或 agent 中的下游组件可以访问工具执行的工件,但我们不想将其暴露给模型本身。例如,如果工具返回 Documents 等自定义对象,我们可能希望将有关此输出的一些视图或元数据传递给模型,而不将原始输出传递给模型。同时,我们可能希望能够在其他地方访问此完整输出,例如在下游工具中。
Tool 和 ToolMessage 接口可以区分工具输出中用于模型的部分(这是 ToolMessage.content)和用于模型外部的部分(ToolMessage.artifact)。
langchain-core >= 0.2.19此功能是在langchain-core == 0.2.19.请确保您的软件包是最新的。
如果我们希望我们的工具区分消息内容和其他工件,我们需要指定response_format="content_and_artifact"在定义我们的工具并确保我们返回一个元组 (content, artifact) 时:
import random
from typing import List, Tuple
from langchain_core.tools import tool
@tool(response_format="content_and_artifact")
def generate_random_ints(min: int, max: int, size: int) -> Tuple[str, List[int]]:
"""Generate size random ints in the range [min, max]."""
array = [random.randint(min, max) for _ in range(size)]
content = f"Successfully generated array of {size} random ints in [{min}, {max}]."
return content, array
如果我们直接使用 tool 参数调用我们的工具,我们将只返回输出的 content 部分:
generate_random_ints.invoke({"min": 0, "max": 9, "size": 10})
'Successfully generated array of 10 random ints in [0, 9].'
如果我们使用 ToolCall 调用我们的工具(就像工具调用模型生成的工具一样),我们将返回一个 ToolMessage,其中包含 Tool 生成的内容和工件:
generate_random_ints.invoke(
{
"name": "generate_random_ints",
"args": {"min": 0, "max": 9, "size": 10},
"id": "123", # required
"type": "tool_call", # required
}
)
ToolMessage(content='Successfully generated array of 10 random ints in [0, 9].', name='generate_random_ints', tool_call_id='123', artifact=[4, 8, 2, 4, 1, 0, 9, 5, 8, 1])
我们在子类化 BaseTool 时可以做同样的事情:
from langchain_core.tools import BaseTool
class GenerateRandomFloats(BaseTool):
name: str = "generate_random_floats"
description: str = "Generate size random floats in the range [min, max]."
response_format: str = "content_and_artifact"
ndigits: int = 2
def _run(self, min: float, max: float, size: int) -> Tuple[str, List[float]]:
range_ = max - min
array = [
round(min + (range_ * random.random()), ndigits=self.ndigits)
for _ in range(size)
]
content = f"Generated {size} floats in [{min}, {max}], rounded to {self.ndigits} decimals."
return content, array
# Optionally define an equivalent async method
# async def _arun(self, min: float, max: float, size: int) -> Tuple[str, List[float]]:
# ...
rand_gen = GenerateRandomFloats(ndigits=4)
rand_gen.invoke(
{
"name": "generate_random_floats",
"args": {"min": 0.1, "max": 3.3333, "size": 3},
"id": "123",
"type": "tool_call",
}
)
ToolMessage(content='Generated 3 floats in [0.1, 3.3333], rounded to 4 decimals.', name='generate_random_floats', tool_call_id='123', artifact=[1.5566, 0.5134, 2.7914])