跳转到主要内容

TL;DR:我们正在引入一种新的抽象,以允许使用更复杂的工具。虽然以前的工具只接受单个字符串输入,但新工具可以接受任意数量的任意类型的输入。我们还引入了一个新的代理类,它可以很好地与这些新类型的工具配合使用。

重要链接:

  • 工具列表
  • 新代理人

早在2022年11月,当我们首次推出LangChain时,代理和工具利用率在我们的设计中发挥了核心作用。我们建立了基于ReAct的首批链之一,这是一篇开创性的论文,将工具的使用带到了提示框架的前沿。

在早期,工具的使用过于简单。一个模型将生成两个字符串:

  • 工具名称
  • 所选工具的输入字符串

这种方法将代理限制为每转一个工具,并且该工具的输入限制为单个字符串。这些限制主要是由于模型的限制;模型甚至很难熟练地完成这些基本任务。可靠地执行更复杂的操作,例如选择多个工具或填充复杂的模式,将是一件愚蠢的事。

然而,更先进的语言模型(如text-davinci-003、gpt-3.5-turbo和gpt-4)的快速发展为现有模型能够可靠实现的目标奠定了基础。这促使我们重新评估LangChain代理框架中对工具使用的限制。

今年早些时候,我们引入了一个“多动作”代理框架,在该框架中,代理可以计划对代理执行器的每一步执行多个动作。在这一成功的基础上,我们现在正在摆脱单一字符串输入的限制,并自豪地提供结构化工具支持!

结构化工具使语言模型和工具之间能够进行更复杂、多方面的交互,从而更容易构建创新、适应性强、功能强大的应用程序。

什么是“结构化工具”?

结构化工具表示代理可以采取的操作。它包装了您提供的任何功能,使代理可以轻松地与它接口。结构化工具对象由其定义:

  • name:一个标签,告诉代理要选择哪个工具。例如,一个名为“GetCurrentWeather”的工具告诉代理它是用来查找当前天气的。
  • 描述:一份简短的说明手册,解释了代理何时以及为什么应该使用该工具。
  • args_schema:为代理通信工具的接口。它通常从封装的函数的签名中提取,并允许对工具输入进行额外的验证逻辑。
  • _run和_arun函数:这些函数定义了工具的内部工作。它可以是简单的,比如返回当前时间,也可以是更复杂的,比如发送消息或控制机器人。

工具名称是其唯一标识符。一个好的名字明确地传达了它的功能,所以一个名为“GetCurrentWeather”的工具比“GCTW”更有用。如果您不清楚工具的名称,那么代理可能也不清楚。如果您允许代理访问多个工具,则该名称还可以提供有关它们之间关系的信息。例如,如果您有“AmazonSearch”、“AmazonCurrentBalance”和“NikeShoppingCart”工具,即使不阅读描述,代理也可以推断出前两者是相关的。

该说明提供了有关如何使用该工具的更详细说明。一个好的描述是简洁的,但能有效地传达工具的功能。如果需要的话,这也可以提供提供简短示例(或反例)的空间。

args_schema是一个Pydantic BaseModel,它定义了要提供给工具的参数(以及它们的类型信息)。它有两个主要的工作:第一,传达代理需要哪些信息。第二项工作是在执行工具的内部功能之前验证这些输入。

最后,_run和伴随的async_arun方法定义了工具的逻辑。您可以在这里放置任何内容,从算术到API请求,再到对其他LLM链的调用。

新的结构化工具

除了这个新的基类之外,我们还发布了以下新工具,这两个工具都继承了这个结构化的工具类。

  • 文件管理—一个工具包,用于您可能需要的所有文件系统操作,包括写入、grep、移动、复制、list_dir、查找
  • Web浏览器-虽然我们以前有用于文档加载程序的浏览器,但现在我们正在发布一个官方的有状态的PlayWright浏览器工具包,让我们的代理访问网站、点击、提交表单和查询数据

有关所有工具(旧的和新的)的列表,请参阅此处的文档。

实施您自己的结构化工具

最快的入门方法是调用StructuredTool.from_function(your_callable)构造函数。

举个例子,假设您想要一个工具来通过请求库与拥抱面部模型进行交互。

import requests
from langchain.tools.base import StructuredTool

API_KEY = "<MY-API-KEY>"

def get_huggingface_models(
    path: Optional[str] = None, query_params: Optional[dict] = None
) -> dict:
    """Tool that calls GET on <https://huggingface.co/models*> apis. Valid params include "search":"search", "author":"author", "filter":"filter" and "sort":"sort"."""
    base_url = "<https://huggingface.co/api/models>"
    headers = {"authorization": f"Bearer {API_KEY}"}
    result = requests.get(base_url + (path or ""), params=query_params, headers=headers)
    return result.json()

