通过前面的文章,我们已经理解了 Agent 运行时上下文、长短期记忆、工具、Middleware 等内容。要进一步理解 Agent 的执行过程,还有一些细节需要掌握。
比如,一次 Agent 调用中,聊天消息保存在哪里?用户 ID 应该放进 State 还是 Context?config 和 context 有什么区别?工具为什么既能读取 State,又能拿到 Context?Runtime 和 ToolRuntime 又分别解决什么问题?
这些概念放在一起时很容易混淆。本文就围绕一次 Agent 执行,把 State、Context、RunnableConfig、Runtime 和 ToolRuntime 之间的关系梳理清楚。
一、Agent 执行中的数据
我们先看一个具体场景。
假设我们正在开发一个客服 Agent。用户会进行多轮对话,Agent 可以查询订单、修改地址,还需要根据当前登录用户访问后台系统。
在这个 Agent 的一次执行过程中,可能同时出现下面这些数据:
用户之前发送的消息
模型之前的回复
当前选中的订单
当前处理阶段
重试次数
当前登录用户的 user_id
tenant_id
数据库连接
业务 Service
thread_id
tags
metadata
callbacks
长期保存的用户偏好
当前 tool_call_id
这些数据虽然都和一次 Agent 调用有关,但它们的来源、生命周期和用途不同。
例如,聊天记录(messages)会随着执行不断变化,而且可能需要在下一轮对话恢复。
user_id 通常在调用 Agent 之前已经确定,在执行过程中不会改变。
thread_id 主要用来告诉运行系统“这次调用属于哪个 Thread”,它和聊天内容本身没有关系。
数据库连接则只是运行时依赖。
针对上面所有涉及到的信息,可以先建立这样一个大致关系:
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,其中最重要的字段就是:
messages
平时调用 Agent 时,我们经常这样写:
agent.invoke({
"messages": [
{"role": "user", "content": "帮我查询订单"}
]
})
这里传入的 messages 不只是一次模型调用的 Prompt,它同时也是 Agent State 的输入。
Agent 后续调用模型、执行工具时,消息会继续加入其中。
State 不只有 messages
实际项目中,经常还需要保存一些业务执行状态,例如:
messages
current_step
selected_order_id
retry_count
approval_status
这时可以扩展 AgentState:
from langchain.agents import AgentState
class CustomState(AgentState):
current_step: str
retry_count: int
然后创建 Agent:
from langchain.agents import create_agent
agent = create_agent(
model="openai:gpt-5.5",
tools=[],
state_schema=CustomState,
)
调用时可以一起传入这些字段:
result = agent.invoke({
"messages": [
{"role": "user", "content": "继续处理"}
],
"current_step": "checking_order",
"retry_count": 0,
})
current_step 和 retry_count 会和 messages 一样,成为 Agent 当前状态的一部分。
State 为什么需要单独存在
Agent 通常不是一次模型调用,而是一个连续执行过程:
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,记录模型执行次数:
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"])
运行结果
1
这里 before_model 先读取当前 State,然后返回:
{
"request_count": 1
}
这个返回值会作为状态更新合并回 State。
所以,我们可以看出:State 即为Agent 执行过程中持续被读取、修改和向后传递的数据。
实际开发中,可以用几个问题来判断某个值是否适合进入 State:
它是否描述当前执行进度?
后续节点是否需要读取?
执行过程中是否可能变化?
之后是否可能需要恢复?
如果这些条件成立,通常就适合放进 State。
但有些数据虽然很多节点都需要,却并不属于执行状态。
最常见的就是当前用户身份。
四、Context 描述调用环境
运行时上下文(Runtime Context)描述的是:
这一次 Agent 是在什么业务环境下运行。
例如:
user_id
tenant_id
权限信息
数据库连接
API Client
业务 Service
Feature Flag
这些信息通常不是 Agent 执行过程中产生的,而是在调用 Agent 之前就已经确定。
例如一个 Web 请求可能先经过认证:
HTTP Request
↓
Authentication
↓
得到 user_id
↓
调用 Agent
这里的 user_id 来自应用系统,而不是 Agent 推理出来的结果。
因此,可以定义一个 Context:
from dataclasses import dataclass
@dataclass
class Context:
user_id: str
tenant_id: str
创建 Agent 时声明:
from langchain.agents import create_agent
agent = create_agent(
model="openai:gpt-5.5",
tools=[],
context_schema=Context,
)
调用时通过单独的 context 参数传入:
result = agent.invoke(
{
"messages": [
{"role": "user", "content": "查询我的账户"}
]
},
context=Context(
user_id="user_123",
tenant_id="tenant_a",
),
)
可以看到,接口本身已经把两种数据区分开了:
agent.invoke(
state_input,
context=runtime_context,
)
前一个参数是 State 输入。
后面的 context 是本次调用的 Runtime Context。
我们再进一步对比一下 State 和 Context 的区别
例如:
{
"current_step": "waiting_payment",
"retry_count": 2,
}
描述的是 Agent 当前执行到了哪里。
所以适合进入 State。
而:
Context(
user_id="user_123",
tenant_id="tenant_a",
)
描述的是这次 Agent 以什么身份、在什么租户环境下运行。
所以适合进入 Context。
可以记住以下规则:
State
执行过程中发生了什么
Context
执行发生在什么业务环境中
还有一个区别很重要。
State 可以由 Checkpointer 按 Thread 保存,而 Context 是每次 invocation 传入的运行时依赖。配置 Checkpointer 并不会自动把 Context 变成短期记忆。
五、Config 控制一次执行
除了 State 和 Context,调用 Agent 时还经常会看到另一个参数:
config
例如短期记忆中常见的写法:
config = {
"configurable": {
"thread_id": "user-1"
}
}
agent.invoke(
{
"messages": [
{"role": "user", "content": "我叫 Bob"}
]
},
config=config,
)
这里的 config 到底是什么?
当前 LangChain 中,更准确的类型名称是:
RunnableConfig
RunnableConfig 定义在 langchain_core.runnables 中,用来配置一次 Runnable 执行。Agent 本身也是 Runnable,因此调用 invoke、stream 等方法时,都可以传入 config。
它常见的字段包括:
configurable
tags
metadata
callbacks
run_name
run_id
max_concurrency
recursion_limit
例如:
config = {
"tags": ["customer-service"],
"metadata": {
"request_source": "web"
},
"configurable": {
"thread_id": "user-1"
}
}
这里其实包含了两类执行配置。
tags、metadata、callbacks 等主要用于 tracing、logging 和执行控制。
而:
"configurable": {
"thread_id": "user-1"
}
用于向已经声明为 configurable 的运行组件传递运行时配置。LangGraph 的 Checkpointer 就会通过这里的 thread_id 识别当前 Thread。
Config 和 Context 不是一回事
这两个对象很容易混淆,因为它们都是调用 invoke 时传入的。
例如:
agent.invoke(
input,
config=config,
context=context,
)
但它们的职责不同。
Context 更偏向业务运行环境:
当前用户是谁
属于哪个租户
使用哪个数据库连接
注入哪个业务 Service
RunnableConfig 更偏向这一次 Runnable 怎样执行:
属于哪个 thread_id
使用哪些 tags
附带哪些 metadata
使用哪些 callbacks
并发限制是多少
递归限制是多少
可以对比一下:
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 主要包含:
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:
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"),
)
运行结果
Bob
这里的两种访问方式正好反映了 State 和 Runtime 的职责:
state["messages"]
读取的是当前执行状态。
runtime.context.user_name
读取的是本次调用的业务上下文。
因此,在 Node 或 Node 风格 Middleware 中,经常会看到这样的函数签名:
def node(
state: AgentState,
runtime: Runtime[Context],
):
...
State 提供“当前执行数据”。
Runtime 提供“当前运行环境”。
七、ToolRuntime 服务于工具
工具也需要访问 Agent 当前的运行环境。
例如:
@tool
def get_order(order_id: str):
...
这里让模型提供:
order_id
很合理,因为订单编号来自用户请求或模型判断。
但如果工具还需要:
user_id
tenant_id
当前 State
当前 Tool Call ID
RunnableConfig
Store
就没有必要继续把这些字段暴露给模型。
否则工具可能变成:
get_order(
order_id,
user_id,
tenant_id,
tool_call_id,
)
其中很多参数都不应该由模型决定。
LangChain 为工具提供了 ToolRuntime:
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 从哪里来。
工具自己从:
runtime.context.user_id
读取即可。
ToolRuntime 中有什么
ToolRuntime 可以访问:
state
context
store
stream_writer
execution_info
server_info
config
tool_call_id
其中前几项和普通 Runtime 的运行能力有关。
但 ToolRuntime 还额外提供:
state
config
tool_call_id
这些信息和当前 Tool 调用直接相关。
例如:
runtime.state
可以读取当前 Agent State。
runtime.context
可以读取 Runtime Context。
runtime.config
得到当前执行的 RunnableConfig。
runtime.tool_call_id
得到当前工具调用对应的 Tool Call ID。
所以可以这样理解:
Runtime
↓
Node、Middleware 使用的运行环境
ToolRuntime
↓
Tool 使用的运行环境
并补充工具执行所需的信息
八、ToolRuntime 连接几类数据
ToolRuntime 很适合用来观察前面几个概念之间的关系。
例如:
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}"
)
这里一次读取了三类数据。
runtime.state["selected_order_id"]
来自 State。
它表示 Agent 当前正在处理哪个订单。
runtime.context.user_id
来自 Context。
它表示这次调用对应哪个登录用户。
runtime.config
则是 RunnableConfig。
其中可以读取当前执行传入的 Config,例如 thread_id。ToolRuntime 明确提供 config,类型就是当前执行的 RunnableConfig。
三者虽然最终都能被 Tool 读取,但来源和用途并不相同:
selected_order_id
↓
执行过程产生
↓
State
user_id
↓
调用前由业务系统提供
↓
Context
thread_id
↓
控制 Runnable / Checkpointer 执行
↓
RunnableConfig
九、工具也可以修改 State
ToolRuntime 不只用于读取数据。
如果工具执行结果会影响后续 Agent 执行,可以通过 Command 更新 State。
例如:
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,
)
],
}
)
示例输出
假设模型执行:
set_language(language="Chinese")
工具执行之后,State 中可以得到:
preferred_language = "Chinese"
同时 messages 中新增:
ToolMessage("Language set to Chinese.")
这里两个更新承担的职责不同。
ToolMessage 让模型知道工具刚才做了什么。
preferred_language 则作为结构化状态保留下来,供后面的 Middleware、Tool 或 Node 使用。
如果工具执行之后产生的是:
订单已经选中
审批已经完成
用户已经认证
当前任务进入 reviewed 阶段
并且这些结果会影响后续流程,就适合更新 State。
如果结果只需要告诉模型一次,普通 Tool Result 通常已经足够,没有必要额外增加 State 字段。
十、综合对比
最后,我们可以把这些概念放到一起看。
| 对象 | 主要负责什么 | 典型内容 | 生命周期 |
|---|---|---|---|
| State | Agent 当前执行状态 | messages、current_step、selected_order_id | 当前执行或 Thread |
| Context | 业务运行环境 | user_id、tenant_id、数据库连接 | 单次调用 |
| RunnableConfig | Runnable 执行配置 | thread_id、tags、metadata、callbacks | 单次执行 |
| Runtime | 提供运行环境能力 | context、store、stream writer | 当前执行 |
| ToolRuntime | Tool 的运行环境 | state、context、config、tool_call_id | 当前 Tool 调用 |
| Store | 跨 Thread 长期数据 | 用户偏好、用户画像 | 长期 |
实际开发中,最容易出现的问题还是 State、Context 和 Config 混用。
下面这些通常更适合 State:
messages
current_step
selected_order_id
retry_count
approval_status
中间处理结果
下面这些通常更适合 Context:
user_id
tenant_id
数据库连接
业务 Service
API Client
权限信息
Feature Flag
下面这些则属于 RunnableConfig:
thread_id
tags
metadata
callbacks
run_name
recursion_limit
max_concurrency
如果某些数据需要跨 Thread 长期保存,例如用户偏好、用户画像和历史事实,更适合 Store。
而某个值如果只在一个函数内部临时使用,后续执行完全不需要,就没有必要进入这些对象,普通局部变量已经足够。
尤其不要因为 State 可以扩展字段,就把所有运行数据都放进去。
数据库连接、Service、Tracing Metadata、Thread 配置和业务状态如果全部混在 State 中,不仅边界会越来越模糊,还会增加 Checkpoint、序列化和调试的复杂度。
可以用下面这组判断作为参考:
执行过程中需要持续变化
↓
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 场景中更清晰地把控数据流与运行时职责边界。
社区讨论
参与讨论
有问题或想法?欢迎继续讨论。