From the first graph to production deployment, Module 1 builds the complete basic cognitive system of LangGraph.
The minimum operating unit of LangGraph: a directed graph composed of State, Node, and Edge. This is the basic skeleton for understanding all subsequent courses.
Connect the real LLM to the picture and introduce the message dialogue system. This is a critical step from "demo diagram" to "real AI application".
messages: Annotated[list, add_messages], out-of-the-box conversation stateMessagesStateis the standard starting point for LangGraph conversational Agent, which comes pre-installed withadd_messagesreducer, which correctly accumulates conversation history without manual configuration.Let the graph automatically decide whether to "call the tool" or "end directly" based on the output of LLM, which is the core routing mechanism for building tools to call Agents.
ToolNode + tools_conditionIt is the "standard suite" of LangGraph tool calls - the combination of the two can complete tool routing with 4 lines of code, without the need to manually parse the tool call results.Add a back edge to Router to form a real Agent loop: LLM reasoning → call tools → observe results → reason again until the problem is solved.
tools → agentThe back edge makes the graph form a loop, supporting unlimited tool call iterations.tools_conditionRoute to ENDAdd "memory" to the Agent so that it can remember the previous content in multiple rounds of dialogue and maintain the state between different steps of the same run.
MemorySaverOnly the entry-level version - it stores the state in memory and is lost when the process restarts. Module 2 will learn to upgrade it toSqliteSaver(Persistent to disk), this is the memory solution available for production.Deploy locally developed graphs as production services. LangGraph Studio provides visual debugging, and LangGraph Cloud provides one-click API deployment.
langgraph devStart the local service,langgraph buildPackaging Docker imagesCore APIs, usage scenarios and key instructions of 6 courses
| Concept/API | Courses | core role | Key usage |
|---|---|---|---|
| StateGraph | L1 | Builder for diagrams, accepting the Schema class | StateGraph(MyState) |
| add_node / add_edge | L1 | Add nodes and edges to the graph | builder.add_node("n", fn) |
| compile / invoke | L1 | Compilation graph/execution graph | graph.invoke({"key": val}) |
| MessagesState | L2 | Built-in conversation status, including add_messages | class S(MessagesState): ... |
| bind_tools | L2 | Inject tool list into LLM | llm.bind_tools([tool1, tool2]) |
| ToolNode | L3 | Tools to automate LLM requests | ToolNode([tool1, tool2]) |
| tools_condition | L3 | Detect whether there are tool calls and routing decisions | add_conditional_edges("agent", tools_condition) |
| ReAct back edge | L4 | The cyclic edge of tools→agent implements iterative reasoning | add_edge("tools", "agent") |
| MemorySaver | L5 | Memory-level checkpointer, saves State snapshot | compile(checkpointer=MemorySaver()) |
| thread_id | L5 | Identify conversation sessions, share history with the same ID | {"configurable": {"thread_id": "x"}} |
| langgraph.json | L6 | Deployment configuration, declaration graph entry and dependencies | {"graphs": {"agent": "./graph.py:graph"}} |
| langgraph dev | L6 | Start the Studio visual local debugging service | CLI commands |
LangGraph uses Node + Edge to describe the AI execution process, which is more intuitive than if/else and more flexible than LangChain Chain - the graph naturally supports branches, loops, and parallelism.
MessagesState + add_messagesreducer is the starting point for all conversational agents. Understanding how it appends messages is a prerequisite for writing a correct Chatbot.
ToolNode + tools_conditionAutomatically handles parsing and execution of tool calls, eliminating the need for manual extractiontool_callsfields, significantly reducing boilerplate code.
Agent = Router + add_edge("tools", "agent"). This back edge makes the graph form a loop, allowing LLM to continue reasoning until the autonomous judgment task is completed.
every timeinvokePass in the samethread_id, the graph will continue from where it ended last time. Different users use different IDs, and their status is completely isolated.
LangGraph Studio can display graph structure, each step state, and tool call details in real time. It is more efficient than any print statement when developing complex Agents.
A travel planning assistant capable of multi-step reasoning, automatic tool calling, and cross-wheel memory of user preferences. It connects all 6 core knowledge points of Module 1 into a complete runnable project.
# ── 依赖导入 ───────────────────────────────────────────
from typing import Annotated
from typing_extensions import TypedDict
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.prebuilt import ToolNode, tools_condition
from langgraph.checkpoint.memory import MemorySaver
# ════════════════════════════════════════════════════
# 【L3】工具定义:@tool 装饰器自动生成 JSON Schema
# LLM 通过 Schema 了解每个工具的参数和用途
# ToolNode 根据 LLM 输出的 tool_call 自动匹配并执行
# ════════════════════════════════════════════════════
@tool
def get_weather(city: str) -> str:
"""获取指定城市的当前天气信息。"""
# 实际项目中调用真实天气 API,此处模拟返回
weather_data = {
"北京": "晴,26°C,微风",
"上海": "多云,22°C,东南风3级",
"成都": "阴,18°C,无风",
}
return weather_data.get(city, f"{city}:天气数据暂不可用")
@tool
def search_attractions(city: str, category: str = "all") -> str:
"""搜索城市的热门景点。category 可为 'nature'(自然)、'culture'(文化)或 'all'(全部)。"""
attractions = {
"北京": ["故宫", "长城", "颐和园", "天坛"],
"上海": ["外滩", "豫园", "东方明珠", "田子坊"],
"成都": ["大熊猫基地", "宽窄巷子", "武侯祠", "锦里"],
}
spots = attractions.get(city, [])
return f"{city}热门景点:{', '.join(spots)}" if spots else f"暂无{city}景点数据"
@tool
def estimate_budget(city: str, days: int, style: str = "standard") -> str:
"""估算旅行预算。style 可为 'budget'(经济)、'standard'(标准)、'luxury'(豪华)。"""
per_day = {"budget": 300, "standard": 600, "luxury": 1500}
daily_cost = per_day.get(style, 600)
total = daily_cost * days
return f"{city} {days}天{style}旅行预计费用:¥{total}(每天约¥{daily_cost})"
# 工具列表:注册所有可用工具
tools = [get_weather, search_attractions, estimate_budget]
@toolDid two things: ① Read the type annotation and docstring of the function, and automatically generate a JSON Schema that LLM can understand; ② Pack the ordinary function intoBaseToolobject, forToolNodeandbind_toolsuse.The docstring of the function is the tool description, the clearer it is written, the more accurately LLM can judge when to call the tool.
# ════════════════════════════════════════════════════
# 【L1 + L2】State 定义
# · 继承 MessagesState(L2):内置 messages 字段 +
# add_messages Reducer,开箱即用
# · 扩展自定义字段(L1):TypedDict 风格添加额外状态
# ════════════════════════════════════════════════════
class TravelState(MessagesState):
# MessagesState 已内置:messages: Annotated[list[AnyMessage], add_messages]
# 下面是扩展字段
destination: str # 用户确定的目的地(默认覆盖策略)
travel_days: int # 旅行天数
# ════════════════════════════════════════════════════
# 【L2】LLM 初始化 + bind_tools
# · bind_tools 将工具的 JSON Schema 注入 LLM 的系统提示
# · LLM 输出 tool_call 时,内容是 JSON 格式的参数
# ════════════════════════════════════════════════════
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
llm_with_tools = llm.bind_tools(tools) # 【L2】核心:让 LLM 知道有哪些工具
# ════════════════════════════════════════════════════
# 【L2 + L4】Agent 节点:ReAct 循环的推理端
# · 读取完整消息历史,带入工具上下文,调用 LLM
# · 返回 {"messages": [新的 AIMessage]}
# · add_messages Reducer 自动追加,不覆盖历史
# ════════════════════════════════════════════════════
def agent(state: TravelState):
system_prompt = SystemMessage(content=(
"你是一个专业的旅行规划助手。你可以查询天气、搜索景点、估算预算。"
"请根据用户需求,综合使用工具制定完整的旅行建议。"
))
# state["messages"] 包含完整的对话历史(由 add_messages Reducer 维护)
response = llm_with_tools.invoke([system_prompt] + state["messages"])
return {"messages": [response]} # 返回字典,Reducer 自动追加
inheritMessagesStatethan defined from zeroTypedDictomittedAnnotated[list, add_messages]configuration. At the same time, it supports free expansion fields——destinationandtravel_daysWith the default overwrite policy, every write from a node replaces the old value.
llm.bind_tools(tools)Append the tool's JSON Schema to the API call on every requesttoolsin the parameters. After LLM sees the tool list, it can output a specialAIMessage,Thattool_callsThe fields contain the tool name and parameters - this is the triggerToolNodeexecution signal.
# ════════════════════════════════════════════════════
# 【L1】StateGraph:图的构建器
# 【L3】ToolNode:自动执行工具调用的内置节点
# ════════════════════════════════════════════════════
tool_node = ToolNode(tools) # 【L3】传入工具列表,自动匹配 tool_call 并执行
builder = StateGraph(TravelState) # 【L1】创建图,传入 State Schema
# ── 注册节点 ────────────────────────────────────────
builder.add_node("agent", agent) # 【L1】LLM 推理节点
builder.add_node("tools", tool_node) # 【L3】工具执行节点
# ── 连接边 ──────────────────────────────────────────
builder.add_edge(START, "agent") # 【L1】入口 → agent
# 【L3】条件边:LLM 有工具调用 → tools,否则 → END
builder.add_conditional_edges(
"agent",
tools_condition, # 内置路由函数,检测 tool_calls 是否存在
)
# 【L4】关键!这条回边让图形成环,实现 ReAct 循环
# tools 执行完工具后,把结果(ToolMessage)追加进 messages,
# 然后重新路由到 agent,让 LLM 看到工具结果继续推理
builder.add_edge("tools", "agent")
# ── 【L5】挂载 MemorySaver,实现跨轮对话记忆 ──────
memory = MemorySaver()
graph = builder.compile(checkpointer=memory) # 【L5】编译时注入 Checkpointer
Noadd_edge("tools", "agent"), the graph ends after calling the tool once. After adding this return edge:Tool results (ToolMessage) are appended to messages → the agent node re-reads the complete message history (including tool results) → LLM infers again based on the new information → may continue to call the tool or answer directly. This loop continues until LLM no longer outputs tool calls,tools_conditionAt this point routing to END.
# ════════════════════════════════════════════════════
# 【L5】thread_id:会话标识
# · 相同 thread_id → 共享同一对话历史
# · 不同 thread_id → 完全独立,互不干扰
# ════════════════════════════════════════════════════
config = {"configurable": {"thread_id": "user_alice_trip_001"}}
# ── 第 1 轮:询问北京旅行建议 ─────────────────────
result_1 = graph.invoke(
{"messages": [HumanMessage(content="我想去北京旅行3天,帮我规划一下")]},
config=config,
)
# Agent 会自动调用:get_weather("北京") → search_attractions("北京") →
# estimate_budget("北京", 3, "standard"),然后综合给出回答
# ── 第 2 轮:追问(自动记得第 1 轮内容)────────────
result_2 = graph.invoke(
{"messages": [HumanMessage(content="如果改成豪华出行,费用大概是多少?")]},
config=config, # 同一 thread_id → 记得"北京3天"的上下文
)
# LLM 知道上下文是北京3天,直接调用 estimate_budget("北京", 3, "luxury")
# ── 多用户隔离示例 ──────────────────────────────────
alice_config = {"configurable": {"thread_id": "alice_001"}}
bob_config = {"configurable": {"thread_id": "bob_001"}}
# alice 和 bob 的对话历史完全隔离,互不影响
# ── 查看完整消息历史 ────────────────────────────────
for msg in result_2["messages"]:
role = msg.__class__.__name__.replace("Message", "")
print(f"[{role}] {msg.content[:80]}")
Each time a node is executed, LangGraph serializes the current State and uses(thread_id, checkpoint_id)Store key in MemorySaver (memory dictionary). next timeinvokePass in the samethread_idWhen , LangGraph first loads the latest snapshot as the initial State, and then executes the new node.This is why round 2 can "remember" what round 1 was about.
messagesField consists ofadd_messagesreducer maintenance, it will convert each round of HumanMessage, AIMessage, ToolMessageAppend allEnter the history list. At the beginning of round 2, the agent node obtainedstate["messages"]Contains all messages from Round 1 - LLM sees the complete context and naturally knows that the destination is Beijing.
# ── langgraph.json(项目根目录)──────────────────────
# 【L6】LangGraph Studio / Cloud 的部署配置文件
# · "graphs":声明图的名称 → 对应的 Python 模块路径
# · "dependencies":项目依赖,打包时自动安装
# · "env":环境变量文件路径
# langgraph.json
{
"graphs": {
"travel_agent": "./travel_agent.py:graph"
// ↑ 文件路径 ↑ 变量名
},
"dependencies": ["."],
"env": ".env"
}
# ── 本地启动 LangGraph Studio ────────────────────────
# 【L6】在项目根目录运行,会自动打开 Studio 界面
# $ langgraph dev
# → 启动本地 API 服务(默认 http://127.0.0.1:2024)
# → 自动打开 Studio 可视化界面
# → 可在 Studio 里直接输入消息测试图的运行
# ── Studio 的调试能力 ────────────────────────────────
# 【L6】每次执行可以看到:
# · 图结构可视化(节点 + 边,含 ReAct 循环)
# · 每个节点执行前后的 State 快照对比
# · 工具调用的输入参数和返回结果
# · 每条消息的完整内容(AIMessage 含 tool_calls)
# · 可以在任意步骤"时间旅行"(查看历史快照)
# ── 云端部署(可选)─────────────────────────────────
# $ langgraph build -t my-travel-agent # 构建 Docker 镜像
# $ langgraph up # 启动生产服务
langgraph.jsonIt is the only entrance for Studio and Cloud to identify projects."graphs"The key declares the mapping relationship "graph name → variable in Python file" - as long astravel_agent.pyThere is a man namedgraphcompiled graph object, Studio can find and visualize it.langgraph devThe command starts the FastAPI service in the background and exposes the standard LangGraph API through which Studio interacts with graphs.
Exact mapping of project parts to 6 sub-courses
| Project composition | Corresponding courses | Core API | Problem solved |
|---|---|---|---|
| TravelState(MessagesState) | L1 + L2 | class S(MessagesState) | Define data contracts + built-in conversation message management |
| @tool tool definition | L2 + L3 | @toolDecorator | Automatically generate JSON Schema for LLM to understand and call |
| llm.bind_tools(tools) | L2 | bind_tools([...]) | Inject tool information into LLM so it knows what to tune |
| agent node function | L2 + L4 | llm_with_tools.invoke(messages) | Read the complete message history and LLM inference determines the next step |
| ToolNode(tools) | L3 | ToolNode([...]) | Automatically parse tool_calls, execute the corresponding tool, and return the result |
| tools_condition conditional edge | L3 | add_conditional_edges("agent", tools_condition) | Detect whether there is a tool call and route to tools or END |
| add_edge("tools", "agent") | L4 | add_edge("tools", "agent") | Forming a ReAct loop - tool results are fed back into LLM inference |
| MemorySaver checkpointer | L5 | compile(checkpointer=MemorySaver()) | State snapshot of each step, supporting multi-step dialogue memory across rounds |
| thread_id configuration | L5 | {"configurable": {"thread_id": "x"}} | Identification session, same ID recovery history, different ID isolation |
| langgraph.json configuration | L6 | {"graphs": {"name": "./file.py:var"}} | Declare the deployment entry, Studio/Cloud identifies the project |