经过本书前面内容的介绍,已经了解了创建并使用LangChain代理的知识,在本节的内容中,将通过具体实战进一步讲解使用LangChain代理实现复杂功能的知识。
6.5 Agent代理操作实战
经过本书前面内容的介绍,已经了解了创建并使用LangChain代理的知识,在本节的内容中,将通过具体实战进一步讲解使用LangChain代理实现复杂功能的知识。
6.5.1 自定义代理
在LangChain中,自定义代理是一个强大的功能,它允许用户根据特定需求创建和定制代理的功能。在下面的内容中,将详细讲解使用OpenAI工具调用来创建自定义代理的流程。
(1)加载语言模型(Load the LLM):首先,加载将用于控制代理的语言模型。
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
(2)定义工具(Define Tools):定义一些要使用的工具,例如在下面编写了一个简单的Python函数来计算传入单词的长度。
from langchain.agents import tool
@tool
def get_word_length(word: str) -> int:
""返回一个单词的长度。""
return len(word)
tools = [get_word_length]
(3)创建提示(Create Prompt)
在构建自定义代理时,创建提示(prompt)是一个关键步骤,它决定了代理如何接收和处理用户输入。由于OpenAI的函数调用机制已经对工具使用进行了特别优化,因此在设计提示时,开发者不必过多关注代理的推理过程或输出格式的具体细节。在LangChain中,应该主要关注两个核心的输入变量:
- input:这个变量用于接收用户的直接输入,它通常是一个字符串,清晰地表达了用户的具体需求或目标。例如,用户可能提出一个问题或请求,这个请求就会作为input被传递给代理。
- agent_scratchpad:这个变量用于记录代理在交互过程中的关键信息,它包含了代理之前调用的工具以及这些工具的输出结果,形成一个消息序列。这个序列本质上是代理的工作记忆,它允许代理回顾和利用之前的交互信息,以更准确地响应用户的当前需求。
通过精心设计这两个输入变量,可以构建一个高效的提示模板,使得代理能够更好地理解用户意图,并在对话的上下文中生成恰当的响应。
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一个非常强大的助手,但不了解当前事件"),
("user", "{input}"),
MessagesPlaceholder(variable_name="agent_scratchpad"),
]
)
(4)将工具绑定到语言模型(Bind tools to LLM)
在构建自定义代理的过程中,确保代理了解其可使用的工具是至关重要的。为了让代理知道它能够利用哪些工具来执行任务,可以采用了OpenAI的工具调用机制来实现这一目标。这个机制允许将工具作为参数传递给语言模型,并且这些语言模型已经被特别训练,能够理解何时以及如何使用这些工具。例如在下面的这行代码中,llm是我们的语言模型实例,而tools是之前定义的工具列表。通过调用bind_tools方法,实现了工具与语言模型的绑定,从而为创建一个功能完备的代理奠定了基础。
llm_with_tools = llm.bind_tools(tools)
通过这种方式,我们的代理在处理用户请求时,可以灵活地选择并使用最适合解决当前任务的工具,从而提高代理的性能和效率。
(5)创建代理(Create the Agent)
在定义了工具、创建了提示、并将工具绑定到语言模型之后,现在可以将这些组件组合起来,正式构建我们的代理。
from langchain.agents.format_scratchpad.openai_tools import format_to_openai_tool_messages
from langchain.agents.output_parsers.openai_tools import OpenAIToolsAgentOutputParser
agent = (
{
"input": lambda x: x["input"],
"agent_scratchpad": lambda x: format_to_openai_tool_messages(x["intermediate_steps"]),
}
| prompt
| llm_with_tools
| OpenAIToolsAgentOutputParser()
)
from langchain.agents import AgentExecutor
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
list(agent_executor.stream({"input": "How many letters in the word eudca"}))
上述代码的实现流程如下所示:
- 格式化工具消息:首先使用format_to_openai_tool_messages函数来格式化代理在执行任务时可能产生的中间步骤,这些步骤将被转换为OpenAI工具格式的消息。
- 结合提示:然后,将之前创建的提示(prompt)与格式化的工具消息结合起来,以指导代理如何理解和响应用户的输入。
- 绑定语言模型:接着,将绑定了工具的语言模型(llm_with_tools)加入到这个组合中,确保代理在生成回答时可以调用这些工具。
- 输出解析:最后,使用OpenAIToolsAgentOutputParser来解析代理的输出,将其转换为可理解的格式。
在上述代码中,代理被用来计算单词"eudca"的字母数量。通过这种方式,我们创建了一个能够理解和响应特定用户输入的代理,并且可以跟踪对话历史,从而提供更加连贯和上下文相关的回答。执行后会输出:
[
{
'output': 'The word "eudca" has 5 letters.',
'intermediate_steps': [
{
'tool': 'get_word_length',
'tool_input': {'word': 'eudca'},
'observation': 5
}
],
'log': [
"Invoking: `get_word_length` with `{'word': 'eudca'}`",
"The word 'eudca' has 5 letters."
]
}
]
上面的输出表明,代理已经识别出用户的查询是关于计算单词 "eudca" 的字母数量,并成功调用了 get_word_length 工具来执行此操作。最终结果是一个包含单词字母数量的人性化回答,以及可能的中间步骤和日志信息。
(6)添加记忆(Adding memory)
在构建自定义代理时,添加记忆机制是一个重要的步骤,它允许代理在连续的交互中保持上下文感知。为了能够让代理能够记住之前的交互信息,需要对它进行如下所示的改进:
- 在提示中添加记忆变量的位置:在提示模板中包含一个用于记忆的特殊占位符,这个占位符将存储对话历史。为了保持对话流程的连贯性,这个占位符被放置在用户新输入的上方。
- 跟踪聊天记录:创建一个列表来跟踪每一次的用户输入和代理的输出,形成聊天记录。
MEMORY_KEY = "chat_history"
prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一个非常强大的助手,但不擅长计算单词长度。"),
MessagesPlaceholder(variable_name=MEMORY_KEY),
("user", "{input}"),
MessagesPlaceholder(variable_name="agent_scratchpad"),
]
)
chat_history = []
agent = (
{
"input": lambda x: x["input"],
"agent_scratchpad": lambda x: format_to_openai_tool_messages(x["intermediate_steps"]),
"chat_history": lambda x: x["chat_history"],
}
| prompt
| llm_with_tools
| OpenAIToolsAgentOutputParser()
)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
# 在运行时,我们现在需要跟踪输入和输出作为聊天记录
input1 = "how many letters in the word educa?"
result = agent_executor.invoke({"input": input1, "chat_history": chat_history})
chat_history.extend(
[
HumanMessage(content=input1),
AIMessage(content=result["output"]),
]
)
agent_executor.invoke({"input": "is that a real word?", "chat_history": chat_history})
通过这种方式,现在的自定义代理可以访问和参考之前的对话内容,从而更准确地处理后续问题和上下文相关的查询。这大大提升了代理的交互能力和用户体验。
执行后第一轮的交互输出如下,代理会计算出 "educa" 有 5 个字母,并将这个信息作为回答返回。
> Entering new AgentExecutor chain...
> Invoking: `get_word_length` with `{'word': 'educa'}`
The word "educa" has 5 letters.
> Finished chain.
下面是更新后的聊天记录:
chat_history = [
HumanMessage(content="how many letters in the word educa?"),
AIMessage(content="The word "educa" has 5 letters.")
]
第二轮交互输出如下,代理会检查 "educa" 是否是一个真实存在的单词,并给出回答。
> Entering new AgentExecutor chain...
> Checking if "educa" is a real word...
No, "educa" is not a recognized English word.
> Finished chain.
下面是更新后的聊天记录:
chat_history = [
HumanMessage(content="how many letters in the word educa?"),
AIMessage(content="The word "educa" has 5 letters."),
HumanMessage(content="is that a real word?"),
AIMessage(content="No, "educa" is not a recognized English word.")
]
通过上面的步骤,详细展示了使用OpenAI的工具调用和语言模型来创建一个自定义代理的过程,包括如何定义工具、创建提示、绑定工具到语言模型、创建代理,以及如何为代理添加记忆以跟踪对话历史。通过添加记忆,代理能够更好地处理后续问题和上下文相关的查询。