【LangGraph】新篇章:LangGraph 工具中访问运行时上下文------ToolRuntime
- 前言
-
- 1、为什么工具需要运行时上下文?
-
- [1.1 工具的传统痛点](#1.1 工具的传统痛点)
- [1.2 ToolRuntime 提供的资源](#1.2 ToolRuntime 提供的资源)
- 2、代码详解:天气查询助手工具
-
- [2.1 定义状态和上下文](#2.1 定义状态和上下文)
- [2.2 定义工具,使用 ToolRuntime](#2.2 定义工具,使用 ToolRuntime)
- [2.3 绑定工具到模型](#2.3 绑定工具到模型)
- [2.4 编写 LLM 节点](#2.4 编写 LLM 节点)
- [2.5 构建图并添加工具节点](#2.5 构建图并添加工具节点)
- [2.6 流式调用并传入上下文和状态](#2.6 流式调用并传入上下文和状态)
- [三、深入 ToolRuntime:访问 Store 和流式输出](#三、深入 ToolRuntime:访问 Store 和流式输出)
-
- [3.1 访问跨会话存储(Store)](#3.1 访问跨会话存储(Store))
- [3.2 从工具发送流式自定义数据](#3.2 从工具发送流式自定义数据)
- [3.3 使用 InjectedStore 和 InjectedState](#3.3 使用 InjectedStore 和 InjectedState)
- 四、常见问题与注意事项
-
- [4.1 为什么我的工具收不到 runtime?](#4.1 为什么我的工具收不到 runtime?)
- [4.2 工具中修改 runtime.state 会生效吗?](#4.2 工具中修改 runtime.state 会生效吗?)
- [4.3 如何调试工具中的 runtime?](#4.3 如何调试工具中的 runtime?)
- [4.4 性能注意事项](#4.4 性能注意事项)
- 五、总结
- 六、内容扩展
上一篇------> 【LangGraph】运行时上下文(Runtime Context)

前言
在第一篇文章中,我们学习了如何在图节点中通过 Runtime[Context] 访问静态上下文
但在真实应用中,大量逻辑封装在工具(Tool)内部------例如数据库查询、API 调用、计算等
这些工具同样需要知道当前用户是谁、使用哪种语言,甚至需要读取图的状态和长期存储 LangGraph 提供了 ToolRuntime 对象,让工具可以无缝访问这些运行时资源 本文通过一个完整的天气查询助手案例,带你掌握 ToolRuntime 的所有用法
1、为什么工具需要运行时上下文?
1.1 工具的传统痛点
考虑一个天气查询工具
在没有 ToolRuntime 的情况下,我们通常通过工具的参数来传递用户信息:
python
def search_weather(user_id: str, city: str) -> str:
# 需要外部传入 user_id
这要求调用工具的上层逻辑(通常是 LLM 节点)必须负责从 State 或上下文中提取 user_id 并填入参数
当工具很多时,这种传参方式非常繁琐且容易出错
更麻烦的是,工具可能还需要:
-
记录是谁调用了它(用于审计)
-
读取用户的历史偏好(从 Store 中获取预算范围)
-
发出流式进度事件
-
根据当前 State 中已经收集的信息调整行为
ToolRuntime 将这些能力统一注入到工具中,让工具变得自包含且无侵入
1.2 ToolRuntime 提供的资源
ToolRuntime 是一个包装类,在工具被调用时由 LangGraph 自动注入
它包含以下属性:
| 属性 | 类型 | 说明 |
|---|---|---|
| runtime.context | Context 类型(自定义) | 静态运行时上下文 |
| runtime.state | dict | 当前图状态的只读副本 |
| runtime.store | BaseStore | 跨会话持久化存储(编译传入 store 才可用) |
| get_stream_writer() | 函数(langgraph.config 导入) | 发送自定义流式事件 |
工具只需要声明一个 runtime: ToolRuntime 参数,LangGraph 会在执行工具时自动填充
2、代码详解:天气查询助手工具
下面我们构建一个完整的 LangGraph 应用,其中包含一个能够访问上下文和状态的天气查询工具。
2.1 定义状态和上下文
python
from dataclasses import dataclass
from langgraph.graph import MessagesState
from langgraph.prebuilt import ToolRuntime
class State(MessagesState):
user_name: str = "" # 动态状态:用户名称,可从对话中提取
@dataclass
class Context:
user_id: str # 静态上下文:用户标识
MessagesState 是 LangGraph 预定义的状态,包含一个 messages: list[AnyMessage] 字段,并配置了 operator.add 合并策略
我们扩展它添加了 user_name
2.2 定义工具,使用 ToolRuntime
python
def search(runtime: ToolRuntime[Context]) -> str:
"""搜索工具,返回天气信息"""
# 访问静态上下文
user_id = runtime.context.user_id
# 访问图状态(动态运行时上下文)
user_name = runtime.state.get("user_name", "用户")
# 打印日志(实际开发中可以写入数据库)
print(f"日志记录:user_id: {user_id}, user_name: {user_name} 调用查询工具")
return f"用户 {user_id} ({user_name}) 查询天气结果:晴天,15-20°"
关键点:
-
工具参数的名称必须为
runtime,类型标注为ToolRuntime[YourContextType] -
runtime.context可以访问我们在调用图时传入的上下文对象 -
runtime.state是当前 State 的只读副本注意:State 是一个
TypedDict,因此取值的语法是runtime.state["user_name"]
2.3 绑定工具到模型
这里我使用的是阿里云的通义千问模型(qwen-turbo)作为示例,(练习的话可以跟我一样,记得提前配置好环境变量)并将工具绑定到模型
python
from langchain.chat_models import init_chat_model
from langchain_core.messages import SystemMessage, HumanMessage
model_with_tool = init_chat_model(
model="qwen-turbo",
model_provider="openai",
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
api_key="your-api-key",
temperature=0
).bind_tools([search])
2.4 编写 LLM 节点
LLM 节点负责调用模型,并决定是否调用工具,这里之前也讲过
python
def llm_call(state: State):
messages = [
SystemMessage(content="你是天气助手,必须调用工具查询天气")
] + state["messages"]
result = model_with_tool.invoke(messages)
return {"messages": [result]}
这里使用了 SystemMessage 强制模型调用工具(实际开发中可根据情况调整)
2.5 构建图并添加工具节点
python
from langgraph.constants import START, END
from langgraph.graph import StateGraph
from langgraph.prebuilt import ToolNode, tools_condition
builder = StateGraph(State, context_schema=Context)
builder.add_node(llm_call)
builder.add_node("tool_node", ToolNode([search]))
builder.add_edge(START, "llm_call")
builder.add_conditional_edges(
"llm_call",
tools_condition,#裁判节点,判断走tool_node 或 END
{
"tools": "tool_node",
"__end__": END
}
)
builder.add_edge("tool_node", "llm_call")
graph = builder.compile()
ToolNode 是 LangGraph 预定义的节点,它知道如何执行工具并返回 ToolMessage
tools_condition 是预定义的条件路由函数:如果最后一条消息包含 tool_calls,则返回 "tools",否则返回 "__end__"
2.6 流式调用并传入上下文和状态
python
for chunk in graph.stream(
{
"messages": [HumanMessage(content="今天烟台莱山区天气怎么样")],
"user_name": "小猫"
},
context={"user_id": "123"}
):
for node, update in chunk.items():
print(f"节点 {node} 更新的消息如下")
update["messages"][-1].pretty_print()
执行流程:
-
用户消息进入 llm_call 节点
-
模型决定调用 search 工具,参数由模型自动生成(城市等)
-
LangGraph 执行 tool_node,调用 search 函数,此时 runtime.context.user_id 为 "123",runtime.state["user_name"] 为 "小猫"
-
工具返回结果字符串,作为 ToolMessage 添加到 State
-
再次回到 llm_call 节点,模型根据工具结果生成最终回答
-
流式输出会依次打印各个节点产生的消息
输出示例(简化):
text
节点 llm_call 更新的消息如下
================================ Ai Message ================================
Tool Calls:
search (call_xxx)
Args:
query: 烟台莱山区天气
节点 tool_node 更新的消息如下
================================ Tool Message ================================
用户 123 (小猫) 查询天气结果:晴天,15-20°
节点 llm_call 更新的消息如下
================================ Ai Message ================================
根据查询,烟台莱山区今天晴天,气温15-20度
三、深入 ToolRuntime:访问 Store 和流式输出
3.1 访问跨会话存储(Store)
如果我们在编译图时传入了 store(例如 InMemoryStore 或 PostgresStore),工具可以通过 runtime.store 读写长期记忆
下面是一个记录用户历史查询的例子:
python
import uuid
def search_with_store(runtime: ToolRuntime[Context], city: str) -> str:
user_id = runtime.context.user_id
namespace = (user_id, "weather_history")
# 保存本次查询
history_entry = {"city": city, "timestamp": "now"}
key = str(uuid.uuid4())
runtime.store.put(namespace, key, history_entry)
# 读取最近5条历史
recent = runtime.store.search(namespace, limit=5)
return f"查询 {city} 结果:晴天。您最近查过 {len(recent)} 次天气。"
3.2 从工具发送流式自定义数据
长时间运行的工具可以通过 get_stream_writer() 发送进度事件,前端可以实时展示进度
python
from langgraph.config import get_stream_writer
import time
def long_task(runtime: ToolRuntime) -> str:
writer = get_stream_writer()
writer({"status": "start", "message": "开始处理..."})
for i in range(1, 4):
time.sleep(1)
writer({"status": "progress", "step": i, "total": 3})
writer({"status": "end", "result": "完成"})
return "任务完成"
在调用图时,需要指定 stream_mode="custom" 或 ["updates", "custom"] 来接收这些自定义事件
3.3 使用 InjectedStore 和 InjectedState
LangGraph 也支持通过类型注解直接注入 Store 或 State,而不通过 runtime:
python
from typing import Annotated, Any
from langgraph.prebuilt import InjectedStore, InjectedState
from langgraph.store.base import BaseStore
@tool
def get_budget(
state: Annotated[dict, InjectedState()],
store: Annotated[BaseStore, InjectedStore()],
user_id: str
) -> str:
# 直接从参数中获取 state 和 store
budget = state.get("budget")
prefs = store.search((user_id, "prefs"))
这种方式使工具签名更清晰,但需要记住使用 Annotated 标记
选择哪种风格取决于个人偏好,ToolRuntime 方式更简洁统一
四、常见问题与注意事项
4.1 为什么我的工具收不到 runtime?
确保:
-
工具函数参数名必须是
runtime,类型为ToolRuntime或ToolRuntime[YourContext] -
你在构建图时正确指定了
context_schema -
你在调用图时传入了
context参数 -
工具是通过
ToolNode执行的,而不是手动调用
4.2 工具中修改 runtime.state 会生效吗?
回答:不会
runtime.state 是只读的副本,任何修改都不会影响图的实际状态
如果你需要更新状态,应该由节点(比如 LLM 节点)通过返回值来完成
4.3 如何调试工具中的 runtime?
可以在工具内打印 dir(runtime) 查看可用属性,或者打印 runtime.context、runtime.state 等LangGraph Studio 的追踪也会显示工具调用时的上下文信息
4.4 性能注意事项
runtime.state 包含整个消息历史,在大规模对话中可能很大
如果工具只需要少量字段,可以考虑在节点中提前提取并存入独立的 State 字段,以减少传递开销
五、总结
本文通过一个完整的天气查询助手案例,详细介绍了如何在 LangGraph 的工具中使用 ToolRuntime 访问:
-
静态运行时上下文(runtime.context)
-
图当前状态(runtime.state)
-
跨会话存储(runtime.store,需要编译时传入)
-
流式自定义数据(配合 get_stream_writer())
我们还对比了 ToolRuntime 方式和 InjectedStore/InjectedState 方式,并给出了最佳实践和注意事项
掌握这些能力后,你的工具将不再是简单的函数,而是能够感知环境、记忆用户、实时反馈的智能组件
结合第一篇文章中图节点的上下文访问,LangGraph 为你提供了从顶层到底层统一、连贯的运行时信息传递方案
六、内容扩展
我们可以尝试:
-
将天气工具改为从真实 API(如高德天气)获取数据,并在工具内部添加重试和超时逻辑
-
使用 PostgresStore 替代 InMemoryStore,实现生产级别的长期记忆
-
组合使用 interrupt() 和 ToolRuntime,在工具中实现人工审批(例如查询高额订单时要求确认)
LangGraph 的运行时上下文机制,是构建生产级、多租户、可观测的 AI Agent 系统的基础设施之一
希望这两篇文章能帮助大家熟练运用这一强大特性
ok啊运行时上下文篇章结束了,我们下期再会~拜拜
