LangChain v1.0 的 Middleware 概览

155 阅读4分钟

注:本文使用 LangChain v1.0+

Middleware 是 Agent 执行过程中的拦截器,可以在特定的执行点修改行为。

中间件有两种风格的钩子:

钩子类型用途执行时机
Node-style顺序执行日志记录、校验、状态更新
Wrap-style环绕执行重试、缓存、动态模型选择

先开胃菜,抛个砖🧱 @dynamic_prompt

@dynamic_prompt 是一个特殊的装饰器,用于拦截模型调用前,动态生成 system prompt

from langchain.agents import create_agent
from langchain.agents.middleware import dynamic_prompt, ModelRequest

class Context(TypedDict):
    user_role: str
    language: str

@dynamic_prompt
def smart_prompt(request: ModelRequest) -> str:
    """根据 context 和 state 生成不同的 system prompt"""
    user_role = request.runtime.context.get("user_role", "user")
    language = request.runtime.context.get("language", "english")
    
    base = {
        "english": "You are a helpful assistant.",
        "chinese": "你是一个有帮助的助手。"
    }[language]
    
    if user_role == "expert":
        return f"{base} Provide deep technical insights."
    elif user_role == "beginner":
        return f"{base} Explain things simply."
    
    return base

agent = create_agent(
    model=model,
    tools=[search_tool],
    middleware=[smart_prompt],
    context_schema=Context
)

# 不同的上下文,得到不同的 system prompt
result = agent.invoke(
    {"messages": [{"role": "user", "content": "什么是 AI?"}]},
    context={"user_role": "expert", "language": "chinese"}
)

工作原理

  1. agent.invoke() 被调用
  2. Agent 开始执行,要在调用 LLM 之前
  3. @dynamic_prompt 拦截,读取 request.runtime.context
  4. 返回字符串作为 system prompt
  5. LLM 收到动态生成的 prompt,然后调用

正餐开始

1. @before_model - 在模型调用前执行

from langchain.agents.middleware import before_model, ModelRequest

@before_model
def log_before_model(state: AgentState, runtime: Runtime[Context]) -> dict | None:
    """记录调用前的信息"""
    print(f"调用模型,消息数: {len(state['messages'])}")
    print(f"用户 ID: {runtime.context.user_name}")
    
    # 可以修改状态
    return None  # 不修改时返回 None

agent = create_agent(
    model=model,
    tools=[search_tool],
    middleware=[log_before_model],
    context_schema=Context
)

2. @after_model - 在模型响应后执行

from langchain.agents.middleware import after_model
from langchain.messages import AIMessage

@after_model
def validate_response(state: AgentState, runtime: Runtime[Context]) -> dict | None:
    """验证模型输出,甚至可以修改或替换"""
    last_message = state["messages"][-1]
    
    if isinstance(last_message, AIMessage):
        # 如果响应包含敏感内容,替换它
        if "secret" in last_message.content.lower():
            return {
                "messages": [AIMessage(content="I cannot share that information.")]
            }
    
    return None

agent = create_agent(
    model=model,
    tools=[search_tool],
    middleware=[validate_response],
    context_schema=Context
)

3. 组合多个中间件

中间件按顺序执行,形成管道:

from langchain.agents.middleware import dynamic_prompt, before_model, after_model

@dynamic_prompt
def personalized_prompt(request: ModelRequest) -> str:
    user_name = request.runtime.context.user_name
    return f"You are a helpful assistant for {user_name}."

@before_model
def log_info(state: AgentState, runtime: Runtime[Context]) -> dict | None:
    print(f"User: {runtime.context.user_name}")
    return None

@after_model
def log_response(state: AgentState, runtime: Runtime[Context]) -> dict | None:
    print(f"Model responded: {state['messages'][-1].content[:50]}...")
    return None

agent = create_agent(
    model=model,
    tools=[search_tool],
    middleware=[
        personalized_prompt,  # ① 第一个执行
        log_info,             # ② 第二个执行
        log_response,         # ③ 第三个执行(在模型响应后)
    ],
    context_schema=Context
)

执行顺序(重要!)

middleware1 ──→ middleware2 ──→ middleware3
   ↓              ↓              ↓
before_agent   before_agent   before_agent
   ↓              ↓              ↓
@dynamic_prompt  @dynamic_prompt  @dynamic_prompt
   ↓              ↓              ↓
before_model   before_model   before_model
   ↓              ↓              ↓
[调用模型 LLM]
   ↓              ↓              ↓
