Skip to main content

代理 Agents

代理的核心思想是使用一个语言模型来选择一系列的动作。在链式编程中,一系列的动作是硬编码的(在代码中)。而在代理中,使用语言模型作为推理引擎来确定要采取的动作及其顺序。

这里有几个关键组件:

代理(Agent)

这是负责决定下一步采取什么动作的类。它由一个语言模型和一个提示驱动。这个提示可以包括以下内容:

  1. 代理的个性(有助于使其以某种方式回应)
  2. 代理的背景上下文(有助于为其提供更多关于所要求执行的任务类型的上下文)
  3. 触发更好推理的提示策略(最著名/广泛使用的是ReAct

LangChain提供了几种不同类型的代理供您开始使用。即使如此,您也可以自定义这些代理。有关代理类型的完整列表,请参见代理类型

工具(Tools)

工具是代理调用的函数。这里有两个重要的考虑因素:

  1. 给代理提供正确的工具访问权限
  2. 以对代理最有帮助的方式描述这些工具

如果两者都没有,您尝试构建的代理将无法工作。如果您没有给代理提供正确的工具集,它将永远无法完成目标。如果您没有正确描述工具,代理将不知道如何正确使用它们。

LangChain提供了一套广泛的工具供您开始使用,但也可以轻松定义自己的工具(包括自定义描述)。有关工具的完整列表,请参见这里

工具包(Toolkits)

对于代理而言,它能够访问的工具集比单个工具更重要。因此,LangChain提供了工具包的概念 - 一组用于实现特定目标的工具。通常一个工具包中包含3-5个工具。

LangChain提供了一套广泛的工具包供您开始使用。有关工具包的完整列表,请参见这里

代理执行器(AgentExecutor)

代理执行器是代理的运行时。它实际上调用代理并执行其选择的动作。以下是该运行时的伪代码:

next_action = agent.get_action(...)  
while next_action != AgentFinish:
observation = run(next_action)
next_action = agent.get_action(..., next_action, observation)
return next_action

虽然这看起来很简单,但这个运行时为您处理了几个复杂性,包括:

  1. 处理代理选择不存在的工具的情况
  2. 处理工具出错的情况
  3. 处理代理产生的无法解析为工具调用的输出的情况
  4. 在所有级别上进行日志记录和可观察性(代理决策、工具调用),可以输出到stdout或LangSmith

其他类型的代理运行时

AgentExecutor类是LangChain支持的主要代理运行时。然而,我们还支持其他更实验性的运行时。这些包括:

开始使用

本文将介绍如何开始构建一个代理。我们将使用LangChain代理类,但展示如何自定义它以提供特定的上下文。然后我们将定义自定义工具,最后在标准的LangChain AgentExecutor中运行它。

设置代理

我们将使用OpenAIFunctionsAgent。这是最简单和最好的代理程序,适合入门。但是,它需要使用ChatOpenAI模型。如果您想使用其他语言模型,我们建议使用ReAct代理程序。

在本指南中,我们将构建一个具有自定义工具访问权限的自定义代理程序。我们选择这个示例是因为我们认为在大多数情况下,您需要自定义代理程序或工具。我们将给代理程序提供一个计算单词长度的工具。这很有用,因为由于标记化,LLMs实际上可能会出错。我们首先创建一个没有记忆的代理程序,然后再展示如何添加记忆。记忆是启用对话的必需品。

首先,让我们加载我们要用来控制代理程序的语言模型。

from langchain.chat_models import ChatOpenAI
llm = ChatOpenAI(temperature=0)

接下来,让我们定义一些要使用的工具。让我们编写一个非常简单的Python函数来计算传入的单词的长度。

from langchain.agents import tool

@tool
def get_word_length(word: str) -> int:
"""返回单词的长度。"""
return len(word)

tools = [get_word_length]

现在让我们创建提示。我们可以使用OpenAIFunctionsAgent.create_prompt辅助函数自动创建提示。这允许多种不同的自定义方式,包括传入自定义的SystemMessage,我们将这样做。

from langchain.schema import SystemMessage
from langchain.agents import OpenAIFunctionsAgent
system_message = SystemMessage(content="你是一个非常强大的助手,但在计算单词长度方面不太好。")
prompt = OpenAIFunctionsAgent.create_prompt(system_message=system_message)

将这些部分组合在一起,我们现在可以创建代理程序。

agent = OpenAIFunctionsAgent(llm=llm, tools=tools, prompt=prompt)

最后,我们创建AgentExecutor - 代理程序的运行时。

from langchain.agents import AgentExecutor
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

现在让我们来测试一下!

agent_executor.run("单词educa有多少个字母?")

输出结果如下:

> 进入新的AgentExecutor链...
调用:`get_word_length`,参数为`{'word': 'educa'}`
5
单词"educa"中有5个字母。
> 完成链。
'单词"educa"中有5个字母。'

太棒了 - 我们有了一个代理程序!但是,这个代理程序是无状态的 - 它不记得之前的交互。这意味着您不能轻松地提出后续问题。让我们通过添加记忆来修复这个问题。

为了做到这一点,我们需要做两件事:

  1. 在提示中添加一个存储记忆变量的位置
  2. 在AgentExecutor中添加记忆(请注意,我们将其添加到这里,而不是添加到代理程序中,因为这是最外层的链)

首先,在提示中添加一个存储记忆的位置。我们通过使用键为"chat_history"的消息占位符来实现。

from langchain.prompts import MessagesPlaceholder

MEMORY_KEY = "chat_history"
prompt = OpenAIFunctionsAgent.create_prompt(
system_message=system_message,
extra_prompt_messages=[MessagesPlaceholder(variable_name=MEMORY_KEY)]
)

接下来,让我们创建一个记忆对象。我们将使用ConversationBufferMemory来实现。重要的是,我们将memory_key也设置为"chat_history"(以与提示对齐),并设置return_messages(使其返回消息而不是字符串)。

from langchain.memory import ConversationBufferMemory

memory = ConversationBufferMemory(memory_key=MEMORY_KEY, return_messages=True)

然后,我们将它们组合在一起!

agent = OpenAIFunctionsAgent(llm=llm, tools=tools, prompt=prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, memory=memory, verbose=True)
agent_executor.run("单词educa有多少个字母?")
agent_executor.run("这是一个真实的单词吗?")