现在位置: 首页 > LangChain 教程 > 正文

LangChain AgentState 状态管理

Agent 在运行过程中需要维护状态,例如对话消息、工具执行结果、学习进度以及结构化输出。

在 LangChain 1.x 中,AgentState 负责定义 Agent 的状态结构,而 LangGraph 负责状态的更新、合并和管理。

本章节介绍 AgentState 的默认字段、Reducer 合并机制、Middleware 流程控制、自定义状态,以及如何通过 Command 和 Checkpointer 管理状态。


一、AgentState 基本结构

AgentState 基于 Python 的 TypedDict,主要包含以下三个字段:

字段必填说明
messages是消息历史,使用 add_messages 合并
jump_to否Middleware 的瞬态流程控制字段
structured_response否结构化输出结果,不作为普通输入字段

可以通过 Python 查看当前版本的定义:

实例

from langchain.agents import AgentState

for name, annotation in AgentState.__annotations__.items():
    print(name, annotation)

print("必填字段:", AgentState.__required_keys__)
print("可选字段:", AgentState.__optional_keys__)

其中最常用的是 messages,保存用户消息、AI 回复以及工具执行结果。

注意:AgentState 只是状态结构定义,并不意味着数据已经保存到数据库。是否能够跨调用恢复,需要配置 Checkpointer。


二、messages:消息历史与 Reducer

messages 使用 add_messages Reducer,负责合并新旧消息。

它支持三种主要操作:

  • 追加:新消息 ID 不存在时,添加到消息列表。
  • 替换:新消息 ID 与已有消息相同时,替换对应消息。
  • 删除:通过 RemoveMessage 删除指定 ID 的消息。

1、追加消息

实例

from langchain.messages import HumanMessage, AIMessage
from langgraph.graph.message import add_messages

existing = [
    HumanMessage(content="你好", id="1"),
    AIMessage(content="你好!", id="2")
]

new = [HumanMessage(content="介绍 Python", id="3")]
result = add_messages(existing, new)

print("合并前:", len(existing))
print("合并后:", len(result))
for msg in result:
    print(msg.id, msg.type, msg.content)

输出:

合并前: 2
合并后: 3
1 human 你好
2 ai 你好!
3 human 介绍 Python

2、相同 ID 替换消息

实例

from langchain.messages import AIMessage
from langgraph.graph.message import add_messages

old = [AIMessage(content="原来的回答", id="msg_1")]
new = [AIMessage(content="修改后的回答", id="msg_1")]

result = add_messages(old, new)
print(len(result), result[0].content)

输出:

1 修改后的回答

这里比较的是消息 id,而不是消息内容或消息类型。

3、RemoveMessage 删除消息

实例

from langchain.messages import HumanMessage, AIMessage, RemoveMessage
from langgraph.graph.message import add_messages

messages = [
    HumanMessage(content="你好", id="1"),
    AIMessage(content="你好!", id="2"),
    HumanMessage(content="介绍 Python", id="3")
]

result = add_messages(messages, [RemoveMessage(id="2")])
for msg in result:
    print(msg.id, msg.content)

输出:

1 你好
3 介绍 Python

注意:删除 Tool Calling 消息时,应同时考虑 AIMessage 和 ToolMessage 的关联关系,避免产生不符合模型 API 要求的消息历史。


三、jump_to:Middleware 流程控制

jump_to 用于让 Middleware 在特定阶段控制 Agent 的下一步执行方向。

值说明
"model"转向模型执行流程
"tools"转向工具节点,需要合法工具调用上下文
"end"结束当前主循环或进入相应的结束钩子

jump_to 属于瞬态状态,用于控制执行流程,不适合保存长期业务数据。

实例:提前结束 Agent

下面使用 @before_model 拦截一个演示关键词,展示如何跳过模型调用。

实例

from dotenv import load_dotenv
from langchain.agents import create_agent
from langchain.agents.middleware import before_model, AgentState
from langchain.messages import AIMessage
from langgraph.runtime import Runtime

load_dotenv()

