工具系统的依赖注入:InjectedState 与 InjectedStore
工具系统的依赖注入:InjectedState 与 InjectedStore
学习目标
本章要解决什么问题:工具函数如何访问 Agent 的当前状态和跨会话持久化数据,而无需在每个工具调用中手动传递?
读者将学到:
InjectedState和InjectedStore的注入机制ToolRuntime提供的运行时上下文- 工具签名中特殊参数的识别方式
前置知识
本章涉及工具调用协议的通用原理,建议先阅读:
项目实践
三种注入类型
LangChain 提供三种依赖注入注解(从 LangGraph 的 prebuilt 模块导入):
| 注解 | 注入内容 | 典型用途 |
|---|---|---|
InjectedState | 当前 Agent 状态(TypedDict) | 读取用户上下文、写入临时数据 |
InjectedStore | 跨会话持久化存储 | 知识检索、用户记忆、长期配置 |
ToolRuntime | 运行时上下文(通过工具签名中的特殊参数) | 发送流事件、访问运行时配置 |
工具函数中的使用
# 伪代码from langchain.tools import InjectedState, InjectedStore
def search_docs(query: str, state: InjectedState, store: InjectedStore) -> str: """根据用户当前上下文搜索文档。""" # 从 Agent 状态中获取上下文 user_context = state.get("user_context", "")
# 从持久化存储中搜索 results = store.search(namespace="docs", query=query)
# 结合上下文过滤结果 return format_results(results, context=user_context)关键设计:InjectedState 和 InjectedStore 是类型注解标记(type annotation markers),在工具签名中声明。ToolNode 在执行工具时检测到这些注解,自动注入对应的值。工具调用者不需要显式传递这些参数。
工具签名中的特殊参数识别
ToolNode 通过检查函数签名中的注解来识别需要注入的参数:
# 伪代码:ToolNode 的注入逻辑for param in tool_signature.parameters.values(): if param.annotation == InjectedState: kwargs[param.name] = current_agent_state elif param.annotation == InjectedStore: kwargs[param.name] = store_instance这意味着:
- 参数名可以是任意的(如
state、agent_state、ctx),只要注解正确 - 注入的参数不会出现在工具的 JSON Schema 中(不会暴露给模型)
- 模型不知道 Agent 状态和存储的存在,因此不会尝试伪造这些参数
ToolRuntime 的流事件
ToolRuntime 提供 stream_writer 方法,允许工具在执行期间发送自定义事件:
# 伪代码async def long_running_tool(state: InjectedState, runtime: ToolRuntime) -> str: runtime.stream_writer({"type": "status", "message": "正在搜索..."}) results = await search() runtime.stream_writer({"type": "status", "message": f"找到 {len(results)} 条结果"}) return format_results(results)这些事件通过 stream_mode="custom" 在 Agent 流式输出时传递给调用者。
问题与规避
注入参数与模型工具参数的混淆
问题:如果工具签名中同时有普通参数和注入参数,模型是否能看到注入参数?
对策:注入参数通过类型注解标记,ToolNode 在序列化 Schema 时自动排除它们。模型只能看到普通参数。
存储的命名空间管理
问题:多个 Agent 或用户共享同一存储时,数据可能互相干扰。
对策:使用 namespace 参数隔离不同 Agent/用户的数据。典型做法是使用用户 ID 或会话 ID 作为命名空间前缀。
设计取舍
依赖注入 vs 全局上下文
| 方案 | 优势 | 代价 |
|---|---|---|
| 依赖注入 | 显式声明、可测试、隔离性好 | 工具签名需要额外参数 |
| 全局上下文 | 工具签名简洁 | 隐式依赖、测试困难、并发不安全 |
LangChain 选择依赖注入,因为工具可能在不同 Agent 实例间复用,显式声明使得依赖关系清晰。
类型注解 vs 装饰器注入
| 方案 | 优势 | 代价 |
|---|---|---|
| 类型注解 | 与 Python 类型系统统一、IDE 支持好 | 需要特定的类型注解 |
| 装饰器 | 更灵活(可以在装饰器中注入) | 增加装饰器层、可读性下降 |
LangChain 从 LangGraph 的 ToolNode 继承类型注解方案,保持与 LangGraph 生态的一致性。