Context 适合保存用户 ID、用户名和权限等调用开始时已经确定的数据,在一次运行中通常按只读方式使用。
但是 Agent 运行起来以后,还会不断产生新的内容。例如用户消息、模型生成的工具调用、工具执行结果,以及工具从外部系统得到的数据。这些数据会随着执行过程变化,因此不能全部放在静态 Context 中。
LangChain 使用 AgentState 管理这类运行时状态。本篇从默认消息状态开始,再为 State 增加自定义字段,并让两个 Tool 通过 State 传递用户名。
1. Agent 中的 State
普通模型调用通常只有一次输入和一次输出:
用户问题 -> 模型回答
具有工具调用能力的 Agent 会经历多个步骤:
用户问题
-> 模型决定调用工具
-> 工具执行
-> 模型读取工具结果
-> 生成最终回答
后面的步骤需要知道前面发生了什么。如果工具执行阶段看不到模型刚刚生成的工具调用,便不知道应该执行哪个 Tool;如果第二次模型调用看不到 ToolMessage,也无法根据工具结果整理答案。
因此,Agent 需要一个贯穿本次执行过程的状态对象。它的主要职责有两个:
- 保存 Agent 执行过程中产生的消息。
- 保存应用自己定义的可变字段。
AgentState 是状态的结构,不是数据库。State 中有数据,不代表这些数据已经持久化。
2. 默认 State 如何保存消息
一次完整的工具调用通常会生成四条消息:
- 用户输入形成 HumanMessage。
- 模型生成包含 tool_calls 的 AIMessage。
- 工具执行结果形成 ToolMessage。
- 模型根据工具结果生成最终 AIMessage。
下图中的每个 State 卡片都是 stream_mode=”values” 返回的完整快照,不是某个节点单独返回的局部更新。后一个快照保留了前面的消息,并通过 add_messages 合并本步骤产生的新消息。

