【LangChain】Context与Runtime:运行时数据注入完全指南

"为什么我的工具总是拿不到用户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" 获取全部源码和模板

相关推荐
Codebee2 小时前
Apex 技能安装过程深度技术揭秘
人工智能
拓端研究室2 小时前
2026年医疗趋势报告:医保改革、创新药、国产替代|附230+份报告PDF、数据、可视化模板汇总下载
大数据·人工智能
ages_1232 小时前
剪流AI智能手机用户真实使用体验分享:实测自动拓客与成交全解析
人工智能·智能手机
Yao.Li2 小时前
PVN3D TensorRT 环境配置指南
人工智能·python·具身智能
薛定猫AI3 小时前
【深度解析】从 Claude Mythos 到自进化 Agent:下一代 AI 智能体技术栈与落地实践
网络·人工智能
zhangshuang-peta3 小时前
MCP 与 AI Agent:为什么 Agent 离不开协议?
人工智能·ai agent·mcp·peta
娃娃略3 小时前
【CFG】——条件生成
人工智能·机器学习
丝斯20113 小时前
AI学习笔记整理(78)——Python学习7
人工智能·笔记·学习