"为什么我的工具总是拿不到用户ID?"
如果你也曾被这个问题折磨到凌晨三点,这篇文章就是为你准备的。
在构建生产级Agent时,数据传递 往往是最让人头疼的环节。LangChain v1.0 引入的 Runtime 机制,本质上解决了一个核心问题:如何让工具"感知"到运行时的上下文,同时又不把这些内部细节暴露给大模型?
想象一下快递配送:用户(LLM)只需要填写收货地址,但快递员(Tool)还需要知道配送站点、客户等级、甚至实时交通状况。Runtime就是那个"快递员专用信息包"------对用户透明,对工具必需。
一、Runtime核心能力
LangChain 的 ToolRuntime 为工具提供了六大核心能力 :
| 组件 | 作用域 | 典型用途 | 类比 |
|---|---|---|---|
| State | 当前对话 | 访问消息历史、计数器、临时标记 | 工作台(当前任务相关) |
| Context | 单次运行 | 用户ID、权限、数据库连接、API密钥 | 工牌(身份认证信息) |
| Store | 跨会话持久化 | 用户偏好、长期记忆、知识库 | 档案柜(历史记录) |
| Stream Writer | 实时流 | 进度反馈、中间状态推送 | 对讲机(实时通讯) |
| Config | 运行配置 | 回调函数、标签、元数据 | 配置手册 |
| Tool Call ID | 单次调用 | 日志追踪、调用链关联 | 工单号 |

代码速览:一个完整的 ToolRuntime 工具
python
from dataclasses import dataclass
from langchain.tools import tool, ToolRuntime
@dataclass
class Context:
user_id: str
user_role: str # "admin" | "user" | "guest"
@tool
def fetch_user_data(query: str, runtime: ToolRuntime[Context]) -> str:
"""
根据查询获取用户数据,自动进行权限检查。
Args:
query: 用户查询内容
"""
# 1. 从 Context 获取身份信息
user_id = runtime.context.user_id
role = runtime.context.user_role
# 2. 从 State 读取对话历史(检查是否有敏感操作前置)
messages = runtime.state.get("messages", [])
# 3. 从 Store 获取用户长期偏好
if runtime.store:
prefs = runtime.store.get(("user_prefs",), user_id)
# 4. 实时推送进度
runtime.stream_writer(f"🔍 正在为用户 {user_id} 查询数据...")
# 5. 权限检查(敏感信息隔离示例)
if "salary" in query.lower() and role != "admin":
return "❌ 权限不足:薪资信息仅限管理员查询"
return f"查询结果 for {user_id}: ..."
二、Context Schema:类型安全的"数据契约"
context_schema 是 LangChain v1.0 最重要的设计改进之一 。
在旧版本中,传递额外数据需要通过 config["configurable"] 这种"黑魔法"方式。新版本的 context_schema 让这一切变得显式、类型安全、可维护。
基础用法:Dataclass 定义
python
from dataclasses import dataclass
from langchain.agents import create_agent
@dataclass
class AppContext:
user_id: str
tenant_id: str # 多租户隔离
request_id: str # 用于日志追踪
feature_flags: dict # 功能开关
agent = create_agent(
model="gpt-4o",
tools=[fetch_user_data, update_settings],
context_schema=AppContext # 声明上下文结构
)
# 调用时注入
result = agent.invoke(
{"messages": [{"role": "user", "content": "查看我的数据"}]},
context=AppContext(
user_id="user_123",
tenant_id="tenant_456",
request_id="req_789",
feature_flags={"beta_feature": True}
)
)

