Langchain的学习心得--自定义工具(两种)

一、定义自定义工具

当构建自己的代理时,您需要为其提供一组工具列表,代理可以使用这些工具。除了调用的实际函数之外,工具由几个组件组成:

name(str),是必需的,并且在提供给代理的工具集中必须是唯一的
description(str),是可选的但建议的,因为代理使用它来确定工具的使用方式
return_direct(bool),默认为 False
args_schema(Pydantic BaseModel),是可选的但建议的,可以用于提供更多信息(例如,few-shot 示例)或用于验证预期参数。

# Import things that are needed generically
from langchain import LLMMathChain, SerpAPIWrapper
from langchain.agents import AgentType, initialize_agent
from langchain.chat_models import ChatOpenAI
from langchain.tools import BaseTool, StructuredTool, Tool, tool

llm = ChatOpenAI(temperature=0)

二、字符串输入和输出

最简单的工具接受单个查询字符串并返回字符串输出。如果您的工具函数需要多个参数,您可能需要跳到下面的StructuredTool 部分。有两种方法可以做到这一点:要么使用 Tool 数据类,要么通过子类化 BaseTool 类。
工具数据类:“Tool”数据类包装接受单个字符串输入并返回字符串输出的函数。

1.Tool dataclass

代码如下(示例):

# Load the tool configs that are needed.
search = SerpAPIWrapper()
llm_math_chain = LLMMathChain(llm=llm, verbose=True)
tools = [
    Tool.from_function(
        func=search.run,
        name="Search",
        description="useful for when you need to answer questions about current events"
        # coroutine= ... <- you can specify an async method if desired as well
    ),
]

可以定义自定义“args_schema”来提供有关输入的更多信息

from pydantic import BaseModel, Field

class CalculatorInput(BaseModel):
    question: str = Field()

tools.append(
    Tool.from_function(
        func=llm_math_chain.run,
        name="Calculator",
        description="useful for when you need to answer questions about math",
        args_schema=CalculatorInput
        # coroutine= ... <- you can specify an async method if desired as well
    )
)
agent = initialize_agent(
    tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True
)

agent.run(
    "Who is Leo DiCaprio's girlfriend? What is her current age raised to the 0.43 power?"
)

2.Subclassing the BaseTool class

您也可以直接继承BaseTool。如果您想要对实例变量进行更多控制,或者想要将回调传播到嵌套链或其他工具,这非常有用。

from typing import Optional, Type

from langchain.callbacks.manager import (
    AsyncCallbackManagerForToolRun,
    CallbackManagerForToolRun,
)


class CustomSearchTool(BaseTool):
    name = "custom_search"
    description = "useful for when you need to answer questions about current events"
	# 接受两个参数:query(类型为 str,表示查询的内容)和 run_manager(类型为 Optional[CallbackManagerForToolRun],可选参数,用于管理工具运行的回调管理器)。返回类型为 str。
    def _run(
        self, query: str, run_manager: Optional[CallbackManagerForToolRun] = None
    ) -> str:
        """Use the tool."""
        return search.run(query)
    #接受两个参数:query(类型为 str)和 run_manager(类型为 Optional[AsyncCallbackManagerForToolRun],可选参数,用于异步操作的回调管理器)。返回类型为 str。抛出 NotImplementedError 异常,指明 CustomSearchTool 不支持异步操作。
    async def _arun(
        self, query: str, run_manager: Optional[AsyncCallbackManagerForToolRun] = None
    ) -> str:
        """Use the tool asynchronously."""
        raise NotImplementedError("custom_search does not support async")


class CustomCalculatorTool(BaseTool):
    name = "Calculator"
    description = "useful for when you need to answer questions about math"
    args_schema: Type[BaseModel] = CalculatorInput

    def _run(
        self, query: str, run_manager: Optional[CallbackManagerForToolRun] = None
    ) -> str:
        """Use the tool."""
        return llm_math_chain.run(query)

    async def _arun(
        self, query: str, run_manager: Optional[AsyncCallbackManagerForToolRun] = None
    ) -> str:
        """Use the tool asynchronously."""
        raise NotImplementedError("Calculator does not support async")
tools = [CustomSearchTool(), CustomCalculatorTool()]
agent = initialize_agent(
    tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True
)

agent.run(
    "Who is Leo DiCaprio's girlfriend? What is her current age raised to the 0.43 power?"
)

3.Using the tool decorator(使用工具修饰器)

