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-2 Chain
📓 Notebook 📖 Explained
LANGGRAPH MODULE 1 · LESSON 2

Chain: LLM + Messages + Tools
The starting point of everything

From message types and chat models to tool bindings to reducers and MessagesState.
This lecture is the basic part for building practical AI applications in LangGraph.

1What problem is this lecture going to solve?

In the first lecture (1-1) of Module 1, Node only does string concatenation and has nothing to do with LLM. At the beginning of this lecture, we willReal LLM callIntroducing LangGraph.

To allow LLM to enter the graph, 4 sub-problems need to be solved:

subproblemsolution
What format is used for LLM input/output?Messages(HumanMessage / AIMessage, etc.)
How to call LLM in Node?Using LangChainChatModel
How to make LLM call external functions?bind_toolsbinding tool
Why is the message list in State not overwritten?Reducer(add_messages)
learning objectives

Once you understand these four parts, you can install an LLM that "can think and call tools" into the Node of LangGraph, which is the basis for all subsequent Agents.

2Messages: the basic unit of LLM dialogue

You have studied LangChain and know that the input of the chat model is a set of "messages". LangGraph completely reuses this system. LangChain defines multiple message types, each corresponding to a role in the conversation:

System
SystemMessage— "Personalization" instructions for LLM. Tell the LLM who it should be and what it should do before the conversation begins. For example: "You are a math assistant."
Human
HumanMessage— Messages sent by users. This is a question or command that you (or your application) pass to LLM.
AI
AIMessage— Response generated by LLM. Can be plain text or containtool_calls(tool call request).
Tool
ToolMessage— After the tool is executed, the result is reported to LLM. Includetool_call_idTo correspond to the result of which tool call.
from langchain_core.messages import AIMessage, HumanMessage

# 构建一段对话历史
messages = [
    AIMessage(content="So you said you were researching ocean mammals?", name="Model"),
    HumanMessage(content="Yes, that's right.", name="Lance"),
    AIMessage(content="Great, what would you like to learn about.", name="Model"),
    HumanMessage(content="I want to learn about the best place to see Orcas.", name="Lance"),
]

# 把消息列表传给 LLM
result = llm.invoke(messages)
# result 是一个 AIMessage 对象
Why do messages use "list"?

LLM is stateless - each call requiresComplete conversation historyPass it in so that it can "remember" the context. The message list is the carrier of conversation history. This list is stored in LangGraph's State so that each LLM call can see the complete history.

3Chat Models: Calling LLM in Node

LangGraph's Node is just an ordinary Python function. In the function body, you can directly call any Chat Model supported by LangChain:

from langchain_openai import ChatOpenAI
# 或者用 DeepSeek、Anthropic、Google 等任意 LangChain 支持的模型

llm = ChatOpenAI(model="gpt-4o-mini")

# 这就是一个调用 LLM 的 Node
def tool_calling_llm(state: MessagesState):
    # state["messages"] 是当前对话历史(列表)
    response = llm_with_tools.invoke(state["messages"])
    # 把 LLM 的回复作为新消息返回
    return {"messages": [response]}

It's that simple. Node gets the message list from State, calls LLM, and puts the result back into State.LangGraph's philosophy: The simpler the Node, the better, and the complexity is managed by the graph structure.

4Tools: Let LLM call functions

What is Tool Calling?

Modern LLMs (GPT-4, DeepSeek, etc.) support the "tool call" feature: you define some Python functions, tell LLM the names and parameters of these functions, and LLM canRequest to call a function(Instead of giving a direct text answer).

# 定义一个工具:普通 Python 函数,有类型注解和 docstring
def multiply(a: int, b: int) -> int:
    """Multiply a and b.

    Args:
        a: first int
        b: second int
    """
    return a * b

# bind_tools:把函数"绑定"给 LLM,让它知道有这个工具可用
llm_with_tools = llm.bind_tools([multiply])

When you ask LLM "What is 2 multiplied by 3?", it will not directly say "6", but will return a string withtool_callsAIMessage:

# LLM 的回复:请求调用工具
tool_call.tool_calls
# 输出:
# [{'name': 'multiply', 'args': {'a': 2, 'b': 3}, 'id': 'call_xxx', 'type': 'tool_call'}]
Important: LLM is just a "request" call and will not actually be executed.

