精读 LangChain 官方文档(八)Runtime 篇:把运行期上下文注入 Agent
本文基于 LangChain Python 官方文档整理:
Runtime:https://docs.langchain.com/oss/python/langchain/runtime
对应开源文档源码:
https://github.com/langchain-ai/docs/blob/main/src/oss/langchain/runtime.mdx
上一篇 Messages 篇讲清楚了一个问题:模型看到的不是一段普通字符串,而是一组带role、content和metadata的消息协议。
但真实 Agent 系统还会遇到另一类问题:有些信息并不应该写进消息里。
例如:
- 当前用户是谁?
- 当前租户是哪一个?
- 工具应该查哪一个数据库?
- 这次调用的运行 ID 是什么?
- 长期记忆应该从哪里读?
- 工具执行到一半时,怎样把进度流给前端?
- 在 LangGraph Server 上运行时,怎样知道当前 assistant、graph 和认证用户?
这些信息不一定是“给模型看的上下文”,但它们是“程序运行必须知道的上下文”。
LangChain 的Runtime文档解决的就是这个工程边界:不要把运行期依赖硬编码进工具,也不要把用户 ID、数据库连接、配置项和权限信息都塞进 prompt,而是通过 Runtime 在一次 Agent 调用里注入给工具和中间件。
这篇文档的核心主线可以概括成一句话:
Runtime = Context + Store + Writer + Execution Info + Server Info。它不是模型上下文,而是 Agent 运行时给工具和中间件使用的依赖注入容器。
如果说Messages解释“模型看到了什么”,那Runtime解释的是“Agent 执行时,程序侧还能拿到什么”。
理解这条主线后,context_schema、context、ToolRuntime、runtime.store、runtime.stream_writer、runtime.execution_info和runtime.server_info就不再是分散 API,而是同一个 Agent runtime 的不同控制面。
1. Runtime(运行时)到底解决什么问题
它解决的问题:Runtime解决的是“Agent 执行时,工具和中间件如何拿到本次运行所需的依赖和元信息”。
官方文档开头说得很关键:LangChain 的create_agent底层运行在 LangGraph Runtime 之上。LangGraph 暴露的Runtime对象包含五类信息:
context:本次 Agent 调用的静态信息,例如用户 ID、数据库连接、API 客户端、配置项。store:长期记忆存储,通常是一个BaseStore实例。stream_writer:用于通过"custom"stream mode 输出工具进度或自定义更新。execution_info:当前执行身份与重试信息,例如thread_id、run_id、attempt。server_info:运行在 LangGraph Server 时的服务端元信息,例如 assistant ID、graph ID、认证用户。
示例:
importosfromdataclassesimportdataclassfromlangchain.agentsimportcreate_agentfromlangchain_openaiimportChatOpenAI@dataclassclassRuntimeContext:user_id:strtenant_id:strmodel=ChatOpenAI(model="qwen3.7-max",api_key=os.environ["QWEN_API_KEY"],base_url=os.environ["QWEN_BASE_URL"],)agent=create_agent(model=model,tools=[],context_schema=RuntimeContext,system_prompt="你是一名中文业务助手,需要根据当前用户上下文回答问题。",)result=agent.invoke({"messages":[{"role":"user","content":"请告诉我当前账号可以使用哪些功能?"}]},context=RuntimeContext(user_id="user-001",tenant_id="mall-a"),)这里:
RuntimeContext:本次运行上下文的数据结构,用来描述工具和中间件能读取哪些字段。user_id:用户标识。真实业务里通常来自登录态或网关鉴权结果。tenant_id:租户标识。多租户系统要靠它隔离数据范围。context_schema:告诉 Agent runtime,context的结构是什么。context=RuntimeContext(...):本次调用真实注入的运行期数据。messages:给模型看的对话输入。
业务场景:
在积分商城、SaaS 客服或企业知识库里,同一句“帮我查一下权益”,不同用户、租户和权限下的答案可能完全不同。Runtime让这些业务依赖以结构化方式进入运行过程,而不是散落在全局变量或 prompt 拼接里。
最简记法:Messages是模型看到的上下文,Runtime是程序执行需要的上下文。
2. context_schema 与 context:把本次运行的静态依赖注入 Agent
它解决的问题:context_schema和context解决的是“怎样把本次调用才知道的信息传给 Agent runtime”。
官方文档给出的模式是:
- 用
context_schema定义上下文结构。 - 调用
agent.invoke(...)时传入context。 - 工具或中间件通过 Runtime 读取这个 context。
这是一种典型的依赖注入思路。工具不需要自己去读全局变量,也不需要从自然语言里猜用户是谁。
示例:
fromdataclassesimportdataclass@dataclassclassCustomerContext:user_id:strmembership_level:strlocale:stragent=create_agent(model=model,tools=[],context_schema=CustomerContext,system_prompt="你是一名会员服务助手,需要用中文回答。",)result=agent.invoke({"messages":[{"role":"user","content":"我现在能享受什么会员权益?"}]},context=CustomerContext(user_id="user-001",membership_level="gold",locale="zh-CN",),)这里:
CustomerContext:上下文 schema,定义本次运行可用的业务字段。membership_level:会员等级,例如gold、silver、basic。locale:语言和地区偏好,例如zh-CN。context_schema=CustomerContext:让 Agent runtime 知道 context 的类型。context=CustomerContext(...):运行时传入的具体值。
业务场景:
会员权益、商品价格、库存范围、数据权限、服务语言都经常依赖用户身份。把这些信息作为context传入,比把它们写进 prompt 更稳定,也更适合测试。
最简记法:context_schema定义“能传什么”,context传入“这次是什么”。
3. Runtime context 不是 LLM context,也不是 context window
它解决的问题:
这一节解决的是一个非常容易混淆的概念:Runtime context不是提示词上下文,也不是模型上下文窗口。
可以把三者区分开:
| 名称 | 中文理解 | 主要给谁用 | 典型内容 |
|---|---|---|---|
messages/ LLM context | 模型上下文 | 模型 | 系统消息、用户消息、工具结果、历史对话 |
runtime.context | 运行期上下文 | 工具和中间件 | 用户 ID、租户、权限、数据库连接、配置 |
context window | 模型上下文窗口 | 模型容量限制 | 模型一次调用能容纳的 token 数 |
示例:
result=agent.invoke({"messages":[{"role":"user","content":"请帮我查询订单状态。"}]},context=CustomerContext(user_id="user-001",membership_level="gold",locale="zh-CN",),)这里:
messages:这部分会进入 Agent state,并在需要时进入模型上下文。context:这部分不会自动变成模型 prompt,而是给工具和中间件读取。context window:模型容量限制,不是一个需要传参的业务对象。
业务场景:
如果用户问“我的订单到了吗”,模型需要看到这句话,但真实订单查询工具还需要user_id、tenant_id、数据库连接或 API 客户端。这些运行期依赖不应该都暴露给模型。
最简记法:
模型上下文负责“让模型理解任务”,Runtime context 负责“让程序正确执行任务”。
4. ToolRuntime:工具读取 Runtime 的入口
它解决的问题:ToolRuntime解决的是“工具函数如何访问 Runtime 里的 context、store 和 writer”。
官方文档说明,在工具函数参数里使用ToolRuntime[Context],就可以访问 Runtime 对象。这个参数是给 LangChain runtime 注入的,不需要模型自己生成。
示例:
fromdataclassesimportdataclassfromlangchain.toolsimportToolRuntime,tool@dataclassclassOrderContext:user_id:strtenant_id:str# 根据当前运行上下文查询用户订单状态@tooldefquery_latest_order(runtime:ToolRuntime[OrderContext])->str:"""查询当前用户最近一笔订单状态。"""user_id=runtime.context.user_id tenant_id=runtime.context.tenant_idreturnf"租户{tenant_id}下,用户{user_id}最近一笔订单已发货,预计明天送达。"agent=create_agent(model=model,tools=[query_latest_order],context_schema=OrderContext,system_prompt="你是一名售后客服助手。需要查订单时,先调用工具,再回答用户。",)result=agent.invoke({"messages":[{"role":"user","content":"我最近一笔订单到哪里了?"}]},context=OrderContext(user_id="user-001",tenant_id="mall-a"),)这里:
ToolRuntime[OrderContext]:工具运行时参数,类型参数说明runtime.context的结构。runtime.context.user_id:读取当前用户 ID。runtime.context.tenant_id:读取当前租户 ID。query_latest_order:工具函数名,表示查询最近订单。@tool:把普通 Python 函数注册成 LangChain 工具。
业务场景:
订单工具不应该让模型从用户自然语言里猜user_id。用户只说“我最近一笔订单”,工具通过 Runtime 拿到真实登录用户,再去后端查询。
最简记法:ToolRuntime是工具函数里的“运行时入口”。
5. runtime.store:在工具里读取长期记忆
它解决的问题:runtime.store解决的是“工具如何读取或写入跨会话的长期记忆”。
官方文档里,Runtime 对象包含一个store,它是用于长期记忆的BaseStore实例。工具可以根据runtime.context中的用户 ID 去读取用户偏好、历史设置或长期记忆。
示例:
fromdataclassesimportdataclassfromlangchain.toolsimportToolRuntime,toolfromlanggraph.store.memoryimportInMemoryStore@dataclassclassPreferenceContext:user_id:str# 读取当前用户在长期记忆中的邮件写作偏好@tooldeffetch_email_preferences(runtime:ToolRuntime[PreferenceContext])->str:"""读取当前用户的邮件写作偏好。"""default_preferences="用户偏好:邮件要简短、礼貌,并先给结论。"ifruntime.storeisNone:returndefault_preferences memory=runtime.store.get(("users",),runtime.context.user_id)ifmemoryisNone:returndefault_preferencesreturnmemory.value.get("preferences",default_preferences)store=InMemoryStore()store.put(("users",),"user-001",{"preferences":"用户偏好:邮件要正式、分点说明,并附下一步建议。"},)agent=create_agent(model=model,tools=[fetch_email_preferences],context_schema=PreferenceContext,store=store,system_prompt="你是一名中文邮件助手,需要先读取用户偏好,再生成草稿。",)这里:
runtime.store:长期记忆存储入口。InMemoryStore:内存版 store,适合演示和本地测试。store.put(...):写入一条长期记忆。store.get(...):读取一条长期记忆。("users",):命名空间,用来组织不同类型的记忆。runtime.context.user_id:用当前运行上下文定位用户。
业务场景:
邮件助手、客服助手、销售助手都需要记住用户偏好。比如“这个用户喜欢正式语气”“这个客户要求报价里先列总价”“这个租户禁用某些工具”。这些不属于单轮消息,却应该影响后续运行。
最简记法:context定位“当前是谁”,store读取“过去记住了什么”。
6. stream_writer:工具进度也能流式输出
它解决的问题:stream_writer解决的是“工具执行过程中的业务进度如何实时告诉前端”。
Streaming 篇已经讲过,Agent 可以流式返回模型 token 或 agent progress。Runtime 文档补充的是:工具内部也可以使用 stream writer,通过"custom"stream mode 输出自定义进度。
示例:
fromdataclassesimportdataclassfromlangchain.toolsimportToolRuntime,tool@dataclassclassReportContext:user_id:str# 分步生成业务报告,并通过 stream_writer 输出工具进度@tooldefgenerate_sales_report(runtime:ToolRuntime[ReportContext])->str:"""生成当前用户可访问的销售报告。"""ifruntime.stream_writer:runtime.stream_writer({"stage":"load_data","message":"正在读取销售数据"})ifruntime.stream_writer:runtime.stream_writer({"stage":"analyze","message":"正在分析核心指标"})ifruntime.stream_writer:runtime.stream_writer({"stage":"write_report","message":"正在生成中文报告"})return"销售报告已生成:本周订单量上升,复购率保持稳定。"这里:
runtime.stream_writer:自定义流式输出入口。stage:阶段字段,表示工具执行到了哪一步。message:给前端展示的中文进度文案。"custom"stream mode:用于接收自定义业务事件的流模式。
业务场景:
数据分析 Agent、文档生成 Agent、投标方案 Agent 往往要执行几十秒甚至几分钟。与其让用户只看到转圈,不如让工具把“正在读取文件、正在清洗数据、正在生成报告”这些进度流出去。
最简记法:
模型流式输出回答,stream_writer流式输出工具进度。
7. execution_info:知道当前运行是谁、哪一轮、重试到第几次
它解决的问题:runtime.execution_info解决的是“工具和中间件如何识别当前执行实例”。
官方文档提到,Runtime 可以访问 execution identity 和 retry information,包括thread_id、run_id和 attempt number。这对于日志、审计、幂等控制和故障排查非常重要。
示例:
fromlangchain.toolsimportToolRuntime,tool# 读取当前运行的执行标识,便于日志和审计追踪@tooldefinspect_execution(runtime:ToolRuntime)->str:"""返回当前运行的执行标识。"""info=runtime.execution_inforeturn(f"当前线程:{info.thread_id};"f"当前运行:{info.run_id};"f"当前尝试次数:{info.attempt}")这里:
runtime.execution_info:当前运行的执行信息。thread_id:对话线程或任务线程标识。run_id:本次运行标识。attempt:当前尝试次数,和重试机制相关。inspect_execution:示例工具名,用来演示读取执行信息。
业务场景:
如果某个退款工具被调用了两次,工程侧需要知道这是不是同一个run_id的重试,还是用户重新发起了一次请求。没有执行信息,日志很容易变成一堆互相对不上的文本。
最简记法:execution_info给每次 Agent 运行贴上可追踪的身份标签。
8. server_info:在 LangGraph Server 上读取服务端身份
它解决的问题:runtime.server_info解决的是“Agent 部署到 LangGraph Server 后,如何读取服务端元信息和认证用户”。
官方文档提醒:server_info只在运行于 LangGraph Server 时可用,本地开发时通常是None。它可以包含 assistant ID、graph ID 和 authenticated user 等信息。
示例:
fromlangchain.toolsimportToolRuntime,tool# 根据 LangGraph Server 认证状态决定是否允许继续执行@tooldefcheck_server_identity(runtime:ToolRuntime)->str:"""检查当前服务端运行身份。"""server=runtime.server_infoifserverisNone:return"当前是本地运行环境,没有 LangGraph Server 元信息。"ifserver.userisNone:return"当前请求没有认证用户,需要拒绝高风险操作。"returnf"当前认证用户:{server.user.identity}"这里:
runtime.server_info:LangGraph Server 运行时的服务端元信息。server.user:认证用户信息,可能为空。server.user.identity:认证用户身份标识。server is None:表示当前不是 LangGraph Server 环境,常见于本地开发。
业务场景:
同一套 Agent 在本地测试、灰度环境和正式服务上运行时,安全策略可能不同。高风险工具可以通过server_info判断是否有认证用户,再决定是否继续。
最简记法:server_info让 Agent 知道自己运行在哪个服务端身份里。
9. Runtime in Middleware:中间件也能读取运行上下文
它解决的问题:
Runtime 不只给工具用,也给中间件用。中间件可以根据运行上下文动态修改提示词、记录日志、控制模型调用前后的行为。
官方文档展示了三类常见入口:
dynamic_prompt:根据request.runtime.context生成动态系统提示词。before_model:模型调用前读取runtime,做日志、校验或上下文调整。after_model:模型调用后读取runtime,做日志、审计或状态处理。
示例:
fromdataclassesimportdataclassfromlangchain.agentsimportAgentState,create_agentfromlangchain.agents.middlewareimportModelRequest,after_model,before_model,dynamic_promptfromlanggraph.runtimeimportRuntime@dataclassclassSupportContext:user_name:strservice_level:str# 根据运行上下文生成个性化系统提示词@dynamic_promptdefpersonalized_prompt(request:ModelRequest)->str:user_name=request.runtime.context.user_name service_level=request.runtime.context.service_levelreturn(f"你是一名中文客服助手。当前用户是{user_name},"f"服务等级是{service_level}。回答要准确、礼貌,并说明下一步。")# 在模型调用前记录当前用户信息@before_modeldeflog_before_model(state:AgentState,runtime:Runtime[SupportContext])->dict|None:print(f"开始处理用户:{runtime.context.user_name}")returnNone# 在模型调用后记录当前用户信息@after_modeldeflog_after_model(state:AgentState,runtime:Runtime[SupportContext])->dict|None:print(f"完成处理用户:{runtime.context.user_name}")returnNoneagent=create_agent(model=model,tools=[],middleware=[personalized_prompt,log_before_model,log_after_model],context_schema=SupportContext,)这里:
dynamic_prompt:动态提示词中间件。ModelRequest:模型调用请求对象,request.runtime可以访问 Runtime。before_model:模型调用前的 hook。after_model:模型调用后的 hook。Runtime[SupportContext]:中间件中注入的运行时对象。AgentState:Agent 当前状态类型。
业务场景:
不同用户、渠道、会员等级可能需要不同回答风格。与其在每次用户消息里拼一大段身份信息,不如用中间件从 Runtime 读取上下文,动态生成稳定提示词。
最简记法:
工具用 Runtime 做业务动作,中间件用 Runtime 控制 Agent 行为。
10. Runtime、State、Store 三者怎么分工
它解决的问题:
这一节解决的是 Runtime 篇和后续 Context Engineering、Memory 篇之间最重要的边界。
可以先记住这张表:
| 数据来源 | 中文解释 | 是否会变化 | 生命周期 | 常见字段 |
|---|---|---|---|---|
runtime.context | 静态运行上下文 | 单次运行内通常不变 | 单次调用 | user_id、tenant_id、权限、配置、数据库连接 |
state | 动态运行状态 | 运行过程中会变化 | 当前线程或当前运行 | messages、工具结果、中间步骤 |
runtime.store | 长期记忆存储 | 可读写 | 跨会话 | 用户偏好、历史事实、长期画像 |
示例:
fromdataclassesimportdataclass@dataclassclassRuntimeContext:user_id:strtenant_id:strresult=agent.invoke({"messages":[{"role":"user","content":"请根据我的偏好生成一封售后邮件。"}]},context=RuntimeContext(user_id="user-001",tenant_id="mall-a"),)这里:
runtime.context.user_id:单次运行的用户身份。state["messages"]:当前对话状态,会随着多轮对话变化。runtime.store:可以读取跨会话保留的用户偏好。
业务场景:
一个邮件助手要同时使用三类数据:本轮用户问题在messages里;用户 ID 和租户在runtime.context里;用户长期写作偏好在runtime.store里。三者混在一起,系统就很难测试、追踪和治理。
最简记法:context是这次运行带来的身份,state是运行中变化的过程,store是跨会话保留下来的记忆。
11. 工程落地:什么时候该用 Runtime,而不是 prompt 或全局变量
它解决的问题:
这一节把 Runtime 文档转成工程判断:哪些信息应该走 Runtime,哪些信息应该进入 messages,哪些信息应该进 store。
可以用下面这张表做设计检查:
| 工程问题 | 更适合放在哪里 | 原因 |
|---|---|---|
| 用户本轮提问 | messages | 需要模型理解 |
| 系统角色和回答风格 | system_prompt或dynamic_prompt | 控制模型行为 |
| 当前登录用户 ID | runtime.context | 工具和中间件需要,模型不一定要看 |
| 当前租户、权限、渠道 | runtime.context | 用于数据隔离和策略判断 |
| 数据库连接、API 客户端 | runtime.context | 程序依赖,不应暴露给模型 |
| 用户长期偏好 | runtime.store | 跨会话复用 |
| 当前对话历史 | state["messages"] | 多轮状态 |
| 运行 ID、线程 ID、重试次数 | runtime.execution_info | 日志、审计、幂等控制 |
| LangGraph Server 认证用户 | runtime.server_info | 服务端安全判断 |
| 工具执行进度 | runtime.stream_writer | 前端进度展示 |
示例:
fromdataclassesimportdataclassfromlangchain.toolsimportToolRuntime,tool@dataclassclassAppContext:user_id:strtenant_id:strcan_refund:bool# 根据运行上下文判断当前用户是否允许发起退款@tooldefcheck_refund_permission(runtime:ToolRuntime[AppContext])->str:"""检查当前用户是否具备退款权限。"""ifnotruntime.context.can_refund:return"当前账号没有退款权限,需要转人工处理。"returnf"用户{runtime.context.user_id}可以在租户{runtime.context.tenant_id}下发起退款。"这里:
can_refund:权限字段,属于程序侧判断,不应该让模型凭自然语言决定。check_refund_permission:工具函数名,表示检查退款权限。runtime.context.can_refund:从运行期上下文读取真实权限。
业务场景:
退款、发券、改地址、删除数据、导出报表等高影响动作,都应该依赖后端权限和运行时身份,而不是只靠模型“觉得可以”。
最简记法:
能决定程序怎么执行的业务依赖,优先放 Runtime;需要模型理解的任务内容,才放 messages。
总结:Runtime 是 Agent 的运行期依赖注入层
读完 Runtime 文档,最重要的不是记住某一个参数,而是建立一个新的分层模型:
Messages -> Agent State -> Runtime -> Tools / Middleware -> Production RuntimeMessages负责描述模型对话。Agent State负责承载运行中的消息和状态变化。Runtime负责把本次运行的静态依赖、长期记忆入口、自定义流输出、执行身份和服务端身份交给工具与中间件。Tools通过ToolRuntime读取这些信息,执行真实业务动作。Middleware通过 Runtime 调整提示词、记录日志、控制模型调用前后的行为。
到了生产环境,execution_info和server_info又把日志、审计、幂等和认证边界接入进来。
所以,Runtime 的最简记法是:
Prompt 负责告诉模型“怎么回答”,Runtime 负责告诉程序“这次该按谁、按什么配置、用什么依赖来执行”。
理解这一层后,再读 Tools、MCP、Human-in-the-loop、Guardrails、Context Engineering 和 Memory 时,它们就不再是一堆孤立功能,而是围绕同一个 Agent runtime 逐步展开的工程能力。