为了更轻松地定义自定义工具,我们提供了 @tool 装饰器。此装饰器可用于从简单函数快速创建工具。装饰器默认使用函数名称作为工具名称,但可以通过传递字符串作为第一个参数来覆盖此名称。此外,装饰器将使用函数的文档字符串作为工具的描述。

from langchain.tools import tool

@tool
def search_api(query: str) -> str:
    """Searches the API for the query."""
    return f"Results for query {query}"

search_api

还可以提供工具名称以及是否直接返回等参数。

@tool("search", return_direct=True)
def search_api(query: str) -> str:
    """Searches the API for the query."""
    return "Results"
class SearchInput(BaseModel):
    query: str = Field(description="should be a search query")


@tool("search", return_direct=True, args_schema=SearchInput)
def search_api(query: str) -> str:
    """Searches the API for the query."""
    return "Results"

4.Custom Structured Tools(定制结构化工具)

如果您的函数需要更多结构化参数,您可以直接使用 StructuredTool 类,或者仍然继承 BaseTool 类。

结构化工具数据类:要从给定函数动态生成结构化工具,最快的入门方法是使用 StructuredTool.from_function()。

import requests
from langchain.tools import StructuredTool


def post_message(url: str, body: dict, parameters: Optional[dict] = None) -> str:
    """Sends a POST request to the given url with the given body and parameters."""
    result = requests.post(url, json=body, params=parameters)
    return f"Status: {result.status_code} - {result.text}"


tool = StructuredTool.from_function(post_message)

继承 BaseTool 类:BaseTool 自动从 _run 方法的签名推断架构。

from typing import Optional, Type

from langchain.callbacks.manager import (
    AsyncCallbackManagerForToolRun,
    CallbackManagerForToolRun,
)


class CustomSearchTool(BaseTool):
    name = "custom_search"
    description = "useful for when you need to answer questions about current events"

    def _run(
        self,
        query: str,
        engine: str = "google",
        gl: str = "us",
        hl: str = "en",
        run_manager: Optional[CallbackManagerForToolRun] = None,
    ) -> str:
        """Use the tool."""
        search_wrapper = SerpAPIWrapper(params={"engine": engine, "gl": gl, "hl": hl})
        return search_wrapper.run(query)

    async def _arun(
        self,
        query: str,
        engine: str = "google",
        gl: str = "us",
        hl: str = "en",
        run_manager: Optional[AsyncCallbackManagerForToolRun] = None,
    ) -> str:
        """Use the tool asynchronously."""
        raise NotImplementedError("custom_search does not support async")


# You can provide a custom args schema to add descriptions or custom validation


class SearchSchema(BaseModel):
    query: str = Field(description="should be a search query")
    engine: str = Field(description="should be a search engine")
    gl: str = Field(description="should be a country code")
    hl: str = Field(description="should be a language code")


class CustomSearchTool(BaseTool):
    name = "custom_search"
    description = "useful for when you need to answer questions about current events"
    args_schema: Type[SearchSchema] = SearchSchema

    def _run(
        self,
        query: str,
        engine: str = "google",
        gl: str = "us",
        hl: str = "en",
        run_manager: Optional[CallbackManagerForToolRun] = None,
    ) -> str:
        """Use the tool."""
        search_wrapper = SerpAPIWrapper(params={"engine": engine, "gl": gl, "hl": hl})
        return search_wrapper.run(query)

    async def _arun(
        self,
        query: str,
        engine: str = "google",
        gl: str = "us",
        hl: str = "en",
        run_manager: Optional[AsyncCallbackManagerForToolRun] = None,
    ) -> str:
        """Use the tool asynchronously."""
        raise NotImplementedError("custom_search does not support async")

使用装饰器:如果签名有多个参数,工具装饰器会自动创建结构化工具。

import requests
from langchain.tools import tool


@tool
def post_message(url: str, body: dict, parameters: Optional[dict] = None) -> str:
    """Sends a POST request to the given url with the given body and parameters."""
    result = requests.post(url, json=body, params=parameters)
    return f"Status: {result.status_code} - {result.text}"

5.Modify existing tools(修改现有工具)

现在,我们展示如何加载现有工具并直接修改它们。在下面的示例中,我们做了一些非常简单的事情,并将搜索工具更改为 Google 搜索名称。

from langchain.agents import load_tools
tools = load_tools(["serpapi", "llm-math"], llm=llm)
tools[0].name = "Google Search"
agent = initialize_agent(
    tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True
)
agent.run(
    "Who is Leo DiCaprio's girlfriend? What is her current age raised to the 0.43 power?"
)

