LangChain LangGraph
Module 1 Module 2 Module 3 Module 4 Module 5 Module 6
Module 1 · Basic Introduction
1 1-1 Simple Graph
2 1-2 Chain
3 1-3 Router
4 1-4 Agent
5 1-5 Agent Memory
6 1-6 Deployment
SUM Module Summary
HomeLangGraphModule 1 · Basic Introduction1-4 Agent
📓 Notebook 📖 Explained
LANGGRAPH MODULE 1 · LESSON 4

Agent: ReAct architecture and infinite tool loop
act → observe → reason → loop

Router executes the tool once and ends. The agent sends the tool results back to the LLM, allowing the LLM to continue reasoning and calling tools—until the task is actually completed.
A single line of code change creates a universal Agent.

1Limitation of the previous lecture: Router can only step

Router is already powerful - it lets LLM decide whether to call a tool, and then actually execute that tool. But Router has a fundamental limitation:After the tool is executed, go directly to END

Router bottleneck

If the user says: "Add 3 and 4, then multiply the result by 2 and divide it by 5" - the Router will only execute the first tool (the addition) and then be done with it. LLM cannot get the addition result and has no chance to do multiplication and division.

This limit comes from an edge:

# Router 中的这一行:
builder.add_edge("tools", END)  # ← 工具执行完 → 结束,LLM 永远看不到工具结果

What's the solution?replace this line. Send tool results back to LLM, allowing LLM to continue inference. This is Agent.

2What is ReAct: Reasoning + Acting cycle

ReAct is the Agent paradigm proposed by Google in 2022. The name comes from the combination of two words:Reasoning (reasoning) +Acting (action).

It describes how an LLM Agent should work:

The nature of the ReAct loop

🧠 Reason LLM analysis problem
decide next step
⚙️ Act Call tool
perform operations
👁️ Observe Get tool results
Append to messages
Loop until LLM determines that the task is completed and no more tools are requested.

The key insights from this cycle are:Tool results themselves are information. LLM sees what the tool returns so it can decide what to do next. Append tool results tomessagesList, equivalent to "let LLM observe the execution results".

ReAct in LangGraph

In LangGraph,messagesThe list is the Agent's "working memory". See the complete conversation history for every LLM call - including all tool calls and tool results. This is how ReAct is implemented at the code level:State carries the complete observation history and drives the next round of reasoning.

3That line of code that changed everything

From Router to Agent, the code changes are minimal. Find the difference in this line and you understand the essence of Agent:

Router (previous lecture)

# tools 执行完 → 结束
builder.add_edge(
  "tools",
  END  ← 这里
)

The tool result is written to messages, but LLM never sees it - the graph is over.

Agent (this lecture)

# tools 执行完 → 回到 assistant
builder.add_edge(
  "tools",
  "assistant"  ← 这里!
)

After the tool results are appended to messages, LLM is called again. It sees the result and can continue reasoning.

core points

builder.add_edge("tools", "assistant")It is the core of the entire Agent. This line turns a linear process into a loop, and turns a single-step tool call into a multi-step reasoning.From Router to Agent, only the target node is changed: from END to "assistant".

4Complete Agent diagram: full code analysis

from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.prebuilt import tools_condition, ToolNode

# ─── 1. 定义工具 ───────────────────────────────────────
def add(a: int, b: int) -> int:
    """Adds a and b."""
    return a + b

def multiply(a: int, b: int) -> int:
    """Multiplies a and b."""
    return a * b

def divide(a: int, b: int) -> float:
    """Divides a by b."""
    return a / b

tools = [add, multiply, divide]

# ─── 2. LLM 绑定工具 ───────────────────────────────────
llm = ChatOpenAI(model="gpt-4o")
llm_with_tools = llm.bind_tools(tools)

# ─── 3. 系统提示(在每次 LLM 调用时注入)─────────────
sys_msg = SystemMessage(content="You are a helpful assistant tasked with performing arithmetic.")

# ─── 4. assistant 节点:把 sys_msg 前置注入 ────────────
def assistant(state: MessagesState):
    return {"messages": [llm_with_tools.invoke([sys_msg] + state["messages"])]}

# ─── 5. 构建图 ────────────────────────────────────────
builder = StateGraph(MessagesState)

builder.add_node("assistant", assistant)         # LLM 节点
builder.add_node("tools", ToolNode(tools))       # 工具执行节点

