创建自己的LLM Agent:让语言模型动起来
语言模型本身不能采取行动——它们只能输出文本。LangChain的一个重要应用就是创建智能代理。代理是一种系统,使用LLM作为推理引擎来确定应该采取哪些行动以及传递什么输入。在执行操作后,可以将结果反馈给LLM,以确定是否需要更多操作,或者是否可以结束。
在本教程中,我们将构建一个可以与搜索引擎互动的智能代理。你将能够向这个代理提问,观看它调用搜索工具,并与它进行对话。
端到端代理
下面的代码片段展示了一个完全功能的代理,使用LLM来决定使用哪些工具。它配备了一个通用的搜索工具,并具有对话记忆功能——意味着它可以作为多轮对话的聊天机器人使用。
在本指南的其余部分,我们将逐步讲解各个组件及其作用——但如果你想直接拿代码上手,请随意使用以下代码!
# 导入相关功能
from langchain_anthropic import ChatAnthropic
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_core.messages import HumanMessage
from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph.prebuilt import create_react_agent
# 创建代理
memory = SqliteSaver.from_conn_string(":memory:")
model = ChatAnthropic(model_name="claude-3-sonnet-20240229")
search = TavilySearchResults(max_results=2)
tools = [search]
agent_executor = create_react_agent(model, tools, checkpointer=memory)
# 使用代理
config = {"configurable": {"thread_id": "abc123"}}
for chunk in agent_executor.stream(
{"messages": [HumanMessage(content="你好,我是小明!我住在旧金山。")]}, config
):
print(chunk)
print("----")
for chunk in agent_executor.stream(
{"messages": [HumanMessage(content="我住的地方天气怎么样?")]}, config
):
print(chunk)
print("----")
API参考:ChatAnthropic
| TavilySearchResults
| HumanMessage
设置环境
Jupyter Notebook
本指南(以及文档中的大多数指南)使用Jupyter notebook,并假设读者也在使用它。Jupyter notebook是学习如何使用LLM系统的完美互动环境,因为很多时候事情会出错(例如输出不符合预期、API挂掉等),观察这些情况是更好地理解LLM构建的好方法。
这个和其他教程最好在Jupyter notebook中运行。查看此处了解安装方法。
安装
要安装LangChain,请运行:
%pip install -U langchain-community langgraph langchain-anthropic tavily-python
详情请参见安装指南。
LangSmith
你用LangChain构建的许多应用程序将包含多步操作和多次LLM调用。随着这些应用程序变得越来越复杂,能够检查链或代理内部发生的具体情况变得至关重要。最好的方法是使用LangSmith。
注册后,确保设置环境变量以开始记录跟踪:
export LANGCHAIN_TRACING_V2="true"
export LANGCHAIN_API_KEY="..."
或者,如果在notebook中,可以这样设置:
import getpass
import os
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = getpass.getpass()
Tavily
我们将使用Tavily(一个搜索引擎)作为工具。要使用它,你需要获取并设置API密钥:
export TAVILY_API_KEY="..."
或者,如果在notebook中,可以这样设置:
import getpass
import os
os.environ["TAVILY_API_KEY"] = getpass.getpass()
定义工具
我们首先需要创建我们要使用的工具。我们的主要工具将是Tavily——一个搜索引擎。LangChain内置了一个工具,可以轻松使用Tavily搜索引擎作为工具。
from langchain_community.tools.tavily_search import TavilySearchResults
search = TavilySearchResults(max_results=2)
search_results = search.invoke("旧金山的天气怎么样?")
print(search_results)
# 如果我们愿意,可以创建其他工具。
# 一旦我们有了所有想要的工具,我们可以将它们放在一个列表中,稍后会引用它们。
tools = [search]
API参考:TavilySearchResults
使用语言模型
接下来,让我们学习如何使用语言模型调用工具。LangChain支持许多不同的语言模型,可以互换使用——选择你想使用的一个!
pip install -qU langchain-openai
import getpass
import os
os.environ["OPENAI_API_KEY"] = getpass.getpass()
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="gpt-4")
你可以通过传入消息列表来调用语言模型。默认情况下,响应是一个内容字符串。
from langchain_core.messages import HumanMessage
response = model.invoke([HumanMessage(content="你好!")])
response.content
API参考:HumanMessage
‘你好!’
现在我们可以看看启用这个模型调用工具是什么样子的。为了启用这个功能,我们使用.bind_tools
来让语言模型知道这些工具。
model_with_tools = model.bind_tools(tools)
我们现在可以调用模型。首先用一个普通消息调用,看看它如何响应。我们可以查看内容字段以及工具调用字段。
response = model_with_tools.invoke([HumanMessage(content="你好!")])
print(f"内容字符串:{response.content}")
print(f"工具调用:{response.tool_calls}")
内容字符串:‘你好!’
工具调用:[]
现在,让我们试着用一些需要调用工具的输入来调用它。
response = model_with_tools.invoke([HumanMessage(content="旧金山的天气怎么样?")])
print(f"内容字符串:{response.content}")
print(f"工具调用:{response.tool_calls}")
内容字符串:
工具调用:[{‘name’: ‘tavily_search_results_json’, ‘args’: {‘query’: ‘旧金山的天气’}, ‘id’: ‘toolu_01VTP7DUvSfgtYxsq9x4EwMp’}]
我们可以看到,现在没有文本内容,但有一个工具调用!它希望我们调用Tavily搜索工具。
这还没有调用工具——它只是告诉我们要这么做。为了实际调用它,我们需要创建我们的代理。
创建代理
现在我们已经定义了工具和LLM,我们可以创建代理。我们将使用LangGraph来构建代理。目前我们使用高级接口来构建代理,但LangGraph的好处在于这个高级接口由低级、高度可控的API支持,以防你想修改代理逻辑。
现在,我们可以用LLM和工具初始化代理。
注意,我们传入的是model
,而不是model_with_tools
。这是因为create_react_agent
会在底层为我们调用.bind_tools
。
from langgraph.prebuilt import create_react_agent
agent_executor = create_react_agent(model, tools)
运行代理
我们现在可以对一些查询运行代理了!注意,目前这些都是无状态查询(它不会记住先前的交互)。注意,代理将在交互结束时返回最终状态(包括所有输入,稍后我们将看到如何仅获取输出)。
首先,让我们看看当不需要调用工具时它如何响应:
response = agent_executor.invoke({"messages": [HumanMessage(content="你好!")]})
response["messages"]
[HumanMessage(content='你好!', id='a820fcc5-9b87-457a-9af0-f21768143ee3'),
AIMessage(content='你好!', response_metadata={'id': 'msg_01VbC493X1VEDyusgttiEr1z', 'model': 'claude-3-sonnet-20240229', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 264, 'output_tokens': 5}}, id='run-0e0ddae8-a85b-4bd6-947c-c36c857a4698-0', usage_metadata={'input_tokens': 264, 'output_tokens': 5, 'total_tokens': 269})]
为了确切了解底层发生了什么(并确保它没有调用工具),我们可以查看LangSmith跟踪。
现在让我们试试一个应该调用工具的示例:
response = agent_executor.invoke(
{"messages": [HumanMessage(content="旧金山的天气怎么样?")]}
)
response["messages"]
[HumanMessage(content='旧金山的天气怎么样?', id='1d6c96bb-4ddb-415c-a579-a07d5264de0d'),
AIMessage(content=[{'id': 'toolu_01Y5EK4bw2LqsQXeaUv8iueF', 'input': {'query': 'weather in san francisco'}, '
name': 'tavily_search_results_json'}], tool_name='tavily_search_results_json', id='run-0e0ddae8-a85b-4bd6-947c-c36c857a4698-1'),
AIMessage(content='根据搜索结果,旧金山目前的天气是...', response_metadata={'id': 'msg_01Y5EK5kPZa6ykHJVuIghgdR', 'model': 'claude-3-sonnet-20240229', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'input_tokens': 14, 'output_tokens': 23}}, id='run-0e0ddae8-a85b-4bd6-947c-c36c857a4698-2', usage_metadata={'input_tokens': 14, 'output_tokens': 23, 'total_tokens': 37})]
我们可以看到代理输出了一个工具调用。为了实际执行该工具调用,我们需要启用代理的工具调用功能。我们将通过执行代码来实现这一点。
将其作为流式数据
为了以流的方式接收数据并观察所有步骤,我们可以使用.stream
方法。这将生成工具调用,允许我们与最终结果交互。
config = {"configurable": {"thread_id": "abc123"}}
for chunk in agent_executor.stream(
{"messages": [HumanMessage(content="你好,我是小明!我住在旧金山。")]}, config
):
print(chunk)
print("----")
for chunk in agent_executor.stream(
{"messages": [HumanMessage(content="我住的地方天气怎么样?")]}, config
):
print(chunk)
print("----")
添加对话记忆
为了让我们的代理记住对话内容,我们将添加一个记忆模块。我们将使用SqliteSaver
来保存会话数据。
from langgraph.checkpoint.sqlite import SqliteSaver
memory = SqliteSaver.from_conn_string(":memory:")
agent_executor = create_react_agent(model, tools, checkpointer=memory)
现在,我们的代理可以在对话过程中记住先前的内容。请注意,在使用记忆时,我们需要为每个对话设置一个唯一的线程ID(例如thread_id
),以便代理知道哪些信息属于哪个对话。
config = {"configurable": {"thread_id": "abc123"}}
for chunk in agent_executor.stream(
{"messages": [HumanMessage(content="你好,我是小明!我住在旧金山。")]}, config
):
print(chunk)
print("----")
for chunk in agent_executor.stream(
{"messages": [HumanMessage(content="我住的地方天气怎么样?")]}, config
):
print(chunk)
print("----")
总结
通过本教程,你学会了如何使用LangChain和LLM构建一个可以调用工具的智能代理。你还学会了如何将对话记忆功能集成到代理中。现在,你可以根据自己的需求扩展和定制这个代理,创建一个更复杂、更强大的系统。
让你的语言模型动起来,开始构建智能代理吧!