跳转到内容

工具系统的依赖注入:InjectedState 与 InjectedStore

工具系统的依赖注入:InjectedState 与 InjectedStore

学习目标

本章要解决什么问题:工具函数如何访问 Agent 的当前状态和跨会话持久化数据,而无需在每个工具调用中手动传递?

读者将学到:

  • InjectedStateInjectedStore 的注入机制
  • 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)

关键设计InjectedStateInjectedStore 是类型注解标记(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

这意味着

  • 参数名可以是任意的(如 stateagent_statectx),只要注解正确
  • 注入的参数不会出现在工具的 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 生态的一致性。

参考来源