从零搭建你的第一个 AI Agent:LangGraph 完全上手指南
本文适合:有 Python 基础、想动手搭 Agent 但不知从哪开始的开发者。读完你将掌握 LangGraph 的核心模型,并能独立跑通一个带工具调用的 Claude Agent。
为什么是 LangGraph?
现在 Agent 框架一抓一大把,为什么选 LangGraph?
我踩过几个坑之后总结了一句话:其他框架帮你把 Agent 封装好了,LangGraph 帮你把 Agent 的骨架搭出来。
封装好的框架上手快,但一旦要做复杂逻辑------多步推理、条件分支、循环调用、状态回滚------你就开始跟框架的"魔法"较劲。LangGraph 没有这个问题,因为它把控制权彻底交给你:Agent 的每一步做什么、什么条件跳哪里、状态怎么传递,全是你自己定义的图结构。
核心优势总结:
- 图结构编排:循环、分支、并行,复杂流程用图来描述天然清晰
- 状态持久化:内置 checkpoint,多轮对话、断点续跑开箱即用
- 流式输出:原生支持 streaming,做实时响应的产品不用另外折腾
- 生产就绪:LangSmith 一键接入,调试和监控都有
- 与 LangChain 生态无缝集成:几百个现成工具直接用
核心概念:三句话讲清楚
LangGraph 把 Agent 的运行过程建模成一个有向图,只有三个核心概念:
State(状态):一个贯穿整个流程的数据结构,所有节点都从这里读数据、往这里写数据。你可以把它理解为 Agent 的"工作内存"。
Node(节点):每个节点是一个 Python 函数,接收当前 state,处理后返回更新后的 state。节点可以是 LLM 调用、工具执行、数据处理------任何逻辑都行。
Edge(边):决定一个节点执行完之后去哪个节点。边可以是固定的(永远走这条路),也可以是条件判断的(根据 state 的内容决定走哪条路)。
css
用户输入 → [Node: 分类] → [条件边] → [Node: 工具调用] → [Node: 生成回答] → 输出
↘ [Node: 直接回答] ↗
就这三个东西,组合起来可以描述任意复杂度的 Agent 流程。
环境准备
bash
pip install langgraph langchain-anthropic
设置 API Key(以 Claude 为例):
bash
export ANTHROPIC_API_KEY="sk-ant-api03-你的key"
验证安装:
python
import langgraph
print(langgraph.__version__) # 0.2.x
第一步:跑通最简单的图
先不管 LLM,写一个纯 Python 的图,理解数据怎么流动:
python
from typing import TypedDict
from langgraph.graph import StateGraph, END
# 定义 State:这个图里流转的所有数据
class State(TypedDict):
input: str
result: str
# 节点函数:接收 state,返回要更新的字段
def process(state: State) -> dict:
text = state["input"].upper()
return {"result": f"处理完成:{text}"}
# 构建图
builder = StateGraph(State)
builder.add_node("process", process) # 注册节点
builder.set_entry_point("process") # 设置入口
builder.add_edge("process", END) # process 完成后结束
graph = builder.compile()
# 运行
output = graph.invoke({"input": "hello langgraph"})
print(output)
# {'input': 'hello langgraph', 'result': '处理完成:HELLO LANGGRAPH'}
注意几个细节:
- 节点函数只需要返回要更新的字段,不需要返回完整 state,LangGraph 会自动合并
END是内置的终止标记graph.invoke()是同步运行,graph.stream()是流式运行
第二步:接入 Claude,做一个对话 Agent
python
from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, END
from langchain_anthropic import ChatAnthropic
from langchain_core.messages import HumanMessage, AIMessage
import operator
# messages 字段用 operator.add 做合并策略
# 这样每次更新是追加消息,而不是覆盖
class State(TypedDict):
messages: Annotated[list, operator.add]
model = ChatAnthropic(model="claude-sonnet-4-20250514")
def call_model(state: State) -> dict:
response = model.invoke(state["messages"])
return {"messages": [response]}
builder = StateGraph(State)
builder.add_node("agent", call_model)
builder.set_entry_point("agent")
builder.add_edge("agent", END)
graph = builder.compile()
result = graph.invoke({
"messages": [HumanMessage(content="用一句话介绍 LangGraph")]
})
print(result["messages"][-1].content)
这里有个重要设计:Annotated[list, operator.add] 告诉 LangGraph,当多个节点都更新 messages 字段时,用 operator.add(列表追加)而不是直接覆盖。这是 LangGraph 的 reducer 机制,让状态合并的行为完全可控。
第三步:加工具,实现 ReAct Agent
这是最核心的部分。ReAct 模式让 Agent 在"推理"和"行动"之间循环:
- Claude 看到问题,决定要不要调用工具
- 如果要调用,执行工具,把结果返回给 Claude
- Claude 继续推理,直到得出最终答案
python
from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, END
from langgraph.prebuilt import ToolNode
from langchain_anthropic import ChatAnthropic
from langchain_core.messages import HumanMessage
from langchain_core.tools import tool
import operator
# --- 定义工具 ---
@tool
def search_weather(city: str) -> str:
"""查询指定城市的实时天气"""
# 实际项目里接真实天气 API
weather_data = {
"北京": "晴天,26°C,东南风 3 级",
"上海": "多云,22°C,东风 2 级",
"广州": "小雨,28°C,南风 1 级",
}
return weather_data.get(city, f"暂无 {city} 的天气数据")
@tool
def calculate(expression: str) -> str:
"""
计算数学表达式。
支持加减乘除和基本函数,例如:'(100 + 200) * 0.8'
"""
try:
result = eval(expression, {"__builtins__": {}}, {})
return f"计算结果:{result}"
except Exception as e:
return f"计算失败:{e}"
tools = [search_weather, calculate]
# --- 搭建 Agent ---
class State(TypedDict):
messages: Annotated[list, operator.add]
# bind_tools 让 Claude 知道有哪些工具可以调用
model = ChatAnthropic(model="claude-sonnet-4-20250514").bind_tools(tools)
def agent_node(state: State) -> dict:
"""核心节点:Claude 推理,决定下一步"""
response = model.invoke(state["messages"])
return {"messages": [response]}
def should_continue(state: State) -> str:
"""条件边:判断 Claude 是否发起了工具调用"""
last_msg = state["messages"][-1]
if hasattr(last_msg, "tool_calls") and last_msg.tool_calls:
return "tools"
return END
# --- 构建图 ---
builder = StateGraph(State)
builder.add_node("agent", agent_node)
builder.add_node("tools", ToolNode(tools)) # 内置工具执行节点
builder.set_entry_point("agent")
# 条件边:agent 执行完后,根据 should_continue 的返回值决定走向
builder.add_conditional_edges("agent", should_continue)
# 工具执行完毕,回到 agent 继续推理(这里形成了循环)
builder.add_edge("tools", "agent")
graph = builder.compile()
# --- 运行 ---
result = graph.invoke({
"messages": [
HumanMessage(content="帮我查一下北京和上海的天气,然后告诉我 (26 + 22) / 2 是多少")
]
})
# 打印完整对话链路
for msg in result["messages"]:
role = msg.__class__.__name__
content = str(msg.content)[:150]
print(f"\n[{role}]\n{content}")
运行后你会看到完整的推理链路:
scss
[HumanMessage]
帮我查一下北京和上海的天气,然后告诉我 (26 + 22) / 2 是多少
[AIMessage]
(Claude 决定调用工具)
[ToolMessage]
晴天,26°C,东南风 3 级
[ToolMessage]
多云,22°C,东风 2 级
[ToolMessage]
计算结果:24.0
[AIMessage]
北京今天晴天,气温 26°C;上海多云,22°C。两地平均气温是 24°C。
这个循环结构是 ReAct Agent 的精髓,可以支持任意轮次的工具调用。
第四步:加记忆,支持多轮对话
Agent 本身是无状态的,每次 invoke 都是全新对话。加上 checkpoint 之后,同一个 thread_id 的历史消息会自动保留:
python
from langgraph.checkpoint.memory import MemorySaver
# 内存版 checkpointer(进程退出后消失)
memory = MemorySaver()
graph = builder.compile(checkpointer=memory)
# 用 thread_id 区分不同用户 / 会话
config = {"configurable": {"thread_id": "user_001"}}
# 第一轮
graph.invoke(
{"messages": [HumanMessage(content="我是小明,我在北京")]},
config=config
)
# 第二轮:自动携带上文
result = graph.invoke(
{"messages": [HumanMessage(content="帮我查一下我所在城市的天气")]},
config=config
)
print(result["messages"][-1].content)
# Claude 会自动关联上文,查询北京的天气
生产环境建议用 SQLite 持久化,重启服务后对话历史不丢失:
python
from langgraph.checkpoint.sqlite import SqliteSaver
memory = SqliteSaver.from_conn_string("./checkpoints.db")
graph = builder.compile(checkpointer=memory)
第五步:流式输出
做产品时几乎必用,让用户实时看到 Agent 的思考过程:
python
# stream 返回每个节点执行后的状态快照
for chunk in graph.stream(
{"messages": [HumanMessage(content="北京天气怎么样?")]},
config={"configurable": {"thread_id": "stream_demo"}}
):
for node_name, output in chunk.items():
print(f"\n>>> 节点 [{node_name}] 输出:")
if "messages" in output:
last = output["messages"][-1]
print(last.content[:200] if last.content else "(工具调用中...)")
如果要做字符级流式(像 ChatGPT 那种逐字显示),用 astream_events:
python
async for event in graph.astream_events(
{"messages": [HumanMessage(content="介绍一下 LangGraph")]},
config=config,
version="v2"
):
if event["event"] == "on_chat_model_stream":
chunk = event["data"]["chunk"]
if chunk.content:
print(chunk.content, end="", flush=True)
完整项目结构
当 Agent 逻辑复杂起来,建议这样组织代码:
bash
my_agent/
├── main.py # 入口:运行 / 启动服务
├── graph.py # 图的定义和编译
├── state.py # State 数据结构定义
├── nodes/
│ ├── agent.py # LLM 推理节点
│ └── router.py # 条件路由逻辑
└── tools/
├── weather.py # 天气工具
├── search.py # 搜索工具
└── calculator.py # 计算工具
graph.py 示例:
python
from langgraph.graph import StateGraph, END
from langgraph.prebuilt import ToolNode
from langgraph.checkpoint.sqlite import SqliteSaver
from state import State
from nodes.agent import agent_node, should_continue
from tools import all_tools
def build_graph():
builder = StateGraph(State)
builder.add_node("agent", agent_node)
builder.add_node("tools", ToolNode(all_tools))
builder.set_entry_point("agent")
builder.add_conditional_edges("agent", should_continue)
builder.add_edge("tools", "agent")
memory = SqliteSaver.from_conn_string("./data/checkpoints.db")
return builder.compile(checkpointer=memory)
graph = build_graph()
常见问题
Q:节点函数返回完整 state 还是只返回更新的字段?
只需要返回要更新的字段。LangGraph 会把返回值和当前 state 合并。如果某个字段定义了 reducer(比如 operator.add),会按 reducer 规则合并;否则直接覆盖。
Q:条件边的函数返回值有什么要求?
返回字符串,对应 add_conditional_edges 第三个参数字典的 key。也可以直接返回节点名称字符串,省略映射字典。
Q:怎么中断 Agent,等待人工确认再继续?
python
graph = builder.compile(
checkpointer=memory,
interrupt_before=["sensitive_node"] # 执行这个节点前暂停
)
暂停后用 graph.invoke(None, config=config) 继续执行。
Q:LangGraph 和 LangChain Agents 有什么区别?
LangChain 的 AgentExecutor 是封装好的黑盒,适合简单场景;LangGraph 是底层图引擎,适合需要精细控制的复杂 Agent。两者不是替代关系,LangGraph 现在是 LangChain 官方推荐的 Agent 构建方式。
总结
LangGraph 的学习路径很清晰:
- 理解 State / Node / Edge 三个概念
- 跑通线性图,搞清楚数据怎么流动
- 接入 LLM,实现基础问答
- 加工具 + 条件循环,实现 ReAct Agent
- 加 checkpoint,支持多轮对话
- 按需扩展:并行节点、子图、人工审核节点
整个框架思路非常直接,没有太多魔法,读懂了源码你会发现它做的事情就是"按照你定义的图,一步步执行节点,传递状态"。
代码都是可以直接运行的,换上你自己的 API Key 就能跑。有问题欢迎评论区交流。
参考资料