这些消息保存在 AgentState.messages 中。Agent 每执行完一个节点,都会提交一部分状态更新;messages 字段通过 add_messages 合并新消息,不需要业务代码每次重新构造完整消息列表。
2.1 默认AgentState 的导入与字段
AgentState 应从 langchain.agents 导入:
from langchain.agents import AgentState
当前定义的主要字段可以简化理解为:
class AgentState(TypedDict):
messages: list[AnyMessage]
jump_to: NotRequired[str | None]
structured_response: NotRequired[object]
- messages 是最重要的字段,保存对话和工具调用消息。
- jump_to 是框架内部控制执行跳转使用的字段,普通 Agent 代码通常不需要操作。
- structured_response 在 Agent 使用结构化输出时保存解析后的结果。
2.2 默认 AgentState 代码
示例代码放在:
llm-learning/langgraph/p08_agent_state/
本文继续使用 .venv_langgraph,不需要安装新的数据库或向量组件:
cd /path/to/llm-learning
source .venv_langgraph/bin/activate
python -m pip install -r langgraph/p08_agent_state/requirements.txt
使用 LangGraph 系列 6 已经创建的模型环境启动 Qwen3:
source .venv_tool_server/bin/activate
"$VIRTUAL_ENV/bin/python" -m mlx_lm server \
--model Qwen3-14B-AWQ-4bit-MLX \
--host 127.0.0.1 \
--port 18080 \
--prompt-cache-size 0 \
--chat-template-args '{"enable_thinking": false}'
先确认模型接口正常:
curl --noproxy '*' http://127.0.0.1:18080/v1/models
代码:
01_inspect_default_agent_state.py:
"""观察默认 AgentState 中的 messages 如何随 Agent 执行逐步增加。
默认 state 结构:{"messages": [HumanMessage, AIMessage, ToolMessage, ...]}
本脚本每执行一步打印一次 messages 列表的长度和最后一条消息。
"""
import httpx
from langchain.agents import create_agent
from langchain.tools import tool
from langchain_openai import ChatOpenAI
@tool
def calculator(a: int, b: int) -> int:
"""计算两个整数的加法。"""
return a + b
model = ChatOpenAI(
model="Qwen3-14B-AWQ-4bit-MLX",
base_url="http://127.0.0.1:18080/v1",
api_key="not-needed",
temperature=0,
max_tokens=128,
http_client=httpx.Client(trust_env=False),
http_async_client=httpx.AsyncClient(trust_env=False),
)
agent = create_agent(
model=model,
tools=[calculator],
system_prompt="你是一个计算助手。遇到加法问题时必须调用 calculator 工具。",
)
def describe_latest_message(messages) -> str:
"""用一句话描述 messages 里最后一条消息在干什么。"""
latest = messages[-1]
kind = type(latest).__name__
# 模型决定调工具:AIMessage 里带 tool_calls
if kind == "AIMessage" and latest.tool_calls:
call = latest.tool_calls[0]
return f"模型要调用 {call['name']},参数 {call['args']}"
# 工具执行完毕:ToolMessage
if kind == "ToolMessage":
return f"工具返回 {latest.content}"
# 模型给出文字回答
if kind == "AIMessage":
return f"模型回答 {latest.content}"
return f"{kind}:{latest.content}"
# agent.stream() 返回的是迭代器(不是 list),每迭代一次 Agent 前进一步。
# stream_mode="values" 时,每次迭代得到的是一个 dict,即当时的完整 state 快照:
# {"messages": [HumanMessage, AIMessage, ...]}
input_state = {"messages": [{"role": "user", "content": "请计算 12 加 8。"}]}
state_snapshots = agent.stream(input_state, stream_mode="values")
step = 1
for state in state_snapshots:
messages = state["messages"]
print(f"步骤 {step} | 共 {len(messages)} 条消息 | {describe_latest_message(messages)}")
step += 1
运行:
python langgraph/p08_agent_state/01_inspect_default_agent_state.py
真实输出如下:
步骤 1 | 共 1 条消息 | HumanMessage:请计算 12 加 8。
步骤 2 | 共 2 条消息 | 模型要调用 calculator,参数 {'a': 12, 'b': 8}
步骤 3 | 共 3 条消息 | 工具返回 20
步骤 4 | 共 4 条消息 | 模型回答 12 加 8 的结果是 20。
stream_mode=”values” 返回每个步骤执行后的完整 State,所以可以直接看到 messages 从 1 条增加到 4 条。这不是四次独立调用,而是同一次 Agent 运行的四个状态快照。
4. 自定义 AgentState
默认 State 已经能够保存消息。如果还要保存用户名、订单号、当前任务阶段或工具生成的中间数据,就需要扩展 AgentState。
本文定义一个 CustomState:
class CustomState(AgentState):
user_name: NotRequired[str]
这里使用 NotRequired,因为 Agent 刚开始运行时还没有把用户名写入 State。用户名会在第一个 Tool 执行后出现。
创建 Agent 时,需要通过 state_schema 注册它:
agent = create_agent(
model=model,
tools=[load_user_name, greet_user],
context_schema=UserContext,
state_schema=CustomState,
)
如果只定义 CustomState,却没有传给 state_schema,Agent 不会按这份自定义 Schema 管理字段。
4.1 . Context 与 State 如何配合
上一篇已经区分了静态 Context 与可变 State。本例把两者连接起来:
- 调用方通过 Context 传入用户名。
- load_user_name 从 runtime.context 读取用户名。
- Tool 返回 Command(update=…),把用户名写入 State。
- greet_user 从 runtime.state 读取用户名。
- Qwen3 根据工具结果生成最终回答。
图中保留了两次 Tool 调用前后的模型决策。Command(update=…) 只把字段和 ToolMessage 提交给 State,并不会直接执行 greet_user;Qwen3 读取第一条工具结果后,才决定发起第二次工具调用。