进阶:Pydantic 与验证
对于更复杂的场景,Pydantic 提供了更强的验证能力:
python
from pydantic import BaseModel, Field, validator
class SecureContext(BaseModel):
user_id: str = Field(..., min_length=5)
permissions: list[str] = Field(default_factory=list)
session_start: datetime = Field(default_factory=datetime.now)
@validator('permissions')
def validate_permissions(cls, v):
allowed = {"read", "write", "delete", "admin"}
invalid = set(v) - allowed
if invalid:
raise ValueError(f"无效权限: {invalid}")
return v
# 在工具中使用
@tool
def delete_resource(resource_id: str, runtime: ToolRuntime[SecureContext]) -> str:
"""删除资源,需要 write 或 admin 权限"""
if "admin" not in runtime.context.permissions:
raise PermissionError("需要管理员权限")
# 执行删除...
三、实战模式:用户身份与权限传递
生产环境的核心诉求:不同用户看到不同数据。
模式1:基于角色的数据过滤(RBAC)
python
from enum import Enum
from dataclasses import dataclass
class Role(Enum):
ADMIN = "admin"
MANAGER = "manager"
USER = "user"
@dataclass
class AuthContext:
user_id: str
role: Role
department: str | None = None
@tool
def query_sales_data(
quarter: str,
runtime: ToolRuntime[AuthContext]
) -> str:
"""
查询销售数据,自动根据角色过滤可见范围。
"""
ctx = runtime.context
# 权限矩阵
if ctx.role == Role.ADMIN:
# 管理员:查看全公司数据
data = fetch_all_sales(quarter)
elif ctx.role == Role.MANAGER:
# 经理:仅限本部门
data = fetch_department_sales(quarter, ctx.department)
else:
# 普通用户:仅个人数据
data = fetch_personal_sales(quarter, ctx.user_id)
# 审计日志(写入Store)
if runtime.store:
runtime.store.put(
("audit",),
f"{ctx.user_id}_{datetime.now().isoformat()}",
{"action": "query_sales", "quarter": quarter, "role": ctx.role.value}
)
return format_sales_report(data)
模式2:多租户隔离
python
@dataclass
class TenantContext:
tenant_id: str
user_id: str
db_pool: AsyncConnectionPool # 数据库连接池
@tool
async def get_customer_list(
filter_status: str | None = None,
runtime: ToolRuntime[TenantContext]
) -> str:
"""
获取客户列表,自动隔离租户数据。
"""
tenant_id = runtime.context.tenant_id
pool = runtime.context.db_pool
# SQL 自动注入 tenant_id 过滤
async with pool.acquire() as conn:
rows = await conn.fetch(
"SELECT * FROM customers WHERE tenant_id = $1 AND ($2::text IS NULL OR status = $2)",
tenant_id, filter_status
)
return json.dumps([dict(r) for r in rows])
⚠️ 重要提示 :当使用 LangGraph Server 部署时,无法直接通过 .invoke() 注入 context。此时需要在 graph 启动时初始化资源,或通过 configurable 传递配置 。
四、敏感信息隔离:安全最佳实践
"不要把数据库密码传给 LLM" ------ 这听起来理所当然,但在复杂的工具链中很容易出错。
安全原则 checklist
✅ Context 中的敏感信息 :API密钥、数据库连接、加密密钥
✅ State 中的会话信息 :认证状态、临时token、上传文件
✅ Store 中的持久化数据 :用户密码哈希、隐私设置
❌ 绝不暴露给 LLM :通过 context_schema 定义的数据不会出现在工具的 JSON Schema 中
实战:安全的支付工具
python
from dataclasses import dataclass
from typing import Annotated
from langchain.tools import InjectedToolCallId
@dataclass
class PaymentContext:
user_id: str
# 这些字段对 LLM 不可见,但工具可以访问
stripe_api_key: str
fraud_check_endpoint: str
max_transaction_amount: Decimal
@tool
def process_payment(
amount: Decimal,
currency: str,
runtime: ToolRuntime[PaymentContext],
# 这个参数会被自动注入,不会暴露给 LLM
tool_call_id: Annotated[str, InjectedToolCallId]
) -> str:
"""
处理支付请求,包含风控检查。
Args:
amount: 支付金额(如 99.99)
currency: 货币代码(如 "USD")
"""
ctx = runtime.context
# 1. 风控检查(使用内部 API,LLM 不知道 endpoint)
risk_score = check_fraud(
ctx.fraud_check_endpoint,
ctx.user_id,
amount,
currency
)
if risk_score > 0.8:
# 记录到长期记忆
if runtime.store:
runtime.store.put(
("security",),
ctx.user_id,
{"last_blocked": datetime.now().isoformat(), "reason": "high_risk"}
)
return "⚠️ 交易被风控系统拦截,请联系客服"
# 2. 限额检查
if amount > ctx.max_transaction_amount:
return f"❌ 超出单笔限额 {ctx.max_transaction_amount}"
# 3. 实时通知用户进度
runtime.stream_writer("🔒 正在连接支付网关...")
# 4. 执行支付(使用密钥,LLM 无法获取)
result = stripe_charge(
api_key=ctx.stripe_api_key, # 安全注入
amount=amount,
currency=currency,
metadata={"user_id": ctx.user_id, "tool_call_id": tool_call_id}
)
runtime.stream_writer("✅ 支付处理完成")
return f"交易成功,ID: {result.id}"

