【LangChain指南】Agents
第一部分:Agents 是什么?为什么需要它?
在开始编码之前,我们必须理解一个核心概念。
问题:大语言模型(LLM)本身有什么局限?
答案:LLM 本质上是一个“文本预测器”。它非常擅长根据输入生成连贯、有信息量的文本,但它不能主动执行操作,比如搜索网络、查询数据库或调用一个 Python 函数。
Agents 的作用:
Agents 就是为了解决这个问题而生的。它是一个系统,利用 LLM 作为“大脑”或“推理引擎”,来决定:
- 需要采取什么行动? (例如,是调用“搜索工具”还是“计算器工具”?)
- 这个行动的输入参数是什么? (例如,搜索什么关键词?)
- 是否需要根据行动结果进行下一步?还是可以给出最终答案?
简单来说,Agents 让 LLM 从一个“只说不做”的聊天机器人,变成了一个“能思考、能动手”的智能助手。
第二部分:使用传统 AgentExecutor
构建代理
这是 LangChain 中最经典、最入门的代理构建方式。我们通过一个完整的例子来学习。
步骤 1: 环境准备与工具定义
首先,我们需要安装必要的库并定义代理可以使用的“工具”。
# 安装必要的库
# !pip install -U langchain langchain-openai langchain-community tavily-pythonimport getpass
import os
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langchain_community.tools.tavily_search import TavilySearchResults# 设置 API Keys
os.environ["OPENAI_API_KEY"] = getpass.getpass("Enter your OpenAI API Key: ")
os.environ["TAVILY_API_KEY"] = getpass.getpass("Enter your Tavily API Key: ")# 定义一个简单的自定义工具
@tool
def multiply(a: int, b: int) -> int:"""将两个整数相乘"""return a * b# 定义一个搜索工具
search = TavilySearchResults(max_results=2)# 将所有工具放入一个列表
tools = [multiply, search]# 初始化语言模型
llm = ChatOpenAI(model="gpt-4o", temperature=0)
步骤 2: 创建代理 (Agent) 和执行器 (AgentExecutor)
代理负责“思考”,执行器负责“执行思考的结果并管理流程”。
from langchain import hub
from langchain.agents import create_tool_calling_agent, AgentExecutor# 从 LangChain Hub 拉取一个预设的、适合工具调用的提示词模板
# 这个模板告诉 LLM 如何思考、如何调用工具。
prompt = hub.pull("hwchase17/openai-functions-agent")# 创建代理 (Agent)
# 注意:这里传入的是原始的 llm,create_tool_calling_agent 会自动帮我们绑定工具。
agent = create_tool_calling_agent(llm, tools, prompt)# 创建代理执行器 (AgentExecutor)
# 它将代理和工具组合起来,形成一个可以运行的完整系统。
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True) # verbose=True 可以看到思考过程
步骤 3: 运行代理
现在,我们可以向代理提问了。
# 提问1: 一个不需要调用工具的简单问题
result = agent_executor.invoke({"input": "你好,今天过得怎么样?"})
print(result["output"])# 提问2: 一个需要调用自定义工具的问题
result = agent_executor.invoke({"input": "37乘以42等于多少?"})
print(result["output"])# 提问3: 一个需要调用搜索工具的问题
result = agent_executor.invoke({"input": "今天北京的天气怎么样?"})
print(result["output"])
AgentExecutor
的优点:
- 简单易用:几行代码就能快速搭建一个功能强大的代理。
- 开箱即用:提供了预设的提示词和执行逻辑。
AgentExecutor
的缺点:
- 灵活性差:内部循环和状态管理是固定的,很难进行深度定制。
- 调试困难:当逻辑复杂时,追踪每一步的状态变化比较麻烦。
- 状态管理弱:虽然可以通过
RunnableWithMessageHistory
添加记忆,但本质上是附加的,不够原生。
第三部分:迁移到 LangGraph
- 构建更强大的代理
为了解决 AgentExecutor
的局限性,LangChain 推出了 LangGraph
。它不是一个简单的代理,而是一个基于图(Graph)的状态机,可以构建任意复杂的、多步骤的、有状态的工作流。
我们将学习如何将上面的 AgentExecutor
迁移到 LangGraph
。
步骤 1: 安装 LangGraph
# !pip install -U langgraph
步骤 2: 使用 create_react_agent
创建 LangGraph 代理
LangGraph
提供了 create_react_agent
这个预构建函数,可以让我们以几乎相同的代码量,获得一个功能更强大的代理。
from langgraph.prebuilt import create_react_agent# 创建 LangGraph 代理
# 注意:这里不需要传入 prompt,它有默认的 ReAct 提示词。
# 工具和模型的传入方式与之前相同。
graph_agent_executor = create_react_agent(llm, tools)# 运行代理
# 输入格式不同:需要传入一个包含 "messages" 键的字典,值是一个消息列表。
from langchain_core.messages import HumanMessagemessages = graph_agent_executor.invoke({"messages": [HumanMessage(content="37乘以42等于多少?")]
})# 输出是整个对话历史,最后一个消息是 AI 的最终回复
final_answer = messages["messages"][-1].content
print(final_answer)
步骤 3: LangGraph
的核心优势演示
优势 1: 原生、强大的状态管理
LangGraph
的核心是一个“状态图”。每次调用 .invoke()
,它都会返回并更新整个对话历史。这使得多轮对话和上下文记忆变得极其自然。
# 第一轮对话
messages = graph_agent_executor.invoke({"messages": [HumanMessage(content="你好,我叫小明。")]
})# 第二轮对话,直接将上一轮的所有消息作为历史传入
messages = graph_agent_executor.invoke({"messages": messages["messages"] + [HumanMessage(content="我刚才告诉你我叫什么名字?")]
})final_answer = messages["messages"][-1].content
print(final_answer) # 输出应该包含 "小明"
优势 2: 高度灵活的提示词控制
在 AgentExecutor
中,我们通过修改 prompt
来控制 LLM 的行为。在 LangGraph
中,控制方式更加灵活。
from langchain_core.messages import SystemMessage# 方式一:传入一个字符串作为系统消息
system_message_str = "你是一个乐于助人的助手,请用中文回答所有问题。"
graph_agent_executor_v2 = create_react_agent(llm, tools, prompt=system_message_str)# 方式二:传入一个 SystemMessage 对象
system_message_obj = SystemMessage(content="你是一个乐于助人的助手,请用中文回答所有问题。")
graph_agent_executor_v3 = create_react_agent(llm, tools, prompt=system_message_obj)# 方式三:传入一个可调用对象 (Callable) 或 Runnable,进行更复杂的预处理
def custom_prompt_modifier(state):# state 是整个图的状态,我们可以对其进行任意修改# 例如,在开头加系统消息,在结尾加一个固定的用户消息modified_messages = [SystemMessage(content="你是一个乐于助人的助手,请用中文回答。"),*state["messages"], # 解包原有的消息列表HumanMessage(content="请在回答的最后加上 '谢谢!'")]return {"messages": modified_messages}graph_agent_executor_v4 = create_react_agent(llm, tools, prompt=custom_prompt_modifier)# 测试
messages = graph_agent_executor_v4.invoke({"messages": [HumanMessage(content="1+1等于几?")]
})
print(messages["messages"][-1].content) # 输出结尾应该有“谢谢!”
优势 3: 无限的可扩展性
create_react_agent
只是一个起点。LangGraph
允许你从零开始构建自己的图,定义节点(Nodes)、边(Edges)和状态(State),从而实现像“带审批流程的代理”、“多代理协作”等 AgentExecutor
无法实现的复杂逻辑。
总结与建议
- 初学者/快速原型:如果你是刚接触 LangChain,或者需要快速搭建一个简单的代理原型,
AgentExecutor
是非常好的起点。它简单、直接。 - 生产环境/复杂应用:一旦你的项目需要更复杂的逻辑、更好的可调试性、更强大的状态管理或多代理协作,强烈建议迁移到
LangGraph
。它代表了 LangChain 未来的方向,提供了无与伦比的灵活性和控制力。
记住,AgentExecutor
是“轮子”,而 LangGraph
是“造轮子的工厂”。学会 LangGraph
,你将能构建出任何你想象得到的智能体工作流。
今天的课就到这里,大家课后可以动手实践一下这两种方式,感受它们的区别。下节课我们将深入 LangGraph
的底层,学习如何从零开始构建一个自定义的图。