get_huggingface_models_tool = StructuredTool.from_function(get_huggingface_models)
models = get_huggingface_models_tool.run({"query_params": {"search": "gpt-j"}})
print(models)

在幕后,这将从函数的签名中推断出args_schema。这是用来告诉代理它可以提供查询参数来搜索,也可以提供路径参数来调用其他子端点。

如果您想要对工具定义进行更多控制,可以直接对BaseTool进行子类化。例如,您可能希望从环境变量中自动加载api密钥。

from typing import Optional, Type

import aiohttp
import requests

from langchain.callbacks.manager import (
    AsyncCallbackManagerForToolRun,
    CallbackManagerForToolRun,
)
from langchain.tools import BaseTool
from pydantic import BaseModel, BaseSettings, Field

class GetHuggingFaceModelsToolSchema(BaseModel):
    path: str = Field(default="", description="the api path")
    query_params: Optional[dict] = Field(
        default=None, description="Optional search parameters"
    )

class GetHuggingFaceModelsTool(BaseTool, BaseSettings):
    """My custom tool."""

    name: str = "get_huggingface_models"
    description: str = """Tool that calls GET on <https://huggingface.co/models*> apis. Valid params include "search":"search", "author":"author", "filter":"filter" and "sort":"sort"."""
    args_schema: Type[GetHuggingFaceModelsToolSchema] = GetHuggingFaceModelsToolSchema
    base_url: str = "<https://huggingface.co/api/models>"
    api_key: str = Field(..., env="HUGGINGFACE_API_KEY")

    @property
    def _headers(self) -> dict:
        return {"authorization": f"Bearer {self.api_key}"}

    def _run(
        self,
        path: str = "",
        query_params: Optional[dict] = None,
        run_manager: Optional[CallbackManagerForToolRun] = None,
    ) -> dict:
        """Run the tool"""
        result = requests.get(
            self.base_url + path, params=query_params, headers=self._headers
        )
        return result.json()

    async def _arun(
        self,
        path: str = "",
        query_params: Optional[dict] = None,
        run_manager: Optional[AsyncCallbackManagerForToolRun] = None,
    ) -> dict:
        """Run the tool asynchronously."""

        async with aiohttp.ClientSession() as session:
            async with session.get(
                self.base_url + path, params=query_params, headers=self._headers
            ) as response:
                return await response.json()

get_models_tool = GetHuggingFaceModelsTool()
models = get_models_tool.run({"query_params": {"search": "gpt-j"}})
print(models)

如何使用结构化工具?

我们添加了一个新的StructuredChatAgent,它可以与这些结构化工具一起本地工作。请参阅此笔记本进行演练。

由于先前代理的默认提示和输出解析器的限制,如果没有额外的自定义,它们就无法有效地使用结构化工具。

要开始,您可以使用以下代码片段实例化结构化聊天代理执行器:

from langchain.agents import initialize_agent, AgentType
from langchain.chat_models import ChatAnthropic
tools = [] # Add any tools here
llm = ChatAnthropic(temperature=0) # or any other LLM
agent_chain = initialize_agent(tools, llm, agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION)

这些工具还与langchain.experimental中的AutoGPT代理兼容。

常见问题

Q: 我可以将结构化工具与现有代理一起使用吗?

A: 如果您的结构化工具接受一个字符串参数:YES,那么它仍然可以使用现有的代理。然而,如果没有进一步的自定义,具有多个参数的结构化工具与以下代理不直接兼容:

  • 零热反应描述
  • 反应文档存储
  • 带搜索的自我询问
  • 会话反应描述
  • 聊天零热点反应描述
  • 聊天会话反应描述

Q: 我仍然可以创建字符串工具吗?

A: 您仍然可以使用Tool构造函数和@Tool装饰器来定义简单的字符串工具。从BaseTool类继承并接受单个字符串参数的工具仍将被视为字符串工具。

Q: 我可以将以前定义的字符串BaseTool与为StructuredTool构建的新代理一起使用吗

A: 是的!结构化工具不需要新的代理执行器,而旧的工具是向前兼容的。原始的Tool类与StructuredTool共享相同的基类,这是另一种表示工具应该开箱即用的方式。

需要json序列化字符串输入的工具可能需要进行一些修改,以便与较新代理的输出解析器进行互操作,或者可以将它们更新为新格式,这将为更复杂的接口提供更好的支持。

文章链接