五、Stream Writer:实时反馈的艺术
长耗时工具(如数据分析、文件处理)最大的用户体验杀手是**"假死"状态**。
runtime.stream_writer 让你可以在工具执行过程中推送实时更新 :
python
@tool
def analyze_large_dataset(
dataset_id: str,
analysis_type: str,
runtime: ToolRuntime
) -> str:
"""
分析大型数据集,实时报告进度。
"""
writer = runtime.stream_writer
writer({"type": "status", "message": "📥 正在加载数据集..."})
df = load_dataset(dataset_id)
writer({"type": "progress", "percent": 20, "message": "🔍 数据清洗中..."})
df_clean = clean_data(df)
writer({"type": "progress", "percent": 50, "message": "🧮 执行统计分析..."})
stats = compute_statistics(df_clean)
writer({"type": "progress", "percent": 80, "message": "📊 生成可视化..."})
charts = generate_charts(stats)
writer({"type": "complete", "message": "✅ 分析完成!"})
return format_report(stats, charts)
前端配合(React 示例):
javascript
// 使用 LangChain 的 useStream hook
const { stream } = useStream();
stream.subscribe((chunk) => {
if (chunk.type === 'progress') {
updateProgressBar(chunk.percent);
showStatus(chunk.message);
}
});
六、完整实战:企业级 Agent 架构
把以上概念整合到一个真实的客服 Agent 场景:
python
from dataclasses import dataclass
from datetime import datetime
from langchain.agents import create_agent
from langchain.agents.middleware import before_model, AgentState
from langchain.tools import tool, ToolRuntime
# ============ 1. 上下文定义 ============
@dataclass
class CustomerServiceContext:
agent_id: str # 客服工号
user_tier: str # "vip" | "premium" | "standard"
session_id: str # 用于全链路追踪
crm_api_token: str # 敏感:CRM系统密钥
knowledge_base_version: str
# ============ 2. 中间件:动态权限提示 ============
@before_model
def inject_privacy_warning(state: AgentState, runtime: Runtime[CustomerServiceContext]) -> dict:
"""在每次模型调用前注入数据隐私提醒"""
if runtime.context.user_tier == "vip":
return {
"messages": [
{"role": "system", "content": "⚠️ 当前用户为VIP,注意保护隐私数据,不得透露其他客户信息"}
]
}
return None
# ============ 3. 工具实现 ============
@tool
def lookup_customer_history(
customer_phone: str,
runtime: ToolRuntime[CustomerServiceContext]
) -> str:
"""查询客户历史记录,自动根据 tier 决定详细程度"""
ctx = runtime.context
# 实时反馈
runtime.stream_writer(f"🔍 正在查询客户 {customer_phone[-4:]}...")
# 使用安全 token 调用 CRM
history = crm_client.query(
token=ctx.crm_api_token,
phone=customer_phone
)
# VIP 客户看到完整历史,普通客户仅看摘要
if ctx.user_tier == "vip":
return format_detailed_history(history)
else:
return format_summary(history)
@tool
def escalate_to_human(
reason: str,
runtime: ToolRuntime[CustomerServiceContext]
) -> str:
"""升级至人工客服"""
ctx = runtime.context
# 写入长期记忆:标记该用户需要人工跟进
if runtime.store:
runtime.store.put(
("escalations",),
ctx.session_id,
{
"agent_id": ctx.agent_id,
"reason": reason,
"timestamp": datetime.now().isoformat(),
"resolved": False
}
)
# 通知监控系统
runtime.stream_writer({
"type": "alert",
"level": "high",
"message": f"工单升级: {reason}"
})
return "已为您转接人工客服,请稍候..."
# ============ 4. 组装 Agent ============
agent = create_agent(
model="claude-3-5-sonnet",
tools=[lookup_customer_history, escalate_to_human, process_refund],
context_schema=CustomerServiceContext,
middleware=[inject_privacy_warning],
store=InMemoryStore() # 生产环境使用 RedisStore
)
# ============ 5. 运行 ============
response = agent.invoke(
{"messages": [{"role": "user", "content": "我要投诉昨天的订单"}]},
context=CustomerServiceContext(
agent_id="CS-10086",
user_tier="vip",
session_id="sess-2025-001",
crm_api_token=os.getenv("CRM_TOKEN"), # 从环境变量安全获取
knowledge_base_version="v2.3"
)
)

总结:设计哲学
LangChain v1.0 的 Runtime 机制体现了一个核心设计哲学:
"显式优于隐式,类型安全优于灵活,安全优先于便利。"
通过 context_schema,我们获得了:
- 类型安全:IDE 自动补全和静态检查
- 安全隔离:敏感信息对 LLM 完全不可见
- 可测试性:可以 Mock Context 进行单元测试
- 可维护性:数据流向清晰,不依赖"魔法"配置
关注公众号【dev派】,发送 "agent" 获取全部源码和模板 