注:本文使用 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"}
)
工作原理
- agent.invoke() 被调用
- Agent 开始执行,要在调用 LLM 之前
@dynamic_prompt拦截,读取request.runtime.context- 返回字符串作为 system prompt
- 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.context | str (新的 system prompt) |
@before_model | 模型调用前(最后) | state, runtime.context | dict 或 None |
@after_model | 模型调用后 | state, runtime.context | dict 或 None |
本文使用的模型服务来自硅基流动平台。新用户通过邀请链接注册可领取 2000万免费token,支持GLM-4.6、Kimi-K2-Thinking、MiniMaxAI/MiniMax-M2、DeepSeek-R2等主流大模型调用,API稳定性与响应速度俱佳。
专属注册链接:cloud.siliconflow.cn/i/AvDmOKTO
(打直球:这是我的推荐码 感谢大佬的支持)
相关文档: