LangChain系列文章
Agents的核心理念是利用语言模型选择一系列要采取的行动。在Chains中,一系列行动是硬编码的(在代码中)。在Agents中,语言模型被用作推理引擎,以确定要采取哪些行动以及顺序。
概念 这里有几个关键组成部分:
这是负责决定下一步该采取什么步骤的链条。这由语言模型和提示驱动。这个链条的输入包括:
输出是下一步要采取的行动或发送给用户的最终响应(代理动作或代理完成)。动作指定一个工具和该工具的输入。
不同的代理有不同的推理提示风格,不同的编码输入方式和不同的解析输出方式。有关内置代理的完整列表,请参见代理类型。您还可以轻松构建自定义代理,我们将在下面的入门部分中展示如何操作。
工具是代理可以调用的函数。围绕工具有两个重要的设计考虑:
如果没有同时考虑这两个方面,你将无法构建一个可工作的代理。如果你不给代理访问正确的工具集,它将永远无法完成你给它的目标。如果你没有很好地描述工具,代理将不知道如何正确使用它们。
LangChain 提供了广泛的内置工具集,但也使定义自己的工具(包括自定义描述)变得容易。有关内置工具的完整列表,请参见工具集成部分。
对于许多常见任务,代理将需要一组相关工具。为此,LangChain提供了工具包的概念 - 大约3-5个工具组成,用于实现特定目标。例如,GitHub工具包中有一个用于搜索GitHub问题的工具,一个用于读取文件的工具,一个用于评论的工具等等。
LangChain提供了广泛的工具包供您使用。有关内置工具包的完整列表,请参阅工具包集成部分。
代理执行器是代理的运行时。这实际上是调用代理,执行它选择的动作,将动作输出传递回代理,并重复。在伪代码中,大致如下:
next_action = agent.get_action(...)
while next_action != AgentFinish:
observation = run(next_action)
next_action = agent.get_action(..., next_action, observation)
return next_action
虽然这可能看起来很简单,但这个运行时为您处理了几个复杂情况,包括:
AgentExecutor类是LangChain支持的主要代理运行时。但是,我们还支持其他更实验性的运行时。这些包括:
您也可以始终创建自己的自定义执行逻辑,我们将在下面展示如何做到这一点。
为了更好地理解代理框架,让我们使用LangChain表达语言(LCEL)从头开始构建一个代理。我们需要构建代理本身,定义自定义工具,并在自定义循环中运行代理和工具。最后,我们将展示如何使用标准的LangChain
AgentExecutor
来使执行更容易。
一些重要的术语(和模式)需要知道:
根据定义,代理在返回用户可见的输出之前会采取一系列自主确定的、依赖输入的步骤。这使得调试这些系统特别棘手,观察特别重要。LangSmith在这种情况下特别有用。
在使用LangChain构建时,任何使用LCEL构建的内置代理或自定义代理都将自动在LangSmith中被追踪。如果我们使用AgentExecutor,不仅会得到代理规划步骤的完整追踪,还会得到工具的输入和输出的追踪。
要设置LangSmith,我们只需要设置以下环境变量:
export LANGCHAIN_TRACING_V2="true"
export LANGCHAIN_API_KEY=""
我们首先需要创建我们的代理。这是负责决定下一步采取什么行动的链条。
在这个例子中,我们将使用OpenAI函数调用来创建这个代理。这通常是创建代理的最可靠方式。
在本指南中,我们将构建一个具有自定义工具访问权限的自定义代理。我们选择这个例子是因为在大多数实际用例中,您将需要自定义代理或工具。我们将创建一个简单的工具,用于计算单词的长度。这很有用,因为由于标记化,LLMs实际上可能会搞砸这个。我们将首先创建它而不带有记忆,但然后我们将展示如何添加记忆。记忆是为了启用对话而必需的。
首先,让我们加载我们将用来控制代理的语言模型。
from langchain.chat_models import ChatOpenAI
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
我们可以看到它在尝试计算字符串"educa"中的字母时遇到了困难。
llm.invoke("how many letters in the word educa?")
AIMessage(content='There are 6 letters in the word "educa".')
接下来,让我们定义一些要使用的工具。让我们编写一个非常简单的Python函数来计算传入的单词的长度。
from langchain.agents import tool
@tool
def get_word_length(word: str) -> int:
"""Returns the length of a word."""
return len(word)
tools = [get_word_length]
现在让我们创建提示。因为OpenAI函数调用是为工具使用而优化的,我们几乎不需要任何关于如何推理或如何输出格式的指令。我们只需要两个输入变量:输入和agent_scratchpad
。输入应该是包含用户目标的字符串。agent_scratchpad
应该是包含先前代理工具调用和相应工具输出的消息序列。
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"You are very powerful assistant, but bad at calculating lengths of words.",
),
("user", "{input}"),
MessagesPlaceholder(variable_name="agent_scratchpad"),
]
)
代理如何知道它可以使用哪些工具?在这种情况下,我们依赖于OpenAI调用LLMs的功能,它们将函数作为单独的参数,并经过专门训练,知道何时调用这些函数。
要将我们的工具传递给代理,我们只需要将它们格式化为OpenAI函数格式,并将它们传递给我们的模型。(通过绑定函数,我们确保它们在每次调用模型时被传递。)
from langchain.tools.render import format_tool_to_openai_function
llm_with_tools = llm.bind(functions=[format_tool_to_openai_function(t) for t in tools])
将这些部分组合在一起,我们现在可以创建代理。我们将导入最后两个实用程序函数:一个用于格式化中间步骤(代理动作、工具输出对)以将其转换为可以发送到模型的输入消息的组件,以及一个用于将输出消息转换为代理动作/代理完成的组件。
from langchain.agents.format_scratchpad import format_to_openai_function_messages
from langchain.agents.output_parsers import OpenAIFunctionsAgentOutputParser
agent = (
{
"input": lambda x: x["input"],
"agent_scratchpad": lambda x: format_to_openai_function_messages(
x["intermediate_steps"]
),
}
| prompt
| llm_with_tools
| OpenAIFunctionsAgentOutputParser()
)
现在我们有了我们的代理,让我们来玩一下吧!让我们传递一个简单的问题和空的中间步骤,看看它返回什么:
agent.invoke({"input": "how many letters in the word educa?", "intermediate_steps": []})
AgentActionMessageLog(tool='get_word_length', tool_input={'word': 'educa'}, log="\nInvoking: `get_word_length` with `{'word': 'educa'}`\n\n\n", message_log=[AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\n "word": "educa"\n}', 'name': 'get_word_length'}})])
我们可以看到它响应一个AgentAction以采取行动(实际上是AgentActionMessageLog - AgentAction的子类,还跟踪完整的消息日志)。
如果我们设置了LangSmith,我们将看到一个跟踪,让我们检查每个步骤中的输入和输出的序列。https://smith.langchain.com/public/04110122-01a8-413c-8cd0-b4df6eefa4b7/r
所以这只是第一步 - 现在我们需要为此编写一个运行时。最简单的方法就是不断循环调用代理,然后执行动作,直到返回AgentFinish为止。让我们在下面编写代码:
from langchain.schema.agent import AgentFinish
user_input = "how many letters in the word educa?"
intermediate_steps = []
while True:
output = agent.invoke(
{
"input": user_input,
"intermediate_steps": intermediate_steps,
}
)
if isinstance(output, AgentFinish):
final_result = output.return_values["output"]
break
else:
print(f"TOOL NAME: {output.tool}")
print(f"TOOL INPUT: {output.tool_input}")
tool = {"get_word_length": get_word_length}[output.tool]
observation = tool.run(output.tool_input)
intermediate_steps.append((output, observation))
print(final_result)
TOOL NAME: get_word_length
TOOL INPUT: {'word': 'educa'}
There are 5 letters in the word "educa".
Woo! It’s working.
# 导入与 OpenAI 语言模型交互的模块。
from langchain.llms import OpenAI
# 导入用于创建和管理提示模板的模块。
from langchain.prompts import PromptTemplate
# 导入用于构建基于大型语言模型的处理链的模块。
from langchain.chains import LLMChain
# 导入从 .env 文件加载环境变量的库。
from dotenv import load_dotenv
# 导入创建和管理 OpenAI 聊天模型实例的类。
from langchain.chat_models import ChatOpenAI
# 加载 .env 文件中的环境变量。
load_dotenv()
# 设置环境变量,包括项目 ID 和 Langchain API 的相关设置。
import os
from uuid import uuid4
unique_id = uuid4().hex[0:8]
os.environ["LANGCHAIN_PROJECT"] = f"Tracing word length - {unique_id}"
# 初始化 LangSmith 客户端。
from langsmith import Client
client = Client()
# 创建 ChatOpenAI 实例。
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0, verbose=True)
# 定义一个自定义工具,用于获取单词的长度。
from langchain.agents import tool
@tool
def get_word_length(word: str) -> int:
"""Returns the length of a word."""
return len(word)
tools = [get_word_length]
# 创建聊天提示模板。
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
prompt = ChatPromptTemplate.from_messages(
[
("system", "You are very powerful assistant, but bad at calculating lengths of words."),
("user", "{input}"),
MessagesPlaceholder(variable_name="agent_scratchpad"),
]
)
# 将 ChatOpenAI 实例与工具绑定。
from langchain.tools.render import format_tool_to_openai_function
llm_with_tools = llm.bind(functions=[format_tool_to_openai_function(t) for t in tools])
# 定义代理。
from langchain.agents.format_scratchpad import format_to_openai_function_messages
from langchain.agents.output_parsers import OpenAIFunctionsAgentOutputParser
agent = (
{
"input": lambda x: x["input"],
"agent_scratchpad": lambda x: format_to_openai_function_messages(x["intermediate_steps"]),
}
| prompt
| llm_with_tools
| OpenAIFunctionsAgentOutputParser()
)
# 处理用户输入,使用代理执行循环,直到获取最终结果。
from langchain.schema.agent import AgentFinish
user_input = "how many letters in the word educa?"
intermediate_steps = []
while True:
output = agent.invoke(
{
"input": user_input,
"intermediate_steps": intermediate_steps,
}
)
if isinstance(output, AgentFinish):
final_result = output.return_values["output"]
break
else:
print(f"TOOL NAME: {output.tool}")
print(f"TOOL INPUT: {output.tool_input}")
tool = {"get_word_length": get_word_length}[output.tool]
observation = tool.run(output.tool_input)
intermediate_steps.append((output, observation))
print(final_result)
输出结果
(develop)⚡ % python Agents/chat_agents_word_length.py ~/Workspace/LLM/langchain-llm-app
TOOL NAME: get_word_length
TOOL INPUT: {'word': 'educa'}
There are 5 letters in the word "educa".
https://github.com/zgpeace/pets-name-langchain/tree/develop
https://python.langchain.com/docs/modules/agents/