@before_model(can_jump_to=["end"])
def check_question(state: AgentState, runtime: Runtime) -> dict | None:
    """拦截演示关键词。"""
    users = [m for m in state["messages"] if m.type == "human"]
    if users and "BLOCK_DEMO" in str(users[-1].content):
        return {
            "messages": [AIMessage(content="请求已被演示规则拦截。")],
            "jump_to": "end"
        }
    return None

agent = create_agent(
    model="deepseek:deepseek-flash",
    tools=[],
    middleware=[check_question],
    system_prompt="你是一名中文编程助手。"
)

result = agent.invoke({"messages": [{"role": "user", "content": "BLOCK_DEMO"}]})
print(result["messages"][-1].content)

输出:

请求已被演示规则拦截。

这里的中间件在模型执行前检查消息,如果符合条件,就添加一条 AIMessage 并跳转到结束流程。

注意:jump_to="tools" 不能凭空执行任意 Python 函数;工具节点仍然需要有效的 Tool Calling 请求。


四、structured_response:结构化输出

如果通过 create_agent() 的 response_format 参数设置结构化输出,Agent 会将解析结果存入 structured_response。

这个字段通常不需要用户在输入 State 时提供,但可以从执行完成后的返回值中获取。

实例:推荐课程

实例

from pydantic import BaseModel, Field
from langchain.agents import create_agent

class CourseRecommendation(BaseModel):
    """课程推荐。"""
    course: str = Field(description="课程名称")
    reason: str = Field(description="推荐理由")
    level: str = Field(description="学习难度")

agent = create_agent(
    model="deepseek:deepseek-flash",
    tools=[],
    response_format=CourseRecommendation,
    system_prompt="你是编程学习顾问,请根据用户需求推荐学习方向。"
)

result = agent.invoke({
    "messages": [{"role": "user", "content": "我是零基础,推荐一个编程学习方向。"}]
})

rec = result["structured_response"]
print("课程:", rec.course)
print("理由:", rec.reason)
print("难度:", rec.level)

如果模型正确完成结构化输出,可能得到:

课程: Python 基础教程
理由: Python 语法简洁,适合零基础学习
难度: 入门

返回的是符合定义的 Pydantic 数据结构,而不是必须自行从自然语言中提取字段。

具体的结构化输出机制由模型及 LangChain 集成决定。模型不支持、Schema 不兼容或解析失败时,可能抛出异常。


五、自定义 AgentState

除了默认字段,还可以通过继承 AgentState 添加业务状态。

例如,学习助手可以保存学习等级、已完成主题和当前目标。

实例

from typing_extensions import NotRequired
from langchain.agents import AgentState

class LearningAgentState(AgentState):
    user_level: NotRequired[str]
    completed_topics: NotRequired[list[str]]
    current_goal: NotRequired[str]

其中 NotRequired 表示初始状态可以省略该字段,但不会自动设置默认值。

读取时建议使用:

level = state.get("user_level", "入门")
topics = state.get("completed_topics", [])

创建 Agent 时指定:

agent = create_agent(
    model="deepseek:deepseek-flash",
    tools=[],
    state_schema=LearningAgentState
)

注意,自定义 State 仅负责描述字段,工具需要通过正式的状态更新机制才能修改这些字段。


六、ToolRuntime:读取 Agent 状态

LangChain 1.x 推荐通过 ToolRuntime 访问当前 Agent State。

例如,可以在工具中读取学习等级和已完成主题:

实例

from langchain.tools import tool, ToolRuntime

@tool
def view_progress(runtime: ToolRuntime) -> str:
    """查看当前学习进度。"""
    level = runtime.state.get("user_level", "入门")
    topics = runtime.state.get("completed_topics", [])
    return f"等级:{level};已完成:{'、'.join(topics) if topics else '无'}"

runtime 由 Agent 执行框架注入,模型不需要提供该参数。

因此,不应假设直接执行 view_progress.invoke({}) 就能获得完整 Agent State。

如果需要修改 State,应使用下一节介绍的 Command。


七、Command:更新 AgentState

直接修改 runtime.state 中的字典或列表,并不是可靠的状态更新方式。

推荐使用 Command(update=...) 提交修改。

实例:记录学习主题

实例