builder.add_edge(START, "assistant")             # 入口
builder.add_conditional_edges("assistant", tools_condition)  # 条件分支
builder.add_edge("tools", "assistant")           # ← 关键!循环回去

react_graph = builder.compile()

# ─── 6. 运行 ─────────────────────────────────────────
messages = [HumanMessage(content="Add 3 and 4. Multiply the output by 2. Divide the output by 5")]
result = react_graph.invoke({"messages": messages})

Code key points analysis

5Agent Loop Illustration: The Secret of the Two-Way Arrow

The complete structure of the Agent graph (compared to Router)

Router (previous lecture)
__start__
tool_calling_llm
tools_condition
There is a tool call
tools
END
No tool call
END
Agent (this lecture)
__start__
assistant
tools_condition
There is a tool call
tools
No tool call
END
Cycle back!
The only difference: the exit of the tools node, fromENDbecame"assistant"
Why is it called "Cycle Diagram"?

Ordinary DAGs (Directed Acyclic Graphs) do not allow cycles. One of the special capabilities of LangGraph isSupport edge loopstools → assistantThis edge changes the graph from linear to a cyclic structure that can be executed repeatedly. This is the core feature that distinguishes LangGraph from ordinary Pipeline frameworks.

6Execution step tracking: (3+4)×2÷5 complete process

User input: "Add 3 and 4. Multiply the output by 2. Divide the output by 5"

This question requires three steps of calculation: 3+4=7, 7×2=14, 14÷5=2.8. Let's track every step of the Agent, andmessagesThe evolution of lists.

step execution node what happened messages new content
initial User input incoming graph HumanMessage("Add 3 and 4...")
Round 1 A assistant LLM analyzes the task and decides to call add(3, 4) first AIMessage(tool_calls=[add(a=3,b=4)])
T tools Execute add(3, 4) → return 7 ToolMessage(content="7", name="add")
Round 2 A assistant LLM sees the result 7 and decides to call multiply(7, 2) AIMessage(tool_calls=[multiply(a=7,b=2)])
T tools Execute multiply(7, 2) → return 14 ToolMessage(content="14", name="multiply")
Round 3 A assistant LLM sees the result 14 and decides to call divide(14, 5) AIMessage(tool_calls=[divide(a=14,b=5)])
T tools Executing divide(14, 5) → returns 2.8 ToolMessage(content="2.8", name="divide")
Round 4 A assistant LLM sees the end result, organizes natural language answers,Don't request tools again AIMessage("The result is 2.8") ← None tool_calls
Finish E END tools_condition detected No tool_calls → route to END After the graph is executed, the final State is returned.

The complete evolution of the messages list

Every time the assistant node is called, it seesmessagesWhat is:

The first time assistant is called, LLM sees:
HumanMessage("Add 3 and 4. Multiply the output by 2. Divide the output by 5")
The second time assistant is called, LLM sees:
HumanMessage("Add 3 and 4...")
AIMessage(tool_calls=[add(3,4)])
ToolMessage("7", name="add") ← New, LLM now knows 3+4=7
The third time assistant is called, LLM sees:
HumanMessage("Add 3 and 4...")
AIMessage(tool_calls=[add(3,4)])
ToolMessage("7", name="add")
AIMessage(tool_calls=[multiply(7,2)])
ToolMessage("14", name="multiply") ← New, LLM now knows 7×2=14
The fourth time assistant is called, LLM sees:
HumanMessage("Add 3 and 4...")
AIMessage(tool_calls=[add(3,4)])
ToolMessage("7", name="add")
AIMessage(tool_calls=[multiply(7,2)])
ToolMessage("14", name="multiply")
AIMessage(tool_calls=[divide(14,5)])
ToolMessage("2.8", name="divide") ← New, LLM now knows 14÷5=2.8
→ LLM determines that all calculations are completed, returns natural language answers, and no longer requests tools
The messages list is the brain of the Agent

NoticemessagesThe list iscumulative growthof. Each loop adds new AIMessage and ToolMessage. LLM sees the complete "conversation history" with every invocation, which is why it can reason correctly about multi-step tasks - working memory is all here.

7The role of SystemMessage

This is another small difference between Agent and Router:assistantWhen the node calls LLM, it will add a message to the front of the message list.SystemMessage

sys_msg = SystemMessage(content="You are a helpful assistant tasked with performing arithmetic.")

def assistant(state: MessagesState):
    return {"messages": [llm_with_tools.invoke(
        [sys_msg] + state["messages"]   # ← sys_msg 注入在最前面
    )]}