这里特意不让第二个 Tool 再次读取 Context。这样可以明确验证:第一个 Tool 写入的 State,确实能够被后续执行步骤读取。
4.2 . 使用 Command 更新 State
普通 Tool 返回字符串或字典时,返回值会转换成工具结果交给模型,但不会自动写入任意自定义 State 字段。
需要修改 State 时,Tool 应返回 Command:
return Command(
update={
"user_name": user_name,
"messages": [
ToolMessage(
content=f"已经把用户名 {user_name} 写入 AgentState。",
tool_call_id=runtime.tool_call_id,
)
],
}
)
这段代码同时更新两个字段:
- user_name 保存应用需要的自定义状态。
- messages 保存模型需要看到的工具执行结果。
4.3 返回 ToolMessage
模型生成工具调用时,会产生一个工具调用 ID。工具执行后的 ToolMessage 必须使用相同 ID,模型才能知道这条结果对应哪一次调用。
当前 API 可以直接通过 runtime.tool_call_id 取得该值,不需要再单独声明 InjectedToolCallId 参数。
如果 Tool 返回 Command 却不补充对应的 ToolMessage,消息历史可能出现只有工具请求、没有工具结果的无效结构,后续模型调用可能失败。
4.4. Tool 如何读取 State
当前代码使用 ToolRuntime:
@tool
def greet_user(runtime: ToolRuntime[UserContext, CustomState]) -> str:
user_name = runtime.state.get("user_name")
return f"祝你使用顺利,{user_name}!"
ToolRuntime 由框架自动注入,不会出现在模型可见的 Tool Schema 中。它同时提供 state、context、config 和 tool_call_id 等运行信息。
早期版本也可能使用 InjectedState:
from typing import Annotated
from langgraph.prebuilt import InjectedState
@tool
def greet_user(state: Annotated[CustomState, InjectedState]) -> str:
return f"祝你使用顺利,{state['user_name']}!"
这种写法仍然可以用于只注入 State 的场景。本文使用 ToolRuntime,因为同一个 Tool 还需要访问 Context 和工具调用 ID,入口更加统一。
4.5. 完整代码
02_custom_state_and_command.py:
"""使用自定义 AgentState、ToolRuntime 和 Command 在工具之间传递用户名。"""
from typing_extensions import NotRequired, TypedDict
import httpx
from langchain.agents import AgentState, create_agent
from langchain.tools import ToolRuntime, tool
from langchain_core.messages import AIMessage, ToolMessage
from langchain_core.utils.function_calling import convert_to_openai_tool
from langchain_openai import ChatOpenAI
from langgraph.types import Command
class UserContext(TypedDict):
"""定义调用开始时传入的静态用户上下文。"""
user_name: str
class CustomState(AgentState):
"""在默认消息状态之外增加运行期间可变的用户名。"""
user_name: NotRequired[str]
@tool
def load_user_name(runtime: ToolRuntime[UserContext, CustomState]) -> Command:
"""先从运行时上下文加载用户名,并把用户名写入 AgentState。"""
if runtime.context is None or "user_name" not in runtime.context:
raise ValueError("必须通过 context 传入 user_name")
user_name = runtime.context["user_name"]
# Command.update 同时更新两部分 State:
# 1. user_name 是本例新增的自定义字段;
# 2. messages 保存与本次 Tool Call 对应的工具结果。
return Command(
update={
"user_name": user_name,
"messages": [
ToolMessage(
content=f"已经把用户名 {user_name} 写入 AgentState。",
tool_call_id=runtime.tool_call_id,
)
],
}
)
@tool
def greet_user(runtime: ToolRuntime[UserContext, CustomState]) -> str:
"""在用户名已经写入 AgentState 后,为当前用户生成祝福语。"""
# 第二个工具读取的是可变 State,不再读取静态 Context。
user_name = runtime.state.get("user_name")
if not user_name:
return "AgentState 中还没有用户名,请先调用 load_user_name 工具。"
return f"祝你使用顺利,{user_name}!"
# 第 1 步:创建模型,再声明 Agent 可以使用的 Context 和 State 结构。
model = ChatOpenAI(
model="Qwen3-14B-AWQ-4bit-MLX",
base_url="http://127.0.0.1:18080/v1",
api_key="not-needed",
temperature=0,
max_tokens=256,
http_client=httpx.Client(trust_env=False),
)
agent = create_agent(
model=model,
tools=[load_user_name, greet_user],
system_prompt=(
"你是一个祝福助手。"
),
# context_schema:声明 invoke(..., context={...}) 可以传入哪些字段。
# Context 是「本次调用的固定输入」,整次运行不变;由 ToolRuntime.context 读取。
context_schema=UserContext,
# state_schema:声明 Agent 运行过程中可读写哪些 state 字段(在默认 messages 之外扩展)。
# State 会随工具执行变化;load_user_name 通过 Command.update 写入 user_name,
# greet_user 再从 ToolRuntime.state 读取。
state_schema=CustomState,
)
def run_for_user(user_name: str) -> None:
"""使用指定用户名独立运行一次 Agent。"""
# 第 2 步:Context 是本次调用的固定输入,State 会在工具执行时变化。
result = agent.invoke(
{"messages": [{"role": "user", "content": "请给当前用户一句祝福语。"}]},
context={"user_name": user_name},
)
# 第 3 步:遍历消息,查看两个工具的实际调用顺序。
tool_names = []
for message in result["messages"]:
if isinstance(message, AIMessage) and message.tool_calls:
# 一条 AIMessage 可能包含多个 Tool Call,所以再遍历一次。
for call in message.tool_calls:
tool_names.append(call["name"])
elif isinstance(message, ToolMessage):
print(f"工具结果:{message.content}")
print(f"当前调用:{user_name}")
print(f"工具调用顺序:{' -> '.join(tool_names)}")
print(f"最终 State 用户名:{result.get('user_name')}")
print(f"最终回答:{result['messages'][-1].content}\n")
# runtime 由框架注入,因此两个工具都没有模型可见参数。
for current_tool in [load_user_name, greet_user]:
tool_schema = convert_to_openai_tool(current_tool)
properties = tool_schema["function"]["parameters"]["properties"]
print(f"{current_tool.name} 的模型可见参数:{properties}")
print()
run_for_user("小明")
run_for_user("小红")
运行:
python langgraph/p08_agent_state/02_custom_state_and_command.py
真实输出如下:
load_user_name 的模型可见参数:{}
greet_user 的模型可见参数:{}
工具结果:已经把用户名 小明 写入 AgentState。
工具结果:祝你使用顺利,小明!
当前调用:小明
工具调用顺序:load_user_name -> greet_user
最终 State 用户名:小明
最终回答:祝你使用顺利,小明!
工具结果:已经把用户名 小红 写入 AgentState。
工具结果:祝你使用顺利,小红!
当前调用:小红
工具调用顺序:load_user_name -> greet_user
最终 State 用户名:小红
最终回答:祝你使用顺利,小红!
这次运行验证了三件事:
- Qwen3 按顺序调用了两个 Tool。
- 第二个 Tool 能读取第一个 Tool 写入的 State。
- 小明和小红的两次 invoke() 没有互相覆盖。
工具调用 ID 是动态值,示例没有打印它,但 runtime.tool_call_id 已经用于创建匹配的 ToolMessage。
5. 状态的追加,修改与删除
普通状态字段如果没有 Reducer,新值通常会覆盖旧值。但 AgentState.messages 已经使用 add_messages 作为内置 Reducer。
它至少解决三个问题:
- 新消息拥有新 ID 时追加到列表。
- 新消息与已有消息 ID 相同时替换原消息。
- 收到 RemoveMessage 时删除对应 ID 的消息。
这里只介绍默认 messages 字段必须理解的行为。自定义 Reducer、并行节点冲突和工作流状态合并留到后续 Workflow 文章。
完整代码:
03_message_update_and_remove.py:
"""结合 LLM 与自定义 State,演示 messages / state 的追加、修改、删除。
流程:
1. 与 02 相同:invoke Agent,LLM + 工具自然追加 messages 和 user_name
2. 在真实对话结果上,用 add_messages 手动「追加 / 同 ID 替换 / RemoveMessage 删除」
3. 用修剪后的 messages 再次 invoke,观察 LLM 只能看到保留下来的历史
messages 的 reducer 规则(add_messages):
- 新 ID → 追加到列表末尾
- 相同 ID → 替换原消息(修改)
- RemoveMessage(id=...) → 删除该条消息
"""
from typing_extensions import NotRequired, TypedDict
import httpx
from langchain.agents import AgentState, create_agent
from langchain.tools import ToolRuntime, tool
from langchain_core.messages import (
AIMessage,
HumanMessage,
RemoveMessage,
ToolMessage,
)
from langgraph.graph.message import add_messages
from langgraph.types import Command
from langchain_openai import ChatOpenAI
class UserContext(TypedDict):
user_name: str
class CustomState(AgentState):
user_name: NotRequired[str]
# 记录对消息历史做过的操作,演示自定义字段也可随 Command 更新。
edit_log: NotRequired[list[str]]
def _message_preview(message) -> str:
content = str(message.content or "").strip()
if content:
return content[:50].replace("\n", " ")
if isinstance(message, AIMessage) and message.tool_calls:
names = ", ".join(tc["name"] for tc in message.tool_calls)
return f"[tool_calls: {names}]"
return ""
def print_state(title: str, state: dict) -> None:
"""打印 state 中的自定义字段和 messages 摘要。"""
print(f"\n{'=' * 60}")
print(title)
print(f"user_name: {state.get('user_name')}")
print(f"edit_log: {state.get('edit_log', [])}")
messages = state["messages"]
print("messages:")
if not messages:
print(" (empty)")
return
id_width = max(len(str(m.id or "")) for m in messages)
type_width = max(len(type(m).__name__) for m in messages)
for message in messages:
msg_id = str(message.id or "")
msg_type = type(message).__name__
preview = _message_preview(message)
print(f" {msg_id:<{id_width}} | {msg_type:<{type_width}} | {preview}")
@tool
def load_user_name(runtime: ToolRuntime[UserContext, CustomState]) -> Command:
"""从 context 读取用户名,写入 AgentState。"""
user_name = runtime.context["user_name"]
return Command(
update={
"user_name": user_name,
"messages": [
ToolMessage(
content=f"已加载用户 {user_name}。[后面测试删除]",
tool_call_id=runtime.tool_call_id,
)
],
}
)
@tool
def greet_user(runtime: ToolRuntime[UserContext, CustomState]) -> str:
"""根据 state 中的用户名生成祝福语。"""
user_name = runtime.state.get("user_name")
if not user_name:
return "请先调用 load_user_name。"
return f"祝你使用顺利,{user_name}!"
model = ChatOpenAI(
model="Qwen3-14B-AWQ-4bit-MLX",
base_url="http://127.0.0.1:18080/v1",
api_key="not-needed",
temperature=0,
max_tokens=256,
http_client=httpx.Client(trust_env=False),
http_async_client=httpx.AsyncClient(trust_env=False),
)
agent = create_agent(
model=model,
tools=[load_user_name, greet_user],
system_prompt="你是祝福助手。",
context_schema=UserContext,
state_schema=CustomState,
)
# ---------------------------------------------------------------------------
# 第 1 步:正常 LLM 交互 —— Agent 自动追加 messages(与 02 相同)
# ---------------------------------------------------------------------------
state = agent.invoke(
{"messages": [{"role": "user", "content": "请给当前用户送上一句祝福语。"}]},
context={"user_name": "小明"},
)
print_state("1. 首次 invoke 后(LLM + 工具自动追加 messages)", state)
# 找到第一条 ToolMessage 和最后一条 AIMessage,供后续修改 / 删除演示。
tool_message = next(m for m in state["messages"] if isinstance(m, ToolMessage))
final_ai_message = next(
m for m in reversed(state["messages"]) if isinstance(m, AIMessage)
)
messages = state["messages"]
edit_log = list(state.get("edit_log") or [])
# ---------------------------------------------------------------------------
# 第 2 步:追加 —— 新 ID 的消息会 append 到末尾
# ---------------------------------------------------------------------------
messages = add_messages(
messages,
[HumanMessage(content="[系统备注] 已记录本次祝福。", id="note-append")],
)
edit_log.append("追加 note-append")
print_state("2. 追加一条 HumanMessage(新 ID)", {**state, "messages": messages, "edit_log": edit_log})
# ---------------------------------------------------------------------------
# 第 3 步:修改 —— 相同 ID 会替换原消息,而不是再追加一条
# ---------------------------------------------------------------------------
messages = add_messages(
messages,
[
AIMessage(
content=final_ai_message.content + "(测试更新)",
id=final_ai_message.id,
)
],
)
edit_log.append(f"修改 AIMessage id={final_ai_message.id}")
print_state("3. 用相同 ID 替换最终 AIMessage", {**state, "messages": messages, "edit_log": edit_log})
# ---------------------------------------------------------------------------
# 第 4 步:删除 —— RemoveMessage 按 ID 移除(不能 del messages[i])
# ---------------------------------------------------------------------------
messages = add_messages(messages, [RemoveMessage(id=tool_message.id)])
edit_log.append(f"删除 ToolMessage id={tool_message.id}")
print_state("4. 删除中间 ToolMessage", {**state, "messages": messages, "edit_log": edit_log})
# ---------------------------------------------------------------------------
# 第 5 步:用修剪后的 state 再次 invoke,LLM 只能看到剩余 messages
# ---------------------------------------------------------------------------
state_after_edit = {
"messages": messages,
"user_name": state.get("user_name"),
"edit_log": edit_log,
}
follow_up = agent.invoke(
{
**state_after_edit,
"messages": add_messages(
messages,
[HumanMessage(content="刚才祝福的是哪个用户?", id="user-followup")],
),
},
context={"user_name": "小明"},
)
print_state("5. 携带修剪后的历史再次 invoke", follow_up)
print(f"\n最终回答:{follow_up['messages'][-1].content}")
运行:
python langgraph/p08_agent_state/03_message_update_and_remove.py
真实输出如下:
============================================================
1. 首次 invoke 后(LLM + 工具自动追加 messages)
user_name: 小明
edit_log: []
messages:
3fe5b0cd-bc1c-473a-bfd7-364f1b580194 | HumanMessage | 请给当前用户送上一句祝福语。
lc_run--019f9f22-ed2f-7f82-8033-7bc6c3234046-0 | AIMessage | [tool_calls: load_user_name, greet_user]
abad69f4-27b1-4133-9205-1b05f4644a19 | ToolMessage | 已加载用户 小明。[后面测试删除]
c57959ab-1feb-4f64-866f-36981281a5d9 | ToolMessage | 请先调用 load_user_name。
lc_run--019f9f22-f6b7-7bd1-9ed9-d6f6f781e001-0 | AIMessage | 已加载用户 小明。祝你每天都有好心情!
============================================================
2. 追加一条 HumanMessage(新 ID)
user_name: 小明
edit_log: ['追加 note-append']
messages:
3fe5b0cd-bc1c-473a-bfd7-364f1b580194 | HumanMessage | 请给当前用户送上一句祝福语。
lc_run--019f9f22-ed2f-7f82-8033-7bc6c3234046-0 | AIMessage | [tool_calls: load_user_name, greet_user]
abad69f4-27b1-4133-9205-1b05f4644a19 | ToolMessage | 已加载用户 小明。[后面测试删除]
c57959ab-1feb-4f64-866f-36981281a5d9 | ToolMessage | 请先调用 load_user_name。
lc_run--019f9f22-f6b7-7bd1-9ed9-d6f6f781e001-0 | AIMessage | 已加载用户 小明。祝你每天都有好心情!
note-append | HumanMessage | [系统备注] 已记录本次祝福。
============================================================
3. 用相同 ID 替换最终 AIMessage
user_name: 小明
edit_log: ['追加 note-append', '修改 AIMessage id=lc_run--019f9f22-f6b7-7bd1-9ed9-d6f6f781e001-0']
messages:
3fe5b0cd-bc1c-473a-bfd7-364f1b580194 | HumanMessage | 请给当前用户送上一句祝福语。
lc_run--019f9f22-ed2f-7f82-8033-7bc6c3234046-0 | AIMessage | [tool_calls: load_user_name, greet_user]
abad69f4-27b1-4133-9205-1b05f4644a19 | ToolMessage | 已加载用户 小明。[后面测试删除]
c57959ab-1feb-4f64-866f-36981281a5d9 | ToolMessage | 请先调用 load_user_name。
lc_run--019f9f22-f6b7-7bd1-9ed9-d6f6f781e001-0 | AIMessage | 已加载用户 小明。祝你每天都有好心情!(测试更新)
note-append | HumanMessage | [系统备注] 已记录本次祝福。
============================================================
4. 删除中间 ToolMessage
user_name: 小明
edit_log: ['追加 note-append', '修改 AIMessage id=lc_run--019f9f22-f6b7-7bd1-9ed9-d6f6f781e001-0', '删除 ToolMessage id=abad69f4-27b1-4133-9205-1b05f4644a19']
messages:
3fe5b0cd-bc1c-473a-bfd7-364f1b580194 | HumanMessage | 请给当前用户送上一句祝福语。
lc_run--019f9f22-ed2f-7f82-8033-7bc6c3234046-0 | AIMessage | [tool_calls: load_user_name, greet_user]
c57959ab-1feb-4f64-866f-36981281a5d9 | ToolMessage | 请先调用 load_user_name。
lc_run--019f9f22-f6b7-7bd1-9ed9-d6f6f781e001-0 | AIMessage | 已加载用户 小明。祝你每天都有好心情!(测试更新)
note-append | HumanMessage | [系统备注] 已记录本次祝福。
============================================================
5. 携带修剪后的历史再次 invoke
user_name: 小明
edit_log: ['追加 note-append', '修改 AIMessage id=lc_run--019f9f22-f6b7-7bd1-9ed9-d6f6f781e001-0', '删除 ToolMessage id=abad69f4-27b1-4133-9205-1b05f4644a19']
messages:
3fe5b0cd-bc1c-473a-bfd7-364f1b580194 | HumanMessage | 请给当前用户送上一句祝福语。
lc_run--019f9f22-ed2f-7f82-8033-7bc6c3234046-0 | AIMessage | [tool_calls: load_user_name, greet_user]
c57959ab-1feb-4f64-866f-36981281a5d9 | ToolMessage | 请先调用 load_user_name。
lc_run--019f9f22-f6b7-7bd1-9ed9-d6f6f781e001-0 | AIMessage | 已加载用户 小明。祝你每天都有好心情!(测试更新)
note-append | HumanMessage | [系统备注] 已记录本次祝福。
user-followup | HumanMessage | 刚才祝福的是哪个用户?
lc_run--019f9f22-fa5f-7db0-b9d0-6ef457a9c7bb-0 | AIMessage | 刚才祝福的用户是小明。
最终回答:刚才祝福的用户是小明。
需要注意,当前 Command 构造函数没有 delete 参数。删除消息不是把 update 改成 delete,而是向 messages 提交一个带目标 ID 的 RemoveMessage,再由 add_messages 完成删除。
6. AgentState 与记忆存储的边界
State 描述 Agent 当前拥有哪些数据,以及节点更新数据时应该怎样合并。它解决的是运行过程中的数据传递问题。
但是下面这段代码连续执行两次时:
agent.invoke(input, context={"user_name": "小明"})
agent.invoke(input, context={"user_name": "小红"})
如果没有配置 Checkpointer,两次调用会创建相互独立的 State。第二次调用不会自动得到第一次调用的消息或 user_name。
后续记忆文章会继续介绍:
- Checkpointer 如何按 Thread 保存短期状态。
- 开发环境和 PostgreSQL 如何持久化 Checkpoint。
- Store 如何保存跨 Thread 使用的长期数据。
这些能力建立在 State 之上,但不属于 AgentState Schema 本身。
7. 小结
默认 AgentState 已经负责管理 Agent 的消息历史。用户消息、工具调用、工具结果和最终回答都会依次进入 messages,add_messages 负责合并这些更新。
业务需要额外的运行状态时,可以继承 AgentState 增加字段,再通过 state_schema 注册。Tool 使用 ToolRuntime.state 读取状态,使用 Command(update=…) 修改状态。
本次实测中,Qwen3 依次调用两个 Tool,将 Context 中的用户名写入 CustomState,再从 State 读取用户名生成祝福。小明和小红两次调用的状态完全隔离。