LangGraph 上下文工程权威指南:构建智能、感知、有记忆的 AI 代理
引言:超越提示词——上下文工程的崛起
在构建高级 AI 代理时,我们很快就会发现,简单的请求-响应循环是远远不够的。为了让代理能够执行复杂、多步骤的任务,它们需要一个“工作记忆”——也就是上下文 (Context)。著名 AI 思想家安德烈·卡帕西 (Andrej Karpathy) 曾将 LLM 比作一个新的操作系统,其中 LLM 是 CPU,而其上下文窗口就是 RAM 。
上下文工程 (Context Engineering) 正是这样一门艺术和科学:构建动态系统,以正确的格式提供正确的信息和工具,从而使 AI 应用能够成功完成任务 。它决定了在代理执行任务的每一步,应该向其有限的“RAM”中加载哪些信息。
LangGraph 为上下文工程提供了一个强大而分层的框架。本教程将深入探讨 LangGraph 管理上下文的三种核心机制,从单次运行的静态配置,到动态演变的短期记忆,再到跨越对话的长期知识库。掌握这些概念是构建真正智能、有感知力代理的关键。
第一部分:理解上下文的维度
在深入 LangGraph 的具体实现之前,我们首先需要建立一个清晰的心智模型。上下文可以沿着两个关键维度进行划分 :
按可变性 (Mutability):
- 静态上下文 (Static Context):在单次运行期间保持不变的不可变数据。例如,用户信息、数据库连接或在任务开始时就已确定的可用工具集。
- 动态上下文 (Dynamic Context):随着应用程序运行而演变的可变数据。例如,不断增长的对话历史、工具调用的中间结果或代理的“草稿纸”内容。
按生命周期 (Lifetime):
- 运行时上下文 (Runtime Context):数据的作用域仅限于单次运行或调用。运行结束后,这些数据就会被丢弃。
- 跨对话上下文 (Cross-conversation Context):数据在多次对话或会话之间持久存在。
LangGraph 精心设计了三种上下文管理机制,分别对应了这些维度的不同组合,为开发者提供了应对不同场景的精确工具 。
上下文类型 | 描述 | 可变性 | 生命周期 | 访问方法 |
---|---|---|---|---|
静态运行时上下文 | 在启动时传入的用户元数据、工具、数据库连接等 | 静态 | 单次运行 | invoke/stream 的 context 参数 |
动态运行时上下文 (State) | 在单次运行中演变的可变数据,如对话历史、中间结果 | 动态 | 单次运行 | LangGraph 的 State 对象 |
动态跨对话上下文 (Store) | 跨对话共享的持久化数据,如用户偏好、历史互动 | 动态 | 跨对话 | LangGraph 的 Store |
导出到 Google 表格
第二部分:静态运行时上下文——为单次运行注入配置
静态运行时上下文代表了那些在任务开始时就已确定,并且在整个执行过程中不会改变的“环境变量” 。这对于向代理传递运行时的特定配置(如用户 ID、API 密钥或要使用的特定模型)至关重要。
实现方式:context 参数
在 LangGraph v0.6 版本之后,传递静态上下文的标准方式是通过 invoke 或 stream 方法的 context 参数。这取代了之前嵌套在 config[‘configurable’] 中的模式,使得 API 更加清晰直观 。
代码示例:
from dataclasses import dataclass# 1. 定义上下文的模式 (Schema)
@dataclass
class ContextSchema:user_name: str# 假设 graph 已经编译好
# 2. 在调用时通过 `context` 参数传入数据
graph.invoke({"messages": [{"role": "user", "content": "hi!"}]}, context={"user_name": "John Smith"}
)
访问静态上下文
一旦传入,静态上下文就可以在图的各个部分被轻松访问,从而影响代理的行为。
- 在代理的提示词 (Prompt) 中访问
这是个性化代理行为最常见的方式。通过 langgraph.runtime.get_runtime,我们可以访问到当前的运行时上下文,并将其动态地注入到系统提示中。
from langgraph.runtime import get_runtime
from langgraph.prebuilt import create_react_agent
from langgraph.prebuilt.chat_agent_executor import AgentState
from langchain_core.messages import AnyMessagedef personalized_prompt(state: AgentState) -> list[AnyMessage]:# 获取当前运行时并指定其类型runtime = get_runtime(ContextSchema)# 从 context 中提取 user_namesystem_msg = f"You are a helpful assistant. Address the user as {runtime.context.user_name}."return [{"role": "system", "content": system_msg}] + state["messages"]agent = create_react_agent(model="anthropic:claude-3-5-sonnet-latest",tools=[...],prompt=personalized_prompt,context_schema=ContextSchema # 告知代理上下文的模式
)# 调用时传入上下文
agent.invoke({"messages": [{"role": "user", "content": "what is the weather in sf"}]},context={"user_name": "John Smith"}
)
在这个例子中,代理会根据传入的 user_name 调整其称呼,从而实现个性化交互 。
- 在工作流的节点 (Node) 中访问
在自定义的工作流节点中,可以通过在函数签名中指定 Runtime 类型来直接访问上下文。
from langgraph.runtime import Runtimedef my_node(state: State, runtime: Runtime):user_name = runtime.context.user_nameprint(f"Executing node for user: {user_name}")#... 节点逻辑...return {}
这种方式使得节点能够根据运行时配置执行不同的逻辑分支 。
- 在工具 (Tool) 中访问
工具也可以是上下文感知的。这使得工具能够执行依赖于用户身份或特定配置的操作。
from langchain_core.tools import tool
from langgraph.runtime import get_runtime@tool
def get_user_email() -> str:"""根据上下文中的用户名检索用户信息。"""runtime = get_runtime(ContextSchema)# 模拟从数据库中获取邮件email = get_user_email_from_db(runtime.context.user_name) return email
在这里,get_user_email 工具可以根据调用时传入的 user_name 从数据库中查询正确的信息 。
第三部分:动态运行时上下文 (State)——代理的短期记忆
动态运行时上下文是代理在单次运行期间的“工作记忆”或“草稿纸” 。它通过 LangGraph 的核心 State 对象来管理,是可变的,用于存储对话历史、工具调用的中间结果以及代理在推理过程中产生的任何临时数据 。
实现方式:自定义 State 模式
通过扩展 LangGraph 预定义的 AgentState 或 MessagesState,或者创建一个全新的 TypedDict,我们可以为代理的短期记忆定义一个自定义的结构 。
代码示例:
from typing_extensions import TypedDict
from langchain_core.messages import AnyMessage
from langgraph.graph import StateGraph
from langgraph.prebuilt.chat_agent_executor import AgentState# 1. 扩展 AgentState 来添加自定义字段
class CustomState(AgentState):user_name: strscratchpad: str# 2. 在节点中访问和更新 State
def node(state: CustomState):# 读取 state 中的数据messages = state["messages"]current_scratchpad = state.get("scratchpad", "")#... 节点逻辑...# 返回对 state 的更新return {"scratchpad": current_scratchpad + "\n- New thought."}# 3. 构建图时使用自定义 State
builder = StateGraph(CustomState)
builder.add_node("my_node", node)
#...
graph = builder.compile()# 4. 在初次调用时初始化 State
graph.invoke({"messages": [{"role": "user", "content": "hi!"}],"user_name": "John Smith","scratchpad": "Initial thoughts..."
})
在这个例子中,CustomState 不仅包含了对话消息,还有一个 user_name 和一个 scratchpad 字段。节点函数可以直接读取和返回对这些字段的更新,LangGraph 会自动将这些更新合并到主状态中 。
启用记忆以跨越调用
默认情况下,State 对象的生命周期仅限于单次 .invoke() 调用。要让这个“短期记忆”在同一次对话的多次调用之间得以保持,您需要启用持久化,即为图配置一个 Checkpointer。这会将 State 的演变历史保存下来,从而实现真正的对话记忆。更多详情请参阅内存指南 。
第四部分:动态跨对话上下文 (Store)——代理的长期记忆
动态跨对话上下文是代理的长期记忆。它用于存储那些需要在不同对话、不同时间甚至不同用户之间持久化和共享的信息,例如用户偏好、历史互动摘要或核心事实 。
实现方式:LangGraph Store
LangGraph 通过 Store 接口来管理长期记忆。与 State 不同,Store 中的数据是持久化的,并且可以被任何有权访问它的工作流读取或更新 。
核心用例:
- 用户个性化:一个代理可以在一次对话中通过 store.put() 记住用户的名字或偏好,然后在未来的任何对话中通过 store.get() 或 store.search() 来回忆起这些信息,提供高度个性化的体验 。
- 知识积累:代理可以通过工具调用将从外部世界学到的新事实存入 Store,从而不断丰富自己的知识库。
由于长期记忆是一个深度话题,我们强烈建议您阅读我们关于内存的完整教程,其中详细介绍了如何配置和使用 Store,包括高级的语义搜索功能 。
结论:构建上下文感知的智能体
LangGraph 的三层上下文管理系统为构建从简单到极其复杂的 AI 代理提供了坚实的基础。通过清晰地分离不同生命周期和可变性的上下文,开发者可以构建出模块化、可维护且功能强大的应用。
决策框架总结:
- 当您需要传递一次性、不可变的配置(如用户 ID、API 密钥)时,请使用静态运行时上下文 (context)。
- 当您需要管理代理在单次任务中的动态工作空间(如消息历史、中间步骤)时,请使用动态运行时上下文 (State)。
- 当您需要让代理永久记住跨越多次对话的信息(如用户偏好、核心事实)时,请使用动态跨对话上下文 (Store)。
掌握上下文工程,就是掌握了赋予 AI 代理感知、记忆和适应能力的关键。通过明智地运用 LangGraph 提供的这些工具,您将能够创造出真正理解并响应其环境的下一代智能体。