from langchain.tools import tool, ToolRuntime
from langchain.messages import ToolMessage
from langgraph.types import Command

@tool
def complete_topic(topic: str, runtime: ToolRuntime) -> Command:
    """将指定学习主题标记为已完成。"""
    topics = list(runtime.state.get("completed_topics", []))
    if topic not in topics:
        topics.append(topic)

    return Command(update={
        "completed_topics": topics,
        "messages": [ToolMessage(
            content=f"已记录完成主题:{topic}",
            tool_call_id=runtime.tool_call_id
        )]
    })

这里通过 Command 同时更新两个字段:

  • completed_topics:保存新的学习主题列表。
  • messages:追加一条与当前调用 ID 关联的工具结果。

在 Agent Tool Calling 场景中,返回 Command 时应保持对应的 ToolMessage,避免破坏消息协议。

注意:这里采用的是列表整体更新。如果多个工具并行修改同一字段,需要额外设计 Reducer 或顺序执行机制。


八、完整实例:学习进度 Agent

下面将自定义 State、ToolRuntime、Command 和 Checkpointer 结合起来,创建可以记录与查询学习进度的 Agent。

文件名:agent_state_demo.py

安装依赖:

pip install -U langchain langchain-deepseek langgraph python-dotenv

配置 .env:

DEEPSEEK_API_KEY=your_deepseek_api_key

完整实例

from typing_extensions import NotRequired
from dotenv import load_dotenv
from langchain.agents import create_agent, AgentState
from langchain.tools import tool, ToolRuntime
from langchain.messages import ToolMessage
from langgraph.types import Command
from langgraph.checkpoint.memory import InMemorySaver

load_dotenv()

class LearningState(AgentState):
    user_level: NotRequired[str]
    completed_topics: NotRequired[list[str]]

@tool
def complete_topic(topic: str, runtime: ToolRuntime) -> Command:
    """记录一个已完成的学习主题。"""
    topics = list(runtime.state.get("completed_topics", []))
    if topic not in topics:
        topics.append(topic)
    return Command(update={
        "completed_topics": topics,
        "messages": [ToolMessage(content=f"已记录:{topic}", tool_call_id=runtime.tool_call_id)]
    })

@tool
def view_progress(runtime: ToolRuntime) -> str:
    """查询当前学习进度。"""
    level = runtime.state.get("user_level", "入门")
    topics = runtime.state.get("completed_topics", [])
    return f"等级:{level};已完成:{'、'.join(topics) if topics else '无'}"

agent = create_agent(
    model="deepseek:deepseek-flash",
    tools=[complete_topic, view_progress],
    state_schema=LearningState,
    checkpointer=InMemorySaver(),
    system_prompt="你是学习助手。用户要求记录主题时调用 complete_topic,查询进度时调用 view_progress。"
)

config = {"configurable": {"thread_id": "learning_demo_001"}}

# 第一轮:记录学习主题
result = agent.invoke({
    "messages": [{"role": "user", "content": "我学完 Python 基础了,请记录。"}],
    "user_level": "入门",
    "completed_topics": ["HTML 基础"]
}, config=config)
print("第一轮状态:", result.get("completed_topics"))

# 第二轮:读取此前保存的状态
result = agent.invoke({
    "messages": [{"role": "user", "content": "查询我已完成的主题。"}]
}, config=config)
print("第二轮状态:", result.get("completed_topics"))
print("AI:", result["messages"][-1].content)

# 读取 Checkpointer 保存的状态
snapshot = agent.get_state(config)
print("已保存:", snapshot.values.get("completed_topics", []))

如果模型正确调用工具,第一轮状态应包含:

['HTML 基础', 'Python 基础']

第二轮使用相同的 thread_id,可以读取此前保存的状态。

这里的 InMemorySaver 只保存进程内存。程序退出后,状态通常会丢失。

注意:模型是否调用工具并不保证。需要强制记录业务数据时,应由确定性的应用逻辑执行,而不是只依赖模型选择工具。


九、购物车 Agent:状态管理实战

购物车是另一个常见的自定义 State 场景。

例如,需要保存购物车商品列表,并根据商品目录计算总价。

下面使用模拟商品数据,不创建真实订单,也不进行支付。

为了防止模型随意修改价格,商品价格由程序内部目录决定,而不是由模型传入。

实例

from typing_extensions import NotRequired
from langchain.agents import create_agent, AgentState
from langchain.tools import tool, ToolRuntime
from langchain.messages import ToolMessage
from langgraph.types import Command

PRODUCTS = {"Python 教程": 49.90, "Java 教程": 39.90, "HTML 教程": 29.90}

class ShoppingState(AgentState):
    cart: NotRequired[list[str]]

@tool
def add_to_cart(item: str, runtime: ToolRuntime) -> Command:
    """将模拟商品加入购物车。"""
    cart = list(runtime.state.get("cart", []))
    if item in PRODUCTS:
        cart.append(item)
        message = f"已加入:{item}"
    else:
        message = f"商品不存在:{item}"

    return Command(update={
        "cart": cart,
        "messages": [ToolMessage(content=message, tool_call_id=runtime.tool_call_id)]
    })

@tool
def view_cart(runtime: ToolRuntime) -> str:
    """查看购物车及总价。"""
    cart = runtime.state.get("cart", [])
    total = sum(PRODUCTS[item] for item in cart)
    return f"购物车:{'、'.join(cart) or '空'};总价:¥{total:.2f}"

agent = create_agent(
    model="deepseek:deepseek-flash",
    tools=[add_to_cart, view_cart],
    state_schema=ShoppingState,
    system_prompt="你是模拟购物车助手。商品价格以工具目录为准,不创建真实订单。"
)

result = agent.invoke({
    "messages": [{"role": "user", "content": "把 Python 教程加入模拟购物车,并查看购物车。"}],
    "cart": []
})

print("购物车:", result.get("cart", []))
print("AI:", result["messages"][-1].content)

如果 Agent 成功执行相关工具,购物车状态为:

['Python 教程']

工具计算出的金额为:

购物车:Python 教程;总价:¥49.90

这里没有让模型提供 price 参数,而是由程序读取预先定义的模拟商品目录。

注意:如果模型并行调用多个修改购物车的工具,可能产生并发状态冲突。生产级购物车应结合数据库事务或专门的状态更新机制管理。


十、自定义 Reducer

AgentState 的自定义字段默认通常采用覆盖式更新。

如果需要合并多个更新,可以通过 Annotated 指定 Reducer。

实例:追加学习日志

实例

import operator
from typing import Annotated
from typing_extensions import NotRequired
from langchain.agents import AgentState

class LearningState(AgentState):
    learning_logs: NotRequired[Annotated[list[str], operator.add]]

当原有状态是:

["学习 HTML"]

新更新为:

["学习 Python"]

Reducer 合并后可以得到:

["学习 HTML", "学习 Python"]

需要注意,operator.add 只是列表拼接,不会自动去重。

如果需要唯一记录、严格顺序或防止重复执行,应设计专门的 Reducer 或在业务层处理。


十一、state_schema 与 Middleware state_schema

除了通过 create_agent(state_schema=...) 定义全局业务状态,Middleware 也可以声明自己的扩展字段。

方式适用场景
create_agent(state_schema=...)Agent 共用的业务状态
Middleware state_schema模型调用计数、内部流程标记等中间件状态

不能假设 create_agent 中声明的同名字段总能覆盖 Middleware 中的字段定义。两个 Schema 的类型和 Reducer 必须保持兼容。

实例:中间件统计模型调用次数

实例

from typing_extensions import NotRequired
from langchain.agents import create_agent
from langchain.agents.middleware import AgentState, after_model
from langgraph.runtime import Runtime

class TrackingState(AgentState):
    model_call_count: NotRequired[int]

@after_model(state_schema=TrackingState)
def count_calls(state: TrackingState, runtime: Runtime) -> dict:
    """累计模型调用次数。"""
    return {"model_call_count": state.get("model_call_count", 0) + 1}

agent = create_agent(
    model="deepseek:deepseek-flash",
    tools=[],
    middleware=[count_calls]
)

result = agent.invoke({"messages": [{"role": "user", "content": "你好"}]})
print("模型调用次数:", result.get("model_call_count"))

对于只调用一次模型的简单任务,通常输出:

模型调用次数: 1

如果 Agent 需要多轮模型调用,该计数也会增加。


十二、Checkpointer:保存 AgentState

默认情况下,Agent 每次调用不一定保留上一次的会话状态。

如果需要跨多次调用恢复消息和自定义字段,可以配置 Checkpointer,并通过 thread_id 区分会话。

实例:保存多轮对话

实例

from langchain.agents import create_agent
from langgraph.checkpoint.memory import InMemorySaver

agent = create_agent(
    model="deepseek:deepseek-flash",
    tools=[],
    checkpointer=InMemorySaver()
)

config = {"configurable": {"thread_id": "session_001"}}

agent.invoke({
    "messages": [{"role": "user", "content": "我正在学习 Python。"}]
}, config=config)

result = agent.invoke({
    "messages": [{"role": "user", "content": "我刚才说在学习什么?"}]
}, config=config)

print(result["messages"][-1].content)

snapshot = agent.get_state(config)
print("消息数量:", len(snapshot.values["messages"]))

这里使用相同的 thread_id,因此第二轮可以读取此前保存的会话消息。

但 InMemorySaver 不是磁盘持久化方案。

需要在服务重启后继续保留数据时,应使用数据库支持的 Checkpointer。


十三、AgentState 与 Store 的区别

维度AgentStateStore
主要用途当前线程的消息与任务状态用户偏好、长期记忆和共享记录
跨调用通过 Checkpointer 恢复通过 Store 读写
数据组织状态字段Namespace 与 Key
持久化取决于 Checkpointer取决于 Store 实现

例如,一个学习助手可以这样设计:

AgentState:
  当前对话历史
  当前学习章节
  本轮任务进度

Store:
  用户已完成的全部课程
  长期学习偏好
  跨会话学习记录

对于需要长期保存的真实业务数据,通常应使用持久化 Store 或数据库,而不是仅依赖 AgentState。


十四、常见问题

1、AgentState 是普通字典吗?

AgentState 使用 TypedDict 描述状态,运行时通常以字典形式传递,但实际合并行为还受到 Reducer 和 LangGraph 执行机制控制。

2、add_messages 是否永远只追加消息?

不是。它还可以根据相同 ID 替换消息,以及使用 RemoveMessage 删除消息。

3、jump_to="tools" 能直接执行某个 Python 函数吗?

不能。它只负责流程路由,工具节点仍需要合法的工具调用请求。

4、structured_response 为什么不存在?

通常是没有配置 response_format,或结构化输出没有成功完成。

5、为什么直接修改 runtime.state 没有效果?

因为这不是可靠的图状态更新方式。工具应使用 Command(update=...) 提交状态变更。

6、Command 需要 ToolMessage 吗?

在 Agent 工具调用场景中,应提供与当前 Tool Call ID 对应的 ToolMessage,保持工具请求与工具响应正确关联。

7、自定义 State 可以省略字段吗?

可以使用 NotRequired 定义可选字段,但读取时需要自行处理默认值。

8、如何解决多个工具同时更新状态的冲突?

可以使用合适的 Reducer、限制并行执行或通过确定性的业务流程处理。

9、State 会自动保存到数据库吗?

不会。需要配置 Checkpointer,并选择适合的持久化实现。

10、AgentState 能保存用户长期学习进度吗?

可以配合 Checkpointer 保存线程状态。但对于需要跨多个会话共享的学习进度,更适合使用 Store 或业务数据库。


总结

AgentState 是 LangChain Agent 状态管理的核心。

本章节主要介绍了:

  • messages:保存消息历史。
  • add_messages:追加、替换和删除消息。
  • jump_to:控制 Middleware 流程跳转。
  • structured_response:保存结构化响应。
  • state_schema:扩展自定义状态字段。
  • ToolRuntime:在工具中访问 State。
  • Command:提交状态更新。
  • Reducer:定义状态合并规则。
  • Checkpointer:保存和恢复会话状态。

可以简单记住:

读取状态:runtime.state
更新状态:Command(update={...})
保存会话:checkpointer + thread_id
长期数据:Store