Why inject it every time instead of putting it in State?

SystemMessage is defined outside the node function, each timeassistantDynamically spliced ​​when called. This has two benefits:

Message typeExistence locationLLM can be seen every timeuse
SystemMessage Node function local variables Yes (dynamic stitching per time) Tell the LLM its roles and responsibilities
HumanMessage State["messages"] Yes (in State) User's original request
AIMessage State["messages"] Yes (in State) LLM's historical decisions and tool call requests
ToolMessage State["messages"] Yes (in State) The result of tool execution is the "Observe" link of ReAct

8When does the loop terminate?

Is the Agent loop infinite? no. The termination condition is very clear, given bytools_conditioncontrol:

Decision logic for tools_condition

assistant node execution completed
tools_condition check
Does the latest AIMessage have tool_calls?
There are tool_calls → "tools"
tools (execution tools)
↩ Back to assistant
no tool_calls → END
__end__
The loop terminates and the answer is returned

LLM decides when to stop. When it determines that all subtasks are completed, its reply no longer containstool_calls, only plain text answers.tools_conditionUpon detecting this, route toEND, the graph is executed.

Note: Risk of infinite loop

In theory, if LLM requested tools forever, the graph would run indefinitely. In practice, you can passreact_graph = builder.compile()when passed inrecursion_limitParameter to limit the maximum number of iterations (default is 25):

# 限制最大递归深度(每个节点执行一次算一步)
result = react_graph.invoke(
    {"messages": messages},
    config={"recursion_limit": 10}  # 超过则抛出 GraphRecursionError
)

9Router vs Agent: The only difference

Let’s compare the two graphs side by side. Everything is exactly the same except for that row of edges:

Router complete code (relevant parts)

# 节点
builder.add_node("tool_calling_llm", tool_calling_llm)
builder.add_node("tools", ToolNode([multiply]))

# 边
builder.add_edge(START, "tool_calling_llm")
builder.add_conditional_edges(
    "tool_calling_llm", tools_condition
)
# ↓ 工具结果 → 结束
builder.add_edge("tools", END)

Agent complete code (relevant parts)

# 节点
builder.add_node("assistant", assistant)
builder.add_node("tools", ToolNode(tools))

# 边
builder.add_edge(START, "assistant")
builder.add_conditional_edges(
    "assistant", tools_condition
)
# ↓ 工具结果 → 回到 LLM(关键!)
builder.add_edge("tools", "assistant")
DimensionsRouterAgent
Exit of tools node END "assistant"(cycle)
Can LLM see tool results? cannot Can (append to messages)
Support multi-step tool calling Not supported (can only be adjusted once) Supported (unlimited times until LLM stops)
graph topology DAG (directed acyclic graph) A cyclic graph (containing cyclic edges)
suitable task Single-step tool task (check database once, check weather once) Multi-step reasoning tasks (complex calculations, search + summary + verification)
SystemMessage Optional Recommended (reinject every round to keep the character stable)

10This is the general Agent architecture

We demonstrated Agent using three mathematical functions, but the architecture is completely general. Replace the tool with anything and the Agent works the same way:

Types of tools that can be accessed

  • Web search (Tavily, Bing)
  • Database query (SQL, vector database)
  • Code execution (Python REPL)
  • API calls (weather, maps, payments)
  • File reading and writing (reading CSV, writing reports)
  • Sub-Agent invocation (Agent nesting)

Types of tasks that can be completed

  • Research Assistant: Search→Summary→Verify→Search again
  • Data analysis: check data→calculate→draw diagram→interpretation
  • Code Assistant: Write code→Run→View error report→Modify
  • Customer service robot: check order → check inventory → process refund
  • Any task that requires "thinking as you do"
ReAct Agent is the standard paradigm for LLM applications

OpenAI’s Assistants API, Anthropic’s tool use, LangChain’s AgentExecutor – they’re all essentially the same thing: a ReAct loop. The value of LangGraph lies in using the language of graphsExplicitlyExpressing this loop allows you to inspect, modify, and extend each step of the Agent's behavior. You now have a grasp of its core structure.

Next step for expansion

Core concepts you have mastered

StateGraph → MessagesState → ToolNode → tools_condition → add_edge("tools", "assistant"). Putting these five together is a fully functional ReAct Agent. All subsequent modules will add functions based on this foundation, rather than reinventing the wheel.