Returned by LLMtool_callsJust a "request" - it says "I want to call multiply(a=2, b=3)", butactual executionRequires your code (or LangGraph's ToolNode) to complete. This is only the "half" of this lecture - Router will complete the "execution tool" step in the next lecture.

How does LLM know when to call a tool?

LLM will use your inputautomatic decisionDo you need to call tools:

user inputLLM behavior
"Hello!"Reply directly with text without calling tools
"Multiply 2 and 3"Return tool_call, request to call multiply
"What's the capital of France?"Reply directly with text (no tools required)

5reducer: Solve the problem of status "overwritten"

Issue: Default behavior destroys conversation history

In the simple diagram of 1-1, State has only one string field, and each Node returns the new value overwriting the old value, which is no problem. But when State is stored inMessage listWhen, something goes wrong:

❌ Default override behavior (bug)

# State 初始:[HumanMsg]
# Node 返回:{"messages": [AIMsg]}
# State 变成:[AIMsg]
# ← HumanMsg 丢失了!

✅ Use add_messages reducer

# State 初始:[HumanMsg]
# Node 返回:{"messages": [AIMsg]}
# State 变成:[HumanMsg, AIMsg]
# ← 追加,历史保留!

What is a reducer?

reducer is a function that defines "how the old value and the new value in State are merged when Node returns a new value." Via PythonAnnotatedType hints to declare:

from typing import Annotated
from langchain_core.messages import AnyMessage
from langgraph.graph.message import add_messages

class MessagesState(TypedDict):
    # Annotated[字段类型, Reducer函数]
    # add_messages:把新消息追加到列表,而不是覆盖
    messages: Annotated[list[AnyMessage], add_messages]

verifyadd_messagesBehavior:

from langgraph.graph.message import add_messages

initial = [AIMessage(content="Hello!"), HumanMessage(content="Hi!")]
new_msg = AIMessage(content="How can I help?")

result = add_messages(initial, new_msg)
# result = [AIMessage("Hello!"), HumanMessage("Hi!"), AIMessage("How can I help?")]
# ← 追加了,没有覆盖!
add_messages also has a hidden function

If the new messageidand those who have newsidsame,add_messagesmeetingrenewThat message instead of appending. This is useful in streaming - updating partially generated messages.

6MessagesState: Official built-in conversation state

Because the requirement of "there is a messages list using add_messages in State" is so common, LangGraph has it built-in directly.MessagesState

from langgraph.graph import MessagesState

# 直接用!等价于手写 TypedDict + Annotated + add_messages
# 还可以继承它来添加自定义字段:
class MyState(MessagesState):
    user_id: str       # 额外添加的字段
    search_results: list
WayAmount of codeRecommended scenarios
Handwriting TypedDict + AnnotatedmanyNot recommended when there is only a message field
Built-in MessagesStatefewIn most cases, just use this
Inherit MessagesStateModerateWhen additional fields are required

7Complete Chain Diagram: Assembled

Now combine all the parts into one picture:

from langgraph.graph import StateGraph, MessagesState, START, END

# Node:一个调用带工具绑定的 LLM 的节点
def tool_calling_llm(state: MessagesState):
    return {"messages": [llm_with_tools.invoke(state["messages"])]}

# 构建图:START → tool_calling_llm → END
builder = StateGraph(MessagesState)
builder.add_node("tool_calling_llm", tool_calling_llm)
builder.add_edge(START, "tool_calling_llm")
builder.add_edge("tool_calling_llm", END)
graph = builder.compile()

Chain diagram structure

__start__
tool_calling_llm
Read messages → call LLM → return AIMessage
__end__

This is a straight line graph (Chain) with no branches. But LLM can decide whether to include tool_calls in the reply.

8Interpretation of running results

The same picture, different inputs get different results:

# 场景 1:普通问候,LLM 直接回复
messages = graph.invoke({"messages": HumanMessage(content="Hello!")})
# messages = {
#   "messages": [
#     HumanMessage("Hello!"),
#     AIMessage("Hi there! How can I assist you today?")  ← 纯文字回复
#   ]
# }

# 场景 2:数学计算,LLM 请求调用工具
messages = graph.invoke({"messages": HumanMessage(content="Multiply 2 and 3")})
# messages = {
#   "messages": [
#     HumanMessage("Multiply 2 and 3"),
#     AIMessage(tool_calls=[{'name':'multiply','args':{'a':2,'b':3}}])  ← 工具调用请求
#   ]
# }
Limitation of this picture: the tool is not executed!

This Chain diagram is simplyRequest returned, the multiply function is not actually executed. The picture stops when it reaches END.

Will be added in the next lecture (Router)ToolNodeandtools_condition, so that the tool is actually executed and the results are returned to the user.

Summary: Map of knowledge points in this lecture

concepteffectkey code
HumanMessage / AIMessageStandard format for conversation messagesfrom langchain_core.messages import ...
ChatModel.invoke(messages)Call LLM in Nodellm.invoke(state["messages"])
bind_toolsLet LLM know which tools can be calledllm.bind_tools([multiply])
add_messages Reducerappend message instead of overwriteAnnotated[list, add_messages]
MessagesStateBuilt-in conversation status, ready to use out of the boxfrom langgraph.graph import MessagesState