after_model    after_model    after_model (反向!)
   ↓              ↓              ↓
after_agent    after_agent    after_agent (反向!)
  • before_ 钩子*: 顺序执行(1 → 2 → 3)
  • after_ 钩子*: 反向执行(3 → 2 → 1)
  • @dynamic_prompt: 也是反向执行

实战例子:完整的中间件系统

import os
from langchain_openai import ChatOpenAI
from langchain.agents import create_agent, AgentState
from langchain.agents.middleware import (
    dynamic_prompt, before_model, after_model, ModelRequest
)
from langchain.tools import tool
from langgraph.runtime import Runtime
from typing import TypedDict

# ===== 配置 =====
model = ChatOpenAI(
    model=os.getenv("MODEL_NAME", "Qwen/Qwen2-7B-Instruct"),
    temperature=0.7,
    base_url=os.getenv("SILICONFLOW_BASE_URL"),
    api_key=os.getenv("SILICONFLOW_API_KEY")
)

# ===== Context Schema =====
class Context(TypedDict):
    user_id: str
    user_role: str  # "admin" 或 "user"
    api_key: str

# ===== 工具 =====
@tool
def search_documents(query: str) -> str:
    """搜索文档"""
    return f"找到关于 {query} 的 3 个结果"

# ===== 中间件 1: 动态 System Prompt =====
@dynamic_prompt
def role_based_prompt(request: ModelRequest) -> str:
    """根据用户角色生成不同的 system prompt"""
    user_role = request.runtime.context.get("user_role", "user")
    
    base = "你是一个智能助手。"
    
    if user_role == "admin":
        return f"{base}\n权限:可以查看所有文档、执行敏感操作。\n始终告知用户他们的决定影响。"
    else:
        return f"{base}\n权限:只能查看公开文档。\n如果用户请求敏感信息,请拒绝。"

# ===== 中间件 2: 调用前日志 =====
@before_model
def log_call_info(state: AgentState, runtime: Runtime[Context]) -> dict | None:
    """记录调用信息"""
    user_id = runtime.context.user_id
    msg_count = len(state.get("messages", []))
    print(f"[日志] 用户 {user_id} 的第 {msg_count} 条消息")
    return None

# ===== 中间件 3: 调用后验证 =====
@after_model
def validate_safety(state: AgentState, runtime: Runtime[Context]) -> dict | None:
    """检查响应是否包含敏感内容"""
    from langchain.messages import AIMessage
    
    last_msg = state["messages"][-1]
    
    if isinstance(last_msg, AIMessage):
        content = last_msg.content.lower()
        
        # 非管理员不能获取 API 密钥信息
        if "api_key" in content and runtime.context.get("user_role") != "admin":
            return {
                "messages": [AIMessage(content="无法显示该内容。")]
            }
    
    return None

# ===== 创建 Agent =====
agent = create_agent(
    model=model,
    tools=[search_documents],
    middleware=[
        role_based_prompt,  # 生成个性化 prompt
        log_call_info,      # 记录信息
        validate_safety     # 验证安全性
    ],
    context_schema=Context
)

# ===== 测试 =====
if __name__ == "__main__":
    # 场景 1: 普通用户
    result1 = agent.invoke(
        {"messages": [{"role": "user", "content": "API 密钥是什么?"}]},
        context={
            "user_id": "user123",
            "user_role": "user",
            "api_key": "secret"
        }
    )
    print("普通用户回答:", result1["messages"][-1].content)
    
    # 场景 2: 管理员
    result2 = agent.invoke(
        {"messages": [{"role": "user", "content": "API 密钥是什么?"}]},
        context={
            "user_id": "admin001",
            "user_role": "admin",
            "api_key": "secret"
        }
    )
    print("管理员回答:", result2["messages"][-1].content)

关键概念总结

装饰器何时执行访问内容返回值
@dynamic_prompt模型调用前request.runtime.contextstr (新的 system prompt)
@before_model模型调用前(最后)state, runtime.contextdictNone
@after_model模型调用后state, runtime.contextdictNone

本文使用的模型服务来自硅基流动平台。新用户通过邀请链接注册可领取 2000万免费token,支持GLM-4.6、Kimi-K2-Thinking、MiniMaxAI/MiniMax-M2、DeepSeek-R2等主流大模型调用,API稳定性与响应速度俱佳。

专属注册链接:cloud.siliconflow.cn/i/AvDmOKTO

(打直球:这是我的推荐码 感谢大佬的支持)

相关文档: