通过前面的文章,我们已经理解了 Agent 运行时上下文、长短期记忆、工具、Middleware 等内容。要进一步理解 Agent 的执行过程,还有一些细节需要掌握。

比如,一次 Agent 调用中,聊天消息保存在哪里?用户 ID 应该放进 State 还是 Context?config 和 context 有什么区别?工具为什么既能读取 State,又能拿到 Context?Runtime 和 ToolRuntime 又分别解决什么问题?

这些概念放在一起时很容易混淆。本文就围绕一次 Agent 执行,把 State、Context、RunnableConfig、Runtime 和 ToolRuntime 之间的关系梳理清楚。

一、Agent 执行中的数据

我们先看一个具体场景。

假设我们正在开发一个客服 Agent。用户会进行多轮对话,Agent 可以查询订单、修改地址,还需要根据当前登录用户访问后台系统。

在这个 Agent 的一次执行过程中,可能同时出现下面这些数据:

代码片段Text
用户之前发送的消息
模型之前的回复
当前选中的订单
当前处理阶段
重试次数

当前登录用户的 user_id
tenant_id
数据库连接
业务 Service

thread_id
tags
metadata
callbacks

长期保存的用户偏好
当前 tool_call_id

这些数据虽然都和一次 Agent 调用有关,但它们的来源、生命周期和用途不同。

例如,聊天记录(messages)会随着执行不断变化,而且可能需要在下一轮对话恢复。

user_id 通常在调用 Agent 之前已经确定,在执行过程中不会改变。

thread_id 主要用来告诉运行系统“这次调用属于哪个 Thread”,它和聊天内容本身没有关系。

数据库连接则只是运行时依赖。

针对上面所有涉及到的信息,可以先建立这样一个大致关系:

代码片段Text
Agent Invocation
│
├── State
│   ├── messages
│   └── 自定义执行状态
│
├── Context
│   ├── user_id
│   ├── tenant_id
│   └── 运行依赖
│
├── RunnableConfig
│   ├── configurable
│   ├── tags
│   ├── metadata
│   ├── callbacks
│   └── ...
│
├── Runtime
│   ├── context
│   ├── store
│   ├── stream_writer
│   ├── execution_info
│   └── server_info
│
└── Tool Execution
    │
    └── ToolRuntime
        ├── state
        ├── context
        ├── store
        ├── config
        ├── tool_call_id
        └── ...

这里最容易混淆的是 State、Context 和 RunnableConfig。

我们先把这三者区分开,后面的 Runtime 和 ToolRuntime 就比较容易理解了。

二、State 保存执行状态

状态(State),指 Agent 执行过程中持续传递,并且可能发生变化的数据。

LangChain 的 create_agent 底层运行在 LangGraph Runtime 之上。模型调用、工具调用、Middleware 执行,都可能读取当前 State,并产生新的 State Update。

Agent 默认使用 AgentState,其中最重要的字段就是:

代码片段Python
messages

平时调用 Agent 时,我们经常这样写:

代码片段Python
agent.invoke({
    "messages": [
        {"role": "user", "content": "帮我查询订单"}
    ]
})

这里传入的 messages 不只是一次模型调用的 Prompt,它同时也是 Agent State 的输入。

Agent 后续调用模型、执行工具时,消息会继续加入其中。

State 不只有 messages

实际项目中,经常还需要保存一些业务执行状态,例如:

代码片段Text
messages
current_step
selected_order_id
retry_count
approval_status

这时可以扩展 AgentState:

代码片段Python
from langchain.agents import AgentState
 
 
class CustomState(AgentState):
    current_step: str
    retry_count: int

然后创建 Agent:

代码片段Python
from langchain.agents import create_agent
 
agent = create_agent(
    model="openai:gpt-5.5",
    tools=[],
    state_schema=CustomState,
)

调用时可以一起传入这些字段:

代码片段Python
result = agent.invoke({
    "messages": [
        {"role": "user", "content": "继续处理"}
    ],
    "current_step": "checking_order",
    "retry_count": 0,
})

current_step 和 retry_count 会和 messages 一样,成为 Agent 当前状态的一部分。

State 为什么需要单独存在

Agent 通常不是一次模型调用,而是一个连续执行过程:

代码片段Text
User
  ↓
Model
  ↓
Tool
  ↓
Model
  ↓
Tool
  ↓
Model
  ↓
Result

后面的步骤经常需要知道前面发生了什么。

例如第一次模型调用生成了 Tool Call,工具执行完成后,第二次模型调用需要读取 Tool Result;某个 Middleware 还可能根据之前的重试次数判断是否继续执行。

这些信息如果只保存在某个函数的局部变量里,就很难在不同节点之间统一传递。

State 提供的就是这一整个过程中共享的执行数据。

如果配置了 Checkpointer,State 还可以按 Thread 保存。下一次使用相同 thread_id 调用时,之前保存的状态可以继续被使用。

三、State 会持续更新

前面已经提到,Agent 执行过程中,Middleware 和 Tool 都可能产生 State Update。

比如增加一个 request_count,记录模型执行次数:

代码片段Python
from langchain.agents import AgentState, create_agent
from langchain.agents.middleware import before_model
from langgraph.runtime import Runtime
 
 
class CustomState(AgentState):
    request_count: int
 
 
@before_model
def count_request(
    state: CustomState,
    runtime: Runtime,
) -> dict:
    return {
        "request_count": state.get("request_count", 0) + 1
    }
 
 
agent = create_agent(
    model="openai:gpt-5.5",
    tools=[],
    state_schema=CustomState,
    middleware=[count_request],
)
 
result = agent.invoke({
    "messages": [
        {"role": "user", "content": "你好"}
    ],
    "request_count": 0,
})
 
print(result["request_count"])

运行结果

代码片段Text
1

这里 before_model 先读取当前 State,然后返回:

代码片段Python
{
    "request_count": 1
}

这个返回值会作为状态更新合并回 State。

所以,我们可以看出:State 即为Agent 执行过程中持续被读取、修改和向后传递的数据。

实际开发中,可以用几个问题来判断某个值是否适合进入 State:

代码片段Text
它是否描述当前执行进度?
后续节点是否需要读取?
执行过程中是否可能变化?
之后是否可能需要恢复?

如果这些条件成立,通常就适合放进 State。

但有些数据虽然很多节点都需要,却并不属于执行状态。

最常见的就是当前用户身份。

四、Context 描述调用环境

运行时上下文(Runtime Context)描述的是:

这一次 Agent 是在什么业务环境下运行。

例如:

代码片段Text
user_id
tenant_id
权限信息
数据库连接
API Client
业务 Service
Feature Flag

这些信息通常不是 Agent 执行过程中产生的,而是在调用 Agent 之前就已经确定。

例如一个 Web 请求可能先经过认证:

代码片段Text
HTTP Request
    ↓
Authentication
    ↓
得到 user_id
    ↓
调用 Agent

这里的 user_id 来自应用系统,而不是 Agent 推理出来的结果。

因此,可以定义一个 Context:

代码片段Python
from dataclasses import dataclass
 
 
@dataclass
class Context:
    user_id: str
    tenant_id: str

创建 Agent 时声明:

代码片段Python
from langchain.agents import create_agent
 
agent = create_agent(
    model="openai:gpt-5.5",
    tools=[],
    context_schema=Context,
)

调用时通过单独的 context 参数传入:

代码片段Python
result = agent.invoke(
    {
        "messages": [
            {"role": "user", "content": "查询我的账户"}
        ]
    },
    context=Context(
        user_id="user_123",
        tenant_id="tenant_a",
    ),
)

可以看到,接口本身已经把两种数据区分开了:

代码片段Python
agent.invoke(
    state_input,
    context=runtime_context,
)

前一个参数是 State 输入。

后面的 context 是本次调用的 Runtime Context。

我们再进一步对比一下 State 和 Context 的区别

例如:

代码片段Python
{
    "current_step": "waiting_payment",
    "retry_count": 2,
}

描述的是 Agent 当前执行到了哪里。

所以适合进入 State。

而:

代码片段Python
Context(
    user_id="user_123",
    tenant_id="tenant_a",
)

描述的是这次 Agent 以什么身份、在什么租户环境下运行。

所以适合进入 Context。

可以记住以下规则:

代码片段Text
State
执行过程中发生了什么

Context
执行发生在什么业务环境中

还有一个区别很重要。

State 可以由 Checkpointer 按 Thread 保存,而 Context 是每次 invocation 传入的运行时依赖。配置 Checkpointer 并不会自动把 Context 变成短期记忆。

五、Config 控制一次执行

除了 State 和 Context,调用 Agent 时还经常会看到另一个参数:

代码片段Python
config

例如短期记忆中常见的写法:

代码片段Python
config = {
    "configurable": {
        "thread_id": "user-1"
    }
}
 
agent.invoke(
    {
        "messages": [
            {"role": "user", "content": "我叫 Bob"}
        ]
    },
    config=config,
)

这里的 config 到底是什么?

当前 LangChain 中,更准确的类型名称是:

代码片段Python
RunnableConfig

RunnableConfig 定义在 langchain_core.runnables 中,用来配置一次 Runnable 执行。Agent 本身也是 Runnable,因此调用 invoke、stream 等方法时,都可以传入 config。

它常见的字段包括:

代码片段Text
configurable
tags
metadata
callbacks
run_name
run_id
max_concurrency
recursion_limit

例如:

代码片段Python
config = {
    "tags": ["customer-service"],
    "metadata": {
        "request_source": "web"
    },
    "configurable": {
        "thread_id": "user-1"
    }
}

这里其实包含了两类执行配置。

tags、metadata、callbacks 等主要用于 tracing、logging 和执行控制。

而:

代码片段Python
"configurable": {
    "thread_id": "user-1"
}

用于向已经声明为 configurable 的运行组件传递运行时配置。LangGraph 的 Checkpointer 就会通过这里的 thread_id 识别当前 Thread。

Config 和 Context 不是一回事

这两个对象很容易混淆,因为它们都是调用 invoke 时传入的。

例如:

代码片段Python
agent.invoke(
    input,
    config=config,
    context=context,
)

但它们的职责不同。

Context 更偏向业务运行环境:

代码片段Text
当前用户是谁
属于哪个租户
使用哪个数据库连接
注入哪个业务 Service

RunnableConfig 更偏向这一次 Runnable 怎样执行:

代码片段Text
属于哪个 thread_id
使用哪些 tags
附带哪些 metadata
使用哪些 callbacks
并发限制是多少
递归限制是多少

可以对比一下:

代码片段Text
Context
    ↓
业务依赖和调用环境

RunnableConfig
    ↓
Runnable 的执行配置

例如 user_id 最好不要因为 Config 支持 metadata 或 configurable,就全部塞进 Config。

如果它确实是 Tool、Middleware 需要使用的业务身份,更清晰的方式通常是放进 Runtime Context。

而 thread_id 则不同。

它不是 Agent 的业务状态,也不是业务依赖,而是运行系统识别 Thread 和 Checkpoint 的配置,因此放在 configurable 中。

六、Runtime 提供运行环境

前面已经分别介绍了 State、Context 和 Config。

接下来再看 Runtime。

Runtime 是 LangGraph 在 Agent 执行期间提供的运行环境对象,它既不是另一份状态,也不能替代 Config。它承载着执行过程中的上下文与工具调用能力,是 Agent 运行时的重要组成部分。

当前 Runtime 主要包含:

代码片段Text
context
store
stream_writer
execution_info
server_info

其中:

context 就是前面传入的 Runtime Context。

store 用于访问长期存储。

stream_writer 用于向 Custom Stream 输出数据。

execution_info 提供当前 Thread、Run、节点重试次数等执行信息。

server_info 则用于 LangGraph Server 环境,例如读取 assistant ID、graph ID 和认证用户信息。

例如 Middleware 可以直接访问 Runtime:

代码片段Python
from dataclasses import dataclass
 
from langchain.agents import AgentState, create_agent
from langchain.agents.middleware import before_model
from langgraph.runtime import Runtime
 
 
@dataclass
class Context:
    user_name: str
 
 
@before_model
def log_user(
    state: AgentState,
    runtime: Runtime[Context],
) -> None:
    print(runtime.context.user_name)
 
 
agent = create_agent(
    model="openai:gpt-5.5",
    tools=[],
    middleware=[log_user],
    context_schema=Context,
)
 
agent.invoke(
    {
        "messages": [
            {"role": "user", "content": "你好"}
        ]
    },
    context=Context(user_name="Bob"),
)

运行结果

代码片段Text
Bob

这里的两种访问方式正好反映了 State 和 Runtime 的职责:

代码片段Python
state["messages"]

读取的是当前执行状态。

代码片段Python
runtime.context.user_name

读取的是本次调用的业务上下文。

因此,在 Node 或 Node 风格 Middleware 中,经常会看到这样的函数签名:

代码片段Python
def node(
    state: AgentState,
    runtime: Runtime[Context],
):
    ...

State 提供“当前执行数据”。

Runtime 提供“当前运行环境”。

七、ToolRuntime 服务于工具

工具也需要访问 Agent 当前的运行环境。

例如:

代码片段Python
@tool
def get_order(order_id: str):
    ...

这里让模型提供:

代码片段Text
order_id

很合理,因为订单编号来自用户请求或模型判断。

但如果工具还需要:

代码片段Text
user_id
tenant_id
当前 State
当前 Tool Call ID
RunnableConfig
Store

就没有必要继续把这些字段暴露给模型。

否则工具可能变成:

代码片段Python
get_order(
    order_id,
    user_id,
    tenant_id,
    tool_call_id,
)

其中很多参数都不应该由模型决定。

LangChain 为工具提供了 ToolRuntime:

代码片段Python
from dataclasses import dataclass
 
from langchain.tools import ToolRuntime, tool
 
 
@dataclass
class Context:
    user_id: str
 
 
@tool
def get_account(
    runtime: ToolRuntime[Context],
) -> str:
    """Get the current user's account."""
 
    user_id = runtime.context.user_id
 
    return f"Account of {user_id}"

runtime 参数由运行系统自动注入,不会作为普通 Tool Argument 暴露给模型。

模型并不需要知道 user_id 从哪里来。

工具自己从:

代码片段Python
runtime.context.user_id

读取即可。

ToolRuntime 中有什么

ToolRuntime 可以访问:

代码片段Text
state
context
store
stream_writer
execution_info
server_info
config
tool_call_id

其中前几项和普通 Runtime 的运行能力有关。

但 ToolRuntime 还额外提供:

代码片段Text
state
config
tool_call_id

这些信息和当前 Tool 调用直接相关。

例如:

代码片段Python
runtime.state

可以读取当前 Agent State。

代码片段Python
runtime.context

可以读取 Runtime Context。

代码片段Python
runtime.config

得到当前执行的 RunnableConfig。

代码片段Python
runtime.tool_call_id

得到当前工具调用对应的 Tool Call ID。

所以可以这样理解:

代码片段Text
Runtime
    ↓
Node、Middleware 使用的运行环境

ToolRuntime
    ↓
Tool 使用的运行环境
并补充工具执行所需的信息

八、ToolRuntime 连接几类数据

ToolRuntime 很适合用来观察前面几个概念之间的关系。

例如:

代码片段Python
from dataclasses import dataclass
 
from langchain.agents import AgentState
from langchain.tools import ToolRuntime, tool
 
 
class CustomState(AgentState):
    selected_order_id: str
 
 
@dataclass
class Context:
    user_id: str
 
 
@tool
def inspect_current_order(
    runtime: ToolRuntime[Context],
) -> str:
    """Inspect the currently selected order."""
 
    user_id = runtime.context.user_id
    order_id = runtime.state["selected_order_id"]
 
    thread_id = runtime.config.get(
        "configurable", {}
    ).get("thread_id")
 
    return (
        f"user={user_id}, "
        f"order={order_id}, "
        f"thread={thread_id}"
    )

这里一次读取了三类数据。

代码片段Python
runtime.state["selected_order_id"]

来自 State。

它表示 Agent 当前正在处理哪个订单。

代码片段Python
runtime.context.user_id

来自 Context。

它表示这次调用对应哪个登录用户。

代码片段Python
runtime.config

则是 RunnableConfig。

其中可以读取当前执行传入的 Config,例如 thread_id。ToolRuntime 明确提供 config,类型就是当前执行的 RunnableConfig。

三者虽然最终都能被 Tool 读取,但来源和用途并不相同:

代码片段Text
selected_order_id
    ↓
执行过程产生
    ↓
State

user_id
    ↓
调用前由业务系统提供
    ↓
Context

thread_id
    ↓
控制 Runnable / Checkpointer 执行
    ↓
RunnableConfig

九、工具也可以修改 State

ToolRuntime 不只用于读取数据。

如果工具执行结果会影响后续 Agent 执行,可以通过 Command 更新 State。

例如:

代码片段Python
from langchain.messages import ToolMessage
from langchain.tools import ToolRuntime, tool
from langgraph.types import Command
 
 
@tool
def set_language(
    language: str,
    runtime: ToolRuntime,
) -> Command:
    """Set the preferred response language."""
 
    return Command(
        update={
            "preferred_language": language,
            "messages": [
                ToolMessage(
                    content=f"Language set to {language}.",
                    tool_call_id=runtime.tool_call_id,
                )
            ],
        }
    )

示例输出

假设模型执行:

代码片段Text
set_language(language="Chinese")

工具执行之后,State 中可以得到:

代码片段Text
preferred_language = "Chinese"

同时 messages 中新增:

代码片段Text
ToolMessage("Language set to Chinese.")

这里两个更新承担的职责不同。

ToolMessage 让模型知道工具刚才做了什么。

preferred_language 则作为结构化状态保留下来,供后面的 Middleware、Tool 或 Node 使用。

如果工具执行之后产生的是:

代码片段Text
订单已经选中
审批已经完成
用户已经认证
当前任务进入 reviewed 阶段

并且这些结果会影响后续流程,就适合更新 State。

如果结果只需要告诉模型一次,普通 Tool Result 通常已经足够,没有必要额外增加 State 字段。

十、综合对比

最后,我们可以把这些概念放到一起看。

对象主要负责什么典型内容生命周期
StateAgent 当前执行状态messages、current_step、selected_order_id当前执行或 Thread
Context业务运行环境user_id、tenant_id、数据库连接单次调用
RunnableConfigRunnable 执行配置thread_id、tags、metadata、callbacks单次执行
Runtime提供运行环境能力context、store、stream writer当前执行
ToolRuntimeTool 的运行环境state、context、config、tool_call_id当前 Tool 调用
Store跨 Thread 长期数据用户偏好、用户画像长期

实际开发中,最容易出现的问题还是 State、Context 和 Config 混用。

下面这些通常更适合 State:

代码片段Text
messages
current_step
selected_order_id
retry_count
approval_status
中间处理结果

下面这些通常更适合 Context:

代码片段Text
user_id
tenant_id
数据库连接
业务 Service
API Client
权限信息
Feature Flag

下面这些则属于 RunnableConfig:

代码片段Text
thread_id
tags
metadata
callbacks
run_name
recursion_limit
max_concurrency

如果某些数据需要跨 Thread 长期保存,例如用户偏好、用户画像和历史事实,更适合 Store。

而某个值如果只在一个函数内部临时使用,后续执行完全不需要,就没有必要进入这些对象,普通局部变量已经足够。

尤其不要因为 State 可以扩展字段,就把所有运行数据都放进去。

数据库连接、Service、Tracing Metadata、Thread 配置和业务状态如果全部混在 State 中,不仅边界会越来越模糊,还会增加 Checkpoint、序列化和调试的复杂度。

可以用下面这组判断作为参考:

代码片段Text
执行过程中需要持续变化
        ↓
      State

调用开始时确定的业务环境
        ↓
     Context

控制本次 Runnable 怎样执行
        ↓
  RunnableConfig

跨 Thread 长期保存
        ↓
      Store

仅当前函数临时使用
        ↓
  Local Variable

总结

本文介绍了 LangChain Agent 运行时中数据管理职责的清晰划分:State、Context 与 RunnableConfig 各司其职,分别对应状态更新、业务环境描述和运行控制。

在 Agent 执行过程中,不同类别的数据不应混存于同一对象中。

State 负责记录执行状态,并随时间持续更新;Context 描述调用所处的业务环境;RunnableConfig 则控制 Runnable 的运行方式。Runtime 是执行环境,提供 Context、Store、Streaming 等能力;Tool 层通过 ToolRuntime 访问这些能力,同时也可读取 State、RunnableConfig 与 Tool Call ID。它们并非彼此孤立的机制,而是分布在 Agent Runtime 的不同层次,对不同生命周期的数据进行读取、更新、持久化与注入。

理解它们的职责与作用,有助于在复杂 Agent 场景中更清晰地把控数据流与运行时职责边界。