跳到主要內容

如何建立工具

在建構 代理程式 時,您需要為其提供可使用的 工具 清單。除了實際呼叫的函式外,工具還包含幾個組件

屬性類型描述
名稱字串在提供給 LLM 或代理程式的一組工具中必須是唯一的。
描述字串描述工具的作用。由 LLM 或代理程式用作上下文。
args_schemapydantic.BaseModel可選但建議使用,如果使用回呼處理器則為必填。它可用於為預期參數提供更多資訊(例如,少數樣本範例)或驗證。
return_direct布林值僅與代理程式相關。當為 True 時,在調用給定工具後,代理程式將停止並將結果直接傳回給使用者。

LangChain 支援從以下項目建立工具

  1. 函式;
  2. LangChain 可執行物件
  3. BaseTool 子類別化 -- 這是最靈活的方法,它提供最大的控制程度,但需要付出更多努力和程式碼。

從函式建立工具可能足以滿足大多數用例,並且可以透過簡單的 @tool 裝飾器 完成。如果需要更多配置 - 例如,同步和非同步實作的規範 - 也可以使用 StructuredTool.from_function 類別方法。

在本指南中,我們概述了這些方法。

提示

如果工具具有精心選擇的名稱、描述和 JSON 結構描述,模型將表現得更好。

從函式建立工具

@tool 裝飾器

@tool 裝飾器是定義自訂工具的最簡單方法。裝飾器預設使用函式名稱作為工具名稱,但可以透過傳遞字串作為第一個引數來覆寫此設定。此外,裝飾器將使用函式的 Docstring 作為工具的描述 - 因此必須提供 Docstring。

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)
API 參考:tool
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
API 參考:tool

請注意,@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)


multiply_by_max.args_schema.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 引數傳遞到工具裝飾器中來自訂它們。

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

Docstring 解析

@tool 可以選擇性地解析 Google Style docstrings,並將 Docstring 組件(例如引數描述)與工具結構描述的相關部分關聯。若要切換此行為,請指定 parse_docstring

@tool(parse_docstring=True)
def foo(bar: str, baz: int) -> str:
"""The foo.

Args:
bar: The bar.
baz: The baz.
"""
return bar


foo.args_schema.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'}
注意

預設情況下,如果 Docstring 未正確解析,@tool(parse_docstring=True) 將引發 ValueError。 有關詳細資訊和範例,請參閱 API 參考

StructuredTool

StructuredTool.from_function 類別方法提供的配置性比 @tool 裝飾器稍高,而無需額外編寫太多程式碼。

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}))
API 參考:StructuredTool
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'}}

從可執行物件建立工具

接受字串或 dict 輸入的 LangChain 可執行物件 可以使用 as_tool 方法轉換為工具,該方法允許指定名稱、描述和引數的其他結構描述資訊。

使用範例

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, Type

from langchain_core.callbacks import (
AsyncCallbackManagerForToolRun,
CallbackManagerForToolRun,
)
from langchain_core.tools import BaseTool
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: Type[BaseModel] = CalculatorInput
return_direct: bool = True

def _run(
self, a: int, b: int, run_manager: Optional[CallbackManagerForToolRun] = None
) -> str:
"""Use the tool."""
return a * b

async def _arun(
self,
a: int,
b: int,
run_manager: Optional[AsyncCallbackManagerForToolRun] = None,
) -> str:
"""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 工具實作 可執行介面 🏃

所有可執行物件都公開 invokeainvoke 方法(以及其他方法,如 batchabatchastream 等)。

因此,即使您僅提供工具的 sync 實作,您仍然可以使用 ainvoke 介面,但有一些重要的事項需要了解

  • LangChain 預設提供非同步實作,假設函式的計算成本很高,因此它會將執行委派給另一個執行緒。
  • 如果您在非同步程式碼庫中工作,則應建立非同步工具而不是同步工具,以避免因該執行緒而產生少量額外負荷。
  • 如果您需要同步和非同步實作,請使用 StructuredTool.from_function 或從 BaseTool 子類別化。
  • 如果同時實作同步和非同步,並且同步程式碼執行速度很快,請覆寫預設的 LangChain 非同步實作,並僅呼叫同步程式碼。
  • 您不能也不應該將同步 invokeasync 工具一起使用。
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
API 參考:StructuredTool
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
API 參考:StructuredTool
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}.")
API 參考:ToolException

以下是預設 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 和 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
API 參考:tool

如果我們使用工具引數直接調用我們的工具,我們將僅取回輸出的內容部分

generate_random_ints.invoke({"min": 0, "max": 9, "size": 10})
'Successfully generated array of 10 random ints in [0, 9].'

如果我們使用 ToolCall(如工具呼叫模型產生的那些)調用我們的工具,我們將取回一個 ToolMessage,其中包含工具產生的內容和工件

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]]:
# ...
API 參考:BaseTool
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])

此頁面是否對您有幫助?