对,这两个是 LangGraph 入门里非常核心的东西:
javascript
from langgraph.graph import MessagesState
from langchain_core.messages import HumanMessage
一句话先讲清楚:
MessagesState:LangGraph 预置的"状态结构",里面默认有一个messages字段。HumanMessage:LangChain Core 里的"用户消息对象",表示一条来自用户的消息。
1. MessagesState 是什么
MessagesState 本质上是一个 TypedDict,官方源码里大概等价于:
kotlin
class MessagesState(TypedDict):
messages: Annotated[list[AnyMessage], add_messages]
也就是说,它描述的 state 长这样:
css
{
"messages": [
HumanMessage(...),
AIMessage(...),
ToolMessage(...),
]
}
它不是普通 class 实例,不是这样用:
ini
state = MessagesState(...)
而是作为 StateGraph 的状态类型:
ini
from langgraph.graph import StateGraph, MessagesState
graph_builder = StateGraph(MessagesState)
然后你调用 graph 时传入:
arduino
graph.invoke({
"messages": [HumanMessage(content="你好")]
})
官方文档也说明,MessagesState 是一个预置 state,只有一个 messages key,这个 key 是 list[AnyMessage],并且使用 add_messages reducer。见官方 Graph API 文档的 MessagesState 小节:docs.langchain.com。
2. 为什么不用普通 list,而要 MessagesState
因为聊天 agent 最常见的状态就是消息历史:
css
[ HumanMessage(content="你好"), AIMessage(content="你好!有什么可以帮你?"), HumanMessage(content="帮我查天气"),]
如果你自己写 state,需要这样:
javascript
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph.message import add_messages
from langchain_core.messages import AnyMessage
class State(TypedDict):
messages: Annotated[list[AnyMessage], add_messages]
LangGraph 觉得这太常用了,所以帮你内置成:
python
from langgraph.graph import MessagesState
class State(MessagesState):
pass
或者直接:
ini
graph_builder = StateGraph(MessagesState)
3. add_messages 是关键
MessagesState 里面最重要的是这个:
css
Annotated[list[AnyMessage], add_messages]
这表示:当某个 node 返回新的 messages 时,LangGraph 不会简单覆盖旧消息,而是用 add_messages 合并。
比如当前 state 是:
css
{
"messages": [
HumanMessage(content="你好")
]
}
某个 node 返回:
css
{
"messages": [
AIMessage(content="你好!")
]
}
最终会变成:
css
{
"messages": [
HumanMessage(content="你好"),
AIMessage(content="你好!")
]
}
如果没有 add_messages,新的 messages 很可能直接覆盖旧的消息历史。
add_messages 还有一个重要能力:它会根据 message 的 id 更新已有消息。也就是说,如果新消息和旧消息 id 一样,它会替换旧消息,而不是追加。这对 human-in-the-loop、修改历史消息、工具调用更新都很有用。官方源码和文档都提到这一点:LangGraph message.py。
4. HumanMessage 是什么
HumanMessage 表示"用户发给模型的一条消息"。
最常见写法:
ini
from langchain_core.messages import HumanMessage
msg = HumanMessage(content="你好,请介绍一下 LangGraph")
它等价于聊天 API 里的:
json
{
"role": "user",
"content": "你好,请介绍一下 LangGraph"
}
但是在 LangChain / LangGraph 里,它是一个 Python 对象。
访问内容:
scss
print(msg.content)
输出:
你好,请介绍一下 LangGraph
注意这里是点号访问,因为 HumanMessage 是真正的对象,不是 dict。
5. HumanMessage 常见属性
HumanMessage 继承自 BaseMessage,常见属性有这些:
ini
msg = HumanMessage(
content="你好",
name="warson",
id="msg-001",
additional_kwargs={"foo": "bar"},
response_metadata={"source": "user_input"}
)
常用属性:
msg.content
消息内容。可以是字符串,也可以是多模态内容列表。
go
msg.type
消息类型。HumanMessage 的 type 是:
arduino
"human"
msg.name
可选名字。比如区分多个用户,或者给消息一个人类可读标识。
python
msg.id
可选唯一 ID。LangGraph 的 add_messages 会用它来判断"追加新消息"还是"替换已有消息"。
msg.additional_kwargs
额外信息。通常用于存 provider 特定字段。
msg.response_metadata
响应元数据。对 AIMessage 更常见,比如 token 数、模型名、响应头等;HumanMessage 也有这个字段。
arduino
msg.text
取纯文本内容。新版本里建议用属性形式:
arduino
msg.text
而不是旧式:
scss
msg.text()
LangChain Core 的源码里可以看到 BaseMessage 定义了 content、additional_kwargs、response_metadata、type、name、id 等字段:BaseMessage source。HumanMessage 自己的 type 是 "human":HumanMessage source。
6. 最小 LangGraph 例子
python
from langgraph.graph import StateGraph, START, END, MessagesState
from langchain_core.messages import HumanMessage, AIMessage
def chatbot(state: MessagesState):
last_message = state["messages"][-1]
user_text = last_message.content
return {
"messages": [
AIMessage(content=f"你刚刚说的是:{user_text}")
]
}
builder = StateGraph(MessagesState)
builder.add_node("chatbot", chatbot)
builder.add_edge(START, "chatbot")
builder.add_edge("chatbot", END)
graph = builder.compile()
result = graph.invoke({
"messages": [
HumanMessage(content="你好 LangGraph")
]
})
print(result["messages"])
结果里的 messages 大概是:
css
[ HumanMessage(content="你好 LangGraph", ...), AIMessage(content="你刚刚说的是:你好 LangGraph", ...)]
注意这个访问方式:
css
state["messages"]
因为 state 是 dict。
但里面的每条 message 是对象,所以访问消息内容用:
css
state["messages"][-1].content
不是:
css
state.messages[-1]["content"]
7. 扩展 MessagesState
实际项目里,state 往往不只有消息,还会有其他字段,比如用户 ID、检索结果、当前任务状态:
python
from langgraph.graph import MessagesState
class State(MessagesState):
user_id: str
documents: list[str]
step_count: int
然后:
arduino
def retrieve(state: State):
query = state["messages"][-1].content
return {
"documents": ["doc1", "doc2"],
"step_count": state.get("step_count", 0) + 1
}
这时候 State 仍然有默认的:
ini
messages: Annotated[list[AnyMessage], add_messages]
只是你额外加了字段。
8. 和 dict 格式能不能混用
可以。官方文档说,使用 add_messages 时,LangGraph 会尝试把输入反序列化成 LangChain message 对象。所以这两种都可以:
css
{
"messages": [HumanMessage(content="你好")]
}
也可以:
json
{
"messages": [{"type": "human", "content": "你好"}]
}
但进入 graph 后,通常会变成 message 对象,所以你应该这样访问:
css
state["messages"][-1].content
官方文档也特别提醒了这一点:Graph API: Serialization。
最重要的心智模型
你可以这样记:
MessagesState
是 LangGraph 的聊天状态容器:
json
{"messages": [...]}
而:
HumanMessage
是 messages 列表里的一条用户消息:
ini
HumanMessage(content="用户说的话")
所以组合起来就是:
css
state = {
"messages": [
HumanMessage(content="你好"),
AIMessage(content="你好,我是助手")
]
}
LangGraph 管 state 怎么在节点之间流动,HumanMessage / AIMessage 管每条聊天消息长什么样。
真人笔记
好问题。你这一行其实混了 Python 类型系统 、LangChain 消息类型 、LangGraph reducer 三层东西,所以第一次看会很"这是什么咒语"。
代码是:
kotlin
class MessagesState(TypedDict):
messages: Annotated[list[AnyMessage], add_messages]
我们拆开看。
1. 整体意思
它定义了一个字典结构:
json
{
"messages": [...]
}
并且告诉 LangGraph:
messages这个字段是一个消息列表。每次节点返回新的 messages 时,不要直接覆盖旧列表,而是用add_messages这个函数来合并。
所以这行可以翻译成中文:
css
MessagesState 是一个 TypedDict。
它有一个 key 叫 messages。
messages 的类型是 list[AnyMessage]。
当 messages 被更新时,使用 add_messages 作为合并规则。
2. TypedDict 是什么
kotlin
class MessagesState(TypedDict):
TypedDict 是 Python 标准库 typing 里的类型工具。
来源:
javascript
from typing import TypedDict
或者有些代码用:
javascript
from typing_extensions import TypedDict
它用来描述字典结构。
比如:
python
from typing import TypedDict
class User(TypedDict):
name: str
age: int
意思是:
makefile
user = {
"name": "Alice",
"age": 20
}
所以 MessagesState 不是普通对象类,而是描述 state 字典应该有哪些 key。
3. messages 是什么
makefile
messages: ...
这是一条类型标注,意思是这个字典里有一个 key 叫 "messages"。
类似:
kotlin
class User(TypedDict):
name: str
表示:
json
{"name": "Alice"}
那么:
kotlin
class MessagesState(TypedDict):
messages: ...
表示:
json
{"messages": ...}
4. listAnyMessage 是什么
css
list[AnyMessage]
表示:
一个列表,列表里的每一项都是
AnyMessage。
比如普通 Python 类型可以写:
ini
names: list[str] = ["Alice", "Bob"]
ages: list[int] = [18, 20]
所以:
ini
messages: list[AnyMessage]
表示:
ini
messages = [
HumanMessage(content="你好"),
AIMessage(content="你好!"),
ToolMessage(content="工具结果", tool_call_id="xxx"),
]
5. AnyMessage 是什么
AnyMessage 不是 Python 原生的。
它来自 LangChain Core:
javascript
from langchain_core.messages import AnyMessage
它表示"任意一种 LangChain 消息类型"。
常见消息类型有:
bash
HumanMessage # 用户消息
AIMessage # AI 回复
SystemMessage # 系统提示词
ToolMessage # 工具返回结果
所以:
AnyMessage
大概可以理解成:
HumanMessage 或 AIMessage 或 SystemMessage 或 ToolMessage 或其他 LangChain message
它是一个类型别名/联合类型,用来告诉类型检查器:这个列表里可以放多种 message 对象。
6. Annotated 是什么
css
Annotated[list[AnyMessage], add_messages]
这是最容易懵的部分。
Annotated 是 Python 标准库 typing 里的类型工具:
javascript
from typing import Annotated
它的作用是:
在原本的类型上,额外附加一些元信息 metadata。
普通写法:
ini
messages: list[AnyMessage]
只是在说:
messages 是一个消息列表。
加上 Annotated:
ini
messages: Annotated[list[AnyMessage], add_messages]
是在说:
messages 是一个消息列表,并且这个字段附带一个额外信息:add_messages。
Python 类型检查器主要看前半部分:
css
list[AnyMessage]
LangGraph 会读取后半部分:
add_messages
然后把它当成 reducer,也就是状态更新合并函数。
7. add_messages 是什么
add_messages 不是 Python 原生的。
它来自 LangGraph:
javascript
from langgraph.graph.message import add_messages
或者有时可以:
javascript
from langgraph.graph import add_messages
它是一个函数,用来合并消息列表。
比如旧 state:
ini
old = [
HumanMessage(content="你好")
]
节点返回新消息:
ini
new = [
AIMessage(content="你好,我是助手")
]
add_messages(old, new) 结果是:
css
[ HumanMessage(content="你好"), AIMessage(content="你好,我是助手")]
也就是追加。
但它比简单的列表相加更聪明:如果消息有相同 id,它会替换旧消息。
ini
old = [
HumanMessage(content="你好", id="1")
]
new = [
HumanMessage(content="你好呀", id="1")
]
结果会是:
bash
[
HumanMessage(content="你好呀", id="1")
]
而不是:
bash
[
HumanMessage(content="你好", id="1"),
HumanMessage(content="你好呀", id="1")
]
8. 为什么 LangGraph 要用 Annotated
因为 LangGraph 的 state 更新不是普通变量赋值。
比如一个节点返回:
kotlin
return {
"messages": [AIMessage(content="你好")]
}
LangGraph 要决定:
bash
这个 messages 是覆盖旧 messages?
还是追加到旧 messages?
还是按 id 合并?
默认情况下,如果你没有告诉它规则,很多字段会被覆盖。
比如:
kotlin
class State(TypedDict):
messages: list[AnyMessage]
可能是:
ini
old = {"messages": [HumanMessage(content="你好")]}
update = {"messages": [AIMessage(content="你好!")]}
result = {"messages": [AIMessage(content="你好!")]}
旧消息没了。
但如果你写:
kotlin
class State(TypedDict):
messages: Annotated[list[AnyMessage], add_messages]
LangGraph 就知道:
css
result = {
"messages": [
HumanMessage(content="你好"),
AIMessage(content="你好!")
]
}
旧消息会保留,新消息会合并进去。
9. 哪些是 Python 原生,哪些是 LangGraph / LangChain 的
这张表最清楚:
kotlin
class Python 原生语法
MessagesState 你/库定义的类名,这里是 LangGraph 内置提供的 state 类型
TypedDict Python typing 标准库,或 typing_extensions
messages 字段名,普通名字
: Python 类型标注语法
Annotated Python typing 标准库,或 typing_extensions
list Python 原生类型
AnyMessage LangChain Core 提供的消息类型别名
add_messages LangGraph 提供的 reducer 函数
如果自己手写完整导入,大概是:
javascript
from typing import Annotated
from typing_extensions import TypedDict
from langchain_core.messages import AnyMessage
from langgraph.graph.message import add_messages
class MessagesState(TypedDict):
messages: Annotated[list[AnyMessage], add_messages]
不过你平时一般不用自己写,因为 LangGraph 已经内置了:
javascript
from langgraph.graph import MessagesState
10. 这行代码的低配版理解
先不要被语法吓到,它本质上就是:
kotlin
class MessagesState(TypedDict):
messages: list
表示 state 里有消息列表。
然后升级一点:
kotlin
class MessagesState(TypedDict):
messages: list[AnyMessage]
表示这个列表里放的是 LangChain message。
再升级一点:
kotlin
class MessagesState(TypedDict):
messages: Annotated[list[AnyMessage], add_messages]
表示这个列表更新时用 add_messages 合并。
所以你可以把它读成:
ini
messages: Annotated[list[AnyMessage], add_messages]
等于:
css
messages 是 list[AnyMessage] 类型;
LangGraph 更新它时,请使用 add_messages。
这个读法就够用了。