LangGraph的MessageState and HumanMessage

对,这两个是 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

消息类型。HumanMessagetype 是:

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 定义了 contentadditional_kwargsresponse_metadatatypenameid 等字段:BaseMessage sourceHumanMessage 自己的 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。

这个读法就够用了。

相关推荐
韩师傅1 小时前
当你的甲方设备过烂,要如何快速出效果?
python·计算机视觉
韩师傅2 小时前
当你的甲方吐槽天空不够蓝,你应该如何应对
python·计算机视觉
Warson_L2 小时前
python的类&继承
python
Warson_L2 小时前
类型标注/type annotation
python
ThreeS5 小时前
手搓MiniVLA全实战教程-一步一步用pytorch解释原理与思路
人工智能·python
金銀銅鐵6 小时前
[Python] 模 n 乘法的逆元计算器
python·数学·游戏
aqi007 小时前
15天学会AI应用开发(十)把文本嵌入模型换成国产模型
人工智能·python·ai编程
金銀銅鐵1 天前
[Python] 扩展欧几里得算法
python·数学·算法