LangGraph (一):从核心概念到亲手构建第一个 ReAct Agent
写在前面【AI大模型教程】
在构建基于大型语言模型(LLM)的应用时,我们很快会发现简单的“一问一答”模式远远不够。真正的智能应用需要具备规划、执行、反思的能力,能够与外部世界交互,并根据情况做出决策。这正是 LangGraph 的用武之地。
与 LangChain 中封装好的 AgentExecutor 不同,LangGraph 提供了一套更底层、更灵活的工具,让我们能够像绘制流程图一样,精确地设计和控制 Agent 的每一步行为。它将 Agent 难以捉摸的“黑盒”思考过程,转变成了对开发者完全透明的“白盒”工作流。
本篇教程将作为一个完整的、自包含的指南,带你走完从 LangGraph 零基础到构建第一个实用 AI Agent 的全过程。读完本文,将掌握构建复杂、有状态、可控的 AI 应用的核心技能。
第一部分:核心概念与基础工作流
在深入构建 Agent 之前,我们必须先掌握 LangGraph 的基本构成和如何创建一个简单的工作流。
1.1 核心概念:State, Node, Edge
构建一个 LangGraph 应用,就像绘制一张流程图,离不开三个基本元素:
•State (状态): 一个在图的整个执行过程中持续存在并被传递的数据对象。可以把它看作是整个工作流的“共享内存”,所有节点都通过它来交换信息。•Node (节点): 一个代表流程图中具体“步骤”的 Python 函数。它接收当前的状态作为输入,执行独立的任务,然后返回一个字典来更新状态。•Edge (边): 负责连接节点,定义了任务从一个节点流向另一个节点的路径。
1.2 基础实践:构建一个线性的两步工作流
我们将从一个包含“起草”和“审阅”两个步骤的报告生成应用开始。这个例子将帮助我们熟悉最核心的 API,而无需立即处理复杂的 Agent 逻辑。
from dotenv import load_dotenv
from langchain_openai importChatOpenAI
from langgraph.graph importStateGraph,END
from typing importTypedDict
import os
# 加载环境变量 (OPENAI_API_KEY 等)
load_dotenv()
# 初始化 LLM
# 建议将模型初始化放在代码的起始部分,以便复用
llm =ChatOpenAI(
model_name=os.environ.get("OPENAI_MODEL","gpt-4o"),
temperature=0.7,
openai_api_base=os.environ.get("OPENAI_BASE_URL"),
openai_api_key=os.environ.get("OPENAI_API_KEY"),
)
# 1. 定义状态
classReportState(TypedDict):
topic: str
draft: str
report: str
# 2. 定义节点函数
def draft_node(state:ReportState)-> dict:
"""起草节点"""
print(">>> 正在执行节点: draft_node")
prompt = f"以技术报告的格式,为主题 '{state['topic']}' 写一份500字左右的草稿。"
draft = llm.invoke(prompt).content
return{"draft": draft}
def review_node(state:ReportState)-> dict:
"""审阅节点"""
print(">>> 正在执行节点: review_node")
prompt = f"你是一位资深编辑。请审阅以下报告草稿,修正语法、改进措辞。草稿:\n\n{state['draft']}"
report = llm.invoke(prompt).content
return{"report": report}
# 3. 组装图
workflow =StateGraph(ReportState)
workflow.add_node("drafter", draft_node)
workflow.add_node("reviewer", review_node)
workflow.set_entry_point("drafter")
workflow.add_edge("drafter","reviewer")
workflow.add_edge("reviewer",END)
# 4. 编译并可视化
linear_app = workflow.compile()
print("线性工作流图结构:")
linear_app.get_graph().print_ascii()
这个简单的线性图让我们掌握了 add_node 和 add_edge 的用法。现在,让我们进入更激动人心的部分。
第二部分:构建真正的 Agent
一个线性的工作流是不够的,真正的 Agent 需要具备决策和与外部交互的能力。
2.1 Agent 的核心思想 (ReAct)
一个智能体通常遵循 ReAct (Reason-Act) 的工作模式,这是一个循环过程:
1.思考 (Reason): 基于当前任务分析并决定下一步策略(是直接回答还是调用工具)。2.行动 (Act): 执行策略,例如调用搜索引擎。3.观察 (Observe): 接收工具返回的结果,再次进入“思考”阶段。
LangGraph 的图结构为实现这一循环提供了完美的框架。
2.2 综合实践:两种方式构建工具 Agent
我们将介绍两种方式来构建 Agent。对于标准场景,强烈推荐使用第一种 create_react_agent 的方式。
方法一 :使用 create_react_agent 简化流程
LangGraph 提供了一个极其方便的高级函数 create_react_agent,它可以将所有繁琐的组装工作封装起来,让你用一行代码就得到一个功能完备的 ReAct Agent。
from langchain_core.messages importHumanMessage
from langchain_tavily importTavilySearch
from langchain_tavily._utilities importTavilySearchAPIWrapper
from langgraph.prebuilt import create_react_agent
# 实例化工具
search_tool =TavilySearch(
max_results=2,
api_wrapper=TavilySearchAPIWrapper(
tavily_api_key=os.environ.get("TAVILY_API_KEY")
)
)
tools =[search_tool]
# 2. 调用工厂函数,一键生成 Agent
# 它会自动处理工具绑定
app = create_react_agent(llm, tools)
# 3. 直接使用
inputs ={"messages":[HumanMessage(content="今天北京的天气怎么样?")]}
result = app.invoke(inputs)
print("\n--- 最终结果 ---")
print(result['messages'][-1].content)
create_react_agent 极大地提升了开发效率,是构建标准 Agent 的首选。
方法二 :手动构建以实现完全控制
如果需要实现高度定制化的逻辑(比如增加一个人工审核节点),理解如何手动构建就至关重要了。
1. 定义 Agent 状态与路由
from typing importAnnotated
from typing_extensions importTypedDict
from langchain_core.messages importBaseMessage
# 定义一个函数,用于将新消息追加到旧消息列表中
def add_messages(left: list[BaseMessage], right: list[BaseMessage])-> list[BaseMessage]:
return left + right
classAgentState(TypedDict):
# 使用 Annotated 指示 messages 字段应通过 add_messages 函数进行更新
messages:Annotated[list[BaseMessage], add_messages]
def router(state:AgentState)-> str:
"""根据 Agent 的最新消息中是否包含工具调用请求,来决定下一步的走向。"""
print("--- 正在执行路由判断 ---")
last_message = state['messages'][-1]
if hasattr(last_message,'tool_calls')and last_message.tool_calls:
print(">>> 路由决策:调用工具")
return"tool_node"
else:
print(">>> 路由决策:直接结束")
return"END"
2. 定义节点并组装图
from langgraph.prebuilt importToolNode
# 将模型与工具绑定,生成一个能调用工具的新 "executable"
agent_model = llm.bind_tools(tools)
def agent_node(state:AgentState):
"""Agent 节点:负责调用模型进行“思考”"""
print("--- 正在执行节点: agent_node (思考) ---")
response = agent_model.invoke(state['messages'])
return{"messages":[response]}
tool_node =ToolNode(tools=[search_tool])
# 手动组装图
graph =StateGraph(AgentState)
graph.add_node("agent_node", agent_node)
graph.add_node("tool_node", tool_node)
graph.set_entry_point("agent_node")
graph.add_conditional_edges(
"agent_node",
router,
{"tool_node":"tool_node","END":END}
)
graph.add_edge("tool_node","agent_node")
agent_app_manual = graph.compile()
# 可视化 Agent 的图结构
print("\n手动构建的 Agent 图结构:")
agent_app_manual.get_graph().print_ascii()
3. 运行与测试
print("\n--- 手动构建 Agent 测试 ---")
# 测试 1: 需要调用工具的问题
inputs_1 ={"messages":[HumanMessage(content="最新的 AI 领域新闻有哪些?")]}
result_1 = agent_app_manual.invoke(inputs_1)
print("\n--- 最终结果 1 ---")
print(result_1['messages'][-1].content)
# 测试 2: 不需要调用工具的问题
inputs_2 ={"messages":[HumanMessage(content="1加1等于多少")]}
result_2 = agent_app_manual.invoke(inputs_2)
print("\n--- 最终结果 2 ---")
print(result_2['messages'][-1].content)
我们会发现,手动构建出的 agent_app_manual 和 create_react_agent 生成的 agent_app_prebuilt 功能上是完全一致的。手动构建给了我们更多的控制权,让我们可以在这个图的任何地方“添砖加瓦”。
总结
在本篇教程中:
•我们从一个简单的线性工作流入手,掌握了 LangGraph 的基础 API。•接着,我们学习了构建一个真正 Agent 所需的核心技术并重点掌握了最高效的构建方式 create_react_agent。•同时,我们也深入了解了手动构建的全过程,这为我们将来实现更复杂的定制化 Agent 打下了坚实的基础。
我们同时掌握了构建 Agent 的“快捷方式”和“根本方法”,足以应对各种不同的开发需求。在后续的教程中,我们将基于此,探索更高级的状态管理、多智能体协作以及应用的持久化等更深入的主题。