动态运行时上下文
动态运行时上下文 表示在单次运行期间可以演变的可变数据,并通过 LangGraph 状态对象进行管理。这包括对话历史、中间结果以及来自工具或 LLM 输出的值。在 LangGraph 中,状态对象在运行期间充当 短期记忆。
示例展示了如何将状态整合到智能体提示中。
py
from langchain.agents import create_agent
from langchain.agents.middleware import dynamic_prompt, ModelRequest
from langchain.agents import AgentState
class CustomState(AgentState):
user_name: str
@dynamic_prompt
def personalized_prompt(request: ModelRequest) -> str:
user_name = request.state.get("user_name", "User")
return f"You are a helpful assistant. User's name is {user_name}"
agent = create_agent(
model="claude-sonnet-4-6",
tools=[...],
state_schema=CustomState,
middleware=[personalized_prompt],
)
agent.invoke({
"messages": "hi!",
"user_name": "John Smith"
})
默认情况下,agent 使用 AgentState 管理短期记忆,特别是通过 messages 键进行对话历史。
您可以扩展 AgentState 以添加其他字段。自定义状态架构使用 state_schema 参数传递给 create_agent。
状态也可以被智能体的工具访问,这些工具可以根据需要读取或更新状态。
短期记忆(状态)
状态代表在对话期间存在的短期记忆。它包括消息历史记录以及你在图状态中定义的任何自定义字段。
在你的工具签名中添加 runtime: ToolRuntime 以访问状态。此参数会自动注入并对 LLM 隐藏 - 它不会出现在工具的模式中。
访问状态
工具可以使用 runtime.state 访问当前对话状态:
python
from langchain.tools import tool, ToolRuntime
from langchain.messages import HumanMessage
@tool
def get_last_user_message(runtime: ToolRuntime) -> str:
"""获取用户最近的消息。"""
messages = runtime.state["messages"]
# 查找最后一条人类消息
for message in reversed(messages):
if isinstance(message, HumanMessage):
return message.content
return "未找到用户消息"
# 访问自定义状态字段
@tool
def get_user_preference(
pref_name: str,
runtime: ToolRuntime
) -> str:
"""获取用户偏好值。"""
preferences = runtime.state.get("user_preferences", {})
return preferences.get(pref_name, "未设置")
agent = create_agent(
model="claude-sonnet-4-6",
tools=[get_user_preference],
state_schema=CustomState,
middleware=[personalized_prompt],
)
更新状态
使用 Command 来更新智能体的状态。这对于需要更新自定义状态字段的工具很有用:
python
from langgraph.types import Command
from langchain.tools import tool
@tool
def set_user_name(new_name: str) -> Command:
"""在对话状态中设置用户名称。"""
return Command(update={"user_name": new_name})
agent = create_agent(
model="claude-sonnet-4-6",
tools=[get_user_preference],
state_schema=CustomState,
middleware=[personalized_prompt],
)
节点式钩子和包装式钩子都可以更新代理状态。机制有所不同:
- 节点式钩子 (before_agent, before_model, after_model, after_agent):直接返回一个字典。该字典使用图的归约器应用于代理状态。
- 包装式钩子 (wrap_model_call, wrap_tool_call):对于模型调用,返回 ExtendedModelResponse 并附带 Command 以将状态更新与模型响应一起注入。对于工具调用,直接返回 Command。当您需要根据在模型或工具调用期间运行的逻辑来跟踪或更新状态时使用,例如摘要触发点、使用情况元数据,或从请求或响应计算出的自定义字段。
节点式钩子
从节点式钩子返回一个字典以将更新合并到代理状态中。字典键映射到状态字段。
py
from langchain.agents.middleware import after_model, AgentState
from langgraph.runtime import Runtime
from typing import Any
from typing_extensions import NotRequired
class TrackingState(AgentState):
model_call_count: NotRequired[int]
@after_model(state_schema=TrackingState)
def increment_after_model(state: TrackingState, runtime: Runtime) -> dict[str, Any] | None:
return {"model_call_count": state.get("model_call_count", 0) + 1}
包装式钩子
从 wrap_model_call 返回带有 Command 的 ExtendedModelResponse 以从模型调用层注入状态更新:
py
from typing import Callable
from langchain.agents.middleware import (
wrap_model_call,
ModelRequest,
ModelResponse,
AgentState,
ExtendedModelResponse
)
from langgraph.types import Command
from typing_extensions import NotRequired
class UsageTrackingState(AgentState):
"""Agent state with token usage tracking."""
last_model_call_tokens: NotRequired[int]
@wrap_model_call(state_schema=UsageTrackingState)
def track_usage(
request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse],
) -> ExtendedModelResponse:
response = handler(request)
return ExtendedModelResponse(
model_response=response,
command=Command(update={"last_model_call_tokens": 150}),
)
Command 流经图的归约器,因此更新会正确应用,消息是累加的而不是替换现有状态。
与多个中间件的组合
当多个中间件层返回 ExtendedModelResponse 时,它们的命令会组合:
- 命令通过归约器应用: 每个 Command 成为单独的状态更新。对于消息,这意味着它们是累加的。
- 冲突时外层获胜: 对于非归约器状态字段,命令先应用内层,然后应用外层。最外层中间件的值在冲突键上具有优先级。
- 重试安全: 如果外层中间件实现了可能导致再次多次调用 handler() 的逻辑(例如重试逻辑),则早期调用的命令会被丢弃。
py
from typing import Annotated, Callable
from langchain.agents.middleware import (
AgentMiddleware,
AgentState,
ExtendedModelResponse,
ModelRequest,
ModelResponse,
)
from langchain.messages import SystemMessage
from langgraph.types import Command
from typing_extensions import NotRequired
def _last_wins(_a: str, b: str) -> str:
"""Reducer: last writer wins (outer overwrites inner)."""
return b
class CustomMiddlewareState(AgentState):
"""Agent state: trace_layer uses last-wins (outer wins), messages use additive reducer."""
# Non-reducer field with last-wins: both middleware write; outermost value wins
trace_layer: NotRequired[Annotated[str, _last_wins]]
class OuterMiddleware(AgentMiddleware):
def wrap_model_call(
self,
request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse],
) -> ExtendedModelResponse:
response = handler(request)
return ExtendedModelResponse(
model_response=response,
command=Command(update={
"trace_layer": "outer",
"messages": [SystemMessage(content="[Outer ran]")],
}),
)
class InnerMiddleware(AgentMiddleware):
"""Adds trace_layer and message. Outer adds to same keys; trace_layer: outer wins, messages: additive."""
def wrap_model_call(
self,
request: ModelRequest,
handler: Callable[[ModelRequest], ModelResponse],
):
response = handler(request)
return ExtendedModelResponse(
model_response=response,
command=Command(update={
"trace_layer": "inner",
"messages": [SystemMessage(content="[Inner ran]")],
}),
)