6.Defining the priorities among Tools(定义工具之间的优先级)

当您制作自定义工具时,您可能希望代理更多地使用自定义工具而不是普通工具。例如,您制作了一个自定义工具,它从您的数据库中获取有关音乐的信息。当用户想要有关歌曲的信息时,您希望代理更多地使用自定义工具而不是普通搜索工具。但代理可能会优先使用普通搜索工具。这可以通过在描述中添加语句来实现,例如,如果问题是关于音乐的,例如“昨天的歌手是谁?”或“2022 年最流行的歌曲是什么?”。下面是一个例子。

# Import things that are needed generically
from langchain.agents import initialize_agent, Tool
from langchain.agents import AgentType
from langchain.llms import OpenAI
from langchain import LLMMathChain, SerpAPIWrapper

search = SerpAPIWrapper()
tools = [
    Tool(
        name="Search",
        func=search.run,
        description="useful for when you need to answer questions about current events",
    ),
    Tool(
        name="Music Search",
        func=lambda x: "'All I Want For Christmas Is You' by Mariah Carey.",  # Mock Function
        description="A Music search engine. Use this more than the normal search if the question is about Music, like 'who is the singer of yesterday?' or 'what is the most popular song in 2022?'",
    ),
]

agent = initialize_agent(
    tools,
    OpenAI(temperature=0),
    agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True,
)

agent.run("what is the most famous song of christmas")

回答:

[1m> Entering new AgentExecutor chain...[0m
[32;1m[1;3m I should use a music search engine to find the answer
Action: Music Search
Action Input: most famous song of christmas[0m[33;1m[1;3m'All I Want For Christmas Is You' by Mariah Carey.[0m[32;1m[1;3m I now know the final answer
Final Answer: 'All I Want For Christmas Is You' by Mariah Carey.[0m

[1m> Finished chain.[0m

"'All I Want For Christmas Is You' by Mariah Carey."

7.Using tools to return directly(使用工具直接返回)

如果调用的话,最好将工具输出直接返回给用户。您可以使用 LangChain 通过将工具的 return_direct 标志设置为 True 来轻松完成此操作。

llm_math_chain = LLMMathChain(llm=llm)
tools = [
    Tool(
        name="Calculator",
        func=llm_math_chain.run,
        description="useful for when you need to answer questions about math",
        return_direct=True,
    )
]
llm = OpenAI(temperature=0)
agent = initialize_agent(
    tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True
)
agent.run("whats 2**.12")

8.Handling Tool Errors(处理工具错误)

当工具遇到错误,并且异常没有被捕获时,代理会停止执行。如果想让代理继续执行,可以抛出 ToolException 异常,并设置相应的 handle_tool_error。当 ToolException 抛出时,代理不会停止工作,而是会根据工具的 handle_tool_error 变量处理异常,并将处理结果作为观察值返回给代理,并以红色打印。可以将 handle_tool_error 设置为 True,也可以设置为统一的字符串值,还可以设置为函数。如果设置为函数,函数应该以 ToolException 为参数,返回 str 值。注意,只抛出 ToolException 异常是无效的,需要先设置工具的 handle_tool_error,因为它的默认值是 False。

from langchain.schema import ToolException

from langchain import SerpAPIWrapper
from langchain.agents import AgentType, initialize_agent
from langchain.chat_models import ChatOpenAI
from langchain.tools import Tool

from langchain.chat_models import ChatOpenAI


def _handle_error(error: ToolException) -> str:
    return (
        "The following errors occurred during tool execution:"
        + error.args[0]
        + "Please try another tool."
    )


def search_tool1(s: str):
    raise ToolException("The search tool1 is not available.")


def search_tool2(s: str):
    raise ToolException("The search tool2 is not available.")


search_tool3 = SerpAPIWrapper()
description = "useful for when you need to answer questions about current events.You should give priority to using it."
tools = [
    Tool.from_function(
        func=search_tool1,
        name="Search_tool1",
        description=description,
        handle_tool_error=True,
    ),
    Tool.from_function(
        func=search_tool2,
        name="Search_tool2",
        description=description,
        handle_tool_error=_handle_error,
    ),
    Tool.from_function(
        func=search_tool3.run,
        name="Search_tool3",
        description="useful for when you need to answer questions about current events",
    ),
]

agent = initialize_agent(
    tools,
    ChatOpenAI(temperature=0),
    agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True,
)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值